diff --git a/src/components/features/SummaryGenerator/useSummary.ts b/src/components/features/SummaryGenerator/useSummary.ts new file mode 100644 index 0000000..b00f08c --- /dev/null +++ b/src/components/features/SummaryGenerator/useSummary.ts @@ -0,0 +1,169 @@ +'use client'; + +import { useState, useCallback } from 'react'; +import type { + SummaryOptions, + SummaryStatus, + SummaryData, + SummaryMessage, + UseSummaryReturn, +} from './types'; + +/** + * 智能摘要核心 Hook + * 管理摘要生成的状态和操作 + */ +export function useSummary(): UseSummaryReturn { + // 生成状态 + const [status, setStatus] = useState('idle'); + const [summary, setSummary] = useState(null); + const [error, setError] = useState(null); + const [streamingContent, setStreamingContent] = useState(''); + + // 摘要选项 + const [options, setOptionsState] = useState({ + length: 'standard', + style: 'bullet', + }); + + // 更新选项 + const setOptions = useCallback((newOptions: Partial) => { + setOptionsState(prev => ({ ...prev, ...newOptions })); + }, []); + + // 生成摘要 + const generate = useCallback(async (conversationId: string, messages: SummaryMessage[]) => { + if (messages.length === 0) { + setError('暂无对话内容'); + return; + } + + setStatus('generating'); + setError(null); + setStreamingContent(''); + + try { + const response = await fetch('/api/summary/generate', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + conversationId, + messages, + options, + }), + }); + + if (!response.ok) { + const errorData = await response.json(); + throw new Error(errorData.error || '生成摘要失败'); + } + + // 处理流式响应 + const reader = response.body?.getReader(); + const decoder = new TextDecoder(); + + if (!reader) { + throw new Error('无法读取响应'); + } + + let fullContent = ''; + + while (true) { + const { done, value } = await reader.read(); + + if (done) break; + + const chunk = decoder.decode(value, { stream: true }); + const lines = chunk.split('\n'); + + for (const line of lines) { + if (line.startsWith('data: ')) { + const data = line.slice(6); + + if (data === '[DONE]') { + // 流式传输完成 + setStatus('completed'); + setSummary({ + content: fullContent, + generatedAt: new Date(), + messageCount: messages.length, + options, + }); + return; + } + + try { + const parsed = JSON.parse(data); + + if (parsed.type === 'content') { + fullContent += parsed.content; + setStreamingContent(fullContent); + } else if (parsed.type === 'error') { + throw new Error(parsed.error); + } + } catch { + // 忽略解析错误,可能是不完整的 JSON + } + } + } + } + + // 如果没有收到 [DONE],也设置为完成 + if (fullContent) { + setStatus('completed'); + setSummary({ + content: fullContent, + generatedAt: new Date(), + messageCount: messages.length, + options, + }); + } + } catch (err) { + setStatus('error'); + setError(err instanceof Error ? err.message : '生成摘要时发生错误'); + } + }, [options]); + + // 重置状态 + const reset = useCallback(() => { + setStatus('idle'); + setSummary(null); + setError(null); + setStreamingContent(''); + }, []); + + // 保存摘要到数据库 + const saveSummary = useCallback(async (conversationId: string): Promise => { + if (!summary) return false; + + try { + const response = await fetch(`/api/conversations/${conversationId}/summary`, { + method: 'PUT', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + summary: summary.content, + }), + }); + + return response.ok; + } catch { + return false; + } + }, [summary]); + + return { + status, + summary, + error, + streamingContent, + options, + setOptions, + generate, + reset, + saveSummary, + }; +}