perf: 渲染逻辑处理

This commit is contained in:
rd
2025-08-27 17:18:47 +08:00
parent 6e7bf7f9e4
commit 1360717647
6 changed files with 262 additions and 100 deletions

View File

@ -29,7 +29,10 @@ export function configAutoImport() {
'merge', 'merge',
'debounce', 'debounce',
'isEqual', 'isEqual',
'isString' 'isString',
'isArray',
'findLast',
'findLastIndex',
], ],
'@/hooks': ['useModal'], '@/hooks': ['useModal'],
}, },

View File

@ -2,9 +2,11 @@
import { Button } from '@arco-design/web-vue'; import { Button } from '@arco-design/web-vue';
import { Bubble } from '@/components/xt-chat/xt-bubble'; import { Bubble } from '@/components/xt-chat/xt-bubble';
import Http from '@/api';
import { downloadByUrl } from '@/utils/tools'; import { downloadByUrl } from '@/utils/tools';
import markdownit from 'markdown-it'; import markdownit from 'markdown-it';
import { message } from 'ant-design-vue'; import { message } from 'ant-design-vue';
import { FILE_TYPE } from '@/components/xt-chat/chat-view/constants';
export default { export default {
emits: ['close'], emits: ['close'],
@ -21,8 +23,6 @@ export default {
setup(props, { emit, expose }) { setup(props, { emit, expose }) {
const bubbleRef = ref(null); const bubbleRef = ref(null);
console.log(props.rightViewInfo)
const md = markdownit({ const md = markdownit({
html: true, html: true,
breaks: true, breaks: true,
@ -30,15 +30,44 @@ export default {
typographer: true, typographer: true,
}); });
const tasks = computed(() => {
return props.rightViewInfo.payload?.tasks ?? [];
});
const isTaskManage = computed(() => {
return props.rightViewInfo.task_type === '任务管理';
});
const isMediaCenter = computed(() => {
return props.rightViewInfo.task_type === '素材中心';
});
const onDownload = () => { const onDownload = () => {
// downloadByUrl(''); // downloadByUrl('');
message.success('下载成功!'); message.success('下载成功!');
}; };
const onAddMediaCenter = () => { const onAddMediaCenter = () => {
const {
api: { endpoint, method },
payload,
} = props.rightViewInfo;
Http[method.toLowerCase()]?.(endpoint, payload).then((res) => {
const { code } = res;
if (code === 200) {
message.success('成功添加至“素材中心”模块。'); message.success('成功添加至“素材中心”模块。');
}
});
}; };
const onAddTaskManage = () => { const onAddTaskManage = () => {
const {
api: { endpoint, method },
payload,
} = props.rightViewInfo;
Http[method.toLowerCase()]?.(endpoint, payload).then((res) => {
const { code } = res;
if (code === 200) {
message.success('成功添加至“任务管理”模块。'); message.success('成功添加至“任务管理”模块。');
}
});
}; };
const abortTyping = () => { const abortTyping = () => {
bubbleRef.value?.abortTyping?.(); bubbleRef.value?.abortTyping?.();
@ -46,6 +75,7 @@ export default {
const renderHeader = () => { const renderHeader = () => {
return ( return (
<header class="header flex justify-end items-center mb-16px px-32px"> <header class="header flex justify-end items-center mb-16px px-32px">
{isMediaCenter.value && (
<Button <Button
type="outline" type="outline"
size="medium" size="medium"
@ -55,6 +85,9 @@ export default {
> >
素材中心 素材中心
</Button> </Button>
)}
{isTaskManage.value && (
<Button <Button
type="outline" type="outline"
size="medium" size="medium"
@ -64,7 +97,8 @@ export default {
> >
任务管理 任务管理
</Button> </Button>
<Button )}
{/*<Button
type="outline" type="outline"
size="medium" size="medium"
class="mr-16px" class="mr-16px"
@ -72,13 +106,74 @@ export default {
onClick={onDownload} onClick={onDownload}
> >
下载 下载
</Button> </Button>*/}
<div class="line mr-24px w-1px h-16px bg-#B1B2B5"></div> <div class="line mr-24px w-1px h-16px bg-#B1B2B5"></div>
<icon-close size={20} class="color-#737478 cursor-pointer" onClick={() => emit('close')} /> <icon-close size={20} class="color-#737478 cursor-pointer" onClick={() => emit('close')} />
</header> </header>
); );
}; };
const renderTaskManage = () => {
const { file_type } = props.rightViewInfo;
return tasks.value.map((item) => {
const { params, execution_time, name } = item;
if (file_type === FILE_TYPE.topic_only) {
return (
<div class="mb-20px">
<p>{params?.media_account ?? '-'}</p>
<p>{`- ${execution_time}${name}`}</p>
</div>
);
} else if (file_type === FILE_TYPE.topic_with_content) {
return (
<div class="mb-20px">
<p>{`${params.media_account}${params.platform}`}</p>
<p>{`日期:${execution_time}`}</p>
<p>{`选题:${params.topic}`}</p>
<p>{`标题${name}`}</p>
<p>{`正文:${params.content}`}</p>
</div>
);
}
return null;
});
};
const renderMediaCenter = () => {
return tasks.value.map((item) => {
const { params, execution_time, name, content } = item;
const { file_type } = props.rightViewInfo;
if (file_type === FILE_TYPE.content_only) {
return (
<div class="mb-20px">
<p>{params?.media_account ?? '-'}</p>
<p>📅 {`${execution_time}`}</p>
<p class="mb-10">📝 {`${name}`}</p>
<p>正文</p>
<p>📝 {`${content}`}</p>
</div>
);
} else if (file_type === FILE_TYPE.topic_with_content) {
return (
<div class="mb-20px">
<p>{`${params.media_account}${params.platform}`}</p>
<p>{`日期:${execution_time}`}</p>
<p>{`选题:${params.topic}`}</p>
<p>{`标题${name}`}</p>
<p>{`正文:${params.content}`}</p>
</div>
);
}
});
};
const renderContainer = () => { const renderContainer = () => {
const renderMessage = () => {
if (isTaskManage.value) {
return renderTaskManage();
}
if (isMediaCenter.value) {
return renderMediaCenter();
}
return null;
};
return ( return (
<section class="flex-1 overflow-y-auto content flex justify-center px-32px"> <section class="flex-1 overflow-y-auto content flex justify-center px-32px">
<Bubble <Bubble
@ -90,7 +185,9 @@ export default {
onTypingComplete={() => { onTypingComplete={() => {
console.log('onTypingComplete'); console.log('onTypingComplete');
}} }}
messageRender={(content) => <div v-html={md.render(content)}></div>} messageRender={() => {
return renderMessage();
}}
/> />
</section> </section>
); );

View File

@ -41,3 +41,14 @@ export enum EnumTeamRunStatus {
RunResponseContent = 'RunResponseContent', // l2执行中 RunResponseContent = 'RunResponseContent', // l2执行中
RunCompleted = 'RunCompleted', // l2完成 RunCompleted = 'RunCompleted', // l2完成
} }
export const FILE_TYPE = {
topic_only: 'topic_only', // 排期&选题
topic_with_content: 'topic_with_content', // 选题&内容稿件
content_only: 'content_only', // 内容稿件
}
export const FILE_TYPE_MAP = {
[FILE_TYPE.topic_only]: '排期&选题',
[FILE_TYPE.topic_with_content]: '选题&内容稿件',
[FILE_TYPE.content_only]: '内容稿件',
}

View File

@ -27,6 +27,7 @@ export default {
const rightViewRef = ref(null); const rightViewRef = ref(null);
const bubbleListRef = ref<any>(null); const bubbleListRef = ref<any>(null);
const sseController = ref<any>(null); const sseController = ref<any>(null);
const searchValue = ref('');
const conversationId = computed(() => { const conversationId = computed(() => {
return route.params.conversationId; return route.params.conversationId;

View File

@ -3,7 +3,14 @@ import { ref } from 'vue';
import type { BubbleListProps } from '@/components/xt-chat/xt-bubble/types'; import type { BubbleListProps } from '@/components/xt-chat/xt-bubble/types';
import markdownit from 'markdown-it'; import markdownit from 'markdown-it';
import { message as antdMessage, Timeline } from 'ant-design-vue'; import { message as antdMessage, Timeline } from 'ant-design-vue';
import { IconFile, IconCaretUp, IconDownload, IconRefresh, IconCopy } from '@arco-design/web-vue/es/icon'; import {
IconFile,
IconCaretUp,
IconDownload,
IconCaretDown,
IconRefresh,
IconCopy,
} from '@arco-design/web-vue/es/icon';
import { Tooltip } from 'ant-design-vue'; import { Tooltip } from 'ant-design-vue';
import TextOverTips from '@/components/text-over-tips/index.vue'; import TextOverTips from '@/components/text-over-tips/index.vue';
@ -131,13 +138,12 @@ export default function useChatHandler({ initSse }): UseChatHandlerReturn {
}; };
// 最终结果处理 // 最终结果处理
const handleFileReview = (data: MESSAGE.Answer) => { const handleFileReview = (extra_data: { type: string; data: any }) => {
const { run_id, output } = data; console.log('handleFileReview', extra_data);
const { type, data } = extra_data;
showRightView.value = true; showRightView.value = true;
rightViewInfo.value = data?.[0] ?? {};
// const _files = output?.files;
// rightViewInfo.value = _files?.[0]?.content || '';
// conversationList.value.push({ // conversationList.value.push({
// run_id, // run_id,
@ -168,28 +174,45 @@ export default function useChatHandler({ initSse }): UseChatHandlerReturn {
initSse({ message: senderRef.value?.searchValue }); initSse({ message: senderRef.value?.searchValue });
}; };
// 获取同一个对话下的最后一个run_task const getAllRunTask = (teamRunTaskId: string) => {
const getLastRunTask = (data: MESSAGE.Answer, teamRunTaskId: string) => { return conversationList.value.filter(
const allRunTask = conversationList.value.filter( (item) => item.role === ANSWER_ROLE && item.teamRunTaskId === teamRunTaskId && !item.isTeamRunTask,
(item) => item.role === ANSWER_ROLE && item.team_run_id === teamRunTaskId && !item.isTeamRunTask,
); );
};
const getRunTask = (run_id: string) => {
return conversationList.value.find((item) => item.run_id === run_id && !item.isTeamRunTask);
};
// 设置当前对话所有过程任务展开收起状态
const setRunTaskCollapse = (teamRunTaskId: string, isCollapse: boolean) => {
getAllRunTask(teamRunTaskId).forEach((item) => {
item.content.isCollapse = isCollapse;
});
};
// 获取同一个对话下的最后一个run_task
const getLastRunTask = (teamRunTaskId: string) => {
const allRunTask = getAllRunTask(teamRunTaskId);
return allRunTask[allRunTask.length - 1] ?? {}; return allRunTask[allRunTask.length - 1] ?? {};
}; };
const getFirstRunTask = (data: MESSAGE.Answer, teamRunTaskId: string) => { const getFirstRunTask = (teamRunTaskId: string) => {
const allRunTask = conversationList.value.filter( const allRunTask = getAllRunTask(teamRunTaskId);
(item) => item.role === ANSWER_ROLE && item.team_run_id === teamRunTaskId && !item.isTeamRunTask,
);
return allRunTask[0] ?? {}; return allRunTask[0] ?? {};
}; };
// 判断当前对话是否含有过程任务
const hasRunTask = (teamRunTaskId: string) => {
return conversationList.value.some((item) => item.teamRunTaskId === teamRunTaskId && !item.isTeamRunTask);
};
const getTeamRunTask = (teamRunTaskId: string) => {
return conversationList.value.find((item) => item.teamRunTaskId === teamRunTaskId);
};
const isLastRunTask = (data: MESSAGE.Answer, teamRunTaskId: string): boolean => { const isLastRunTask = (data: MESSAGE.Answer, teamRunTaskId: string): boolean => {
const { run_id } = data; const { run_id } = data;
return getLastRunTask(data, teamRunTaskId).run_id === run_id; return getLastRunTask(teamRunTaskId).run_id === run_id;
}; };
const isFirstRunTask = (data: MESSAGE.Answer, teamRunTaskId: string): boolean => { const isFirstRunTask = (data: MESSAGE.Answer, teamRunTaskId: string): boolean => {
const { run_id } = data; const { run_id } = data;
return getFirstRunTask(data, teamRunTaskId).run_id === run_id; return getFirstRunTask(teamRunTaskId).run_id === run_id;
}; };
const isLastTeamRunTask = (data: MESSAGE.Answer) => { const isLastTeamRunTask = (data: MESSAGE.Answer) => {
const { run_id } = data; const { run_id } = data;
const lastElement = conversationList.value[conversationList.value.length - 1]; const lastElement = conversationList.value[conversationList.value.length - 1];
@ -203,28 +226,47 @@ export default function useChatHandler({ initSse }): UseChatHandlerReturn {
conversationList.value.push({ conversationList.value.push({
run_id, run_id,
key: run_id, key: run_id,
team_run_id: lastTeamRunTaskId.value, teamRunTaskId: lastTeamRunTaskId.value,
content: data, content: { ...data, runStatus: EnumTeamRunStatus.RunStarted },
output: data.output, output: data.output,
role: ANSWER_ROLE, role: ANSWER_ROLE,
messageRender: (data: MESSAGE.Answer) => { messageRender: (data: MESSAGE.Answer) => {
const { node, output, runStatus, isCollapse = true } = data;
const isRulCompleted = runStatus === EnumTeamRunStatus.RunCompleted;
// console.log('messageRender', isCollapse);
let outputEleClass: string = `thought-chain-output border-l-#E6E6E8 border-l-1px pl-12px relative left-6px mb-4px`; let outputEleClass: string = `thought-chain-output border-l-#E6E6E8 border-l-1px pl-12px relative left-6px mb-4px`;
!isLastRunTask(data, lastTeamRunTaskId.value) && (outputEleClass += ' hasLine pb-12px pt-4px'); !isLastRunTask(data, lastTeamRunTaskId.value) && (outputEleClass += ' hasLine pb-12px pt-4px');
return ( return (
<> <>
{isFirstRunTask(data, lastTeamRunTaskId.value) && ( {isFirstRunTask(data, lastTeamRunTaskId.value) && (
<div class="flex items-center"> <div class="flex items-center">
<span class="font-family-medium color-#211F24 text-14px font-400 lh-22px mr-4px"></span> <span class="font-family-medium color-#211F24 text-14px font-400 lh-22px mr-4px"></span>
<IconCaretUp size={16} class="color-#211F24" /> {isCollapse ? (
<IconCaretUp
size={16}
class="color-#211F24 cursor-pointer"
onClick={() => setRunTaskCollapse(lastTeamRunTaskId.value, false)}
/>
) : (
<IconCaretDown
size={16}
class="color-#211F24 cursor-pointer"
onClick={() => setRunTaskCollapse(lastTeamRunTaskId.value, true)}
/>
)}
</div> </div>
)} )}
{isCollapse && (
<div class="relative thought-chain-item"> <div class="relative thought-chain-item">
<div class="flex items-center mb-4px"> <div class="flex items-center mb-4px">
<img src={icon2} width={13} height={13} class="mr-4px" /> <img src={isRulCompleted ? icon1 : icon2} width={13} height={13} class="mr-4px" />
<div>{data.node}</div> <div>{node}</div>
</div> </div>
<div v-html={md.render(data.output ?? '')} class={outputEleClass} /> <div v-html={md.render(output)} class={outputEleClass} />
</div> </div>
)}
</> </>
); );
}, },
@ -237,31 +279,32 @@ export default function useChatHandler({ initSse }): UseChatHandlerReturn {
const existingItem = conversationList.value.find((item) => item.run_id === run_id); const existingItem = conversationList.value.find((item) => item.run_id === run_id);
if (existingItem && output) { if (existingItem && output) {
existingItem.content.output += output; existingItem.content.output += output;
existingItem.content.runStatus = EnumTeamRunStatus.RunResponseContent;
} }
}; };
// 过程节点结束 // 过程节点结束
const handleRunTaskEnd = (data: MESSAGE.Answer) => { const handleRunTaskEnd = (data: MESSAGE.Answer) => {
const { run_id, output } = data; const { output } = data;
const existingItem = conversationList.value.find((item) => item.run_id === run_id); const existingItem = getRunTask(data.run_id);
if (existingItem) { if (existingItem) {
existingItem.content.output += output; existingItem.content.output += output;
existingItem.content.runStatus = EnumTeamRunStatus.RunCompleted;
} }
}; };
// 任务开始 // 任务开始
const handleTeamRunTaskStart = (data: MESSAGE.Answer) => { const handleTeamRunTaskStart = (data: MESSAGE.Answer) => {
// console.log('handleRunTaskStart');
const { run_id } = data; const { run_id } = data;
lastTeamRunTaskId.value = run_id; lastTeamRunTaskId.value = run_id;
currentTaskId.value = run_id; currentTaskId.value = run_id;
conversationList.value.push({ conversationList.value.push({
run_id, run_id,
isTeamRunTask: true, isTeamRunTask: true,
team_run_id: lastTeamRunTaskId.value, teamRunTaskId: lastTeamRunTaskId.value,
key: run_id, key: run_id,
content: data, content: { ...data, teamRunStatus: EnumTeamRunStatus.TeamRunStarted },
output: data.output, output: data.output,
role: ANSWER_ROLE, role: ANSWER_ROLE,
messageRender: (data: MESSAGE.Answer) => { messageRender: (data: MESSAGE.Answer) => {
@ -275,28 +318,43 @@ export default function useChatHandler({ initSse }): UseChatHandlerReturn {
const existingItem = conversationList.value.find((item) => item.run_id === run_id); const existingItem = conversationList.value.find((item) => item.run_id === run_id);
if (existingItem && output) { if (existingItem && output) {
existingItem.content.output += output; existingItem.content.output += output;
existingItem.content.teamRunStatus = EnumTeamRunStatus.TeamRunResponseContent;
} }
}; };
// 任务结束 // 任务结束
const handleTeamRunTaskEnd = (data: MESSAGE.Answer) => { const handleTeamRunTaskEnd = (data: MESSAGE.Answer) => {
const { run_id, team_session_state } = data;
const lastRunTask = getLastRunTask(data, lastTeamRunTaskId.value);
resetGenerateStatus(); resetGenerateStatus();
console.log('handleTeamRunTaskEnd', { data }, { lastRunTask });
if (lastRunTask) { const { run_id, extra_data } = data;
lastRunTask.footer = () => { const _hasRunTask = hasRunTask(lastTeamRunTaskId.value);
const _targetTask = _hasRunTask ? getLastRunTask(run_id) : getTeamRunTask(lastTeamRunTaskId.value);
if (isEmpty(_targetTask)) {
return;
}
if (_hasRunTask) {
setRunTaskCollapse(lastTeamRunTaskId.value, false);
} else {
_targetTask.content.teamRunStatus = EnumTeamRunStatus.TeamRunCompleted;
}
if (extra_data) {
handleFileReview(extra_data);
}
_targetTask.footer = () => {
return ( return (
<div class="flex items-center"> <div class="flex items-center">
<Tooltip title="复制" onClick={() => onCopy(lastRunTask.content.output)}> {!extra_data
// ? (
// <Tooltip title="下载" onClick={onDownload}>
// <IconDownload size={16} class="color-#737478 cursor-pointer mr-12px" />
// </Tooltip>
// ) :
&& (
<Tooltip title="复制" onClick={() => onCopy(_targetTask.content.output)}>
<IconCopy size={16} class="color-#737478 cursor-pointer mr-12px" /> <IconCopy size={16} class="color-#737478 cursor-pointer mr-12px" />
</Tooltip> </Tooltip>
{team_session_state && (
<Tooltip title="下载" onClick={onDownload}>
<IconDownload size={16} class="color-#737478 cursor-pointer mr-12px" />
</Tooltip>
)} )}
{isLastTeamRunTask(data) && ( {isLastTeamRunTask(data) && (
<Tooltip title="重新生成" onClick={() => onRefresh(run_id)}> <Tooltip title="重新生成" onClick={() => onRefresh(run_id)}>
@ -306,11 +364,6 @@ export default function useChatHandler({ initSse }): UseChatHandlerReturn {
</div> </div>
); );
}; };
}
if (team_session_state) {
handleFileReview(data);
}
}; };
// 消息处理主函数 // 消息处理主函数

View File

@ -1,25 +1,22 @@
declare global { declare global {
namespace MESSAGE { namespace MESSAGE {
type TASK_STATUS = type RUN_TASK_STATUS = 'RunStarted' | 'RunResponseContent' | 'RunCompleted';
| 'RunStarted' type TEAM_RUN_TASK_STATUS = 'TeamRunStarted' | 'TeamRunResponseContent' | 'TeamRunCompleted';
| 'RunResponseContent'
| 'RunCompleted'
| 'TeamRunStarted'
| 'TeamRunResponseContent'
| 'TeamRunCompleted';
interface Answer { interface Answer {
message: string; message?: string;
node: string; node?: string;
output: string; output?: string;
run_id: string; run_id?: string;
team_run_id: string; teamRunTaskId?: string;
status: TASK_STATUS; isCollapse?: boolean;
extra_data: { status?: RUN_TASK_STATUS | TEAM_RUN_TASK_STATUS;
runStatus?: RUN_TASK_STATUS;
teamRunStatus?: TEAM_RUN_TASK_STATUS;
extra_data?: {
type: string; type: string;
data: Record<string, any>; data: Record<string, any>;
}; };
team_session_state: any;
} }
} }
} }