Files
FiDA_Front/src/views/home/agent/components/Agent.vue
2026-02-24 11:17:01 +08:00

260 lines
6.6 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="agent-container flex flex-col">
<div class="agent-header flex align-center space-between">
<div class="header-title-wrapper">
<div class="agent-title">{{ props.title }}</div>
<div class="agent-name">AI Assistant 1.0</div>
</div>
<SvgIcon name="equal" color="#0d0d0d" size="24" />
</div>
<div class="agent-body flex-1 flex flex-col">
<List ref="listRef" :message-list="messageList" />
<Input is-agent-mode @send="handleSendMessage" />
</div>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, computed, onUnmounted } from 'vue'
import List from './List.vue'
import Input from '../../components/Input.vue'
import { fetchAgentReply } from '@/api/agent'
import type { AgentParamsType } from '@/api/agent'
import { useUserInfoStore } from '@/stores'
const userStore = useUserInfoStore()
const props = withDefaults(
defineProps<{
title: string
}>(),
{
title: 'Retro Sofa Sketch'
}
)
const messageList = ref([])
const listRef = ref()
const params = reactive<AgentParamsType>({
threadId: '',
message: '',
token: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIyIiwiaWF0IjoxNzcwNzkxMzEyLCJleHAiOjE3NzA4Nzc3MTJ9.xydPinm9l5Yq6GMkfaaVvdHjiINaYrp5VkRM7B9g83A',
checkpointId: '',
configParams: {
type: 'Chair',
region: 'China',
style: 'Transitional',
temperature: 0.7
},
imageUrlList: []
})
const abort = new AbortController()
onUnmounted(() => {
abort.abort()
})
const handleSendMessage = async (message: string) => {
console.log('Message sent:', message)
params.message = message.text
params.imageUrlList = message.images || []
messageList.value.push({
id: messageList.value.length + 1,
text: message.text,
isUser: true
})
// Add AI loading message
const aiMessage = reactive({
id: messageList.value.length + 1,
text: '',
isUser: false,
loading: true,
thinking: false,
thinkingText: '',
thinkingCollapsed: false,
streaming: true
})
messageList.value.push(aiMessage)
// const threadId = '' //
// console.log('token---', params.token, '参数---', params)
try {
const urlParams = new URLSearchParams<AgentParamsType>({
...params,
configParams: JSON.stringify(params.configParams)
})
const response = await fetch(`/api/ai-design/chat?${urlParams.toString()}`, {
method: 'GET',
signal: abort.signal
})
// 检查响应内容类型,判断是否为流式响应
const contentType = response.headers.get('content-type') || ''
const isStreamResponse =
contentType.includes('text/event-stream') || contentType.includes('stream')
if (!response.ok) {
// 非流式错误响应,使用 text() 读取错误信息
const errorText = await response.text()
console.error('请求错误:', errorText)
aiMessage.text = '发送失败,请重试'
aiMessage.streaming = false
aiMessage.loading = false
return
}
// 不是流式响应,使用 text()读取错误信息
if (!isStreamResponse) {
const text = await response.text()
try {
const errorData = JSON.parse(text)
console.error('非流式响应:', errorData)
} catch (e) {
console.error('非流式响应文本:', text)
}
aiMessage.text = '发送失败,请重试'
aiMessage.streaming = false
aiMessage.loading = false
return
}
// 流式响应处理
let contentBody = ''
let buffer = ''
const reader = response.body?.getReader()
if (!reader) throw new Error('无法获取流读取器')
const decoder = new TextDecoder()
try {
let flag = true
while (flag) {
const { done, value } = await reader.read()
if (done) {
console.log('传输结束 end---', contentBody)
aiMessage.streaming = false
aiMessage.loading = false
flag = false
break
}
buffer += decoder.decode(value, { stream: true })
// 优先按空行拆分事件块SSE标准
let events = buffer.split(/\n\n/)
buffer = events.pop() // 保留不完整块
for (let event of events) {
if (!event.trim()) continue
// 过滤掉 id: 等字段,只取 data:
const dataLines = event
.split(/\n/)
.filter((line) => line.startsWith('data:'))
.map((line) => line.replace(/^data:\s*/, '').trim())
if (dataLines.length === 0) continue
const jsonText = dataLines.join('\n')
try {
const jsonData = JSON.parse(jsonText)
// 赋值 thread_id 和 checkpoint_id
if (jsonData.thread_id) params.threadId = jsonData.thread_id
if (jsonData.checkpoint_id) params.checkpointId = jsonData.checkpoint_id
if (
jsonData.content &&
jsonData.content.length > 0 &&
jsonData.type !== 'end'
) {
contentBody += jsonData.content
aiMessage.text = contentBody
}
if (jsonData.type === 'end') {
aiMessage.streaming = false
aiMessage.loading = false
flag = false
break
}
} catch (e) {
// 检查是否为纯文本 [DONE]
if (jsonText.trim() === '[DONE]') {
console.log('结束-----------------------')
aiMessage.streaming = false
aiMessage.loading = false
flag = false
break
}
// JSON 不完整:保留到下一次循环
if (!jsonText.trim().endsWith('}')) {
buffer = 'data: ' + jsonText // 重新放回缓存
continue
} else {
console.warn('⚠️ JSON 格式错误,跳过:', jsonText)
}
}
}
}
} catch (error) {
console.error('流式传输错误:', error)
aiMessage.text = '发送失败,请重试'
aiMessage.streaming = false
aiMessage.loading = false
} finally {
reader.releaseLock()
}
} catch (error) {
console.error('fetch请求失败:', error)
aiMessage.text = '发送失败,请重试'
aiMessage.streaming = false
aiMessage.loading = false
}
}
</script>
<style lang="less" scoped>
.c-svg {
width: initial;
}
.agent-container {
// width: 39%;
width: 63.4rem;
background-color: #fff;
border-radius: 2rem;
box-shadow: 0px 15px 21px 0px #0000000d;
.agent-header {
height: 7.4rem;
border-bottom: 0.1rem solid #c9c9c9;
font-family: 'GeneralMedium';
padding: 1.4rem 3.4rem 1.4rem 3.1rem;
.agent-title {
font-size: 1.8rem;
margin-bottom: 0.6rem;
}
.agent-name {
font-size: 1.4rem;
}
}
.agent-body {
padding: 3.2rem;
overflow: hidden;
row-gap: 2.4rem;
.assist-input-wrapper {
width: 100%;
height: 14.4rem;
min-height: 14.4rem !important;
max-height: 14.4rem !important;
}
}
.input-wrapper {
height: 14.4rem;
padding: 0 2rem 3rem;
}
}
</style>