diff --git a/src/assets/img/agent/icon-end.png b/src/assets/img/agent/icon-end.png new file mode 100644 index 0000000..894073e Binary files /dev/null and b/src/assets/img/agent/icon-end.png differ diff --git a/src/assets/img/agent/icon-loading.png b/src/assets/img/agent/icon-loading.png new file mode 100644 index 0000000..ea35805 Binary files /dev/null and b/src/assets/img/agent/icon-loading.png differ diff --git a/src/views/home/components/conversation-detail/rightView.vue b/src/components/xt-chat/chat-view/components/right-view/index.vue similarity index 100% rename from src/views/home/components/conversation-detail/rightView.vue rename to src/components/xt-chat/chat-view/components/right-view/index.vue diff --git a/src/components/xt-chat/chat-view/components/sender-input/index.vue b/src/components/xt-chat/chat-view/components/sender-input/index.vue new file mode 100644 index 0000000..251aca6 --- /dev/null +++ b/src/components/xt-chat/chat-view/components/sender-input/index.vue @@ -0,0 +1,105 @@ + + + diff --git a/src/components/xt-chat/chat-view/constants.ts b/src/components/xt-chat/chat-view/constants.ts new file mode 100644 index 0000000..727e493 --- /dev/null +++ b/src/components/xt-chat/chat-view/constants.ts @@ -0,0 +1,31 @@ +import type { Ref } from 'vue'; +import type { BubbleListProps } from '@/components/xt-chat/xt-bubble/types'; + +// 定义角色常量 +export const QUESTION_ROLE = 'question'; +export const ANSWER_ROLE = 'text'; +export const FILE_ROLE = 'file'; +export const THOUGHT_ROLE = 'thought'; + +export const ROLE_STYLE = { + width: '600px', + margin: '0 auto', +}; + +export const ANSWER_STYLE = { + ...ROLE_STYLE, + paddingLeft: '12px', + borderLeft: '1px solid #E6E6E8', + position: 'relative', + left: '6px', +}; + +export interface UseChatHandlerReturn { + roles: BubbleListProps['roles']; + currentTaskId: Ref; + handleMessage: (parsedData: { event: string; data: any }) => void; + generateLoading: Ref; + conversationList: Ref; + showRightView: Ref; + rightViewContent: Ref; +} diff --git a/src/components/xt-chat/chat-view/index.vue b/src/components/xt-chat/chat-view/index.vue new file mode 100644 index 0000000..64d1cc3 --- /dev/null +++ b/src/components/xt-chat/chat-view/index.vue @@ -0,0 +1,138 @@ + + + diff --git a/src/views/home/components/conversation-detail/style.scss b/src/components/xt-chat/chat-view/style.scss similarity index 65% rename from src/views/home/components/conversation-detail/style.scss rename to src/components/xt-chat/chat-view/style.scss index 1915663..faf50ca 100644 --- a/src/views/home/components/conversation-detail/style.scss +++ b/src/components/xt-chat/chat-view/style.scss @@ -1,4 +1,4 @@ -.conversation-detail-wrap { +.chat-view-wrap { .cts { color: var(--Text-1, #737478); font-size: 14px; @@ -16,5 +16,16 @@ justify-content: center; align-items: center; } + &.process-row { + position: relative; + &::after { + top: 0; + left: 0; + position: absolute; + width: 1px; + height: 100%; + background: #e6e6e8; + } + } } } diff --git a/src/components/xt-chat/chat-view/useChatHandler.tsx b/src/components/xt-chat/chat-view/useChatHandler.tsx new file mode 100644 index 0000000..ad19154 --- /dev/null +++ b/src/components/xt-chat/chat-view/useChatHandler.tsx @@ -0,0 +1,228 @@ +import { ref, } from 'vue'; + +import type { BubbleListProps } from '@/components/xt-chat/xt-bubble/types'; +import markdownit from 'markdown-it'; +import { message as antdMessage } from 'ant-design-vue'; +import { IconFile, IconCaretUp, IconDownload, IconRefresh } from '@arco-design/web-vue/es/icon'; + +import { Tooltip } from 'ant-design-vue'; +import TextOverTips from '@/components/text-over-tips/index.vue'; +import { genRandomId } from '@/utils/tools'; + +import icon1 from "@/assets/img/agent/icon-end.png" +import icon2 from "@/assets/img/agent/icon-loading.png" + +import { useClipboard } from '@vueuse/core'; +import { QUESTION_ROLE, ANSWER_ROLE, FILE_ROLE, THOUGHT_ROLE, ROLE_STYLE, ANSWER_STYLE } from './constants'; +import type { UseChatHandlerReturn } from "./constants" + +/** + * 聊天处理器Hook + * @returns 包含角色配置、消息处理函数和对话列表的对象 + */ +export default function useChatHandler(): UseChatHandlerReturn { + // 在内部定义对话列表 + const { copy } = useClipboard(); + + const conversationList = ref([]); + const generateLoading = ref(false); + const currentTaskId = ref(null); + const showRightView = ref(false); + const rightViewContent = ref(''); + + // 初始化markdown + const md = markdownit({ + html: true, + breaks: true, + linkify: true, + typographer: true, + }); + + // 定义角色配置 + const roles: BubbleListProps['roles'] = { + [ANSWER_ROLE]: { + placement: 'start', + variant: 'borderless', + typing: { step: 2, interval: 100 }, + onTypingComplete: () => { + currentTaskId.value = null; + }, + style: ROLE_STYLE + }, + [FILE_ROLE]: { + placement: 'start', + variant: 'borderless', + typing: { step: 2, interval: 100 }, + messageRender: (items) => { + return items.map((item) => ( +
+ +
+ + 创建时间:08-04 12:40 +
+
+ )); + }, + style: ROLE_STYLE + }, + [THOUGHT_ROLE]: { + placement: 'start', + variant: 'borderless', + style: ROLE_STYLE + }, + [QUESTION_ROLE]: { + placement: 'end', + shape: 'round', + style: ROLE_STYLE + }, + }; + + // 下载处理 + const onDownload = (content: string) => { + console.log('onDownload', content); + // 这里可以添加实际的下载逻辑 + }; + + const onCopy = (content: string) => { + copy(content); + antdMessage.success('复制成功!'); + }; + + // 开始处理 + const handleStart = (data: any) => { + const { run_id } = data; + conversationList.value.push({ + run_id, + role: ANSWER_ROLE, + content: ( +
+ 智能思考 + +
+ ), + }); + }; + + // 节点更新处理 + const handleNodeUpdate = (data: any) => { + const { run_id, status, output } = data; + + switch (status) { + case 'TeamRunResponseContent': + conversationList.value.push({ + run_id, + content: data, + role: ANSWER_ROLE, + messageRender: (item) => ( +
+ + {item.message} +
+ ) + }); + break; + case 'TeamRunCompleted': + conversationList.value.push({ + run_id, + content: output, + role: ANSWER_ROLE, + messageRender: (content: string) =>
, + style: ANSWER_STYLE + }); + break; + } + }; + + // 最终结果处理 + const handleFinalResult = (data: any) => { + const { run_id, output } = data; + + if (showRightView) { + showRightView.value = true; + } + const _files = output?.files; + rightViewContent.value = _files?.[0]?.content || ''; + + conversationList.value.push({ + run_id, + id: currentTaskId.value, + role: FILE_ROLE, + content: _files, + style: ANSWER_STYLE, + footer: ({ item }: { item: any }) => { + const nonQuestionElements = conversationList.value.filter((item) => item.role !== QUESTION_ROLE); + const isLastAnswer = nonQuestionElements[nonQuestionElements.length - 1]?.id === item.id; + + // return ( + //
+ // onDownload(rightViewContent?.value || '')}> + // + // + // {isLastAnswer && onRefresh && ( + // onRefresh(currentTaskId.value!, conversationList.value.length)}> + // + // + // )} + //
+ // ); + }, + }); + }; + + // 重置生成状态 + const resetGenerateStatus = () => { + generateLoading.value = false; + }; + + // 错误处理 + const handleError = () => { + resetGenerateStatus(); + antdMessage.error('连接服务器失败'); + }; + + const onRefresh = (tempId: string, tempIndex: number) => { + generateLoading.value = true; + conversationList.value.splice(tempIndex, 1, { + id: tempId, + loading: true, + }); + }; + + // 消息处理主函数 + const handleMessage = (parsedData: { event: string; data: any }) => { + const { event, data } = parsedData; + switch (event) { + case 'start': + handleStart(data); + break; + case 'node_update': + handleNodeUpdate(data); + break; + case 'final_result': + handleFinalResult(data); + break; + case 'end': + resetGenerateStatus(); + break; + case 'error': + handleError(); + break; + default: + break; + } + }; + + return { + roles, + currentTaskId, + handleMessage, + generateLoading, + conversationList, + showRightView, + rightViewContent, + }; +} \ No newline at end of file diff --git a/src/stores/modules/chat/index.ts b/src/stores/modules/chat/index.ts index 2c84a9c..b1ff24b 100644 --- a/src/stores/modules/chat/index.ts +++ b/src/stores/modules/chat/index.ts @@ -1,5 +1,6 @@ import { defineStore } from 'pinia'; -import { createSession } from '@/api/all/chat'; +import { createSession, getAgentInfo } from '@/api/all/chat'; + import { handleUserHome } from '@/utils/user'; interface ChatState { @@ -24,6 +25,12 @@ export const useChatStore = defineStore('chat', { clearSearchValue() { this.searchValue = ''; }, + async getAgentInfo() { + const { code, data } = await getAgentInfo(); + if (code === 200) { + this.setAgentInfo(data); + } + }, setAgentInfo(agentInfo: agentInfo) { this.agentInfo = agentInfo; }, diff --git a/src/types/chat.ts b/src/types/chat.ts new file mode 100644 index 0000000..2116a91 --- /dev/null +++ b/src/types/chat.ts @@ -0,0 +1,7 @@ +declare global { + namespace CHAT { + export type TInputInfo = { + message: string; + }; + } +} diff --git a/src/utils/querySSE.ts b/src/utils/querySSE.ts index e9cb27a..7850465 100644 --- a/src/utils/querySSE.ts +++ b/src/utils/querySSE.ts @@ -63,11 +63,11 @@ export default async (config: SSEConfig, url: string = DEFAULT_SSE_URL): Promise } }, onerror(error: Error) { - console.error('SSE error:', error); + // console.error('SSE error:', error); handleError?.(error); }, onclose() { - console.log('SSE connection closed'); + // console.log('SSE connection closed'); handleClose?.(); }, async onopen(response: Response) { diff --git a/src/views/home/components/conversation-detail/index.vue b/src/views/home/components/conversation-detail/index.vue deleted file mode 100644 index 91c586a..0000000 --- a/src/views/home/components/conversation-detail/index.vue +++ /dev/null @@ -1,322 +0,0 @@ - - - diff --git a/src/views/home/components/created/index.vue b/src/views/home/components/created/index.vue index 7ac72cd..3705d91 100644 --- a/src/views/home/components/created/index.vue +++ b/src/views/home/components/created/index.vue @@ -43,9 +43,6 @@ export default { senderRef.value?.focus(); }; - onMounted(() => { - chatStore.clearSearchValue(); - }); return () => (
diff --git a/src/views/home/index.vue b/src/views/home/index.vue index 9443500..48f1dac 100644 --- a/src/views/home/index.vue +++ b/src/views/home/index.vue @@ -1,38 +1,37 @@