feat: 优化 Chat 页面和 Agents 页面

- 优化 Chat 页面交互和消息显示
- 增强 Agents 页面功能
- 改进 ChatAgentSelector 组件
- 优化 ChatMessage 和 ChatSidebar 组件
- 更新聊天逻辑 useAgents 和 chat 模块

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-15 19:48:42 +08:00
parent a35a88effc
commit bce8b9240b
7 changed files with 362 additions and 195 deletions

View File

@@ -1,4 +1,5 @@
<script setup lang="ts">
import { ElMessageBox } from 'element-plus'
import type { Agent } from '@/views/chat/chat'
const props = defineProps<{
@@ -14,12 +15,32 @@ const emit = defineEmits<{
(e: 'toggleSelect', agent: Agent): void
(e: 'confirm'): void
(e: 'update:groupChatName', value: string): void
(e: 'delete', agent: Agent): void
}>()
// 点击智能体 - 只是选择,不直接确认
const handleAgentClick = (agent: Agent) => {
emit('toggleSelect', agent)
}
// 删除智能体
const handleDeleteAgent = async (agent: Agent, event: Event) => {
event.stopPropagation()
try {
await ElMessageBox.confirm(
`确定要删除智能体 "${agent.name}" 吗?删除后无法恢复。`,
'删除确认',
{
confirmButtonText: '确定删除',
cancelButtonText: '取消',
type: 'warning',
}
)
emit('delete', agent)
} catch {
// 用户取消删除
}
}
</script>
<template>
@@ -29,10 +50,10 @@ const handleAgentClick = (agent: Agent) => {
<div class="bg-dark-800 rounded-xl w-full max-w-md border border-dark-600 shadow-2xl" @click.stop>
<div class="p-4 border-b border-dark-600">
<h3 class="text-lg font-semibold text-white">
{{ selectMode === 'single' ? '选择智能体' : '选择群聊成员' }}
{{ selectMode === 'single' ? '选择会话' : '选择群聊成员' }}
</h3>
<p class="text-sm text-gray-400 mt-1">
{{ selectMode === 'single' ? '选择一个智能体开始对话' : '选择多个智能体创建群聊' }}
{{ selectMode === 'single' ? '选择一个智能体开始对话' : '选择多个智能体创建群聊' }}
</p>
</div>
@@ -53,7 +74,7 @@ const handleAgentClick = (agent: Agent) => {
v-for="agent in chatAgents"
:key="agent.id"
@click="handleAgentClick(agent)"
class="w-full flex items-center gap-3 px-3 py-2.5 rounded-lg transition-all duration-200"
class="w-full flex items-center gap-3 px-3 py-2.5 rounded-lg transition-all duration-200 group"
:class="selectedAgents.some(a => a.id === agent.id)
? 'bg-orange-500/20 border border-orange-500/50'
: 'bg-dark-700 hover:bg-dark-600 border border-transparent'"

View File

@@ -63,7 +63,12 @@ const copyMessage = async () => {
: 'bg-[#1e1e28] text-gray-100 rounded-bl-sm'"
>
<span v-html="getMessageContent(message.content, message.role === 'user')"></span>
<span v-if="message.isStreaming" class="inline-block w-0.5 h-4 ml-0.5 bg-orange-300 cursor-blink align-middle"></span>
<!-- 等待提示 - 三个点动画 -->
<span v-if="message.isStreaming" class="inline-flex items-center ml-1 align-middle">
<span class="w-1.5 h-1.5 mx-0.5 bg-orange-400 rounded-full animate-bounce" style="animation-delay: 0ms;"></span>
<span class="w-1.5 h-1.5 mx-0.5 bg-orange-400 rounded-full animate-bounce" style="animation-delay: 150ms;"></span>
<span class="w-1.5 h-1.5 mx-0.5 bg-orange-400 rounded-full animate-bounce" style="animation-delay: 300ms;"></span>
</span>
<!-- 复制按钮 -->
<Transition name="fade">

View File

@@ -1,12 +1,14 @@
<script setup lang="ts">
import { computed } from 'vue'
import { ElMessageBox } from 'element-plus'
import type { Agent, ChatSession, GroupChat } from '@/views/chat/chat'
defineProps<{
const props = defineProps<{
collapsed: boolean
chatAgents: Agent[]
selectedAgent: Agent | null
chatSessions: ChatSession[]
groupChats: GroupChat[]
groupChats?: GroupChat[]
}>()
const emit = defineEmits<{
@@ -14,8 +16,16 @@ const emit = defineEmits<{
(e: 'selectAgent', agent: Agent): void
(e: 'selectSession', session: ChatSession): void
(e: 'selectGroup', group: GroupChat): void
(e: 'deleteSession', session: ChatSession): void
}>()
// 根据 agent_id 获取智能体名称
const getAgentName = (agentId: number | string | undefined) => {
if (!agentId) return '未知智能体'
const agent = props.chatAgents.find(a => a.id === agentId)
return agent?.name || '未知智能体'
}
const formatRelativeTime = (date: Date) => {
const now = new Date()
const diff = now.getTime() - date.getTime()
@@ -27,6 +37,25 @@ const formatRelativeTime = (date: Date) => {
if (days < 7) return `${days}天前`
return date.toLocaleDateString('zh-CN')
}
// 删除会话
const handleDeleteSession = async (session: ChatSession, event: Event) => {
event.stopPropagation()
try {
await ElMessageBox.confirm(
`确定要删除与 "${session.title}" 的对话吗?`,
'删除确认',
{
confirmButtonText: '确定删除',
cancelButtonText: '取消',
type: 'warning',
}
)
emit('deleteSession', session)
} catch {
// 用户取消删除
}
}
</script>
<template>
@@ -69,47 +98,40 @@ const formatRelativeTime = (date: Date) => {
</div>
</div>
<!-- AI 助手选择 -->
<div class="px-3 pb-3">
<div class="text-xs text-white/40 uppercase tracking-wider mb-2 px-1">选择 AI 助手</div>
<div class="space-y-1">
<button
v-for="agent in chatAgents"
:key="agent.id"
@click="emit('selectAgent', agent)"
class="w-full flex items-center gap-2 px-3 py-2 rounded-lg transition-all duration-200"
:class="selectedAgent?.id === agent.id
? 'bg-orange-500/15 text-orange-400'
: 'text-white/60 hover:bg-white/5 hover:text-white'"
>
<span class="text-base">{{ agent.avatar }}</span>
<span class="text-sm truncate">{{ agent.name }}</span>
<span
v-if="agent.status === 'online'"
class="w-1.5 h-1.5 rounded-full bg-emerald-400 ml-auto"
></span>
</button>
</div>
</div>
<!-- 群聊列表 -->
<!-- 会话列表 -->
<div class="flex-1 overflow-y-auto px-3 pb-3">
<div class="text-xs text-white/40 uppercase tracking-wider mb-2 px-1">群聊</div>
<div class="text-xs text-white/40 uppercase tracking-wider mb-2 px-1">会话</div>
<div class="space-y-1">
<button
v-for="group in groupChats"
:key="group.id"
@click="emit('selectGroup', group)"
v-for="session in chatSessions"
:key="session.id"
@click="emit('selectSession', session)"
class="w-full text-left px-3 py-2.5 rounded-lg hover:bg-white/5 transition-all duration-200 group"
>
<div class="flex items-center gap-2">
<svg class="w-4 h-4 text-white/30 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z"></path>
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z"></path>
</svg>
<span class="text-sm text-white/70 group-hover:text-white truncate">{{ group.name }}</span>
<span class="text-sm text-white/70 group-hover:text-white truncate flex-1">{{ session.title || '新会话' }}</span>
<!-- 删除按钮 -->
<span
@click="handleDeleteSession(session, $event)"
class="hidden group-hover:flex w-6 h-6 items-center justify-center rounded-md text-white/30 hover:text-red-400 hover:bg-red-500/20 transition-colors"
title="删除会话"
>
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"></path>
</svg>
</span>
</div>
<div class="flex items-center gap-2 mt-1 pl-6">
<span class="text-xs text-orange-500/70">{{ getAgentName(session.agent_id) }}</span>
<span class="text-xs text-white/30">{{ formatRelativeTime(session.timestamp) }}</span>
</div>
<div class="text-xs text-white/30 mt-1 pl-6">{{ group.members.length }} members</div>
</button>
<div v-if="!chatSessions || chatSessions.length === 0" class="text-xs text-white/30 text-center py-4">
暂无会话记录
</div>
</div>
</div>
</div>

View File

@@ -1,4 +1,5 @@
<script setup lang="ts">
import { onUnmounted } from 'vue'
import { Play, Pause, Edit, Trash2 } from 'lucide-vue-next'
import { useAgents } from './agents/useAgents'
import './agents/agents.css'
@@ -57,8 +58,13 @@ const {
getSkillsDisplayText,
toggleSkillSelection,
selectAllSkills,
statusClass
statusClass,
cleanup
} = useAgents()
onUnmounted(() => {
cleanup()
})
</script>
<template>
@@ -162,18 +168,18 @@ const {
class="btn-icon"
:title="agent.status === 'active' ? 'Deactivate' : 'Activate'"
>
<Pause v-if="agent.status === 'active'" class="w-4 h-4 text-gray-500 hover:text-yellow-400 transition-colors" />
<Play v-else class="w-4 h-4 text-gray-500 hover:text-green-400 transition-colors" />
<Pause v-if="agent.status === 'active'" class="w-4 h-4 text-gray-400 hover:text-yellow-400 transition-colors" />
<Play v-else class="w-4 h-4 text-gray-400 hover:text-green-400 transition-colors" />
</button>
<button @click="openEdit(agent)" class="btn-icon" title="Edit">
<Edit class="w-4 h-4 text-gray-500 hover:text-white transition-colors" />
<Edit class="w-4 h-4 text-gray-400 hover:text-white transition-colors" />
</button>
<button
@click.stop="deleteAgent(agent.id)"
class="btn-icon"
title="Delete"
>
<Trash2 class="w-4 h-4 text-gray-500 hover:text-red-400 transition-colors" />
<Trash2 class="w-4 h-4 text-gray-400 hover:text-red-500 transition-colors" />
</button>
</div>
</td>

View File

@@ -1,5 +1,5 @@
<script setup lang="ts">
import { ref, nextTick, onMounted, onUnmounted } from 'vue'
import { ref, nextTick, watch, onMounted, onUnmounted } from 'vue'
import { useChat } from './chat/chat'
import ChatHeader from '@/components/chat/ChatHeader.vue'
import ChatMessage from '@/components/chat/ChatMessage.vue'
@@ -45,19 +45,126 @@ const {
const messagesContainer = ref<HTMLElement | null>(null)
// Mock 流式响应(用于测试前端流式效果)
const mockStreamResponse = async (content: string, messageIndex: number) => {
const chars = content.split('')
for (let i = 0; i < chars.length; i++) {
messages.value[messageIndex].content += chars[i]
// 构建 API 请求体
const buildRequestBody = (userContent: string) => {
const requestBody: any = {
agent_id: String(selectedAgent.value?.id || 1),
message: userContent,
}
if (selectedModel.value) {
requestBody.model_id = selectedModel.value.id
}
if (currentSessionId.value) {
requestBody.session_id = currentSessionId.value
}
return requestBody
}
// 解析流式响应数据
const parseStreamData = (rawData: string): string => {
if (!rawData || rawData === '[DONE]') return ''
try {
const parsed = JSON.parse(rawData)
if (typeof parsed === 'string') {
return parsed
}
return parsed.content || parsed.delta?.content || ''
} catch {
return ''
}
}
// 处理流式响应
const handleStreamResponse = async (response: Response) => {
const reader = response.body.getReader()
const decoder = new TextDecoder('utf-8')
let buffer = ''
const aiMessageIndex = messages.value.length - 1
while (true) {
const { done, value } = await reader.read()
if (done) break
const decoded = decoder.decode(value, { stream: true })
buffer += decoded
const lines = buffer.split('\n')
buffer = lines.pop() || ''
for (const line of lines) {
if (line.startsWith('data: ')) {
const content = parseStreamData(line.slice(6).trim())
if (content) {
messages.value[aiMessageIndex].content += content
await nextTick()
scrollToBottom()
await new Promise(resolve => setTimeout(resolve, 15))
}
messages.value[messageIndex].isStreaming = false
}
}
}
// 处理剩余buffer
if (buffer.startsWith('data: ')) {
const content = parseStreamData(buffer.slice(6).trim())
if (content) {
messages.value[aiMessageIndex].content += content
}
}
messages.value[aiMessageIndex].isStreaming = false
isLoading.value = false
scrollToBottom()
// 保存 AI 消息
await saveMessage('assistant', messages.value[aiMessageIndex].content)
// 第二轮对话结束后生成标题
if (messages.value.length === 5 && currentSessionId.value) {
generateSessionTitle()
}
}
// 处理消息发送错误
const handleMessageError = (error: any) => {
const errorIndex = messages.value.findIndex(m => m.isStreaming)
if (errorIndex > -1) {
messages.value[errorIndex].content = `Error: ${error.message || 'Failed to send message'}`
messages.value[errorIndex].isStreaming = false
}
isLoading.value = false
}
// 重置输入框
const resetInputHeight = () => {
nextTick(() => {
const textarea = document.querySelector('.chat-input-textarea') as HTMLTextAreaElement
if (textarea) {
textarea.style.height = 'auto'
}
})
}
// 创建用户消息对象
const createUserMessage = (content: string) => ({
id: Date.now(),
role: 'user' as const,
content,
timestamp: new Date()
})
// 创建 AI 消息对象
const createAssistantMessage = () => ({
id: Date.now() + 1,
role: 'assistant' as const,
content: '',
timestamp: new Date(),
isStreaming: true
})
// 滚动到底部
const scrollToBottom = () => {
if (messagesContainer.value) {
@@ -65,6 +172,11 @@ const scrollToBottom = () => {
}
}
// 监听消息变化,自动滚动到底部
watch(messages, () => {
nextTick(() => scrollToBottom())
}, { deep: true })
// 切换模型下拉框
const toggleModelDropdown = () => {
showModelDropdown.value = !showModelDropdown.value
@@ -76,133 +188,91 @@ const handleSelectModel = (model: any) => {
showModelDropdown.value = false
}
// 删除会话
const handleDeleteSession = async (session: any) => {
try {
const response = await fetch(`/api/chat/sessions/${session.id}`, {
method: 'DELETE',
})
if (response.ok) {
const index = chatSessions.value.findIndex((s: any) => s.id === session.id)
if (index > -1) {
chatSessions.value.splice(index, 1)
}
if (currentSessionId.value === session.id) {
currentSessionId.value = null
messages.value = []
}
}
} catch {
// ignore
}
}
// 生成会话标题
const generateSessionTitle = async () => {
if (!currentSessionId.value) return
try {
const response = await fetch('/api/chat/sessions/generate-title', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ session_id: currentSessionId.value })
})
if (response.ok) {
const data = await response.json()
const sessionIndex = chatSessions.value.findIndex((s: any) => s.id === currentSessionId.value)
if (sessionIndex > -1) {
chatSessions.value[sessionIndex].title = data.title
}
}
} catch {
// ignore
}
}
// 发送消息
const sendMessage = async () => {
if (!inputMessage.value.trim() || isLoading.value) return
const userContent = inputMessage.value.trim()
inputMessage.value = ''
resetInputHeight()
// 重置输入框高度
nextTick(() => {
const textarea = document.querySelector('.chat-input-textarea') as HTMLTextAreaElement
if (textarea) {
textarea.style.height = 'auto'
}
})
// 如果没有会话,创建一个新会话
if (!currentSessionId.value) {
await createSession()
const session = await createSession()
if (!session) return
}
const userMessage = {
id: Date.now(),
role: 'user' as const,
content: userContent,
timestamp: new Date()
}
const userMessage = createUserMessage(userContent)
messages.value.push(userMessage)
// 保存用户消息到后端
await saveMessage('user', userContent)
const aiMessage = {
id: Date.now() + 1,
role: 'assistant' as const,
content: '',
timestamp: new Date(),
isStreaming: true
}
const aiMessage = createAssistantMessage()
messages.value.push(aiMessage)
nextTick(() => scrollToBottom())
isLoading.value = true
try {
const requestBody: any = {
agent_id: String(selectedAgent.value?.id || 1),
message: userContent,
}
if (selectedModel.value) {
requestBody.model_id = selectedModel.value.id
}
// 传入 session_id
if (currentSessionId.value) {
requestBody.session_id = currentSessionId.value
}
const response = await fetch(`/api/agent/chat/stream`, {
const response = await fetch('/api/agent/chat/stream', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(requestBody),
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(buildRequestBody(userContent)),
})
if (!response.ok) {
throw new Error(`Request failed: ${response.status}`)
}
// 真正的流式处理:边读取边显示
const reader = response.body.getReader()
const decoder = new TextDecoder()
let buffer = ''
const aiMessageIndex = messages.value.length - 1
while (true) {
const { done, value } = await reader.read()
if (done) break
buffer += decoder.decode(value, { stream: true })
const lines = buffer.split('\n')
buffer = lines.pop() || ''
for (const line of lines) {
if (line.startsWith('data: ')) {
const data = line.slice(6).trim()
if (data && data !== '[DONE]') {
// 直接累加内容并显示(真正的流式)
messages.value[aiMessageIndex].content += data
await nextTick()
scrollToBottom()
}
}
}
}
// 处理剩余buffer中的数据
if (buffer.startsWith('data: ')) {
const data = buffer.slice(6).trim()
if (data && data !== '[DONE]') {
messages.value[aiMessageIndex].content += data
}
}
messages.value[aiMessageIndex].isStreaming = false
isLoading.value = false
scrollToBottom()
// 保存 AI 消息到后端
await saveMessage('assistant', messages.value[aiMessageIndex].content)
await handleStreamResponse(response)
} catch (error: any) {
console.error('[Stream] 错误:', error)
const errorIndex = messages.value.findIndex(m => m.isStreaming)
if (errorIndex > -1) {
messages.value[errorIndex].content = `Error: ${error.message || 'Failed to send message'}`
messages.value[errorIndex].isStreaming = false
}
isLoading.value = false
handleMessageError(error)
}
}
// 初始化
onMounted(() => {
console.log('[Chat] Component mounted, calling init()')
init()
})
@@ -269,6 +339,7 @@ onUnmounted(() => {
@select-agent="selectAgent"
@select-session="selectSession"
@select-group="selectGroup"
@delete-session="handleDeleteSession"
/>
<!-- 智能体选择弹窗 -->

View File

@@ -278,7 +278,7 @@ async function createAgent() {
const skillsLabels = newAgent.value.selectedSkills.map(id => getSkillLabel(id)).join(', ')
agents.value.unshift({
id: result.agent_id,
id: result.agent_id_str || result.agent_id,
name: newAgent.value.name,
avatar: newAgent.value.avatar,
description: newAgent.value.description,
@@ -341,6 +341,7 @@ async function saveEdit() {
body: JSON.stringify({
name: editingAgent.value.name,
description: editingAgent.value.description,
avatar: editingAgent.value.avatar,
skills: skills,
role_description: editingAgent.value.prompt,
model_provider: selectedModel?.provider || '',
@@ -353,6 +354,7 @@ async function saveEdit() {
if (agent) {
agent.name = editingAgent.value.name
agent.description = editingAgent.value.description
agent.avatar = editingAgent.value.avatar
agent.skills = editingAgent.value.skillsMode === 'all' ? '*' : editingAgent.value.selectedSkills.join(', ')
agent.model = selectedModel?.name || ''
}

View File

@@ -20,7 +20,7 @@ export interface ChatMessage {
}
export interface Agent {
id: number
id: string | number
name: string
avatar: string
description: string
@@ -40,7 +40,7 @@ export interface ChatSession {
}
export interface GroupChat {
id: number
id: string | number
name: string
members: string[]
lastMessage: string
@@ -82,7 +82,6 @@ export const renderMarkdown = (content: string): string => {
const processed = preprocessContent(content)
return marked.parse(processed) as string
} catch (e) {
console.error('Markdown parse error:', e)
return content
}
}
@@ -150,24 +149,21 @@ export function useChat() {
try {
const response = await fetch(`/model/list`)
const data = await response.json()
console.log('[Chat] Raw models:', data.list)
if (data.list) {
// 只过滤出 active 的 chat 模型 (status: 1=active, 0=inactive)
const activeChatModels = data.list.filter((m: ChatModel) =>
m.model_type === 'chat' && m.status === 1
)
console.log('[Chat] Filtered chat models:', activeChatModels)
chatModels.value = activeChatModels
// 默认选中第一个 active 的 chat 模型
if (chatModels.value.length > 0) {
selectedModel.value = chatModels.value[0]
console.log('[Chat] Selected model:', selectedModel.value)
}
}
} catch (error) {
console.error('Failed to fetch models:', error)
} catch {
// 静默处理
} finally {
modelsLoading.value = false
}
@@ -178,7 +174,6 @@ export function useChat() {
try {
const response = await fetch('/api/agent/list')
const data = await response.json()
console.log('[Chat] Agents:', data)
if (data.agents) {
chatAgents.value = data.agents.map((agent: any) => ({
@@ -186,9 +181,9 @@ export function useChat() {
name: agent.name,
avatar: agent.avatar || '🧠',
description: agent.description || '',
accentColor: agent.accent_color || '#f97316',
accentColor: '#f97316',
gradient: 'from-orange-500/20 to-amber-500/20',
status: agent.status === 'active' ? 'online' : 'offline'
status: agent.is_active ? 'online' : 'offline'
}))
// 默认选中第一个智能体
@@ -196,8 +191,8 @@ export function useChat() {
selectedAgent.value = chatAgents.value[0]
}
}
} catch (error) {
console.error('Failed to fetch agents:', error)
} catch {
// 静默处理
}
}
@@ -207,7 +202,6 @@ export function useChat() {
const userId = localStorage.getItem('user_id') || 'default-user'
const response = await fetch(`/api/chat/sessions?user_id=${userId}&limit=50`)
const data = await response.json()
console.log('[Chat] Sessions:', data)
if (data.list) {
chatSessions.value = data.list.map((s: any) => ({
@@ -219,9 +213,13 @@ export function useChat() {
timestamp: new Date(s.created_at || Date.now()),
status: s.status
}))
// 自动选择最近的会话并加载消息
// 页面加载时不自动选择会话,显示空页面
// 用户点击"新建聊天"或选择智能体时才创建会话
}
} catch (error) {
console.error('Failed to fetch sessions:', error)
} catch {
// 静默处理
}
}
@@ -231,7 +229,6 @@ export function useChat() {
const userId = localStorage.getItem('user_id') || 'default-user'
const response = await fetch(`/api/chat/groups?user_id=${userId}`)
const data = await response.json()
console.log('[Chat] Groups:', data)
if (data.list) {
groupChats.value = data.list.map((g: any) => ({
@@ -242,13 +239,13 @@ export function useChat() {
timestamp: new Date(g.created_at || Date.now())
}))
}
} catch (error) {
console.error('Failed to fetch groups:', error)
} catch {
// 静默处理
}
}
// 创建群聊
const createGroup = async (name: string, agentIds: number[]) => {
const createGroup = async (name: string, agentIds: (string | number)[]) => {
try {
const userId = localStorage.getItem('user_id') || 'default-user'
const response = await fetch('/api/chat/groups', {
@@ -260,8 +257,12 @@ export function useChat() {
agent_ids: JSON.stringify(agentIds)
})
})
if (!response.ok) {
console.error('Create group failed:', response.status, await response.text())
return null
}
const group = await response.json()
console.log('[Chat] Created group:', group)
// 添加到群聊列表
groupChats.value.unshift({
@@ -273,8 +274,7 @@ export function useChat() {
})
return group
} catch (error) {
console.error('Failed to create group:', error)
} catch {
return null
}
}
@@ -293,8 +293,12 @@ export function useChat() {
model_id: selectedModel.value?.id
})
})
if (!response.ok) {
return null
}
const session = await response.json()
console.log('[Chat] Created session:', session)
// 添加到会话列表
chatSessions.value.unshift({
@@ -307,8 +311,7 @@ export function useChat() {
currentSessionId.value = session.id
return session
} catch (error) {
console.error('Failed to create session:', error)
} catch {
return null
}
}
@@ -318,7 +321,6 @@ export function useChat() {
try {
const response = await fetch(`/api/chat/sessions/${sessionId}/messages?limit=100`)
const data = await response.json()
console.log('[Chat] Messages:', data)
if (data.list) {
messages.value = data.list.map((m: any) => ({
@@ -328,27 +330,37 @@ export function useChat() {
timestamp: new Date(m.created_at)
}))
}
} catch (error) {
console.error('Failed to fetch messages:', error)
} catch {
// 静默处理
}
}
// 保存消息到后端
const saveMessage = async (role: 'user' | 'assistant', content: string) => {
if (!currentSessionId.value) return
const sessionId = currentSessionId.value
if (!sessionId || typeof sessionId !== 'string' || sessionId.trim() === '') {
return
}
// 检查内容是否有效
if (!content || typeof content !== 'string' || content.trim() === '') {
return
}
const payload = {
session_id: sessionId,
role: role,
content: content
}
try {
await fetch('/api/chat/messages', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
session_id: currentSessionId.value,
role: role,
content: content
body: JSON.stringify(payload)
})
})
} catch (error) {
console.error('Failed to save message:', error)
} catch {
// 静默处理
}
}
@@ -360,8 +372,8 @@ export function useChat() {
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ title })
})
} catch (error) {
console.error('Failed to update session:', error)
} catch {
// 静默处理
}
}
@@ -376,8 +388,8 @@ export function useChat() {
currentSessionId.value = null
messages.value = []
}
} catch (error) {
console.error('Failed to delete session:', error)
} catch {
// 静默处理
}
}
@@ -452,19 +464,47 @@ export function useChat() {
showAgentSelector.value = false
}
// 选择助手
const selectAgent = (agent: Agent) => {
// 选择助手 - 如果是同一智能体则不创建新会话
const selectAgent = async (agent: Agent) => {
// 如果选择的是同一智能体,不创建新会话,直接返回
if (selectedAgent.value?.id === agent.id) {
return
}
selectedAgent.value = agent
// 创建新会话
const session = await createSession(`${agent.name} 的对话`)
if (session) {
currentSessionId.value = session.id
}
messages.value = [
{ id: 1, role: 'assistant', content: `你好!我是 ${agent.name}。有什么我可以帮助你的吗?`, timestamp: new Date() }
{ id: Date.now(), role: 'assistant', content: `你好!我是 ${agent.name},你的 AI 助手。有什么我可以帮助你的吗?`, timestamp: new Date() }
]
// 保存助手欢迎消息
if (currentSessionId.value) {
await saveMessage('assistant', messages.value[0].content)
}
}
// 选择群聊
const selectGroup = (group: GroupChat) => {
const selectGroup = async (group: GroupChat) => {
// 创建新会话
const session = await createSession(group.name)
if (session) {
currentSessionId.value = session.id
}
messages.value = [
{ id: 1, role: 'assistant', content: `你好!欢迎进入群聊 "${group.name}"${group.members.length} 位智能体已加入。`, timestamp: new Date() }
{ id: Date.now(), role: 'assistant', content: `你好!欢迎进入群聊 "${group.name}"${group.members.length} 位智能体已加入。`, timestamp: new Date() }
]
// 保存助手欢迎消息
if (currentSessionId.value) {
await saveMessage('assistant', messages.value[0].content)
}
}
// 选择历史对话