dify-proxy-fontend/packages/effects/common-ui/src/ui/ppt/ppt-work-view.vue
Kven e4745ae789 feat(@vben/common-ui): 优化文档处理功能
- 添加 markdown 解析功能
- 优化文档预览和工作视图组件
- 添加文档生成相关功能
- 修复一些与文档处理相关的问题
2025-05-12 19:52:53 +08:00

340 lines
8.3 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<script setup lang="ts">
import type { BubbleListProps } from 'ant-design-x-vue';
import type { DrawerPlacement } from '@vben/common-ui';
import type { PPTTempItem } from './typing';
import { h, ref, watch } from 'vue';
import { useVbenDrawer } from '@vben/common-ui';
import { Card } from '@vben-core/shadcn-ui';
import { UserOutlined } from '@ant-design/icons-vue';
import { Button, Flex, Typography } from 'ant-design-vue';
import { Attachments, BubbleList, Sender } from 'ant-design-x-vue';
import markdownit from 'markdown-it';
import PptPreview from './ppt-perview.vue';
interface WorkflowParams {
appid: string;
}
interface WorkflowContext {
userId: string;
conversationId: string;
files: unknown[];
inputs: Record<string, unknown>;
}
interface WorkflowResult {
data: {
outputs: {
result: string;
};
};
}
interface ResultItem {
key: number;
role: 'ai' | 'user';
content: string;
footer?: any;
}
interface Props {
itemMessage?: ResultItem;
item?: PPTTempItem;
runWorkflow?: (
params: WorkflowParams,
context: WorkflowContext,
) => Promise<WorkflowResult>;
}
defineOptions({
name: 'SpiderWorkView',
});
const props = withDefaults(defineProps<Props>(), {
itemMessage: () => null,
item: () => null,
runWorkflow: () => async () => ({
data: {
outputs: {
result: '',
},
},
}),
});
// 初始化 markdown 解析器
const md = markdownit({ html: true, breaks: true });
const renderMarkdown: BubbleListProps['roles'][string]['messageRender'] = (
content: string,
) => {
return h(Typography, [
h('div', {
innerHTML: content,
style: {
padding: '8px',
lineHeight: '1.6',
whiteSpace: 'break-spaces',
},
}),
]);
};
const [PreviewDrawer, previewDrawerApi] = useVbenDrawer({
// 连接抽离的组件
connectedComponent: PptPreview,
// placement: 'left',
});
function openPreviewDrawer(
placement: DrawerPlacement = 'right',
filename?: string,
) {
const fileData = filename.value;
previewDrawerApi.setState({ placement }).setData(fileData).open();
}
// 判断是否是以 .pptx 结尾的 URL
function isPptxURL(str: string): boolean {
return str.endsWith('.pptx');
}
// 使用正则提取 /static/ 后面的文件名部分
function extractPptxFilename(url: string): null | string {
const match = url.match(/\/static\/([a-f0-9-]+\.pptx)$/i);
if (match) {
return match[1]; // 返回类似9e1c421e-991c-411f-8176-6350a97e70f3.pptx
}
return null;
}
const startFetching = async () => {
isFetching.value = true;
fetchStatus.value = 'fetching';
try {
const res = await props.runWorkflow(
{
appid: 'ee3889b6-50fa-463e-b956-3b93447727fc',
},
{
userId: '1562',
conversationId: '',
files: [],
inputs: {
declarationDoc: value.value,
},
},
);
const { result } = res.data.outputs;
const filename = ref('');
if (result && isPptxURL(result)) {
filename.value = extractPptxFilename(result);
}
// 保存抓取结果 url http://47.112.173.8:6802/static/66f3cfd95e364a239d8036390db658ae.pptx
fetchResult.value = `/static/${filename.value}`;
resultItems.value.push({
key: resultItems.value.length + 1,
role: 'ai',
content: '文档已生成',
footer: h(Flex, null, [
h(
Button,
{
size: 'nomarl',
type: 'primary',
onClick: () => {
openPreviewDrawer('right', filename);
},
},
'文档预览',
),
h(
Button,
{
size: 'nomarl',
type: 'primary',
style: {
marginLeft: '10px',
},
onClick: () => {
// 创建隐藏的 <a> 标签用于触发下载
const link = document.createElement('a');
link.href = result; // 设置下载链接
link.download = filename; // 设置下载文件名
document.body.append(link); // 将 <a> 标签添加到页面中
link.click(); // 触发点击事件开始下载
link.remove(); // 下载完成后移除 <a> 标签
},
},
'文档下载',
),
]),
});
} catch (error) {
console.error(error);
}
// 模拟抓取完成
isFetching.value = false;
fetchStatus.value = 'completed';
};
const roles: BubbleListProps['roles'] = {
user: {
placement: 'end',
typing: false,
avatar: { icon: h(UserOutlined), style: { background: '#87d068' } },
},
ai: {
placement: 'start',
typing: false,
style: {
maxWidth: 600,
marginInlineEnd: 44,
},
styles: {
footer: {
width: '100%',
},
},
avatar: { icon: h(UserOutlined), style: { background: '#fde3cf' } },
messageRender: (content) =>
h(Typography, [
h('div', {
innerHTML: md.render(content),
style: {
padding: '8px',
lineHeight: '1.6',
whiteSpace: 'break-spaces',
},
}),
]),
},
file: {
placement: 'start',
avatar: { icon: h(UserOutlined), style: { visibility: 'hidden' } },
variant: 'borderless',
messageRender: (items: any) =>
h(
Flex,
{ vertical: true, gap: 'middle' },
items.map((item: any) =>
h(Attachments.FileCard, { key: item.uid, item }),
),
),
},
};
const isFetching = ref(false);
const fetchResult = ref('');
const fetchStatus = ref('');
const value = ref('');
const resultItems = ref<ResultItem[]>([]);
// 监听 itemMessage 变化并更新 resultItems
watch(
() => props.itemMessage,
(newVal) => {
resultItems.value = [];
if (newVal && newVal.length > 0) {
newVal.forEach((msg) => {
// resultItems.value.push({
// key: resultItems.value.length + 1,
// role: msg.role, // 'user' or 'ai'
// content: msg.content,
// footer: msg.footer,
// });
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('');
if (msg.content && isPptxURL(msg.content)) {
filename.value = extractPptxFilename(msg.content);
}
resultItems.value.push({
key: resultItems.value.length + 1,
role: msg.role, // 'user' or 'ai'
content: '文档已生成',
footer: h(Flex, null, [
h(
Button,
{
size: 'nomarl',
type: 'primary',
onClick: () => {
openPreviewDrawer('right', filename);
},
},
'文档预览',
),
h(
Button,
{
size: 'nomarl',
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> 标签
},
},
'文档下载',
),
]),
});
}
});
}
},
{ deep: true },
);
</script>
<template>
<div>
<PreviewDrawer />
<div style="height: calc(67vh - 120px); margin: 20px; overflow-y: auto">
<BubbleList
:roles="roles"
:typing="true"
:items="resultItems"
:message-render="renderMarkdown"
/>
</div>
<Card class="w-full">
<Sender
v-model:value="value"
:loading="isFetching"
:disabled="isFetching"
:auto-size="{ minRows: 6, maxRows: 12 }"
@submit="startFetching"
/>
</Card>
</div>
</template>