From 2599cb802f48e69d32fe4a2ab1f7945710b811f2 Mon Sep 17 00:00:00 2001 From: lichunn <269031597@qq.com> Date: Fri, 6 Feb 2026 15:26:35 +0800 Subject: [PATCH 01/19] =?UTF-8?q?fix=EF=BC=9ARefresh=20the=20page=20after?= =?UTF-8?q?=20logging=20in?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/composable/defaultGlobalService.ts | 36 +++++++++++-------- packages/common/composable/http/index.ts | 10 ++++++ packages/common/js/canvas.ts | 4 +-- 3 files changed, 33 insertions(+), 17 deletions(-) diff --git a/packages/common/composable/defaultGlobalService.ts b/packages/common/composable/defaultGlobalService.ts index 29a448fc4f..04838b7b29 100644 --- a/packages/common/composable/defaultGlobalService.ts +++ b/packages/common/composable/defaultGlobalService.ts @@ -1,4 +1,11 @@ -import { useMessage, defineService, getMetaApi, META_SERVICE } from '@opentiny/tiny-engine-meta-register' +import { + useCanvas, + useResource, + useMessage, + defineService, + getMetaApi, + META_SERVICE +} from '@opentiny/tiny-engine-meta-register' import { reactive, watch } from 'vue' const getBaseInfo = () => { @@ -55,24 +62,25 @@ const userState = reactive({ needToLogin: false }) +const { subscribe, publish } = useMessage() + const getLoginStatus = () => userState.needToLogin const setNeedToLogin = (value: boolean) => { userState.needToLogin = value + if (!value) { - const defaultTenantId = userState.userInfo.tenant?.[0]?.id - if (defaultTenantId) { - const currentUrl = new URL(window.location.href) - const currentTenant = getBaseInfo().tenantId - - const filterList = userState.userInfo.tenant.filter((item) => item.id === currentTenant) || [] - // 只有当tenant值不存在时才更新 - if (!filterList?.length) { - currentUrl.searchParams.set('tenant', String(defaultTenantId)) - window.history.replaceState(window.history.state, '', currentUrl.href) + watch( + useCanvas().isCanvasApiReady, + (ready) => { + if (ready) { + useResource().fetchResource() + } + }, + { + immediate: true } - } - window.location.reload() + ) } } @@ -104,8 +112,6 @@ const fetchAppInfo = (appId: string) => getMetaApi(META_SERVICE.Http).get(`/app- const fetchAppList = (platformId: string) => getMetaApi(META_SERVICE.Http).get(`/app-center/api/apps/list/${platformId}`) -const { subscribe, publish } = useMessage() - const postLocationHistoryChanged = (data: any) => publish({ topic: 'locationHistoryChanged', data }) /** diff --git a/packages/common/composable/http/index.ts b/packages/common/composable/http/index.ts index c67165a2e7..5ac9e39cf1 100644 --- a/packages/common/composable/http/index.ts +++ b/packages/common/composable/http/index.ts @@ -108,6 +108,12 @@ const whiteList = [ const notAuthList = ['app-center/api/chat/completions', 'app-center/api/ai/chat', 'app-center/api/ai/search'] const LoginErrorCode = ['CM004', 'CM005', 'CM006', 'CM007', 'CM336', 'CM339'] +// 新增:重置认证状态的函数 +const resetAuthState = () => { + isUnauthorized = false + abortControllers.clear() +} + // 创建 AbortController 并关联到请求 const createAbortController = (config) => { const controller = new AbortController() @@ -191,6 +197,10 @@ const requestHandler = (config) => { return new Promise(() => {}) } } else { + // 有 token 时重置认证状态 + if (isUnauthorized) { + resetAuthState() + } if (!notAuthList.some((url) => config.url.includes(url))) { config.headers.Authorization = `Bearer ${token}` } diff --git a/packages/common/js/canvas.ts b/packages/common/js/canvas.ts index 4fb1ddd19f..cb69c3f655 100644 --- a/packages/common/js/canvas.ts +++ b/packages/common/js/canvas.ts @@ -26,7 +26,7 @@ interface CanvasStatus { } export const getCanvasStatus = (data: Occupier | undefined): CanvasStatus => { - const globalState = getMetaApi(META_SERVICE.GlobalService).getState() + const userInfo = getMetaApi(META_SERVICE.GlobalService).getUserInfo() const isDemo = useResource().appSchemaState.isDemo let state = '' @@ -35,7 +35,7 @@ export const getCanvasStatus = (data: Occupier | undefined): CanvasStatus => { } else if (!data) { state = PAGE_STATUS.Release } else { - state = globalState.userInfo.id === data.id ? PAGE_STATUS.Occupy : PAGE_STATUS.Lock + state = userInfo.id === data.id ? PAGE_STATUS.Occupy : PAGE_STATUS.Lock } return { From c0b4a9e73d169df3f6e1f234775e1848cb5e1c51 Mon Sep 17 00:00:00 2001 From: lichunn <269031597@qq.com> Date: Mon, 9 Feb 2026 10:20:25 +0800 Subject: [PATCH 02/19] =?UTF-8?q?fix:=E6=9B=B4=E6=96=B0userinfo=E7=9A=84id?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/plugins/materials/src/composable/useResource.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/plugins/materials/src/composable/useResource.ts b/packages/plugins/materials/src/composable/useResource.ts index d8a57ebb45..f70a78714c 100644 --- a/packages/plugins/materials/src/composable/useResource.ts +++ b/packages/plugins/materials/src/composable/useResource.ts @@ -119,7 +119,7 @@ const initBlock = async (blockId: string) => { const initPageOrBlock = async () => { const { pageId, blockId } = getMetaApi(META_SERVICE.GlobalService).getBaseInfo() const pagePluginApi = getMetaApi(META_APP.AppManage) - const globalState = getMetaApi(META_SERVICE.GlobalService).getState() + const userInfo = getMetaApi(META_SERVICE.GlobalService).getUserInfo() if (pageId) { const data = await pagePluginApi.getPageById(pageId) @@ -137,7 +137,7 @@ const initPageOrBlock = async () => { const getPageInfo = () => { // 页面是否被他人锁定 (被锁定 且 非当前用户锁定) const isPageOccupierdByOthers = (page) => { - return page.meta?.occupier && page.meta.occupier.id !== globalState.userInfo.id + return page.meta?.occupier && page.meta.occupier.id !== userInfo.id } // 首页 const homePage = appSchemaState.pageTree.find((page) => page?.meta?.isHome) From 2e31c9b72f8dfd852c67ecb4fdc95f4feb6c4d26 Mon Sep 17 00:00:00 2001 From: lichunn <269031597@qq.com> Date: Mon, 9 Feb 2026 14:50:53 +0800 Subject: [PATCH 03/19] test: --- packages/toolbars/user/src/Main.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/toolbars/user/src/Main.vue b/packages/toolbars/user/src/Main.vue index a1b98748ea..f2603fb069 100644 --- a/packages/toolbars/user/src/Main.vue +++ b/packages/toolbars/user/src/Main.vue @@ -13,7 +13,7 @@ {{ userInfo.username }}
-
创建组织
+
创建组织22
From c5bb10511973033d4b725fd205f247ba85dc6caf Mon Sep 17 00:00:00 2001 From: lichunn <269031597@qq.com> Date: Mon, 9 Feb 2026 15:00:09 +0800 Subject: [PATCH 04/19] test: --- packages/toolbars/user/src/Main.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/toolbars/user/src/Main.vue b/packages/toolbars/user/src/Main.vue index f2603fb069..a1b98748ea 100644 --- a/packages/toolbars/user/src/Main.vue +++ b/packages/toolbars/user/src/Main.vue @@ -13,7 +13,7 @@ {{ userInfo.username }}
-
创建组织22
+
创建组织
From 99a6dc6308124f8ea169fdf2550f72b4d6e5cd01 Mon Sep 17 00:00:00 2001 From: lichunn <269031597@qq.com> Date: Fri, 13 Feb 2026 15:10:42 +0800 Subject: [PATCH 05/19] =?UTF-8?q?fix=EF=BC=9A=E7=99=BB=E5=BD=95=E6=A8=A1?= =?UTF-8?q?=E5=9D=97ui=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../design-core/src/login/ForgotPassword.vue | 22 ++++++++++++++-- packages/design-core/src/login/Index.vue | 26 ++++++++++++++----- packages/design-core/src/login/Login.vue | 12 ++------- packages/design-core/src/login/Register.vue | 5 ++-- .../design-core/src/login/RegisterSuccess.vue | 26 ++++++------------- 5 files changed, 52 insertions(+), 39 deletions(-) diff --git a/packages/design-core/src/login/ForgotPassword.vue b/packages/design-core/src/login/ForgotPassword.vue index c3f8bfbbc9..e75c5037d7 100644 --- a/packages/design-core/src/login/ForgotPassword.vue +++ b/packages/design-core/src/login/ForgotPassword.vue @@ -55,6 +55,9 @@ 提交 +
+ +
@@ -131,6 +134,10 @@ export default { } } + const toLogin = () => { + emit('changeStatus', useLogin().LOGIN) + } + watch( () => state.forgotData.password, () => { @@ -139,7 +146,8 @@ export default { ) return { state, - handleForgot + handleForgot, + toLogin } } } @@ -150,7 +158,7 @@ export default { color: #191919; font-size: 24px; font-weight: 600; - margin-bottom: 28px; + margin-bottom: 36px; } .pw-tips { @@ -170,6 +178,16 @@ export default { } } +.forgot-bottom { + display: flex; + justify-content: center; + font-size: 14px; + .to-login { + cursor: pointer; + color: #1476ff; + } +} + :deep(.tiny-form-item__content) { margin-left: 0 !important; } diff --git a/packages/design-core/src/login/Index.vue b/packages/design-core/src/login/Index.vue index 4354e56c8c..40b5c07f8c 100644 --- a/packages/design-core/src/login/Index.vue +++ b/packages/design-core/src/login/Index.vue @@ -61,12 +61,11 @@ export default { height: 100vh; background: linear-gradient(to top left, #e7f0ff, #fff); display: flex; - padding: 10%; + padding: 10% 10% 0 10%; .login-left { flex: 2; .login-img { max-height: 500px; - min-height: 298px; width: 100%; height: 100%; background-image: url(../../assets/login-bg.svg); @@ -83,15 +82,30 @@ export default { box-sizing: border-box; max-width: 440px; min-width: 340px; - max-height: 500px; - min-height: 298px; width: 100%; - height: 100%; background: #fff; border-radius: 12px; box-shadow: 0 8px 40px 0 #dce6f6; - padding: 48px 60px; + padding: 52px 60px 80px 60px; + max-height: 100%; + overflow-y: auto; } } + + :deep(.tiny-form-item__content) { + margin-left: 0 !important; + } + :deep(.tiny-button.tiny-button.tiny-button.tiny-button) { + width: 100%; + background: #595959; + height: 32px; + margin-top: 20px; + } + :deep(.tiny-input.tiny-input .tiny-input__inner.tiny-input__inner) { + height: 32px; + } + :deep(.tiny-form.tiny-form.tiny-form .tiny-form-item) { + margin-bottom: 20px; + } } diff --git a/packages/design-core/src/login/Login.vue b/packages/design-core/src/login/Login.vue index f880a487c7..8b986a16b0 100644 --- a/packages/design-core/src/login/Login.vue +++ b/packages/design-core/src/login/Login.vue @@ -93,15 +93,14 @@ export default { color: #191919; font-size: 24px; font-weight: 600; - margin-bottom: 28px; + margin-bottom: 36px; } .login-bottom { - margin-top: 16px; display: flex; justify-content: space-between; color: #1476ff; - margin-bottom: 32px; + font-size: 14px; div { cursor: pointer; } @@ -140,11 +139,4 @@ export default { cursor: pointer; } } -:deep(.tiny-form-item__content) { - margin-left: 0 !important; -} -:deep(.tiny-button.tiny-button.tiny-button.tiny-button) { - width: 100%; - background: #595959; -} diff --git a/packages/design-core/src/login/Register.vue b/packages/design-core/src/login/Register.vue index bbbd1e04bf..58e5d68fba 100644 --- a/packages/design-core/src/login/Register.vue +++ b/packages/design-core/src/login/Register.vue @@ -157,7 +157,7 @@ export default { color: #191919; font-size: 24px; font-weight: 600; - margin-bottom: 28px; + margin-bottom: 36px; } .pw-tips { @@ -178,11 +178,10 @@ export default { } .register-bottom { - margin-top: 16px; display: flex; justify-content: center; color: #808080; - margin-bottom: 32px; + font-size: 14px; .to-login { cursor: pointer; color: #1476ff; diff --git a/packages/design-core/src/login/RegisterSuccess.vue b/packages/design-core/src/login/RegisterSuccess.vue index f420ade21f..1cb95fed38 100644 --- a/packages/design-core/src/login/RegisterSuccess.vue +++ b/packages/design-core/src/login/RegisterSuccess.vue @@ -19,6 +19,7 @@ import { reactive, computed } from 'vue' import { TinyCheckbox, TinyButton } from '@opentiny/vue' import useLogin from './js/useLogin' +import { useModal } from '@opentiny/tiny-engine-meta-register' export default { components: { @@ -37,14 +38,13 @@ export default { emit('changeStatus', useLogin().LOGIN) } - const copy = () => { - const textarea = document.createElement('textarea') - - textarea.value = `${publicKey.value}` - document.body.appendChild(textarea) - textarea.select() - document.execCommand('copy') - document.body.removeChild(textarea) + const copy = async () => { + try { + await navigator.clipboard.writeText(publicKey.value) + useModal().message({ message: '复制成功', status: 'success' }) + } catch (err) { + useModal().message({ message: '复制失败', status: 'error' }) + } } return { @@ -94,14 +94,4 @@ export default { margin-bottom: 20px; } } - -:deep(.tiny-button.tiny-button.tiny-button.tiny-button) { - margin-top: 20px; - background: #fff; - color: #191919; - border: 1px solid #c9c9c9; -} -:deep(.tiny-button.tiny-button.tiny-button.tiny-button.tiny-button--primary:not(.is-disabled)):hover { - background: #fff; -} From 165377678c8fe0ea4bffdec0f1ed98cd63f1b930 Mon Sep 17 00:00:00 2001 From: lichunn <269031597@qq.com> Date: Fri, 13 Feb 2026 15:58:08 +0800 Subject: [PATCH 06/19] fix: input font size --- packages/design-core/src/login/Index.vue | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/design-core/src/login/Index.vue b/packages/design-core/src/login/Index.vue index 40b5c07f8c..59148ea848 100644 --- a/packages/design-core/src/login/Index.vue +++ b/packages/design-core/src/login/Index.vue @@ -100,10 +100,16 @@ export default { background: #595959; height: 32px; margin-top: 20px; + font-size: 14px; } :deep(.tiny-input.tiny-input .tiny-input__inner.tiny-input__inner) { height: 32px; + font-size: 14px; } + :deep(.tiny-input.tiny-input .tiny-input__inner.tiny-input__inner)::placeholder { + font-size: 14px; + } + :deep(.tiny-form.tiny-form.tiny-form .tiny-form-item) { margin-bottom: 20px; } From dd9554ff5655a8d08bf44ae9f620c1970fe4c29d Mon Sep 17 00:00:00 2001 From: lichunn <269031597@qq.com> Date: Mon, 30 Mar 2026 16:20:35 +0800 Subject: [PATCH 07/19] =?UTF-8?q?feat:=E8=BF=81=E7=A7=BBBubble=E5=92=8CSen?= =?UTF-8?q?der=E7=BB=84=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/plugins/robot/package.json | 6 +- packages/plugins/robot/src/Main.vue | 4 +- .../robot/src/components/chat/RobotChat.vue | 97 ++++++++++++++----- .../components/renderers/MarkdownRenderer.vue | 8 +- packages/plugins/robot/src/constants/index.ts | 1 + .../plugins/robot/src/constants/status.ts | 28 ++++++ 6 files changed, 111 insertions(+), 33 deletions(-) create mode 100644 packages/plugins/robot/src/constants/status.ts diff --git a/packages/plugins/robot/package.json b/packages/plugins/robot/package.json index 02853706e1..0df1040c0c 100644 --- a/packages/plugins/robot/package.json +++ b/packages/plugins/robot/package.json @@ -29,9 +29,9 @@ "@opentiny/tiny-engine-common": "workspace:*", "@opentiny/tiny-engine-meta-register": "workspace:*", "@opentiny/tiny-engine-utils": "workspace:*", - "@opentiny/tiny-robot": "0.3.1", - "@opentiny/tiny-robot-kit": "0.3.1", - "@opentiny/tiny-robot-svgs": "0.3.1", + "@opentiny/tiny-robot": "0.4.0", + "@opentiny/tiny-robot-kit": "0.4.0", + "@opentiny/tiny-robot-svgs": "0.4.0", "@opentiny/tiny-schema-renderer": "1.0.0-beta.6", "@vueuse/core": "^9.13.0", "dompurify": "^3.0.1", diff --git a/packages/plugins/robot/src/Main.vue b/packages/plugins/robot/src/Main.vue index 4eeba186b8..c4df1c993c 100644 --- a/packages/plugins/robot/src/Main.vue +++ b/packages/plugins/robot/src/Main.vue @@ -18,7 +18,7 @@ v-model:fullscreen="fullscreen" v-model:show="robotVisible" v-model:input="inputMessage" - :status="chatStatus" + :status="mappedStatus" :prompt-items="promptItems" :bubble-renderers="bubbleRenderers" :allowFiles="isVisualModel && robotSettingState.chatMode === ChatMode.Agent" @@ -147,7 +147,7 @@ const showTeleport = ref(false) const showSetting = ref(false) const { - chatStatus, + mappedStatus, inputMessage, messages, changeChatMode, diff --git a/packages/plugins/robot/src/components/chat/RobotChat.vue b/packages/plugins/robot/src/components/chat/RobotChat.vue index 155e2f3198..7cd402e1d9 100644 --- a/packages/plugins/robot/src/components/chat/RobotChat.vue +++ b/packages/plugins/robot/src/components/chat/RobotChat.vue @@ -22,8 +22,12 @@ @item-click="handlePromptItemClick" >
- - + + + +
@@ -39,9 +43,6 @@ :showWordLimit="false" @submit="handleSendMessage" @cancel="handleAbortRequest" - :allowFiles="selectedAttachments.length < 1 && props.allowFiles" - uploadTooltip="支持上传1张图片" - @files-selected="handleSingleFilesSelected" > - @@ -74,11 +89,16 @@ import { TrSender, TrWelcome, TrAttachments, + UploadButton, + VoiceButton, + BubbleRendererMatchPriority, type BubbleRoleConfig, type PromptProps, - type RawFileAttachment + type RawFileAttachment, + type BubbleContentRendererMatch } from '@opentiny/tiny-robot' -import { type ChatMessage, GeneratingStatus } from '@opentiny/tiny-robot-kit' +import { type ChatMessage } from '@opentiny/tiny-robot-kit' +import { GeneratingStatus } from '../../constants/status' import { LoadingRenderer, MarkdownRenderer, ImgRenderer } from '../renderers' import { useNotify } from '@opentiny/tiny-engine-meta-register' @@ -123,6 +143,24 @@ watch( } ) +const contentRendererMatches: BubbleContentRendererMatch[] = [ + { + priority: BubbleRendererMatchPriority.NORMAL, + find: (message: any) => !Boolean(message.loading) && message.content, + renderer: MarkdownRenderer + }, + { + priority: BubbleRendererMatchPriority.NORMAL, + find: (message: any) => message?.content?.[0]?.type === 'img' || message?.content?.[0]?.type === 'image', + renderer: ImgRenderer + }, + { + priority: BubbleRendererMatchPriority.LOADING, + find: (message) => Boolean(message.loading), + renderer: LoadingRenderer + } +] + // 处理文件选择事件 const handleSingleFilesSelected = (files: File[] | null, retry = false) => { if (!files?.length) return @@ -172,38 +210,43 @@ const handleSingleFileRetry = (file: RawFileAttachment) => { handleSingleFilesSelected([file.rawFile], true) } +// 语音输入处理 +const handleSpeechStart = () => {} + +const handleSpeechEnd = (transcript: string) => { + if (transcript) { + inputMessage.value = transcript + } +} + +const handleSpeechError = () => { + useNotify({ + type: 'error', + message: '语音识别失败,请重试' + }) +} + const getSvgIcon = (name: string, style?: CSSProperties) => { return h(resolveComponent('svg-icon'), { name, style: { fontSize: '32px', ...style } }) } const aiAvatar = getSvgIcon('AI') const welcomeIcon = getSvgIcon('AI', { fontSize: '48px' }) -const contentRenderers = computed(() => ({ - markdown: MarkdownRenderer, - loading: LoadingRenderer, - img: ImgRenderer, - ...props.bubbleRenderers -})) - -const roles: Record = { +const roleConfigs: Record = { assistant: { placement: 'start', avatar: aiAvatar, - contentRenderer: MarkdownRenderer, - customContentField: 'renderContent' + contentResolver: (message: any) => message.renderContent || message.content }, user: { placement: 'end', - contentRenderer: MarkdownRenderer, - customContentField: 'renderContent' + contentResolver: (message: any) => message.renderContent || message.content }, system: { hidden: true } } -const senderRef = ref | null>(null) - // 发送消息 const handleSendMessage = async (content: string) => { const messageContent = content || inputMessage.value @@ -362,13 +405,13 @@ const handlePromptItemClick = (ev: unknown, item: { description?: string }) => { } } :deep([data-role='user']) { - --tr-bubble-content-bg: var(--tr-color-primary-light); + --tr-bubble-box-bg: var(--tr-color-primary-light); } } &.fullscreen { :deep([data-role='assistant']) { - --tr-bubble-content-bg: transparent; + --tr-bubble-box-bg: transparent; .tr-bubble__content { padding: 8px 0 0; } @@ -471,4 +514,10 @@ const handlePromptItemClick = (ev: unknown, item: { description?: string }) => { .robot-bubble-list { height: 100%; } + +.aborted { + margin-top: 6px; + font-size: 12px; + opacity: 0.7; +} diff --git a/packages/plugins/robot/src/components/renderers/MarkdownRenderer.vue b/packages/plugins/robot/src/components/renderers/MarkdownRenderer.vue index 90fbb860b5..14fbbb6678 100644 --- a/packages/plugins/robot/src/components/renderers/MarkdownRenderer.vue +++ b/packages/plugins/robot/src/components/renderers/MarkdownRenderer.vue @@ -28,9 +28,9 @@ hljs.registerLanguage('xml', xml) hljs.registerLanguage('shell', shell) const props = defineProps({ - content: { - type: String, - required: true + message: { + type: Object as () => Options, + default: () => ({}) }, theme: { type: String as () => 'light' | 'dark', @@ -66,7 +66,7 @@ const markdownIt = new MarkdownIt({ }) const renderContent = computed(() => { - return DOMPurify.sanitize(markdownIt.render(props.content)) + return DOMPurify.sanitize(markdownIt.render(props.message.content)) }) diff --git a/packages/plugins/robot/src/constants/index.ts b/packages/plugins/robot/src/constants/index.ts index 504e83352d..434f4f7d1f 100644 --- a/packages/plugins/robot/src/constants/index.ts +++ b/packages/plugins/robot/src/constants/index.ts @@ -1 +1,2 @@ export * from './model-config' +export * from './status' diff --git a/packages/plugins/robot/src/constants/status.ts b/packages/plugins/robot/src/constants/status.ts new file mode 100644 index 0000000000..c58b0bee94 --- /dev/null +++ b/packages/plugins/robot/src/constants/status.ts @@ -0,0 +1,28 @@ +/** + * 状态常量定义 + * 保持与 tiny-robot v0.3.x 兼容,用于状态管理 + */ + +/** + * 消息状态枚举 + */ +export enum STATUS { + PENDING = 'pending', + STREAMING = 'streaming', + FINISHED = 'finished', + ERROR = 'error', + ABORTED = 'aborted' +} + +/** + * 生成中的状态列表 + */ +export const GeneratingStatus: STATUS[] = [STATUS.PENDING, STATUS.STREAMING] + +/** + * 消息状态接口 + */ +export interface MessageState { + status: STATUS + errorMsg?: string +} From f2e165f95e1b11bcf38f4e03a9282e6f3aedd44b Mon Sep 17 00:00:00 2001 From: lichunn <269031597@qq.com> Date: Thu, 14 May 2026 18:01:18 +0800 Subject: [PATCH 08/19] =?UTF-8?q?feat=EF=BC=9A=E8=BF=81=E7=A7=BBtiny-robot?= =?UTF-8?q?=E7=9A=84useMessage=E5=92=8CuseConversation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../robot/src/components/chat/RobotChat.vue | 55 +++- .../components/header-extension/History.vue | 8 +- .../src/composables/core/useConversation.ts | 306 +++++++++++++++--- .../src/composables/core/useMessageStream.ts | 40 ++- .../src/composables/features/useToolCalls.ts | 17 +- .../src/composables/modes/useAgentMode.ts | 7 +- .../src/composables/modes/useChatMode.ts | 79 +++-- .../plugins/robot/src/composables/useChat.ts | 94 ++++-- .../plugins/robot/src/constants/status.ts | 2 +- .../plugins/robot/src/services/aiClient.ts | 17 +- 10 files changed, 458 insertions(+), 167 deletions(-) diff --git a/packages/plugins/robot/src/components/chat/RobotChat.vue b/packages/plugins/robot/src/components/chat/RobotChat.vue index 7cd402e1d9..07ac66bdab 100644 --- a/packages/plugins/robot/src/components/chat/RobotChat.vue +++ b/packages/plugins/robot/src/components/chat/RobotChat.vue @@ -23,7 +23,13 @@ > - + @@ -91,6 +97,7 @@ import { TrAttachments, UploadButton, VoiceButton, + BubbleRenderers, BubbleRendererMatchPriority, type BubbleRoleConfig, type PromptProps, @@ -143,10 +150,26 @@ watch( } ) -const contentRendererMatches: BubbleContentRendererMatch[] = [ +const contentRendererMatches = computed(() => [ + { + priority: BubbleRendererMatchPriority.LOADING, + find: (message) => Boolean(message.loading), + renderer: LoadingRenderer + }, + { + priority: BubbleRendererMatchPriority.NORMAL, + find: (message: any, content: any) => content?.type === 'tool' && message.tool_calls?.length, + renderer: BubbleRenderers.Tools + }, + { + priority: BubbleRendererMatchPriority.NORMAL, + find: (message: any, content: any) => + content?.type !== 'tool' && typeof message.reasoning_content === 'string' && message.reasoning_content, + renderer: BubbleRenderers.Reasoning + }, { priority: BubbleRendererMatchPriority.NORMAL, - find: (message: any) => !Boolean(message.loading) && message.content, + find: (message: any) => !message.loading && message.content, renderer: MarkdownRenderer }, { @@ -154,12 +177,12 @@ const contentRendererMatches: BubbleContentRendererMatch[] = [ find: (message: any) => message?.content?.[0]?.type === 'img' || message?.content?.[0]?.type === 'image', renderer: ImgRenderer }, - { - priority: BubbleRendererMatchPriority.LOADING, - find: (message) => Boolean(message.loading), - renderer: LoadingRenderer - } -] + ...Object.entries(props.bubbleRenderers).map(([type, renderer]) => ({ + priority: BubbleRendererMatchPriority.NORMAL, + find: (_message: any, content: any) => content?.type === type, + renderer + })) +]) // 处理文件选择事件 const handleSingleFilesSelected = (files: File[] | null, retry = false) => { @@ -232,15 +255,21 @@ const getSvgIcon = (name: string, style?: CSSProperties) => { const aiAvatar = getSvgIcon('AI') const welcomeIcon = getSvgIcon('AI', { fontSize: '48px' }) +const resolveMessageContent = (message: any) => { + if (Array.isArray(message.renderContent) && message.renderContent.length > 0) { + return message.renderContent + } + + return message.content +} + const roleConfigs: Record = { assistant: { placement: 'start', - avatar: aiAvatar, - contentResolver: (message: any) => message.renderContent || message.content + avatar: aiAvatar }, user: { - placement: 'end', - contentResolver: (message: any) => message.renderContent || message.content + placement: 'end' }, system: { hidden: true diff --git a/packages/plugins/robot/src/components/header-extension/History.vue b/packages/plugins/robot/src/components/header-extension/History.vue index efba3b013d..104f11f295 100644 --- a/packages/plugins/robot/src/components/header-extension/History.vue +++ b/packages/plugins/robot/src/components/header-extension/History.vue @@ -28,7 +28,7 @@