feat(Hook): 实现智能摘要 useSummary Hook
- 实现流式摘要生成逻辑 - 支持摘要选项配置 (长度/风格) - 添加生成状态管理 - 支持摘要更新和保存
This commit is contained in:
parent
5d301364fb
commit
d61d689db0
169
src/components/features/SummaryGenerator/useSummary.ts
Normal file
169
src/components/features/SummaryGenerator/useSummary.ts
Normal file
@ -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<SummaryStatus>('idle');
|
||||||
|
const [summary, setSummary] = useState<SummaryData | null>(null);
|
||||||
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
const [streamingContent, setStreamingContent] = useState<string>('');
|
||||||
|
|
||||||
|
// 摘要选项
|
||||||
|
const [options, setOptionsState] = useState<SummaryOptions>({
|
||||||
|
length: 'standard',
|
||||||
|
style: 'bullet',
|
||||||
|
});
|
||||||
|
|
||||||
|
// 更新选项
|
||||||
|
const setOptions = useCallback((newOptions: Partial<SummaryOptions>) => {
|
||||||
|
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<boolean> => {
|
||||||
|
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,
|
||||||
|
};
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user