Compare commits
1 Commits
main
...
repository
Author | SHA1 | Date | |
---|---|---|---|
3113b9b670 |
@ -86,7 +86,11 @@ export async function sendChatFlowStream(
|
|||||||
appId: any,
|
appId: any,
|
||||||
data: ChatflowApi.CompletionsBody,
|
data: ChatflowApi.CompletionsBody,
|
||||||
) {
|
) {
|
||||||
return requestClient.post(`/v1/chat/completions/stream/${appId}`, data);
|
return requestClient.request(`/v1/chat/completions/stream/${appId}`, {
|
||||||
|
data,
|
||||||
|
method: 'POST',
|
||||||
|
responseType: 'stream',
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// word
|
// word
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import type { RouteRecordRaw } from 'vue-router';
|
import type { RouteRecordRaw } from 'vue-router';
|
||||||
|
|
||||||
import { HugeAi, SvgPPT, SvgSpider, SvgWord } from '@vben/icons';
|
import { HugeAi, SvgPPT, SvgRepository, SvgSpider, SvgWord } from '@vben/icons';
|
||||||
|
|
||||||
const routes: RouteRecordRaw[] = [
|
const routes: RouteRecordRaw[] = [
|
||||||
{
|
{
|
||||||
@ -46,6 +46,17 @@ const routes: RouteRecordRaw[] = [
|
|||||||
authority: ['ai:ppt'],
|
authority: ['ai:ppt'],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'repository',
|
||||||
|
path: '/ai/repository',
|
||||||
|
component: () => import('#/views/repository/index.vue'),
|
||||||
|
meta: {
|
||||||
|
icon: SvgRepository,
|
||||||
|
title: '知识库生成',
|
||||||
|
order: 4,
|
||||||
|
authority: ['ai:repository'],
|
||||||
|
},
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
@ -4,7 +4,7 @@ import type { WorkflowItem } from '@vben/common-ui';
|
|||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
|
|
||||||
import { WorkflowsView } from '@vben/common-ui';
|
import { WorkflowsView } from '@vben/common-ui';
|
||||||
import { SvgPPT, SvgSpider, SvgWord } from '@vben/icons';
|
import { SvgPPT, SvgRepository, SvgSpider, SvgWord } from '@vben/icons';
|
||||||
|
|
||||||
const items: WorkflowItem[] = [
|
const items: WorkflowItem[] = [
|
||||||
{
|
{
|
||||||
@ -25,6 +25,12 @@ const items: WorkflowItem[] = [
|
|||||||
description: '自动生成PPT文档',
|
description: '自动生成PPT文档',
|
||||||
path: '/ai/ppt',
|
path: '/ai/ppt',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
icon: SvgRepository,
|
||||||
|
title: '知识库生成',
|
||||||
|
description: '自动生成知识库文档',
|
||||||
|
path: '/ai/repository',
|
||||||
|
},
|
||||||
];
|
];
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
|
2
apps/web-antd/src/views/repository/components/index.ts
Normal file
2
apps/web-antd/src/views/repository/components/index.ts
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export { default as RepositoryListView } from './repository-list-view.vue';
|
||||||
|
export { default as RepositoryWorkView } from './repository-work-view.vue';
|
@ -0,0 +1,171 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { ConversationsProps } from 'ant-design-x-vue';
|
||||||
|
|
||||||
|
import type { MenuItem, Props } from '../typing';
|
||||||
|
|
||||||
|
import { computed, ref, watch } from 'vue';
|
||||||
|
|
||||||
|
import { Menu } from 'ant-design-vue';
|
||||||
|
import { Conversations } from 'ant-design-x-vue';
|
||||||
|
|
||||||
|
defineOptions({
|
||||||
|
name: 'RepositoryListView',
|
||||||
|
});
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
|
items: () => [],
|
||||||
|
temp: () => [],
|
||||||
|
});
|
||||||
|
const emit = defineEmits(['click', 'clickMode', 'newConversation']);
|
||||||
|
const defaultConversationsItems = computed(() => {
|
||||||
|
return props.items.map((item) => {
|
||||||
|
return {
|
||||||
|
key: item.id,
|
||||||
|
label: item.id,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
});
|
||||||
|
const conversationsItems = ref(defaultConversationsItems);
|
||||||
|
const activeKey = ref(defaultConversationsItems.value[0]?.key);
|
||||||
|
|
||||||
|
// 将 props.items 映射为 key=id, label=name, title=name 的结构
|
||||||
|
const transformItems = computed(() => {
|
||||||
|
return props.temp.map((item) => ({
|
||||||
|
key: item.id,
|
||||||
|
label: item.name,
|
||||||
|
title: item.name,
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
const itemsData = ref<MenuItem[]>([
|
||||||
|
{
|
||||||
|
title: '',
|
||||||
|
label: '',
|
||||||
|
key: '',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
const handleMenuClick = (item: any) => {
|
||||||
|
const selectedItem = itemsData.value.find((i) => i.key === item.key);
|
||||||
|
if (selectedItem) {
|
||||||
|
// 转换字段:title -> name, key -> id
|
||||||
|
const transformedItem = {
|
||||||
|
name: selectedItem.title,
|
||||||
|
id: selectedItem.key,
|
||||||
|
};
|
||||||
|
emit('clickMode', transformedItem); // 发送转换后的数据
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleNewConversation = () => {
|
||||||
|
emit('newConversation');
|
||||||
|
};
|
||||||
|
|
||||||
|
const onConversationClick: ConversationsProps['onActiveChange'] = (key) => {
|
||||||
|
activeKey.value = key;
|
||||||
|
|
||||||
|
const matchedItem = props.items.find((item) => item.id === key);
|
||||||
|
if (matchedItem) {
|
||||||
|
emit('click', matchedItem);
|
||||||
|
} else {
|
||||||
|
emit('click', null); // 或者根据业务需求处理未找到的情况
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const selectedKeys = ref<string[]>([]);
|
||||||
|
const openKeys = ref([]);
|
||||||
|
|
||||||
|
// 监听 transformItems 变化,更新 itemsData
|
||||||
|
watch(
|
||||||
|
() => transformItems.value,
|
||||||
|
(newVal) => {
|
||||||
|
itemsData.value = newVal;
|
||||||
|
},
|
||||||
|
{ immediate: true },
|
||||||
|
);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => itemsData.value,
|
||||||
|
(newVal) => {
|
||||||
|
if (newVal.length > 0 && newVal[0]) {
|
||||||
|
selectedKeys.value = [newVal[0].key]; // 默认选中第一个菜单项
|
||||||
|
handleMenuClick(newVal[0]);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ immediate: true },
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="menu">
|
||||||
|
<div class="addBtn">模板</div>
|
||||||
|
<Menu
|
||||||
|
v-model:open-keys="openKeys"
|
||||||
|
v-model:selected-keys="selectedKeys"
|
||||||
|
mode="vertical"
|
||||||
|
class="mode"
|
||||||
|
:items="itemsData"
|
||||||
|
@click="handleMenuClick"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div class="addBtn">会话</div>
|
||||||
|
|
||||||
|
<Button type="submit" class="newBtn" @click="handleNewConversation">
|
||||||
|
新建会话
|
||||||
|
</Button>
|
||||||
|
<!-- 🌟 会话管理 -->
|
||||||
|
<Conversations
|
||||||
|
:items="conversationsItems"
|
||||||
|
class="conversations"
|
||||||
|
:active-key="activeKey"
|
||||||
|
@active-change="onConversationClick"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="less">
|
||||||
|
.layout {
|
||||||
|
width: 100%;
|
||||||
|
min-width: 1400px;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
background: hsl(216deg 21.74% 95.49%);
|
||||||
|
font-family: AlibabaPuHuiTi, v-deep(var(--ant-font-family)), sans-serif;
|
||||||
|
border-radius: v-deep(var(--ant-border-radius));
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu {
|
||||||
|
background: #ffffff;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mode {
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.conversations {
|
||||||
|
padding: 0 12px;
|
||||||
|
height: 400px;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.addBtn {
|
||||||
|
float: left;
|
||||||
|
//border: 1px solid #1677ff34;
|
||||||
|
color: #666666;
|
||||||
|
width: calc(100% - 24px);
|
||||||
|
margin: 12px 12px 24px 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.newBtn {
|
||||||
|
float: left;
|
||||||
|
width: calc(100% - 24px);
|
||||||
|
margin: 12px 12px 12px 12px;
|
||||||
|
padding: 5px;
|
||||||
|
border-radius: 5px;
|
||||||
|
background-color: rgba(19, 118, 163, 0.1);
|
||||||
|
}
|
||||||
|
</style>
|
@ -0,0 +1,60 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref } from 'vue';
|
||||||
|
|
||||||
|
import { useVbenDrawer } from '@vben/common-ui';
|
||||||
|
|
||||||
|
import VueOfficePptx from '@vue-office/pptx';
|
||||||
|
import { message } from 'ant-design-vue';
|
||||||
|
|
||||||
|
const isLoading = ref(false); // 新增:加载状态变量
|
||||||
|
const pptx = ref();
|
||||||
|
const pptStyle = ref({
|
||||||
|
height: 'calc(100vh - 100px)',
|
||||||
|
width: '100%',
|
||||||
|
margin: 'auto',
|
||||||
|
background: '#ffffff',
|
||||||
|
});
|
||||||
|
const renderedHandler = () => {
|
||||||
|
isLoading.value = false; // 文档渲染完成,关闭加载状态
|
||||||
|
message.success('渲染完成');
|
||||||
|
};
|
||||||
|
const errorHandler = () => {
|
||||||
|
isLoading.value = false; // 出错时也关闭加载状态
|
||||||
|
message.error('渲染失败');
|
||||||
|
};
|
||||||
|
const [Drawer, drawerApi] = useVbenDrawer({
|
||||||
|
onCancel() {
|
||||||
|
drawerApi.close();
|
||||||
|
},
|
||||||
|
onClosed() {
|
||||||
|
drawerApi.setState({ overlayBlur: 0, placement: 'right' });
|
||||||
|
},
|
||||||
|
onOpenChange(isOpen: boolean) {
|
||||||
|
if (isOpen) {
|
||||||
|
const data = drawerApi.getData<Record<string, any>>();
|
||||||
|
if (data) {
|
||||||
|
isLoading.value = true; // 开始加载文档,开启加载状态
|
||||||
|
pptx.value = data; // 更新 pptx 的值
|
||||||
|
}
|
||||||
|
// url.value = drawerApi.getData<Record<string, any>>();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<Drawer class="w-[880px]" title="文档预览" :footer="false">
|
||||||
|
<div v-if="isLoading" class="loading-overlay">正在加载文档,请稍候...</div>
|
||||||
|
<VueOfficePptx
|
||||||
|
:src="pptx"
|
||||||
|
:style="pptStyle"
|
||||||
|
@rendered="renderedHandler"
|
||||||
|
@error="errorHandler"
|
||||||
|
/>
|
||||||
|
</Drawer>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.pptx-preview-wrapper {
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
</style>
|
@ -0,0 +1,336 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { ThoughtChainItem } from 'ant-design-x-vue';
|
||||||
|
|
||||||
|
import type { DrawerPlacement } from '@vben/common-ui';
|
||||||
|
|
||||||
|
import type { PropsWork, ResultItem } from '../typing';
|
||||||
|
|
||||||
|
import type { WorkflowResult } from '#/views/word/typing';
|
||||||
|
|
||||||
|
import { h, ref, watch } from 'vue';
|
||||||
|
|
||||||
|
import { useVbenDrawer } from '@vben/common-ui';
|
||||||
|
import { SvgPPT } from '@vben/icons';
|
||||||
|
import { useAccessStore, useUserStore } from '@vben/stores';
|
||||||
|
|
||||||
|
import {
|
||||||
|
Card,
|
||||||
|
CardContent,
|
||||||
|
CardHeader,
|
||||||
|
CardTitle,
|
||||||
|
VbenIcon,
|
||||||
|
} from '@vben-core/shadcn-ui';
|
||||||
|
|
||||||
|
import {
|
||||||
|
CheckCircleOutlined,
|
||||||
|
InfoCircleOutlined,
|
||||||
|
LoadingOutlined,
|
||||||
|
// UserOutlined,
|
||||||
|
} from '@ant-design/icons-vue';
|
||||||
|
import { Button, notification, Space } from 'ant-design-vue';
|
||||||
|
import {
|
||||||
|
// Attachments,
|
||||||
|
Sender,
|
||||||
|
ThoughtChain,
|
||||||
|
Welcome,
|
||||||
|
XStream,
|
||||||
|
} from 'ant-design-x-vue';
|
||||||
|
|
||||||
|
import { getToken } from '#/api/csrf';
|
||||||
|
|
||||||
|
import PptPreview from './repository-preview.vue';
|
||||||
|
|
||||||
|
defineOptions({ name: 'PptWorkView' });
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<PropsWork>(), {
|
||||||
|
itemMessage: Array,
|
||||||
|
item: () => {
|
||||||
|
return {
|
||||||
|
id: '',
|
||||||
|
name: '',
|
||||||
|
};
|
||||||
|
},
|
||||||
|
runWorkflow: () => async () => ({
|
||||||
|
data: {
|
||||||
|
outputs: {
|
||||||
|
files: [
|
||||||
|
{
|
||||||
|
filename: '',
|
||||||
|
url: '',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
const userStore = useUserStore();
|
||||||
|
|
||||||
|
// ==================== State ====================
|
||||||
|
const content = ref('');
|
||||||
|
const agentRequestLoading = ref(false);
|
||||||
|
const fetchStatus = ref('');
|
||||||
|
const resultItems = ref<ResultItem[]>([]);
|
||||||
|
const accessStore = useAccessStore();
|
||||||
|
const mockServerResponseData: ThoughtChainItem[] = [];
|
||||||
|
const chainItems = ref<ThoughtChainItem[]>(mockServerResponseData);
|
||||||
|
// const fetchResult = ref('');
|
||||||
|
const pptMessage = ref('');
|
||||||
|
|
||||||
|
const [PreviewDrawer, previewDrawerApi] = useVbenDrawer({
|
||||||
|
// 连接抽离的组件
|
||||||
|
connectedComponent: PptPreview,
|
||||||
|
// placement: 'left',
|
||||||
|
});
|
||||||
|
function openPreviewDrawer(placement: DrawerPlacement = 'right', file?: any) {
|
||||||
|
previewDrawerApi.setState({ placement }).setData(file.url).open();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== Event ====================
|
||||||
|
function getStatusIcon(status: ThoughtChainItem['status']) {
|
||||||
|
switch (status) {
|
||||||
|
case 'error': {
|
||||||
|
return h(InfoCircleOutlined);
|
||||||
|
}
|
||||||
|
case 'pending': {
|
||||||
|
return h(LoadingOutlined);
|
||||||
|
}
|
||||||
|
case 'success': {
|
||||||
|
return h(CheckCircleOutlined);
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function downloadPpt(files: any) {
|
||||||
|
// 创建隐藏的 <a> 标签用于触发下载
|
||||||
|
const link = document.createElement('a');
|
||||||
|
link.href = files.url; // 设置下载链接
|
||||||
|
link.download = files.filename; // 设置下载文件名
|
||||||
|
document.body.append(link); // 将 <a> 标签添加到页面中
|
||||||
|
link.click(); // 触发点击事件开始下载
|
||||||
|
link.remove(); // 下载完成后移除 <a> 标签
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateChainItem(status: ThoughtChainItem['status']) {
|
||||||
|
if (mockServerResponseData.length > 0) {
|
||||||
|
mockServerResponseData[mockServerResponseData.length - 1].status = status;
|
||||||
|
mockServerResponseData[mockServerResponseData.length - 1].icon =
|
||||||
|
getStatusIcon(status);
|
||||||
|
mockServerResponseData[mockServerResponseData.length - 1].description =
|
||||||
|
`status: ${status}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseAndRenderStream(text: WorkflowResult) {
|
||||||
|
let parsedData;
|
||||||
|
try {
|
||||||
|
parsedData = JSON.parse(text?.data);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to parse event data:', error);
|
||||||
|
chainItems.value = [];
|
||||||
|
notification.error({
|
||||||
|
message: '流式数据解析失败',
|
||||||
|
description: '无法解析来自服务器的数据,请检查网络连接或稍后重试。',
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { event, data } = parsedData;
|
||||||
|
if (event === 'node_started' && data) {
|
||||||
|
mockServerResponseData.push({
|
||||||
|
key: data.nodeId,
|
||||||
|
title: data.title,
|
||||||
|
description: data.nodeType,
|
||||||
|
icon: getStatusIcon('pending'),
|
||||||
|
status: 'pending',
|
||||||
|
});
|
||||||
|
chainItems.value = [...mockServerResponseData];
|
||||||
|
} else if (
|
||||||
|
event === 'node_finished' &&
|
||||||
|
data &&
|
||||||
|
mockServerResponseData.length > 0
|
||||||
|
) {
|
||||||
|
updateChainItem('success');
|
||||||
|
chainItems.value = [...mockServerResponseData];
|
||||||
|
if (data.outputs.files?.length > 0) {
|
||||||
|
pptMessage.value = data.outputs.files[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const startFetching = async () => {
|
||||||
|
resultItems.value = [];
|
||||||
|
pptMessage.value = '';
|
||||||
|
agentRequestLoading.value = true;
|
||||||
|
fetchStatus.value = 'fetching';
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await fetch(`/api/v1/workflow/run/stream/${props.item.id}`, {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({
|
||||||
|
userId: userStore.userInfo?.userId || '',
|
||||||
|
conversationId: '',
|
||||||
|
files: [],
|
||||||
|
inputs: {
|
||||||
|
declarationDoc: content.value,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
headers: {
|
||||||
|
'Accept-Language': 'zh-CN',
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-CSRF-TOKEN': getToken() || '',
|
||||||
|
Authorization: `Bearer ${accessStore.accessToken}`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
// 实时更新 AI 消息
|
||||||
|
for await (const chunk of XStream({ readableStream: res.body })) {
|
||||||
|
parseAndRenderStream(chunk);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Stream processing failed:', error);
|
||||||
|
notification.error({
|
||||||
|
message: '请求失败',
|
||||||
|
description: 'AI 回答获取失败,请稍后再试',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 模拟抓取完成
|
||||||
|
content.value = '';
|
||||||
|
agentRequestLoading.value = false;
|
||||||
|
fetchStatus.value = 'completed';
|
||||||
|
};
|
||||||
|
|
||||||
|
// ==================== Nodes ====================
|
||||||
|
|
||||||
|
// 监听 itemMessage 变化并更新 resultItems
|
||||||
|
watch(
|
||||||
|
() => props.itemMessage,
|
||||||
|
(newVal) => {
|
||||||
|
resultItems.value = [];
|
||||||
|
chainItems.value = [];
|
||||||
|
content.value = '';
|
||||||
|
pptMessage.value = newVal[1]?.content;
|
||||||
|
},
|
||||||
|
{ deep: true },
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="layout">
|
||||||
|
<PreviewDrawer />
|
||||||
|
<div class="chat">
|
||||||
|
<div class="chat-left">
|
||||||
|
<!-- 🌟 输入框 -->
|
||||||
|
<Sender
|
||||||
|
:value="content"
|
||||||
|
class="sender"
|
||||||
|
placeholder="文档内容"
|
||||||
|
:auto-size="{ minRows: 6, maxRows: 18 }"
|
||||||
|
:loading="agentRequestLoading"
|
||||||
|
:disabled="agentRequestLoading"
|
||||||
|
@submit="startFetching"
|
||||||
|
@change="(value) => (content = value)"
|
||||||
|
>
|
||||||
|
<template #header>
|
||||||
|
<Sender.Header :open="true" :title="props.item?.name || ''" />
|
||||||
|
</template>
|
||||||
|
</Sender>
|
||||||
|
</div>
|
||||||
|
<Space direction="vertical" size:16 class="chat-right">
|
||||||
|
<!-- 思维链 -->
|
||||||
|
<ThoughtChain size="large" :items="chainItems">
|
||||||
|
<ThoughtChainItem
|
||||||
|
v-for="item in chainItems"
|
||||||
|
:key="item.key"
|
||||||
|
style="background: #0d0d10"
|
||||||
|
/>
|
||||||
|
</ThoughtChain>
|
||||||
|
<Welcome
|
||||||
|
v-if="chainItems.length <= 0 && !pptMessage"
|
||||||
|
variant="borderless"
|
||||||
|
icon="https://mdn.alipayobjects.com/huamei_iwk9zp/afts/img/A*s5sNRo5LjfQAAAAAAAAAAAAADgCCAQ/fmt.webp"
|
||||||
|
title="欢迎使用知识库"
|
||||||
|
description="请选择模板列表中需要生成文件的模板,输入参数后开始生成生成文件。"
|
||||||
|
/>
|
||||||
|
<ADivider v-if="pptMessage && chainItems.length > 0" />
|
||||||
|
<Card
|
||||||
|
v-if="pptMessage"
|
||||||
|
title="PPT"
|
||||||
|
class="w-5/12 cursor-pointer hover:shadow-xl"
|
||||||
|
style="background: transparent"
|
||||||
|
>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle class="flex-start flex text-xl">
|
||||||
|
<VbenIcon :icon="SvgPPT" class="size-8 flex-shrink-0" />
|
||||||
|
知识库已生成,有效时间五分钟
|
||||||
|
</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
|
||||||
|
<CardContent class="flex items-center justify-around">
|
||||||
|
<Button @click="() => openPreviewDrawer('right', pptMessage)">
|
||||||
|
预览
|
||||||
|
</Button>
|
||||||
|
<Button @click="downloadPpt(pptMessage)">下载</Button>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</Space>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="less">
|
||||||
|
.layout {
|
||||||
|
width: 100%;
|
||||||
|
//min-width: 1400px;
|
||||||
|
max-height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0 auto;
|
||||||
|
font-family: AlibabaPuHuiTi, v-deep(var(--ant-font-family)), sans-serif;
|
||||||
|
border-radius: v-deep(var(--ant-border-radius));
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
//max-height: 88vh;
|
||||||
|
//margin: 0 auto;
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: flex;
|
||||||
|
//flex-direction: column;
|
||||||
|
padding: 0 12px 0 12px;
|
||||||
|
}
|
||||||
|
.chat-left {
|
||||||
|
width: 40%;
|
||||||
|
height: 100%;
|
||||||
|
background: #ffffff;
|
||||||
|
//margin: 0 auto;
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 12px 12px 0 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-right {
|
||||||
|
width: 60%;
|
||||||
|
height: auto;
|
||||||
|
margin: 0 0 0 10px;
|
||||||
|
padding: 20px;
|
||||||
|
background: rgb(255, 255, 255);
|
||||||
|
}
|
||||||
|
|
||||||
|
.placeholder {
|
||||||
|
padding-top: 32px;
|
||||||
|
text-align: left;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sender {
|
||||||
|
background: #ffffff;
|
||||||
|
width: 100%;
|
||||||
|
min-height: 100px;
|
||||||
|
box-shadow: v-deep(var(--ant-box-shadow));
|
||||||
|
}
|
||||||
|
</style>
|
119
apps/web-antd/src/views/repository/index.vue
Normal file
119
apps/web-antd/src/views/repository/index.vue
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import type { RepositoryTempItem, ResultItem } from './typing';
|
||||||
|
|
||||||
|
import { onMounted, ref } from 'vue';
|
||||||
|
|
||||||
|
import { notification } from 'ant-design-vue';
|
||||||
|
|
||||||
|
import {
|
||||||
|
getAppListByType,
|
||||||
|
getWorkflowInfo,
|
||||||
|
getWorkflowList,
|
||||||
|
sendPpt,
|
||||||
|
} from '#/api';
|
||||||
|
|
||||||
|
import { RepositoryListView, RepositoryWorkView } from './components';
|
||||||
|
|
||||||
|
const temp = ref<RepositoryTempItem[]>([]);
|
||||||
|
|
||||||
|
const history = ref([]);
|
||||||
|
const loading = ref(true);
|
||||||
|
const itemMessage = ref<ResultItem[]>([]);
|
||||||
|
|
||||||
|
const getLogs = async (appid: string, limit: number) => {
|
||||||
|
loading.value = true;
|
||||||
|
history.value = await getWorkflowList({
|
||||||
|
appid,
|
||||||
|
limit,
|
||||||
|
});
|
||||||
|
// 将结果倒序后再赋值给 history
|
||||||
|
// history.value = res.reverse();
|
||||||
|
loading.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getTemp = async () => {
|
||||||
|
const res = await getAppListByType(2);
|
||||||
|
if (Array.isArray(res) && res.length > 0) {
|
||||||
|
// 提取 id 和 name 并赋值给 temp
|
||||||
|
temp.value = res.map(({ id, name }) => ({ id, name }));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
async function handleClick(item: RepositoryTempItem) {
|
||||||
|
// temp = item;
|
||||||
|
const res = await getWorkflowInfo({
|
||||||
|
appid: item?.appId || '',
|
||||||
|
workflowRunId: item.workflowRunId,
|
||||||
|
});
|
||||||
|
itemMessage.value = [];
|
||||||
|
if (res.inputs) {
|
||||||
|
itemMessage.value.push({
|
||||||
|
key: itemMessage.value.length + 1,
|
||||||
|
role: 'user',
|
||||||
|
content: res.inputs.declarationDoc,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (res.outputs) {
|
||||||
|
itemMessage.value.push({
|
||||||
|
key: itemMessage.value.length + 1,
|
||||||
|
role: 'ai',
|
||||||
|
content: res.outputs.files[0],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleNewConversation() {
|
||||||
|
// 清空对话内容
|
||||||
|
itemMessage.value = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentTemp = ref<RepositoryTempItem | undefined>();
|
||||||
|
|
||||||
|
async function handleClickMode(item: RepositoryTempItem) {
|
||||||
|
notification.success({
|
||||||
|
message: `${item.name}`,
|
||||||
|
description: '已选取',
|
||||||
|
duration: 3,
|
||||||
|
});
|
||||||
|
currentTemp.value = item;
|
||||||
|
await getLogs(item.id, 5);
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
getTemp();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="layout">
|
||||||
|
<div class="lg:w-1/6">
|
||||||
|
<RepositoryListView
|
||||||
|
title="选择模板"
|
||||||
|
:temp="temp"
|
||||||
|
:items="history"
|
||||||
|
:loading="loading"
|
||||||
|
@click-mode="handleClickMode"
|
||||||
|
@click="handleClick"
|
||||||
|
@new-conversation="handleNewConversation"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<RepositoryWorkView
|
||||||
|
:item="currentTemp"
|
||||||
|
:run-workflow="sendPpt"
|
||||||
|
:item-message="itemMessage"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="less">
|
||||||
|
.layout {
|
||||||
|
width: 100%;
|
||||||
|
//min-width: 1400px;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
padding: 0;
|
||||||
|
background: hsl(216deg 21.74% 95.49%);
|
||||||
|
font-family: AlibabaPuHuiTi, v-deep(var(--ant-font-family)), sans-serif;
|
||||||
|
border-radius: v-deep(var(--ant-border-radius));
|
||||||
|
}
|
||||||
|
</style>
|
79
apps/web-antd/src/views/repository/typing.ts
Normal file
79
apps/web-antd/src/views/repository/typing.ts
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
interface RepositoryTempItem {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
workflowRunId?: string;
|
||||||
|
appId?: string;
|
||||||
|
userId?: string;
|
||||||
|
taskId?: string;
|
||||||
|
url?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface RepositoryHistoryItem {
|
||||||
|
id: string;
|
||||||
|
workflowRun: {
|
||||||
|
id: string;
|
||||||
|
};
|
||||||
|
createdByEndUser: {
|
||||||
|
id: string;
|
||||||
|
sessionId: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
items?: RepositoryHistoryItem[];
|
||||||
|
temp: RepositoryTempItem[];
|
||||||
|
title: string;
|
||||||
|
loading: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ResultItem {
|
||||||
|
key: number;
|
||||||
|
role: 'ai' | 'user';
|
||||||
|
content: any;
|
||||||
|
footer?: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface WorkflowContext {
|
||||||
|
conversationId: string;
|
||||||
|
userId: string;
|
||||||
|
inputs: {
|
||||||
|
[key: string]: any;
|
||||||
|
};
|
||||||
|
files: [];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface WorkflowResult {
|
||||||
|
data: {
|
||||||
|
outputs: {
|
||||||
|
files: [
|
||||||
|
{
|
||||||
|
filename: string;
|
||||||
|
url: string;
|
||||||
|
},
|
||||||
|
];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface PropsWork {
|
||||||
|
itemMessage?: ResultItem[];
|
||||||
|
item?: RepositoryTempItem;
|
||||||
|
runWorkflow?: (appId: any, data: WorkflowContext) => Promise<WorkflowResult>;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface MenuItem {
|
||||||
|
key: string;
|
||||||
|
label: string;
|
||||||
|
title: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type {
|
||||||
|
MenuItem,
|
||||||
|
Props,
|
||||||
|
PropsWork,
|
||||||
|
RepositoryHistoryItem,
|
||||||
|
RepositoryTempItem,
|
||||||
|
ResultItem,
|
||||||
|
WorkflowContext,
|
||||||
|
WorkflowResult,
|
||||||
|
};
|
@ -14,6 +14,7 @@ const SvgAntdvLogoIcon = createIconifyIcon('svg:antdv-logo');
|
|||||||
const SvgSpider = createIconifyIcon('svg:la-spider');
|
const SvgSpider = createIconifyIcon('svg:la-spider');
|
||||||
const SvgPPT = createIconifyIcon('svg:powerpoint');
|
const SvgPPT = createIconifyIcon('svg:powerpoint');
|
||||||
const SvgWord = createIconifyIcon('svg:word');
|
const SvgWord = createIconifyIcon('svg:word');
|
||||||
|
const SvgRepository = createIconifyIcon('iconoir:repository');
|
||||||
|
|
||||||
export {
|
export {
|
||||||
SvgAntdvLogoIcon,
|
SvgAntdvLogoIcon,
|
||||||
@ -26,6 +27,7 @@ export {
|
|||||||
SvgCardIcon,
|
SvgCardIcon,
|
||||||
SvgDownloadIcon,
|
SvgDownloadIcon,
|
||||||
SvgPPT,
|
SvgPPT,
|
||||||
|
SvgRepository,
|
||||||
SvgSpider,
|
SvgSpider,
|
||||||
SvgWord,
|
SvgWord,
|
||||||
};
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user