feat(@vben/common-ui): 优化 AI 工具模块功能和路由

- 修改路由路径,统一为 /ai前缀
- 新增表单弹窗组件,用于输入项目信息
- 优化聊天消息展示逻辑,支持文档预览和下载
- 新增个人页面路由和组件
- 调整模板数据结构和样式
This commit is contained in:
Kven 2025-05-16 23:02:11 +08:00
parent 9cead8075b
commit ad79e5113e
15 changed files with 378 additions and 204 deletions

View File

@ -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);
}

View File

@ -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';

View File

@ -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`);
}

View File

@ -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,

View File

@ -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();

View File

@ -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;
}

View File

@ -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) {
<SpiderWorkView
title="目标网址:"
:item="temp"
:run-spider="sendWorkflow"
:run-spider="runSpider"
:item-message="itemMessage"
/>
</div>

View File

@ -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<WordTempItem>({
id: 'baca08c1-e92b-4dc9-a445-3584803f54d4',
name: '海南职创申报书生成',
@ -54,11 +62,12 @@ function handleClickMode(item: WordTempItem) {
temp = item;
getParameters(temp.id);
}
const itemMessage = ref<ResultItem[]>([]);
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"
/>
</div>
</template>

View File

@ -101,7 +101,7 @@ const openKeys = ref([]);
v-model:selected-keys="selectedKeys"
mode="vertical"
class="mode"
:items="items"
:items="itemsData"
@click="handleMenuClick"
/>
<!-- 🌟 添加会话 -->

View File

@ -37,7 +37,7 @@ const [Drawer, drawerApi] = useVbenDrawer({
const data = drawerApi.getData<Record<string, any>>();
if (data) {
isLoading.value = true; //
pptx.value = `/pptx/${data}`; // docx
pptx.value = `/pptx/${data}`; // pptx
}
// url.value = drawerApi.getData<Record<string, any>>();
}

View File

@ -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(
<div class="layout">
<PreviewDrawer />
<div class="chat">
<Space
v-if="resultItems.length === 0"
direction="vertical"
size:16
style="flex: 1; padding-top: 20px"
>
<Welcome
variant="borderless"
icon="https://mdn.alipayobjects.com/huamei_iwk9zp/afts/img/A*s5sNRo5LjfQAAAAAAAAAAAAADgCCAQ/fmt.webp"
title="欢迎使用PPT自动生成"
description="请选择模板列表中需要生成文件的模板,输入参数后开始生成生成文件。"
/>
</Space>
<!-- 🌟 消息列表 -->
<Bubble.List
v-else
variant="shadow"
:typing="true"
:items="resultItems"

View File

@ -37,12 +37,14 @@ const conversationsItems = ref(defaultConversationsItems);
const activeKey = ref(defaultConversationsItems.value[0]?.key);
const itemsData = ref([
{
key: 'a2a55334-a111-45e6-942f-9f3f70af8826',
key: '77c068fd-d5b6-4c33-97d8-db5511a09b26',
label: '全国公共资源交易平台_信息爬取',
title: '全国公共资源交易平台_信息爬取',
},
{
key: 'c736edd0-925d-4877-9223-56aab7342311',
label: '广州公共资源交易中心_信息获取',
title: '全国公共资源交易平台_信息爬取',
},
]);
@ -71,24 +73,6 @@ const onConversationClick: ConversationsProps['onActiveChange'] = (key) => {
</script>
<template>
<!-- <Card style="height: calc(100vh - 120px); overflow-y: auto">-->
<!-- <CardHeader class="py-4">-->
<!-- <CardTitle class="text-lg">{{ title }}</CardTitle>-->
<!-- <CardDescription>请选择需要爬取的网站</CardDescription>-->
<!-- </CardHeader>-->
<!-- <CardContent class="flex flex-wrap p-5 pt-0">-->
<!-- <ul class="divide-border w-full divide-y" role="list">-->
<!-- <li-->
<!-- v-for="item in items"-->
<!-- :key="item.id"-->
<!-- @click="$emit('click', item)"-->
<!-- class="flex cursor-pointer justify-between gap-x-6 py-5"-->
<!-- >-->
<!-- {{ item.name }}-->
<!-- </li>-->
<!-- </ul>-->
<!-- </CardContent>-->
<!-- </Card>-->
<div class="menu">
<div class="addBtn">数据信息列表</div>
<Menu
@ -96,12 +80,12 @@ const onConversationClick: ConversationsProps['onActiveChange'] = (key) => {
v-model:selected-keys="selectedKeys"
mode="vertical"
class="mode"
:items="items"
:items="itemsData"
@click="handleMenuClick"
/>
<!-- 🌟 添加会话 -->
<!-- <Button type="link" class="addBtn">会话</Button>-->
<div class="addBtn">会话</div>
<div class="addBtn">获取记录</div>
<!-- 🌟 添加会话 -->
<!-- 🌟 会话管理 -->

View File

@ -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<Props>(), {
}),
});
// 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<ResultItem[]>([]);
// const items = computed<BubbleListProps['items']>(() => {
// 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: () => {
// <a>
const link = document.createElement('a');
link.href = result; //
link.href = msg.content; //
link.download = filename; //
document.body.append(link); // <a>
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(
<template>
<!-- style="flex-direction: column"-->
<div class="flex h-full" style="width: 70%">
<div
class="flex h-full"
style="flex-direction: column; width: 70%; padding-top: 20px"
>
<PreviewDrawer />
<!-- <PreviewDrawer />-->
<Space
v-if="resultItems.length === 0"
direction="vertical"
size:16
style="flex: 1"
>
<Welcome
variant="borderless"
icon="https://mdn.alipayobjects.com/huamei_iwk9zp/afts/img/A*s5sNRo5LjfQAAAAAAAAAAAAADgCCAQ/fmt.webp"
title="欢迎使用AI平台信息获取"
description="请选择数据信息列表中需要获取信息的链接,选取时间后开始爬取获取信息。"
/>
</Space>
<Bubble.List
v-else
variant="shadow"
:typing="true"
:items="resultItems"
@ -403,13 +387,13 @@ watch(
>
{{ isFetching ? '抓取中...' : '开始抓取' }}
</Button>
<Button
class="mx-2"
:disabled="fetchStatus !== 'completed'"
@click="downloadFile"
>
下载文件
</Button>
<!-- <Button-->
<!-- class="mx-2"-->
<!-- :disabled="fetchStatus !== 'completed'"-->
<!-- @click="downloadFile"-->
<!-- >-->
<!-- 下载文件-->
<!-- </Button>-->
</CardFooter>
</Card>
</div>

View File

@ -96,7 +96,7 @@ const openKeys = ref([]);
v-model:selected-keys="selectedKeys"
mode="vertical"
class="mode"
:items="items"
:items="itemsData"
@click="handleMenuClick"
/>
<!-- 🌟 添加会话 -->

View File

@ -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<Props>(), {
item: () => null,
itemMessage: () => null,
paramsData: () => null,
runChatflow: () => async () => ({
data: {
@ -81,13 +95,6 @@ const props = withDefaults(defineProps<Props>(), {
// 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<Record<string, any>>();
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<BubbleListProps['items']>(() => {
// // 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: () => {
// <a>
const link = document.createElement('a');
link.href = msg.content; //
link.download = filename; //
document.body.append(link); // <a>
link.click(); //
link.remove(); // <a>
},
},
'文档下载',
),
]),
});
} else {
resultItems.value.push({
key: resultItems.value.length + 1,
role: msg.role, // 'user' or 'ai'
content: msg.content,
});
}
}
});
}
},
{ deep: true },
);
</script>
<template>
<div class="layout">
<PreviewDrawer />
<Modal>
<Form />
</Modal>
<div class="chat">
<a-space class="w-full" direction="vertical" style="padding-top: 20px">
<a-input
v-model:value="projectName"
required
placeholder="项目名称"
:status="inputStatus"
<Space
v-if="resultItems.length === 0"
direction="vertical"
size:16
style="flex: 1; padding-top: 20px"
>
<Welcome
variant="borderless"
icon="https://mdn.alipayobjects.com/huamei_iwk9zp/afts/img/A*s5sNRo5LjfQAAAAAAAAAAAAADgCCAQ/fmt.webp"
title="欢迎使用AI申报书WORD生成"
description="请选择模板列表中需要生成文件的模板,输入参数后开始生成生成内容或文件。"
/>
</a-space>
</Space>
<!-- <a-space class="w-full" direction="vertical" style="padding-top: 20px">-->
<!-- <a-input-->
<!-- v-model:value="projectName"-->
<!-- required-->
<!-- placeholder="项目名称"-->
<!-- :status="inputStatus"-->
<!-- />-->
<!-- </a-space>-->
<!-- 🌟 消息列表 -->
<Bubble.List
v-else
:items="resultItems"
:roles="roles"
style="flex: 1"
@ -432,7 +553,7 @@ const handleFileChange: AttachmentsProps['onChange'] = (info) =>
:value="content"
class="sender"
:loading="agentRequestLoading"
@submit="startFetching"
@submit="openFormModal"
@change="(value) => (content = value)"
>
<template #prefix>