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',
'debounce',
'isEqual',
'isString'
'isString',
'isArray',
'findLast',
'findLastIndex',
],
'@/hooks': ['useModal'],
},

View File

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

View File

@ -41,3 +41,14 @@ export enum EnumTeamRunStatus {
RunResponseContent = 'RunResponseContent', // 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 bubbleListRef = ref<any>(null);
const sseController = ref<any>(null);
const searchValue = ref('');
const conversationId = computed(() => {
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 markdownit from 'markdown-it';
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 TextOverTips from '@/components/text-over-tips/index.vue';
@ -131,13 +138,12 @@ export default function useChatHandler({ initSse }): UseChatHandlerReturn {
};
// 最终结果处理
const handleFileReview = (data: MESSAGE.Answer) => {
const { run_id, output } = data;
const handleFileReview = (extra_data: { type: string; data: any }) => {
console.log('handleFileReview', extra_data);
const { type, data } = extra_data;
showRightView.value = true;
// const _files = output?.files;
// rightViewInfo.value = _files?.[0]?.content || '';
rightViewInfo.value = data?.[0] ?? {};
// conversationList.value.push({
// run_id,
@ -168,28 +174,45 @@ export default function useChatHandler({ initSse }): UseChatHandlerReturn {
initSse({ message: senderRef.value?.searchValue });
};
// 获取同一个对话下的最后一个run_task
const getLastRunTask = (data: MESSAGE.Answer, teamRunTaskId: string) => {
const allRunTask = conversationList.value.filter(
(item) => item.role === ANSWER_ROLE && item.team_run_id === teamRunTaskId && !item.isTeamRunTask,
const getAllRunTask = (teamRunTaskId: string) => {
return conversationList.value.filter(
(item) => item.role === ANSWER_ROLE && item.teamRunTaskId === 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] ?? {};
};
const getFirstRunTask = (data: MESSAGE.Answer, teamRunTaskId: string) => {
const allRunTask = conversationList.value.filter(
(item) => item.role === ANSWER_ROLE && item.team_run_id === teamRunTaskId && !item.isTeamRunTask,
);
const getFirstRunTask = (teamRunTaskId: string) => {
const allRunTask = getAllRunTask(teamRunTaskId);
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 { 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 { run_id } = data;
return getFirstRunTask(data, teamRunTaskId).run_id === run_id;
return getFirstRunTask(teamRunTaskId).run_id === run_id;
};
const isLastTeamRunTask = (data: MESSAGE.Answer) => {
const { run_id } = data;
const lastElement = conversationList.value[conversationList.value.length - 1];
@ -203,28 +226,47 @@ export default function useChatHandler({ initSse }): UseChatHandlerReturn {
conversationList.value.push({
run_id,
key: run_id,
team_run_id: lastTeamRunTaskId.value,
content: data,
teamRunTaskId: lastTeamRunTaskId.value,
content: { ...data, runStatus: EnumTeamRunStatus.RunStarted },
output: data.output,
role: ANSWER_ROLE,
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`;
!isLastRunTask(data, lastTeamRunTaskId.value) && (outputEleClass += ' hasLine pb-12px pt-4px');
return (
<>
{isFirstRunTask(data, lastTeamRunTaskId.value) && (
<div class="flex items-center">
<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>
)}
{isCollapse && (
<div class="relative thought-chain-item">
<div class="flex items-center mb-4px">
<img src={icon2} width={13} height={13} class="mr-4px" />
<div>{data.node}</div>
<img src={isRulCompleted ? icon1 : icon2} width={13} height={13} class="mr-4px" />
<div>{node}</div>
</div>
<div v-html={md.render(data.output ?? '')} class={outputEleClass} />
<div v-html={md.render(output)} class={outputEleClass} />
</div>
)}
</>
);
},
@ -237,31 +279,32 @@ export default function useChatHandler({ initSse }): UseChatHandlerReturn {
const existingItem = conversationList.value.find((item) => item.run_id === run_id);
if (existingItem && output) {
existingItem.content.output += output;
existingItem.content.runStatus = EnumTeamRunStatus.RunResponseContent;
}
};
// 过程节点结束
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) {
existingItem.content.output += output;
existingItem.content.runStatus = EnumTeamRunStatus.RunCompleted;
}
};
// 任务开始
const handleTeamRunTaskStart = (data: MESSAGE.Answer) => {
// console.log('handleRunTaskStart');
const { run_id } = data;
lastTeamRunTaskId.value = run_id;
currentTaskId.value = run_id;
conversationList.value.push({
run_id,
isTeamRunTask: true,
team_run_id: lastTeamRunTaskId.value,
teamRunTaskId: lastTeamRunTaskId.value,
key: run_id,
content: data,
content: { ...data, teamRunStatus: EnumTeamRunStatus.TeamRunStarted },
output: data.output,
role: ANSWER_ROLE,
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);
if (existingItem && output) {
existingItem.content.output += output;
existingItem.content.teamRunStatus = EnumTeamRunStatus.TeamRunResponseContent;
}
};
// 任务结束
const handleTeamRunTaskEnd = (data: MESSAGE.Answer) => {
const { run_id, team_session_state } = data;
const lastRunTask = getLastRunTask(data, lastTeamRunTaskId.value);
resetGenerateStatus();
console.log('handleTeamRunTaskEnd', { data }, { lastRunTask });
if (lastRunTask) {
lastRunTask.footer = () => {
const { run_id, extra_data } = data;
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 (
<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" />
</Tooltip>
{team_session_state && (
<Tooltip title="下载" onClick={onDownload}>
<IconDownload size={16} class="color-#737478 cursor-pointer mr-12px" />
</Tooltip>
)}
{isLastTeamRunTask(data) && (
<Tooltip title="重新生成" onClick={() => onRefresh(run_id)}>
@ -306,11 +364,6 @@ export default function useChatHandler({ initSse }): UseChatHandlerReturn {
</div>
);
};
}
if (team_session_state) {
handleFileReview(data);
}
};
// 消息处理主函数

View File

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