From ad79e5113e7db722d927ee156bc675594313d1d6 Mon Sep 17 00:00:00 2001 From: Kven <2955163637@qq.com> Date: Fri, 16 May 2025 23:02:11 +0800 Subject: [PATCH] =?UTF-8?q?feat(@vben/common-ui):=20=E4=BC=98=E5=8C=96=20A?= =?UTF-8?q?I=20=E5=B7=A5=E5=85=B7=E6=A8=A1=E5=9D=97=E5=8A=9F=E8=83=BD?= =?UTF-8?q?=E5=92=8C=E8=B7=AF=E7=94=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 修改路由路径,统一为 /ai前缀 - 新增表单弹窗组件,用于输入项目信息 - 优化聊天消息展示逻辑,支持文档预览和下载 - 新增个人页面路由和组件 - 调整模板数据结构和样式 --- apps/web-antd/src/api/core/chatflow.ts | 14 + apps/web-antd/src/api/core/index.ts | 1 + apps/web-antd/src/api/core/spider.ts | 18 ++ .../src/router/routes/modules/aiFlow.ts | 6 +- apps/web-antd/src/views/home/index.vue | 6 +- apps/web-antd/src/views/ppt/index.vue | 8 +- apps/web-antd/src/views/spider/index.vue | 6 +- apps/web-antd/src/views/word/index.vue | 42 ++- .../common-ui/src/ui/ppt/ppt-list-view.vue | 2 +- .../common-ui/src/ui/ppt/ppt-perview.vue | 2 +- .../common-ui/src/ui/ppt/ppt-work-view.vue | 20 +- .../src/ui/spider/spider-list-view.vue | 26 +- .../src/ui/spider/spider-work-view.vue | 140 ++++----- .../common-ui/src/ui/word/word-list-view.vue | 2 +- .../common-ui/src/ui/word/word-work-view.vue | 289 +++++++++++++----- 15 files changed, 378 insertions(+), 204 deletions(-) create mode 100644 apps/web-antd/src/api/core/spider.ts diff --git a/apps/web-antd/src/api/core/chatflow.ts b/apps/web-antd/src/api/core/chatflow.ts index 50086e9..45fc7ab 100644 --- a/apps/web-antd/src/api/core/chatflow.ts +++ b/apps/web-antd/src/api/core/chatflow.ts @@ -40,6 +40,13 @@ export namespace ChatflowApi { conversationId: string; appId: string; } + + export interface ChatMessageParams { + userId: string; + conversationId: string; + firstId: string; + limit: string; + } } // 聊天流 @@ -78,3 +85,10 @@ export function deleteChatflow(data: ChatflowApi.deleteParams) { export function getChatParameters(appId: string) { return requestClient.get(`/v1/chat/parameters/${appId}`); } + +export function getChatflowMessage( + appId: string, + data: ChatflowApi.ChatMessageParams, +) { + return requestClient.post(`/v1/chat/messages/${appId}`, data); +} diff --git a/apps/web-antd/src/api/core/index.ts b/apps/web-antd/src/api/core/index.ts index 1243365..c39d529 100644 --- a/apps/web-antd/src/api/core/index.ts +++ b/apps/web-antd/src/api/core/index.ts @@ -5,5 +5,6 @@ export * from './log'; export * from './menu'; export * from './role'; export * from './server'; +export * from './spider'; export * from './user'; export * from './workflow'; diff --git a/apps/web-antd/src/api/core/spider.ts b/apps/web-antd/src/api/core/spider.ts new file mode 100644 index 0000000..182d5a8 --- /dev/null +++ b/apps/web-antd/src/api/core/spider.ts @@ -0,0 +1,18 @@ +import { requestClient } from '#/api/request'; + +// 全国公共资源爬虫 +export function runSpider(data: any) { + return requestClient.post(`/spider/run`, { data }); +} + +export function getSpiderStatus() { + return requestClient.get(`/spider/status`); +} + +export function getSpiderLogs() { + return requestClient.get(`/v1/workflow/list`); +} + +export function stopSpider() { + return requestClient.get(`/spider/stop`); +} diff --git a/apps/web-antd/src/router/routes/modules/aiFlow.ts b/apps/web-antd/src/router/routes/modules/aiFlow.ts index 53fecbd..2566224 100644 --- a/apps/web-antd/src/router/routes/modules/aiFlow.ts +++ b/apps/web-antd/src/router/routes/modules/aiFlow.ts @@ -15,7 +15,7 @@ const routes: RouteRecordRaw[] = [ children: [ { name: 'spider', - path: '/aiflow/spider', + path: '/ai/spider', component: () => import('#/views/spider/index.vue'), meta: { icon: SvgSpider, @@ -26,7 +26,7 @@ const routes: RouteRecordRaw[] = [ }, { name: 'word', - path: '/aiflow/word', + path: '/ai/word', component: () => import('#/views/word/index.vue'), meta: { icon: SvgWord, @@ -37,7 +37,7 @@ const routes: RouteRecordRaw[] = [ }, { name: 'ppt', - path: '/aiflow/ppt', + path: '/ai/ppt', component: () => import('#/views/ppt/index.vue'), meta: { icon: SvgPPT, diff --git a/apps/web-antd/src/views/home/index.vue b/apps/web-antd/src/views/home/index.vue index 6aa7953..adc29db 100644 --- a/apps/web-antd/src/views/home/index.vue +++ b/apps/web-antd/src/views/home/index.vue @@ -11,19 +11,19 @@ const items: WorkflowItem[] = [ icon: SvgSpider, title: '数据抓取工具', description: '自动抓取', - path: '/spider', + path: '/ai/spider', }, { icon: SvgWord, title: 'Word文档生成工具', description: '自动生成word文档', - path: '/word', + path: '/ai/word', }, { icon: SvgPPT, title: 'PPT生成工具', description: '自动生成PPT文档', - path: '/ppt', + path: '/ai/ppt', }, ]; const router = useRouter(); diff --git a/apps/web-antd/src/views/ppt/index.vue b/apps/web-antd/src/views/ppt/index.vue index 32cad99..c68b88f 100644 --- a/apps/web-antd/src/views/ppt/index.vue +++ b/apps/web-antd/src/views/ppt/index.vue @@ -5,7 +5,7 @@ import { onMounted, reactive, ref } from 'vue'; import { PptListView, PptWorkView } from '@vben/common-ui'; -import { message } from 'ant-design-vue'; +import { notification } from 'ant-design-vue'; import { getWorkflowInfo, getWorkflowList, sendWorkflow } from '#/api'; @@ -58,7 +58,11 @@ async function handleClick(item: PPTTempItem) { } async function handleClickMode(item: PPTTempItem) { - message.success(`已选取${item.name}为模板`); + notification.success({ + message: `${item.name}`, + description: '已选取', + duration: 3, + }); temp = item; } diff --git a/apps/web-antd/src/views/spider/index.vue b/apps/web-antd/src/views/spider/index.vue index 905b2a5..a567ae3 100644 --- a/apps/web-antd/src/views/spider/index.vue +++ b/apps/web-antd/src/views/spider/index.vue @@ -7,8 +7,8 @@ import { SpiderListView, SpiderWorkView } from '@vben/common-ui'; import { notification } from 'ant-design-vue'; -import { getWorkflowInfo, getWorkflowList, sendWorkflow } from '#/api'; - +import { getWorkflowInfo, getWorkflowList, runSpider } from '#/api'; +// sendWorkflow interface ResultItem { key: number; role: 'ai' | 'user'; @@ -82,7 +82,7 @@ async function handleClickMode(item: PPTTempItem) { diff --git a/apps/web-antd/src/views/word/index.vue b/apps/web-antd/src/views/word/index.vue index 4837790..bb61cfa 100644 --- a/apps/web-antd/src/views/word/index.vue +++ b/apps/web-antd/src/views/word/index.vue @@ -9,11 +9,19 @@ import { message, notification } from 'ant-design-vue'; import { deleteChatflow, + getChatflowMessage, getChatList, getChatParameters, sendChatflow, } from '#/api'; +interface ResultItem { + key: number; + role: 'ai' | 'user'; + content: string; + footer?: any; +} + let temp = reactive({ id: 'baca08c1-e92b-4dc9-a445-3584803f54d4', name: '海南职创申报书生成', @@ -54,11 +62,12 @@ function handleClickMode(item: WordTempItem) { temp = item; getParameters(temp.id); } +const itemMessage = ref([]); async function deleteLog(item: any) { const res = await deleteChatflow({ appId: temp.id, - id: item, + conversationId: item, userId: '1562', }); if (res.code === 0) { @@ -74,9 +83,33 @@ async function getParameters(id: string) { params.value = res.suggestedQuestions; } -function handleClick(item: WordTempItem) { - message.error('暂不支持查看历史'); - temp = item; +async function handleClick(item: string) { + const res = await getChatflowMessage(temp.id, { + userId: '1562', + firstId: '', + conversationId: item, + limit: '', + }); + itemMessage.value = []; + if (res.data.length > 0) { + res.data.forEach((msg) => { + if (msg.inputs) { + itemMessage.value.push({ + key: itemMessage.value.length + 1, + role: 'user', // 'user' or 'ai' + content: msg.inputs.projectName, + footer: msg.footer, + }); + } + if (msg.answer) { + itemMessage.value.push({ + key: itemMessage.value.length + 1, + role: 'ai', + content: msg.answer, + }); + } + }); + } } onMounted(() => { @@ -101,6 +134,7 @@ onMounted(() => { :item="temp" :params-data="params" :run-chatflow="sendChatflow" + :item-message="itemMessage" /> diff --git a/packages/effects/common-ui/src/ui/ppt/ppt-list-view.vue b/packages/effects/common-ui/src/ui/ppt/ppt-list-view.vue index 7562aa1..4a13e08 100644 --- a/packages/effects/common-ui/src/ui/ppt/ppt-list-view.vue +++ b/packages/effects/common-ui/src/ui/ppt/ppt-list-view.vue @@ -101,7 +101,7 @@ const openKeys = ref([]); v-model:selected-keys="selectedKeys" mode="vertical" class="mode" - :items="items" + :items="itemsData" @click="handleMenuClick" /> diff --git a/packages/effects/common-ui/src/ui/ppt/ppt-perview.vue b/packages/effects/common-ui/src/ui/ppt/ppt-perview.vue index cc99f5b..9131f1f 100644 --- a/packages/effects/common-ui/src/ui/ppt/ppt-perview.vue +++ b/packages/effects/common-ui/src/ui/ppt/ppt-perview.vue @@ -37,7 +37,7 @@ const [Drawer, drawerApi] = useVbenDrawer({ const data = drawerApi.getData>(); if (data) { isLoading.value = true; // 开始加载文档,开启加载状态 - pptx.value = `/pptx/${data}`; // 更新 docx 的值 + pptx.value = `/pptx/${data}`; // 更新 pptx 的值 } // url.value = drawerApi.getData>(); } diff --git a/packages/effects/common-ui/src/ui/ppt/ppt-work-view.vue b/packages/effects/common-ui/src/ui/ppt/ppt-work-view.vue index 204cf17..2140f31 100644 --- a/packages/effects/common-ui/src/ui/ppt/ppt-work-view.vue +++ b/packages/effects/common-ui/src/ui/ppt/ppt-work-view.vue @@ -14,8 +14,8 @@ import { PaperClipOutlined, UserOutlined, } from '@ant-design/icons-vue'; -import { Badge, Button, Flex, Typography } from 'ant-design-vue'; -import { Attachments, Bubble, Sender } from 'ant-design-x-vue'; +import { Badge, Button, Flex, Space, Typography } from 'ant-design-vue'; +import { Attachments, Bubble, Sender, Welcome } from 'ant-design-x-vue'; import WordPreview from '../word/word-preview.vue'; @@ -342,7 +342,7 @@ watch( h( Button, { - size: 'nomarl', + size: 'normal', type: 'primary', style: { marginLeft: '10px', @@ -373,8 +373,22 @@ watch( + + + { - - - - - - - - - - - - - - - - - - 数据信息列表 { v-model:selected-keys="selectedKeys" mode="vertical" class="mode" - :items="items" + :items="itemsData" @click="handleMenuClick" /> - 会话 + 获取记录 diff --git a/packages/effects/common-ui/src/ui/spider/spider-work-view.vue b/packages/effects/common-ui/src/ui/spider/spider-work-view.vue index 12d2e23..3081921 100644 --- a/packages/effects/common-ui/src/ui/spider/spider-work-view.vue +++ b/packages/effects/common-ui/src/ui/spider/spider-work-view.vue @@ -9,7 +9,6 @@ import { h, ref, watch } from 'vue'; import { useVbenDrawer } from '@vben-core/popup-ui'; import { - Button, Card, CardContent, CardFooter, @@ -18,8 +17,15 @@ import { } from '@vben-core/shadcn-ui'; import { UserOutlined } from '@ant-design/icons-vue'; -import { Flex, message, RangePicker } from 'ant-design-vue'; -import { Attachments, Bubble } from 'ant-design-x-vue'; +import { + Button, + Flex, + message, + notification, + RangePicker, + Space, +} from 'ant-design-vue'; +import { Attachments, Bubble, Welcome } from 'ant-design-x-vue'; import dayjs, { Dayjs } from 'dayjs'; import SpiderPreview from './spider-preview.vue'; @@ -78,40 +84,7 @@ const props = withDefaults(defineProps(), { }), }); -// const styles = computed(() => { -// return { -// 'placeholder': { -// 'padding-top': '32px', -// 'text-align': 'left', -// 'flex': 1, -// }, -// } as const -// }) - -// const placeholderNode = computed(() => h( -// Space, -// { direction: "vertical", size: 16, style: styles.value.placeholder }, -// [ -// h( -// Welcome, -// { -// variant: "borderless", -// icon: "https://mdn.alipayobjects.com/huamei_iwk9zp/afts/img/A*s5sNRo5LjfQAAAAAAAAAAAAADgCCAQ/fmt.webp", -// title: "Hello, I'm Ant Design X", -// description: "Base on Ant Design, AGI product interface solution, create a better intelligent vision~", -// extra: h(Space, {}, [h(Button, { icon: h(ShareAltOutlined) }), h(Button, { icon: h(EllipsisOutlined) })]), -// } -// ), -// ] -// )) - const resultItems = ref([]); -// const items = computed(() => { -// if (resultItems.value.length === 0) { -// return [{ content: placeholderNode, variant: 'borderless' }] -// } -// return resultItems -// }) const [PreviewDrawer, previewDrawerApi] = useVbenDrawer({ // 连接抽离的组件 @@ -162,24 +135,36 @@ const startFetching = async () => { } try { + notification.info({ + message: '正在获取中...', + duration: 3, + }); const res = await props.runSpider( { - appid: props.item.id, - }, - { - userId: '1562', - conversationId: '', - files: [], - inputs: { - publish_start_time, - publish_end_time, - }, + publish_start_time, + publish_end_time, + llm_api_key: '77c068fd-d5b6-4c33-97d8-db5511a09b26', }, + // { + // appid: props.item.id, + // }, + // { + // userId: '1562', + // conversationId: '', + // files: [], + // inputs: { + // publish_start_time, + // publish_end_time, + // }, + // }, ); if (res.data.outputs.result) { // 保存抓取结果 fetchResult.value = res.data.outputs.result; - message.success('抓取成功'); + notification.success({ + message: '获取成功', + duration: 3, + }); const { result } = res.data.outputs; const filename = ref(''); @@ -209,7 +194,7 @@ const startFetching = async () => { h( Button, { - size: 'nomarl', + size: 'normal', type: 'primary', style: { marginLeft: '10px', @@ -217,7 +202,7 @@ const startFetching = async () => { onClick: () => { // 创建隐藏的 标签用于触发下载 const link = document.createElement('a'); - link.href = result; // 设置下载链接 + link.href = msg.content; // 设置下载链接 link.download = filename; // 设置下载文件名 document.body.append(link); // 将 标签添加到页面中 link.click(); // 触发点击事件开始下载 @@ -234,31 +219,13 @@ const startFetching = async () => { message.error('抓取无结果'); } } catch (error) { - console.error(error); + message.error(`${error}`); } // 模拟抓取完成 isFetching.value = false; }; -// 下载文件逻辑 -const downloadFile = () => { - if (!fetchResult.value || !fetchResult.value.startsWith('http')) { - message.error('无效的文件链接'); - return; - } - - const fileUrl = fetchResult.value; - const fileName = decodeURIComponent( - fileUrl.slice(Math.max(0, fileUrl.lastIndexOf('/') + 1)), - ); - - const link = document.createElement('a'); - link.href = fileUrl; - link.download = fileName; - link.click(); -}; - // 列表角色 const roles: BubbleListProps['roles'] = { user: { @@ -333,7 +300,7 @@ watch( h( Button, { - size: 'nomarl', + size: 'normal', type: 'primary', onClick: () => { openPreviewDrawer('right', filename); @@ -344,7 +311,7 @@ watch( h( Button, { - size: 'nomarl', + size: 'normal', type: 'primary', style: { marginLeft: '10px', @@ -373,10 +340,27 @@ watch( - + + + + {{ isFetching ? '抓取中...' : '开始抓取' }} - - 下载文件 - + + + + + + + diff --git a/packages/effects/common-ui/src/ui/word/word-list-view.vue b/packages/effects/common-ui/src/ui/word/word-list-view.vue index 62d01c1..8abdd09 100644 --- a/packages/effects/common-ui/src/ui/word/word-list-view.vue +++ b/packages/effects/common-ui/src/ui/word/word-list-view.vue @@ -96,7 +96,7 @@ const openKeys = ref([]); v-model:selected-keys="selectedKeys" mode="vertical" class="mode" - :items="items" + :items="itemsData" @click="handleMenuClick" /> diff --git a/packages/effects/common-ui/src/ui/word/word-work-view.vue b/packages/effects/common-ui/src/ui/word/word-work-view.vue index c28df8f..60abaf2 100644 --- a/packages/effects/common-ui/src/ui/word/word-work-view.vue +++ b/packages/effects/common-ui/src/ui/word/word-work-view.vue @@ -11,7 +11,8 @@ import type { WordTempItem } from './typing'; import { computed, h, ref, watch } from 'vue'; -import { useVbenDrawer } from '@vben-core/popup-ui'; +import { useVbenForm } from '@vben-core/form-ui'; +import { useVbenDrawer, useVbenModal } from '@vben-core/popup-ui'; import { CloudUploadOutlined, @@ -19,7 +20,14 @@ import { UserOutlined, } from '@ant-design/icons-vue'; // import type { VNode } from 'vue'; -import { Badge, Button, Flex, message, Typography } from 'ant-design-vue'; +import { + Badge, + Button, + Flex, + message, + Space, + Typography, +} from 'ant-design-vue'; import { Attachments, Bubble, @@ -27,15 +35,21 @@ import { Sender, useXAgent, useXChat, + Welcome, } from 'ant-design-x-vue'; import markdownit from 'markdown-it'; import WordPreview from './word-preview.vue'; +interface ResultItem { + key: number; + role: 'ai' | 'user'; + content: string; + footer?: any; +} interface WorkflowParams { appid: string; } - interface WorkflowContext { userId: string; conversationId: string; @@ -45,7 +59,6 @@ interface WorkflowContext { }; files: []; } - interface WorkflowResult { conversationId: string; answer: {}; @@ -55,8 +68,8 @@ interface WorkflowResult { }; }; } - interface Props { + itemMessage?: ResultItem; item?: WordTempItem; paramsData?: {}; runChatflow?: ( @@ -69,6 +82,7 @@ defineOptions({ name: 'PlaygroundIndependentSetup' }); const props = withDefaults(defineProps(), { item: () => null, + itemMessage: () => null, paramsData: () => null, runChatflow: () => async () => ({ data: { @@ -81,13 +95,6 @@ const props = withDefaults(defineProps(), { // const { token } = theme.useToken(); -interface ResultItem { - key: number; - role: 'ai' | 'user'; - content: string; - footer?: any; -} - // 初始化 markdown 解析器 const md = markdownit({ html: true, breaks: true }); @@ -231,11 +238,20 @@ watch( ); // ==================== Event ==================== -// function onSubmit(nextContent: string) { -// if (!nextContent) return; -// onRequest(nextContent); -// content.value = ''; -// } + +// 判断是否是以 .docx 结尾的 URL +function isDocxURL(str: string): boolean { + return str.endsWith('.docx'); +} + +// 使用正则提取 /static/ 后面的文件名部分 +function extractDocxFilename(url: string): null | string { + const match = url.match(/\/static\/([a-f0-9-]+\.docx)$/i); + if (match) { + return match[1]; // 返回类似:9e1c421e-991c-411f-8176-6350a97e70f3.docx + } + return null; +} const startFetching = async () => { // 项目名称为空时阻止发送并提示 @@ -264,6 +280,9 @@ const startFetching = async () => { files: [], inputs: { projectName: projectName.value, + projectContext: '无', + keyAvoidTechOrKeyword: '无', + userInitialInnovationPoint: '无', }, content: content.value || '', }, @@ -272,20 +291,6 @@ const startFetching = async () => { const { answer } = res; conversationId.value = res.conversationId; - // 判断是否是以 .docx 结尾的 URL - function isDocxURL(str: string): boolean { - return str.endsWith('.docx'); - } - - // 使用正则提取 /static/ 后面的文件名部分 - function extractDocxFilename(url: string): null | string { - const match = url.match(/\/static\/([a-f0-9-]+\.docx)$/i); - if (match) { - return match[1]; // 返回类似:9e1c421e-991c-411f-8176-6350a97e70f3.docx - } - return null; - } - const filename = ref(''); if (answer && isDocxURL(answer)) { @@ -332,7 +337,7 @@ const startFetching = async () => { resultItems.value.push({ key: resultItems.value.length + 1, role: 'ai', - content: res.answer.trim(), + content: res.answer, }); } @@ -346,6 +351,79 @@ const startFetching = async () => { fetchStatus.value = 'completed'; }; +function openFormModal() { + formModalApi + .setData({ + // 表单值 + values: { + projectName: 'abc', + projectContext: '123', + keyAvoidTechOrKeyWord: 'abc', + userInitialInnovationPoint: 'abc', + }, + }) + .open(); +} + +const [Form, formApi] = useVbenForm({ + handleSubmit: startFetching, + schema: [ + { + component: 'Input', + componentProps: { + placeholder: '请输入', + }, + fieldName: 'projectName', + label: '项目名称', + }, + { + component: 'Input', + componentProps: { + placeholder: '请输入', + }, + fieldName: 'projectContext', + label: '项目背景', + }, + { + component: 'Input', + componentProps: { + placeholder: '请输入', + }, + fieldName: 'keyAvoidTechOrKeyWord', + label: '规避关键词', + }, + { + component: 'Input', + componentProps: { + placeholder: '请输入', + }, + fieldName: 'userInitialInnovationPoint', + label: '初步创新点', + }, + ], + showDefaultActions: false, +}); + +const [Modal, formModalApi] = useVbenModal({ + fullscreenButton: false, + onCancel() { + formModalApi.close(); + }, + onConfirm: async () => { + await formApi.validateAndSubmitForm(); + // modalApi.close(); + }, + onOpenChange(isOpen: boolean) { + if (isOpen) { + const { values } = formModalApi.getData>(); + if (values) { + formApi.setValues(values); + } + } + }, + title: '内嵌表单示例', +}); + const onPromptsItemClick: PromptsProps['onItemClick'] = (info) => { content.value = info.data.description; }; @@ -353,67 +431,110 @@ const onPromptsItemClick: PromptsProps['onItemClick'] = (info) => { const handleFileChange: AttachmentsProps['onChange'] = (info) => (attachedFiles.value = info.fileList); -// ==================== Nodes ==================== -// const placeholderNode = computed(() => -// h( -// Space, -// { direction: 'vertical', size: 16, style: value.placeholder }, -// [ -// h(Welcome, { -// variant: 'borderless', -// icon: 'https://mdn.alipayobjects.com/huamei_iwk9zp/afts/img/A*s5sNRo5LjfQAAAAAAAAAAAAADgCCAQ/fmt.webp', -// title: "Hello, I'm Ant Design X", -// description: -// 'Base on Ant Design, AGI product interface solution, create a better intelligent vision~', -// extra: h(Space, {}, [ -// h(Button, { icon: h(ShareAltOutlined) }), -// h(Button, { icon: h(EllipsisOutlined) }), -// ]), -// }), -// h(Prompts, { -// title: 'Do you want?', -// items: placeholderPromptsItems, -// styles: { -// list: { -// width: '100%', -// }, -// item: { -// flex: 1, -// }, -// }, -// onItemClick: onPromptsItemClick, -// }), -// ], -// ), -// ); +// 监听 itemMessage 变化并更新 resultItems +watch( + () => props.itemMessage, + (newVal) => { + resultItems.value = []; + if (newVal && newVal.length > 0) { + newVal.forEach((msg) => { + if (msg.role === 'user') { + resultItems.value.push({ + key: resultItems.value.length + 1, + role: msg.role, // 'user' or 'ai' + content: msg.content, + footer: msg.footer, + }); + } else { + const filename = ref(''); -// const items = computed(() => { -// // if (messages.value.length === 0) { -// // return [{ content: placeholderNode, variant: 'borderless' }]; -// // } -// return messages.value.map(({ id, message, status }) => ({ -// key: id, -// loading: status === 'loading', -// role: status === 'local' ? 'local' : 'ai', -// content: message, -// })); -// }); + if (msg.content && isDocxURL(msg.content)) { + filename.value = extractDocxFilename(msg.content); + resultItems.value.push({ + key: resultItems.value.length + 1, + role: msg.role, // 'user' or 'ai' + content: '文档已生成', + footer: h(Flex, null, [ + h( + Button, + { + size: 'normal', + type: 'primary', + onClick: () => { + openPreviewDrawer('right', filename); + }, + }, + '文档预览', + ), + h( + Button, + { + size: 'normal', + type: 'primary', + style: { + marginLeft: '10px', + }, + onClick: () => { + // 创建隐藏的 标签用于触发下载 + const link = document.createElement('a'); + link.href = msg.content; // 设置下载链接 + link.download = filename; // 设置下载文件名 + document.body.append(link); // 将 标签添加到页面中 + link.click(); // 触发点击事件开始下载 + link.remove(); // 下载完成后移除 标签 + }, + }, + '文档下载', + ), + ]), + }); + } else { + resultItems.value.push({ + key: resultItems.value.length + 1, + role: msg.role, // 'user' or 'ai' + content: msg.content, + }); + } + } + }); + } + }, + { deep: true }, +); + + + - - + - + + + + + + + + + + :value="content" class="sender" :loading="agentRequestLoading" - @submit="startFetching" + @submit="openFormModal" @change="(value) => (content = value)" >