chore: reasoning block (#4551)
* chore: reasoning text block * chore: update interface support all theme * chore: update failed test * chore: update state collapsed based on message index * fix: use reserve_id instead of message index * chore: clean up * chore: fix loading indicator --------- Co-authored-by: Louis <louis@jan.ai>
This commit is contained in:
parent
f2bb9c91de
commit
043284f51e
@ -509,61 +509,61 @@ __metadata:
|
||||
|
||||
"@janhq/core@file:../../core/package.tgz::locator=%40janhq%2Fassistant-extension%40workspace%3Aassistant-extension":
|
||||
version: 0.1.10
|
||||
resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=546b6d&locator=%40janhq%2Fassistant-extension%40workspace%3Aassistant-extension"
|
||||
resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=af6a7b&locator=%40janhq%2Fassistant-extension%40workspace%3Aassistant-extension"
|
||||
dependencies:
|
||||
rxjs: "npm:^7.8.1"
|
||||
ulidx: "npm:^2.3.0"
|
||||
checksum: 10c0/abb0aeb570396ef5d2fe482410a178f422ced7da132fa28f92b930eba191439a6a9b3567641c283eb6bf6413ace2950669393fc8f1e415d442e22d7107c23a44
|
||||
checksum: 10c0/482f1f5363ca64f48a40c3d2937ac07dcf42a6d2dd6c82356359cec986e9312611f84ca8cdaed4ef338d978cd163909a2708d75f8e957aa51b09447aae83eca0
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@janhq/core@file:../../core/package.tgz::locator=%40janhq%2Fconversational-extension%40workspace%3Aconversational-extension":
|
||||
version: 0.1.10
|
||||
resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=546b6d&locator=%40janhq%2Fconversational-extension%40workspace%3Aconversational-extension"
|
||||
resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=af6a7b&locator=%40janhq%2Fconversational-extension%40workspace%3Aconversational-extension"
|
||||
dependencies:
|
||||
rxjs: "npm:^7.8.1"
|
||||
ulidx: "npm:^2.3.0"
|
||||
checksum: 10c0/abb0aeb570396ef5d2fe482410a178f422ced7da132fa28f92b930eba191439a6a9b3567641c283eb6bf6413ace2950669393fc8f1e415d442e22d7107c23a44
|
||||
checksum: 10c0/482f1f5363ca64f48a40c3d2937ac07dcf42a6d2dd6c82356359cec986e9312611f84ca8cdaed4ef338d978cd163909a2708d75f8e957aa51b09447aae83eca0
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@janhq/core@file:../../core/package.tgz::locator=%40janhq%2Fengine-management-extension%40workspace%3Aengine-management-extension":
|
||||
version: 0.1.10
|
||||
resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=546b6d&locator=%40janhq%2Fengine-management-extension%40workspace%3Aengine-management-extension"
|
||||
resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=af6a7b&locator=%40janhq%2Fengine-management-extension%40workspace%3Aengine-management-extension"
|
||||
dependencies:
|
||||
rxjs: "npm:^7.8.1"
|
||||
ulidx: "npm:^2.3.0"
|
||||
checksum: 10c0/abb0aeb570396ef5d2fe482410a178f422ced7da132fa28f92b930eba191439a6a9b3567641c283eb6bf6413ace2950669393fc8f1e415d442e22d7107c23a44
|
||||
checksum: 10c0/482f1f5363ca64f48a40c3d2937ac07dcf42a6d2dd6c82356359cec986e9312611f84ca8cdaed4ef338d978cd163909a2708d75f8e957aa51b09447aae83eca0
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@janhq/core@file:../../core/package.tgz::locator=%40janhq%2Finference-cortex-extension%40workspace%3Ainference-cortex-extension":
|
||||
version: 0.1.10
|
||||
resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=546b6d&locator=%40janhq%2Finference-cortex-extension%40workspace%3Ainference-cortex-extension"
|
||||
resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=af6a7b&locator=%40janhq%2Finference-cortex-extension%40workspace%3Ainference-cortex-extension"
|
||||
dependencies:
|
||||
rxjs: "npm:^7.8.1"
|
||||
ulidx: "npm:^2.3.0"
|
||||
checksum: 10c0/abb0aeb570396ef5d2fe482410a178f422ced7da132fa28f92b930eba191439a6a9b3567641c283eb6bf6413ace2950669393fc8f1e415d442e22d7107c23a44
|
||||
checksum: 10c0/482f1f5363ca64f48a40c3d2937ac07dcf42a6d2dd6c82356359cec986e9312611f84ca8cdaed4ef338d978cd163909a2708d75f8e957aa51b09447aae83eca0
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@janhq/core@file:../../core/package.tgz::locator=%40janhq%2Fmodel-extension%40workspace%3Amodel-extension":
|
||||
version: 0.1.10
|
||||
resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=546b6d&locator=%40janhq%2Fmodel-extension%40workspace%3Amodel-extension"
|
||||
resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=af6a7b&locator=%40janhq%2Fmodel-extension%40workspace%3Amodel-extension"
|
||||
dependencies:
|
||||
rxjs: "npm:^7.8.1"
|
||||
ulidx: "npm:^2.3.0"
|
||||
checksum: 10c0/abb0aeb570396ef5d2fe482410a178f422ced7da132fa28f92b930eba191439a6a9b3567641c283eb6bf6413ace2950669393fc8f1e415d442e22d7107c23a44
|
||||
checksum: 10c0/482f1f5363ca64f48a40c3d2937ac07dcf42a6d2dd6c82356359cec986e9312611f84ca8cdaed4ef338d978cd163909a2708d75f8e957aa51b09447aae83eca0
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@janhq/core@file:../../core/package.tgz::locator=%40janhq%2Fmonitoring-extension%40workspace%3Amonitoring-extension":
|
||||
version: 0.1.10
|
||||
resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=546b6d&locator=%40janhq%2Fmonitoring-extension%40workspace%3Amonitoring-extension"
|
||||
resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=af6a7b&locator=%40janhq%2Fmonitoring-extension%40workspace%3Amonitoring-extension"
|
||||
dependencies:
|
||||
rxjs: "npm:^7.8.1"
|
||||
ulidx: "npm:^2.3.0"
|
||||
checksum: 10c0/abb0aeb570396ef5d2fe482410a178f422ced7da132fa28f92b930eba191439a6a9b3567641c283eb6bf6413ace2950669393fc8f1e415d442e22d7107c23a44
|
||||
checksum: 10c0/482f1f5363ca64f48a40c3d2937ac07dcf42a6d2dd6c82356359cec986e9312611f84ca8cdaed4ef338d978cd163909a2708d75f8e957aa51b09447aae83eca0
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
||||
@ -290,7 +290,13 @@ export default function ModelHandler() {
|
||||
.catch(() => undefined)
|
||||
if (updatedMessage) {
|
||||
deleteMessage(message.id)
|
||||
addNewMessage(updatedMessage)
|
||||
addNewMessage({
|
||||
...updatedMessage,
|
||||
metadata: {
|
||||
...updatedMessage.metadata,
|
||||
reserve_id: message.id,
|
||||
},
|
||||
})
|
||||
setTokenSpeed((prev) =>
|
||||
prev ? { ...prev, message: updatedMessage.id } : undefined
|
||||
)
|
||||
|
||||
@ -56,7 +56,7 @@
|
||||
"slate-react": "0.110.3",
|
||||
"swr": "^2.2.5",
|
||||
"tailwind-merge": "^2.0.0",
|
||||
"tailwindcss": "3.3.5",
|
||||
"tailwindcss": "3.4.17",
|
||||
"ulidx": "^2.3.0",
|
||||
"use-debounce": "^10.0.0",
|
||||
"uuid": "^9.0.1",
|
||||
|
||||
@ -160,7 +160,12 @@ const ChatBody = memo(
|
||||
>
|
||||
{items.map((virtualRow) => (
|
||||
<div
|
||||
key={messages[virtualRow.index]?.id}
|
||||
key={
|
||||
(messages[virtualRow.index]?.metadata
|
||||
?.reserve_id as string) ??
|
||||
messages[virtualRow.index]?.id ??
|
||||
virtualRow.index
|
||||
}
|
||||
data-index={virtualRow.index}
|
||||
ref={virtualizer.measureElement}
|
||||
>
|
||||
|
||||
@ -0,0 +1,59 @@
|
||||
import React from 'react'
|
||||
|
||||
import { atom, useAtom } from 'jotai'
|
||||
import { ChevronDown, ChevronUp, Loader } from 'lucide-react'
|
||||
|
||||
interface Props {
|
||||
text: string
|
||||
status: string
|
||||
id: string
|
||||
}
|
||||
|
||||
const thinkingBlockStateAtom = atom<{ [id: string]: boolean }>({})
|
||||
|
||||
const ThinkingBlock = ({ id, text, status }: Props) => {
|
||||
const [thinkingState, setThinkingState] = useAtom(thinkingBlockStateAtom)
|
||||
|
||||
const isExpanded = thinkingState[id] ?? false
|
||||
|
||||
const loading = !text.includes('</think>') && status === 'pending'
|
||||
|
||||
const handleClick = () => {
|
||||
setThinkingState((prev) => ({ ...prev, [id]: !isExpanded }))
|
||||
}
|
||||
|
||||
if (!text.replace(/<\/?think>/g, '').trim()) return null
|
||||
|
||||
return (
|
||||
<div className="mx-auto w-full">
|
||||
<div className="mb-4 rounded-lg border border-dashed border-[hsla(var(--app-border))] p-2">
|
||||
<div
|
||||
className="flex cursor-pointer items-center gap-3"
|
||||
onClick={handleClick}
|
||||
>
|
||||
{loading && (
|
||||
<Loader className="h-4 w-4 animate-spin text-[hsla(var(--primary-bg))]" />
|
||||
)}
|
||||
<button className="flex items-center gap-2 focus:outline-none">
|
||||
{isExpanded ? (
|
||||
<ChevronUp className="h-4 w-4" />
|
||||
) : (
|
||||
<ChevronDown className="h-4 w-4" />
|
||||
)}
|
||||
<span className="font-medium">
|
||||
{loading ? 'Thinking...' : 'Thought'}
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{isExpanded && (
|
||||
<div className="mt-2 pl-6 text-[hsla(var(--text-secondary))]">
|
||||
{text.replace(/<\/?think>/g, '').trim()}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ThinkingBlock
|
||||
@ -16,6 +16,7 @@ import MessageToolbar from '../MessageToolbar'
|
||||
import DocMessage from './DocMessage'
|
||||
import ImageMessage from './ImageMessage'
|
||||
import { MarkdownTextMessage } from './MarkdownTextMessage'
|
||||
import ThinkingBlock from './ThinkingBlock'
|
||||
|
||||
import { activeAssistantAtom } from '@/helpers/atoms/Assistant.atom'
|
||||
import {
|
||||
@ -41,6 +42,21 @@ const MessageContainer: React.FC<
|
||||
[props.content]
|
||||
)
|
||||
|
||||
const { reasoningSegment, textSegment } = useMemo(() => {
|
||||
const isThinking = text.includes('<think>') && !text.includes('</think>')
|
||||
if (isThinking) return { reasoningSegment: text, textSegment: '' }
|
||||
|
||||
const match = text.match(/<think>([\s\S]*?)<\/think>/)
|
||||
if (match?.index === undefined)
|
||||
return { reasoningSegment: undefined, textSegment: text }
|
||||
|
||||
const splitIndex = match.index + match[0].length
|
||||
return {
|
||||
reasoningSegment: text.slice(0, splitIndex),
|
||||
textSegment: text.slice(splitIndex),
|
||||
}
|
||||
}, [text])
|
||||
|
||||
const image = useMemo(
|
||||
() =>
|
||||
props.content.find((e) => e.type === ContentType.Image)?.image_url?.url,
|
||||
@ -144,9 +160,16 @@ const MessageContainer: React.FC<
|
||||
)}
|
||||
dir="ltr"
|
||||
>
|
||||
{reasoningSegment && (
|
||||
<ThinkingBlock
|
||||
id={(props.metadata?.reserve_id as string) ?? props.id}
|
||||
text={reasoningSegment}
|
||||
status={props.status}
|
||||
/>
|
||||
)}
|
||||
<MarkdownTextMessage
|
||||
id={props.id}
|
||||
text={text}
|
||||
text={textSegment}
|
||||
isUser={isUser}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@ -18,6 +18,7 @@ module.exports = {
|
||||
'slide-in': 'slide-in 1.2s cubic-bezier(.41,.73,.51,1.02)',
|
||||
'leave': 'leave 150ms ease-in forwards',
|
||||
'bounce-right': 'bounce-right 3s infinite',
|
||||
'spin': 'spin 2s linear infinite',
|
||||
},
|
||||
keyframes: {
|
||||
'wave': {
|
||||
@ -47,6 +48,10 @@ module.exports = {
|
||||
'40%': { transform: 'translateX(-8px)' },
|
||||
'60%': { transform: 'translateX(-4px)' },
|
||||
},
|
||||
'spin': {
|
||||
'0%': { transform: 'rotate(0deg)' },
|
||||
'100%': { transform: 'rotate(360deg)' },
|
||||
},
|
||||
},
|
||||
extend: {
|
||||
fontFamily: {
|
||||
|
||||
59
yarn.lock
59
yarn.lock
@ -1138,7 +1138,7 @@ __metadata:
|
||||
slate-react: "npm:0.110.3"
|
||||
swr: "npm:^2.2.5"
|
||||
tailwind-merge: "npm:^2.0.0"
|
||||
tailwindcss: "npm:3.3.5"
|
||||
tailwindcss: "npm:3.4.17"
|
||||
ts-jest: "npm:^29.2.5"
|
||||
typescript: "npm:^5.3.3"
|
||||
ulidx: "npm:^2.3.0"
|
||||
@ -6141,7 +6141,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"chokidar@npm:^3.5.3, chokidar@npm:^3.6.0":
|
||||
"chokidar@npm:^3.6.0":
|
||||
version: 3.6.0
|
||||
resolution: "chokidar@npm:3.6.0"
|
||||
dependencies:
|
||||
@ -8451,7 +8451,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"fast-glob@npm:^3.0.3, fast-glob@npm:^3.2.9, fast-glob@npm:^3.3.0, fast-glob@npm:^3.3.2":
|
||||
"fast-glob@npm:^3.0.3, fast-glob@npm:^3.2.9, fast-glob@npm:^3.3.2":
|
||||
version: 3.3.2
|
||||
resolution: "fast-glob@npm:3.3.2"
|
||||
dependencies:
|
||||
@ -11483,7 +11483,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"jiti@npm:^1.19.1, jiti@npm:^1.21.6":
|
||||
"jiti@npm:^1.21.6":
|
||||
version: 1.21.7
|
||||
resolution: "jiti@npm:1.21.7"
|
||||
bin:
|
||||
@ -11946,7 +11946,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"lilconfig@npm:^2.0.3, lilconfig@npm:^2.0.5, lilconfig@npm:^2.1.0":
|
||||
"lilconfig@npm:^2.0.3, lilconfig@npm:^2.0.5":
|
||||
version: 2.1.0
|
||||
resolution: "lilconfig@npm:2.1.0"
|
||||
checksum: 10c0/64645641aa8d274c99338e130554abd6a0190533c0d9eb2ce7ebfaf2e05c7d9961f3ffe2bfa39efd3b60c521ba3dd24fa236fe2775fc38501bf82bf49d4678b8
|
||||
@ -14609,7 +14609,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"postcss-load-config@npm:^4.0.1, postcss-load-config@npm:^4.0.2":
|
||||
"postcss-load-config@npm:^4.0.2":
|
||||
version: 4.0.2
|
||||
resolution: "postcss-load-config@npm:4.0.2"
|
||||
dependencies:
|
||||
@ -14763,7 +14763,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"postcss-nested@npm:^6.0.1, postcss-nested@npm:^6.2.0":
|
||||
"postcss-nested@npm:^6.2.0":
|
||||
version: 6.2.0
|
||||
resolution: "postcss-nested@npm:6.2.0"
|
||||
dependencies:
|
||||
@ -14908,7 +14908,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"postcss-selector-parser@npm:^6.0.10, postcss-selector-parser@npm:^6.0.11, postcss-selector-parser@npm:^6.0.4, postcss-selector-parser@npm:^6.0.5, postcss-selector-parser@npm:^6.0.9, postcss-selector-parser@npm:^6.1.1, postcss-selector-parser@npm:^6.1.2":
|
||||
"postcss-selector-parser@npm:^6.0.10, postcss-selector-parser@npm:^6.0.4, postcss-selector-parser@npm:^6.0.5, postcss-selector-parser@npm:^6.0.9, postcss-selector-parser@npm:^6.1.1, postcss-selector-parser@npm:^6.1.2":
|
||||
version: 6.1.2
|
||||
resolution: "postcss-selector-parser@npm:6.1.2"
|
||||
dependencies:
|
||||
@ -14983,7 +14983,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"postcss@npm:^8.4.23, postcss@npm:^8.4.47":
|
||||
"postcss@npm:^8.4.47":
|
||||
version: 8.4.49
|
||||
resolution: "postcss@npm:8.4.49"
|
||||
dependencies:
|
||||
@ -15959,7 +15959,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"resolve@npm:^1.1.7, resolve@npm:^1.11.0, resolve@npm:^1.19.0, resolve@npm:^1.20.0, resolve@npm:^1.22.1, resolve@npm:^1.22.2, resolve@npm:^1.22.4, resolve@npm:^1.22.8":
|
||||
"resolve@npm:^1.1.7, resolve@npm:^1.11.0, resolve@npm:^1.19.0, resolve@npm:^1.20.0, resolve@npm:^1.22.1, resolve@npm:^1.22.4, resolve@npm:^1.22.8":
|
||||
version: 1.22.10
|
||||
resolution: "resolve@npm:1.22.10"
|
||||
dependencies:
|
||||
@ -15985,7 +15985,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"resolve@patch:resolve@npm%3A^1.1.7#optional!builtin<compat/resolve>, resolve@patch:resolve@npm%3A^1.11.0#optional!builtin<compat/resolve>, resolve@patch:resolve@npm%3A^1.19.0#optional!builtin<compat/resolve>, resolve@patch:resolve@npm%3A^1.20.0#optional!builtin<compat/resolve>, resolve@patch:resolve@npm%3A^1.22.1#optional!builtin<compat/resolve>, resolve@patch:resolve@npm%3A^1.22.2#optional!builtin<compat/resolve>, resolve@patch:resolve@npm%3A^1.22.4#optional!builtin<compat/resolve>, resolve@patch:resolve@npm%3A^1.22.8#optional!builtin<compat/resolve>":
|
||||
"resolve@patch:resolve@npm%3A^1.1.7#optional!builtin<compat/resolve>, resolve@patch:resolve@npm%3A^1.11.0#optional!builtin<compat/resolve>, resolve@patch:resolve@npm%3A^1.19.0#optional!builtin<compat/resolve>, resolve@patch:resolve@npm%3A^1.20.0#optional!builtin<compat/resolve>, resolve@patch:resolve@npm%3A^1.22.1#optional!builtin<compat/resolve>, resolve@patch:resolve@npm%3A^1.22.4#optional!builtin<compat/resolve>, resolve@patch:resolve@npm%3A^1.22.8#optional!builtin<compat/resolve>":
|
||||
version: 1.22.10
|
||||
resolution: "resolve@patch:resolve@npm%3A1.22.10#optional!builtin<compat/resolve>::version=1.22.10&hash=c3c19d"
|
||||
dependencies:
|
||||
@ -17449,7 +17449,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"sucrase@npm:^3.32.0, sucrase@npm:^3.35.0":
|
||||
"sucrase@npm:^3.35.0":
|
||||
version: 3.35.0
|
||||
resolution: "sucrase@npm:3.35.0"
|
||||
dependencies:
|
||||
@ -17561,40 +17561,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"tailwindcss@npm:3.3.5":
|
||||
version: 3.3.5
|
||||
resolution: "tailwindcss@npm:3.3.5"
|
||||
dependencies:
|
||||
"@alloc/quick-lru": "npm:^5.2.0"
|
||||
arg: "npm:^5.0.2"
|
||||
chokidar: "npm:^3.5.3"
|
||||
didyoumean: "npm:^1.2.2"
|
||||
dlv: "npm:^1.1.3"
|
||||
fast-glob: "npm:^3.3.0"
|
||||
glob-parent: "npm:^6.0.2"
|
||||
is-glob: "npm:^4.0.3"
|
||||
jiti: "npm:^1.19.1"
|
||||
lilconfig: "npm:^2.1.0"
|
||||
micromatch: "npm:^4.0.5"
|
||||
normalize-path: "npm:^3.0.0"
|
||||
object-hash: "npm:^3.0.0"
|
||||
picocolors: "npm:^1.0.0"
|
||||
postcss: "npm:^8.4.23"
|
||||
postcss-import: "npm:^15.1.0"
|
||||
postcss-js: "npm:^4.0.1"
|
||||
postcss-load-config: "npm:^4.0.1"
|
||||
postcss-nested: "npm:^6.0.1"
|
||||
postcss-selector-parser: "npm:^6.0.11"
|
||||
resolve: "npm:^1.22.2"
|
||||
sucrase: "npm:^3.32.0"
|
||||
bin:
|
||||
tailwind: lib/cli.js
|
||||
tailwindcss: lib/cli.js
|
||||
checksum: 10c0/a57c0a9cdba9db19097e34e25b7e4690fab43f31ba200afc3bb9635a03036ca93e9884a17b616fb8a2486d57d2ecc9a06862ce4685b3ace57f7a67436e7594a0
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"tailwindcss@npm:^3.4.1":
|
||||
"tailwindcss@npm:3.4.17, tailwindcss@npm:^3.4.1":
|
||||
version: 3.4.17
|
||||
resolution: "tailwindcss@npm:3.4.17"
|
||||
dependencies:
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user