import { ref, computed, watch, nextTick } from 'vue' import { conversationsAPI, messagesAPI, toolsAPI, providersAPI } from './api.js' import { streamManager } from './streamManager.js' import { useStreamStore } from './streamStore.js' // 对话管理 Composable export function useConversations() { const streamStore = useStreamStore() // 状态 const list = ref([]) const providers = ref([]) const page = ref(1) const pageSize = 20 const total = ref(0) const loading = ref(true) const error = ref('') const selectedId = ref(null) const selectedConv = ref(null) const convMessages = ref([]) const loadingMessages = ref(false) const enabledTools = ref([]) // 计算属性 const totalPages = computed(() => Math.ceil(total.value / pageSize)) const currentStreamState = computed(() => { return streamStore.getStreamState(selectedId.value) }) const sending = computed(() => { const state = streamStore.getStreamState(selectedId.value) return state && state.status === 'streaming' }) // 检查指定会话是否有活跃流 const hasActiveStream = (convId) => { return streamStore.hasActiveStream(convId) } // 加载启用的工具列表 const loadEnabledTools = async () => { try { const res = await toolsAPI.list() if (res.success) { const tools = res.data?.tools || [] enabledTools.value = tools.map(t => t.function?.name || t.name) } } catch (e) { console.error('Failed to load tools:', e) } } // 加载会话列表 const fetchData = async () => { loading.value = true error.value = '' try { const [convRes, provRes, toolsRes] = await Promise.allSettled([ conversationsAPI.list({ page: page.value, page_size: pageSize }), providersAPI.list(), toolsAPI.list() ]) if (convRes.status === 'fulfilled' && convRes.value.success) { list.value = convRes.value.data?.items || [] total.value = convRes.value.data?.total || 0 // 默认选中第一个会话 if (list.value.length > 0 && !selectedId.value) { selectConv(list.value[0]) } } if (provRes.status === 'fulfilled' && provRes.value.success) { providers.value = provRes.value.data?.providers || [] } if (toolsRes.status === 'fulfilled' && toolsRes.value.success) { enabledTools.value = (toolsRes.value.data?.tools || []).map(t => t.function?.name || t.name) } } catch (e) { error.value = e.message } finally { loading.value = false } } // 选择会话 const selectConv = async (c) => { selectedId.value = c.id selectedConv.value = c await fetchConvMessages(c.id) setupStreamWatch() } // 获取会话消息 const fetchConvMessages = async (convId) => { loadingMessages.value = true convMessages.value = [] try { const res = await messagesAPI.list(convId) if (res.success) { convMessages.value = res.data?.messages || [] // 加载完成后强制滚动到底部(初始加载总是显示最新消息) nextTick(() => { if (typeof initialScrollCallback === 'function') { initialScrollCallback() } }) } } catch (e) { console.error('获取消息失败:', e) } finally { loadingMessages.value = false } } // 发送消息 const sendMessage = async (content) => { if (!content.trim() || !selectedConv.value || sending.value) return const trimmedContent = content.trim() // 添加用户消息到列表 const userMsgId = 'user-' + Date.now() const userMsg = { id: userMsgId, role: 'user', content: trimmedContent, created_at: new Date().toISOString() } convMessages.value.push(userMsg) // 如果还没有标题或标题为默认标题,使用第一条消息作为标题 const currentTitle = selectedConv.value?.title const isDefaultTitle = !currentTitle || currentTitle === 'New Conversation' || currentTitle.trim() === '' if (isDefaultTitle) { const title = trimmedContent.slice(0, 30) + (trimmedContent.length > 30 ? '...' : '') selectedConv.value.title = title // 更新列表中的标题 const conv = list.value.find(c => c.id === selectedConv.value.id) if (conv) conv.title = title // 调用 API 保存 await conversationsAPI.update(selectedConv.value.id, { title }) } // 使用 StreamManager 发送流式请求 await streamManager.startStream( selectedConv.value.id, { conversation_id: selectedConv.value.id, content: trimmedContent, enabled_tools: enabledTools.value }, userMsgId ) } // 创建会话 const createConv = async (form) => { const res = await conversationsAPI.create(form) if (res.success && res.data?.id) { await fetchData() const newConv = list.value.find(c => c.id === res.data.id) if (newConv) { await selectConv(newConv) } return res.data } throw new Error(res.message) } // 删除会话 const deleteConv = async (c) => { if (hasActiveStream(c.id)) { streamManager.cancelStream(c.id) } await conversationsAPI.delete(c.id) if (selectedId.value === c.id) { selectedId.value = null selectedConv.value = null } await fetchData() } // 更新会话标题 const updateConvTitle = async (c, newTitle) => { c.title = newTitle // 更新列表中的标题 const conv = list.value.find(item => item.id === c.id) if (conv) conv.title = newTitle // 调用 API 保存 await conversationsAPI.update(c.id, { title: newTitle }) } // 删除消息 const deleteMessage = async (msgId) => { if (!selectedConv.value) return try { await messagesAPI.delete(msgId) // 从本地列表中移除 convMessages.value = convMessages.value.filter(m => m.id !== msgId) } catch (e) { console.error('删除消息失败:', e) throw e } } // 重新生成消息(删除 assistant 消息并重新发送用户消息) const regenerateMessage = async (msgId) => { if (!selectedConv.value || sending.value) return // 找到要重新生成的消息 const msgIndex = convMessages.value.findIndex(m => m.id === msgId) if (msgIndex === -1) return // 找到对应的用户消息(assistant 消息的前一条) const userMsgIndex = msgIndex - 1 if (userMsgIndex < 0 || convMessages.value[userMsgIndex].role !== 'user') return const userMsg = convMessages.value[userMsgIndex] // 删除 assistant 消息 convMessages.value = convMessages.value.filter(m => m.id !== msgId) // 调用 API 删除 assistant 消息 try { await messagesAPI.delete(msgId) } catch (e) { console.error('删除消息失败:', e) } // 重新发送用户消息 await sendMessage(userMsg.content) } // 设置流状态监听 let unwatchStream = null const setupStreamWatch = () => { if (unwatchStream) { unwatchStream() } unwatchStream = watch( () => streamStore.getStreamState(selectedId.value), (state) => { if (!state) return if (state.status === 'done') { const completedMessage = { id: state.id, role: 'assistant', process_steps: state.process_steps, token_count: state.token_count, usage: state.usage, created_at: new Date().toISOString() } convMessages.value.push(completedMessage) streamStore.clearStream(selectedId.value) } else if (state.status === 'error') { console.error('Stream error:', state.error) streamStore.clearStream(selectedId.value) } }, { deep: true } ) } // 初始化 const init = async () => { await fetchData() } // 清理 const cleanup = () => { if (unwatchStream) { unwatchStream() } } // 初始滚动回调(由外部设置) let initialScrollCallback = null const setOnInitialScroll = (callback) => { initialScrollCallback = callback } return { // 状态 list, providers, page, totalPages, loading, error, selectedId, selectedConv, convMessages, loadingMessages, sending, currentStreamState, // 方法 hasActiveStream, fetchData, selectConv, fetchConvMessages, sendMessage, createConv, deleteConv, updateConvTitle, deleteMessage, regenerateMessage, loadEnabledTools, setOnInitialScroll, init, cleanup } }