feat(@vben/web-antd): 新增新建会话功能并优化界面展示

This commit is contained in:
Kven 2025-06-12 21:44:12 +08:00
parent 8d17c6eb9f
commit e4349221b2
13 changed files with 132 additions and 35 deletions

View File

@ -16,7 +16,7 @@ const props = withDefaults(defineProps<Props>(), {
items: () => [], items: () => [],
temp: () => [], temp: () => [],
}); });
const emit = defineEmits(['click', 'clickMode']); const emit = defineEmits(['click', 'clickMode', 'newConversation']);
const defaultConversationsItems = computed(() => { const defaultConversationsItems = computed(() => {
return props.items.map((item) => { return props.items.map((item) => {
return { return {
@ -51,6 +51,10 @@ const handleMenuClick = (item: { key: string }) => {
} }
}; };
const handleNewConversation = () => {
emit('newConversation');
};
const onConversationClick: ConversationsProps['onActiveChange'] = (key) => { const onConversationClick: ConversationsProps['onActiveChange'] = (key) => {
activeKey.value = key; activeKey.value = key;
@ -73,6 +77,17 @@ watch(
}, },
{ immediate: true }, { immediate: true },
); );
watch(
() => itemsData.value,
(newVal) => {
if (newVal && newVal.length > 0) {
selectedKeys.value = [newVal[0].key]; //
handleMenuClick(newVal[0]);
}
},
{ immediate: true },
);
</script> </script>
<template> <template>
@ -88,6 +103,10 @@ watch(
/> />
<div class="addBtn">会话</div> <div class="addBtn">会话</div>
<Button type="submit" class="newBtn" @click="handleNewConversation">
新建会话
</Button>
<!-- 🌟 会话管理 --> <!-- 🌟 会话管理 -->
<Conversations <Conversations
:items="conversationsItems" :items="conversationsItems"
@ -182,4 +201,13 @@ watch(
width: calc(100% - 24px); width: calc(100% - 24px);
margin: 12px 12px 24px 12px; 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> </style>

View File

@ -110,6 +110,7 @@ const handleFileChange: AttachmentsProps['onChange'] = (info) =>
// } // }
const startFetching = async () => { const startFetching = async () => {
resultItems.value = [];
agentRequestLoading.value = true; agentRequestLoading.value = true;
fetchStatus.value = 'fetching'; fetchStatus.value = 'fetching';
resultItems.value.push({ resultItems.value.push({
@ -192,6 +193,7 @@ watch(
() => props.itemMessage, () => props.itemMessage,
(newVal) => { (newVal) => {
resultItems.value = []; resultItems.value = [];
content.value = '';
if (newVal && newVal.length > 0) { if (newVal && newVal.length > 0) {
newVal.forEach((msg) => { newVal.forEach((msg) => {
if (msg.role === 'user') { if (msg.role === 'user') {
@ -272,7 +274,7 @@ watch(
:typing="true" :typing="true"
:items="resultItems" :items="resultItems"
:roles="roles" :roles="roles"
style="flex: 1" style="flex: 1; padding: 10px 10px 0"
/> />
<!-- 🌟 输入框 --> <!-- 🌟 输入框 -->

View File

@ -60,6 +60,11 @@ async function handleClick(item: PPTTempItem) {
} }
} }
function handleNewConversation() {
//
itemMessage.value = [];
}
const currentTemp = ref<null | PPTTempItem>(null); const currentTemp = ref<null | PPTTempItem>(null);
async function handleClickMode(item: PPTTempItem) { async function handleClickMode(item: PPTTempItem) {
@ -87,6 +92,7 @@ onMounted(() => {
:loading="loading" :loading="loading"
@click-mode="handleClickMode" @click-mode="handleClickMode"
@click="handleClick" @click="handleClick"
@new-conversation="handleNewConversation"
/> />
</div> </div>
<PptWorkView <PptWorkView

View File

@ -40,6 +40,7 @@ const props = withDefaults(defineProps<Props>(), {
id: '', id: '',
name: '', name: '',
url: '', url: '',
description: '',
}), }),
runSpider: () => async () => ({ runSpider: () => async () => ({
msg: '', msg: '',
@ -372,7 +373,7 @@ watch(
<CardTitle class="text-lg">{{ title }}</CardTitle> <CardTitle class="text-lg">{{ title }}</CardTitle>
</CardHeader> </CardHeader>
<CardContent class="flex flex-wrap pt-0"> <CardContent class="flex flex-wrap pt-0">
{{ item ? item.url : '请选择左侧列表' }} {{ props.item ? props.item.description : '请选择左侧列表' }}
</CardContent> </CardContent>
<CardFooter class="flex justify-end"> <CardFooter class="flex justify-end">
<RangePicker <RangePicker

View File

@ -30,6 +30,7 @@ const transformItems = computed(() => {
key: item.id, key: item.id,
label: item.name, label: item.name,
title: item.name, title: item.name,
description: item.description,
})); }));
}); });
@ -43,6 +44,7 @@ const handleMenuClick = (item: { key: string }) => {
name: selectedItem.title, name: selectedItem.title,
id: selectedItem.key, id: selectedItem.key,
url: selectedItem.url, url: selectedItem.url,
description: selectedItem.description,
}; };
emit('clickMode', transformedItem); // emit('clickMode', transformedItem); //
} }
@ -66,6 +68,16 @@ watch(
}, },
{ immediate: true }, { immediate: true },
); );
watch(
() => itemsData.value,
(newVal) => {
if (newVal && newVal.length > 0) {
selectedKeys.value = [newVal[0].key]; //
openKeys.value = [newVal[0].key]; //
}
},
{ immediate: true },
);
</script> </script>
<template> <template>

View File

@ -33,6 +33,7 @@ const props = withDefaults(defineProps<Props>(), {
id: '', id: '',
name: '', name: '',
url: '', url: '',
description: '',
}), }),
runSpiderGz: () => async () => ({ runSpiderGz: () => async () => ({
outputs: { outputs: {
@ -292,7 +293,7 @@ watch(
<CardTitle class="text-lg">{{ title }}</CardTitle> <CardTitle class="text-lg">{{ title }}</CardTitle>
</CardHeader> </CardHeader>
<CardContent class="flex flex-wrap pt-0"> <CardContent class="flex flex-wrap pt-0">
{{ item ? item.url : '请选择左侧列表' }} {{ props.item ? props.item.description : '请选择左侧列表' }}
</CardContent> </CardContent>
<CardFooter class="flex justify-end"> <CardFooter class="flex justify-end">
<Button :disabled="!item" type="primary" @click="startFetching"> <Button :disabled="!item" type="primary" @click="startFetching">

View File

@ -36,7 +36,11 @@ const getTemp = async () => {
const res = await getAppListByType(3); const res = await getAppListByType(3);
if (Array.isArray(res) && res.length > 0) { if (Array.isArray(res) && res.length > 0) {
// id name temp // id name temp
temp.value = res.map(({ id, name }) => ({ id, name })); temp.value = res.map(({ id, name, description }) => ({
id,
name,
description,
}));
} }
}; };
@ -58,6 +62,7 @@ async function handleClick(item: WordFlowItem) {
const currentTemp = ref<TempItem>({ const currentTemp = ref<TempItem>({
id: '77c068fd-d5b6-4c33-97d8-db5511a09b26', id: '77c068fd-d5b6-4c33-97d8-db5511a09b26',
name: '全国公共资源交易平台_信息爬取', name: '全国公共资源交易平台_信息爬取',
description: 'https://ygp.gdzwfw.gov.cn/',
}); });
async function handleClickMode(item: TempItem) { async function handleClickMode(item: TempItem) {

View File

@ -2,6 +2,7 @@ interface TempItem {
id: string; id: string;
name: string; name: string;
url?: string; url?: string;
description?: string;
} }
interface ResultItem { interface ResultItem {

View File

@ -18,7 +18,7 @@ const props = withDefaults(defineProps<PropsHistory>(), {
items: () => [], items: () => [],
temp: () => [], temp: () => [],
}); });
const emit = defineEmits(['click', 'clickMode', 'delete']); const emit = defineEmits(['click', 'clickMode', 'delete', 'newConversation']);
const defaultConversationsItems = computed(() => { const defaultConversationsItems = computed(() => {
return props.items.map((item) => { return props.items.map((item) => {
return { return {
@ -58,6 +58,10 @@ const onConversationClick: ConversationsProps['onActiveChange'] = (key) => {
emit('click', key); emit('click', key);
}; };
const handleNewConversation = () => {
emit('newConversation');
};
const menuConfig: ConversationsProps['menu'] = (conversation) => ({ const menuConfig: ConversationsProps['menu'] = (conversation) => ({
items: [ items: [
{ {
@ -83,6 +87,17 @@ watch(
}, },
{ immediate: true }, { immediate: true },
); );
watch(
() => itemsData.value,
(newVal) => {
if (newVal && newVal.length > 0) {
selectedKeys.value = [newVal[0].key]; //
handleMenuClick(newVal[0]);
}
},
{ immediate: true },
);
</script> </script>
<template> <template>
@ -99,8 +114,10 @@ watch(
<!-- 🌟 添加会话 --> <!-- 🌟 添加会话 -->
<!-- <Button type="link" class="addBtn">会话</Button>--> <!-- <Button type="link" class="addBtn">会话</Button>-->
<div class="addBtn">会话</div> <div class="addBtn">会话</div>
<!-- 🌟 添加会话 --> <!-- 🌟 新增新建会话按钮 -->
<Button type="submit" class="newBtn" @click="handleNewConversation">
新建会话
</Button>
<!-- 🌟 会话管理 --> <!-- 🌟 会话管理 -->
<Conversations <Conversations
:items="conversationsItems" :items="conversationsItems"
@ -199,4 +216,12 @@ watch(
width: calc(100% - 24px); width: calc(100% - 24px);
margin: 12px 12px 24px 12px; 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> </style>

View File

@ -58,6 +58,12 @@ const props = withDefaults(defineProps<Props>(), {
}, },
}, },
}), }),
projectInfo: () => ({
projectName: '',
projectContext: '',
projectKeyAvoidTechOrKeyword: '',
userInitialInnovationPoint: '',
}),
}); });
// markdown // markdown
@ -148,12 +154,7 @@ const content = ref('');
// const conversationsItems = ref(defaultConversationsItems); // const conversationsItems = ref(defaultConversationsItems);
const attachedFiles = ref<AttachmentsProps['items']>([]); const attachedFiles = ref<AttachmentsProps['items']>([]);
const agentRequestLoading = ref(false); const agentRequestLoading = ref(false);
const projectInfo = ref({ const projectInfo = ref({ ...props.projectInfo });
projectName: '',
projectContext: '',
projectKeyAvoidTechOrKeyword: '',
userInitialInnovationPoint: '',
});
const open = ref(false); const open = ref(false);
// const inputStatus = ref<string>(''); // const inputStatus = ref<string>('');
const fetchStatus = ref(''); const fetchStatus = ref('');
@ -178,20 +179,6 @@ function openPreviewDrawer(
} }
// ==================== Event ==================== // ==================== Event ====================
// .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;
// }
function extractDocxInfo( function extractDocxInfo(
markdownLink: string, markdownLink: string,
): null | { filename: string; url: string } { ): null | { filename: string; url: string } {
@ -310,6 +297,7 @@ watch(
() => props.itemMessage, () => props.itemMessage,
(newVal) => { (newVal) => {
resultItems.value = []; resultItems.value = [];
content.value = '';
if (newVal && newVal.length > 0) { if (newVal && newVal.length > 0) {
newVal.forEach((msg) => { newVal.forEach((msg) => {
if (msg.role === 'user') { if (msg.role === 'user') {
@ -372,6 +360,14 @@ watch(
}, },
{ deep: true }, { deep: true },
); );
watch(
() => props.projectInfo,
(newVal) => {
projectInfo.value = { ...newVal };
},
{ deep: true, immediate: true },
);
</script> </script>
<template> <template>
@ -429,7 +425,7 @@ watch(
v-else v-else
:items="resultItems" :items="resultItems"
:roles="roles" :roles="roles"
style="flex: 1" style="flex: 1; padding: 10px"
:message-render="renderMarkdown" :message-render="renderMarkdown"
/> />
@ -445,12 +441,13 @@ watch(
:value="content" :value="content"
class="sender" class="sender"
:loading="agentRequestLoading" :loading="agentRequestLoading"
:disabled="agentRequestLoading"
@submit="startFetching" @submit="startFetching"
@change="(value) => (content = value)" @change="(value) => (content = value)"
> >
<template #prefix> <template #prefix>
<Badge :dot="attachedFiles.length > 0 && !headerOpen"> <Badge :dot="attachedFiles.length > 0 && !headerOpen">
<Button type="text" @click="() => (open = !open)"> <Button @click="() => (open = !open)">
<template #icon> <template #icon>
<EditOutlined /> <EditOutlined />
</template> </template>

View File

@ -23,6 +23,7 @@ const temp = ref([]);
const hitsory = ref([]); const hitsory = ref([]);
const loading = ref(true); const loading = ref(true);
const params = ref({}); const params = ref({});
const projectInfo = ref({});
const getLogs = async (appid: string) => { const getLogs = async (appid: string) => {
loading.value = true; loading.value = true;
@ -68,17 +69,30 @@ function handleClickMode(item: WordTempItem) {
getParameters(item.id); getParameters(item.id);
} }
function handleNewConversation() {
//
projectInfo.value = {
projectName: '',
projectContext: '',
projectKeyAvoidTechOrKeyword: '',
userInitialInnovationPoint: '',
};
//
itemMessage.value = [];
}
const itemMessage = ref<ResultItem[]>([]); const itemMessage = ref<ResultItem[]>([]);
async function deleteLog(item: any) { async function deleteLog(item: any) {
const res = await deleteChatflow({ const res = await deleteChatflow({
appId: temp.value.id, appId: currentTemp.value.id,
conversationId: item, conversationId: item,
userId: '1562', userId: '1562',
}); });
if (res.code === 0) { if (res.message === '删除成功') {
message.success('删除成功'); message.success('删除成功');
getLogs(temp.value.id); getLogs(currentTemp.value.id);
} else { } else {
message.error('删除失败'); message.error('删除失败');
} }
@ -96,6 +110,7 @@ async function handleClick(item: string) {
conversationId: item, conversationId: item,
limit: '', limit: '',
}); });
projectInfo.value = res.data[0].inputs;
itemMessage.value = []; itemMessage.value = [];
if (res.data.length > 0) { if (res.data.length > 0) {
res.data.forEach((msg) => { res.data.forEach((msg) => {
@ -120,6 +135,7 @@ async function handleClick(item: string) {
onMounted(() => { onMounted(() => {
getTemp(); getTemp();
handleNewConversation();
}); });
</script> </script>
@ -134,6 +150,7 @@ onMounted(() => {
@click-mode="handleClickMode" @click-mode="handleClickMode"
@click="handleClick" @click="handleClick"
@delete="deleteLog" @delete="deleteLog"
@new-conversation="handleNewConversation"
/> />
</div> </div>
<WordWorkView <WordWorkView
@ -141,6 +158,7 @@ onMounted(() => {
:params-data="params" :params-data="params"
:run-chatflow="sendWord" :run-chatflow="sendWord"
:item-message="itemMessage" :item-message="itemMessage"
:project-info="projectInfo"
/> />
</div> </div>
</template> </template>

View File

@ -48,6 +48,7 @@ interface Props {
appId: any, appId: any,
context: WorkflowContext, context: WorkflowContext,
) => Promise<WorkflowResult>; ) => Promise<WorkflowResult>;
projectInfo: object;
} }
interface PropsHistory { interface PropsHistory {

View File

@ -11,8 +11,8 @@ export default defineConfig(async () => {
rewrite: (path) => path.replace(/^\/api/, ''), rewrite: (path) => path.replace(/^\/api/, ''),
// mock代理目标地址 // mock代理目标地址
// target: 'http://172.16.35.190:9090/api', // target: 'http://172.16.35.190:9090/api',
// target: 'http://43.139.10.64:8082/api', target: 'http://43.139.10.64:8082/api',
target: 'http://localhost:8081/api', // target: 'http://localhost:8081/api',
// target: 'http://192.168.3.238:8081/api', // target: 'http://192.168.3.238:8081/api',
ws: true, ws: true,
}, },