import { processDataStream } from 'ai' export interface LlmChatMessage { id: string role: 'user' | 'assistant' content: string reasoning?: string } export interface UseLlmChatOptions { modelId: () => number | null apiEndpoint?: string systemPrompt?: () => string enableThinking?: () => boolean } export function useLlmChat(options: UseLlmChatOptions) { const { modelId, apiEndpoint = '/api/llm/chat', systemPrompt, enableThinking, } = options const messages = ref([]) const isLoading = ref(false) const errorMessage = ref('') let abortController: AbortController | null = null function generateId(): string { return Date.now().toString(36) + Math.random().toString(36).slice(2) } async function sendMessage(text: string) { const trimmed = text.trim() const mid = modelId() if (!trimmed || mid === null || isLoading.value) return errorMessage.value = '' const userMsg: LlmChatMessage = { id: generateId(), role: 'user', content: trimmed } messages.value.push(userMsg) const assistantMsg: LlmChatMessage = { id: generateId(), role: 'assistant', content: '' } messages.value.push(assistantMsg) const assistantIdx = messages.value.length - 1 isLoading.value = true abortController = new AbortController() try { const res = await fetch(apiEndpoint, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ modelId: mid, messages: [ ...(systemPrompt?.() ? [{ role: 'system' as const, content: systemPrompt() }] : []), ...messages.value .filter(m => m.content) .map(m => ({ role: m.role, content: m.content })), ], enableThinking: enableThinking?.() ?? false, }), signal: abortController.signal, }) if (!res.ok) { const errText = await res.text() throw new Error(errText || `请求失败 (${res.status})`) } if (!res.body) { throw new Error('响应体为空') } await processDataStream({ stream: res.body, onReasoningPart: (text) => { const msg = messages.value[assistantIdx] if (msg) msg.reasoning = (msg.reasoning ?? '') + text }, onTextPart: (text) => { const msg = messages.value[assistantIdx] if (msg) msg.content += text }, onErrorPart: (error) => { errorMessage.value = error || '流式响应出错' }, }) } catch (err: any) { if (err.name === 'AbortError') { // user stopped } else { errorMessage.value = err.message || '请求失败' const msg = messages.value[assistantIdx] if (msg && !msg.content) { messages.value.splice(assistantIdx, 1) } } } finally { isLoading.value = false abortController = null } } function stopGeneration() { if (abortController) { abortController.abort() abortController = null } } function clearChat() { messages.value = [] errorMessage.value = '' } return { messages, isLoading, errorMessage, sendMessage, stopGeneration, clearChat, } }