feat(hooks): 添加核心业务逻辑 hooks
- useConversations: 会话列表 CRUD 管理 - useSettings: 用户设置读取和更新 - useStreamChat: 流式聊天消息处理 - 支持 SSE 流式响应 - 支持思考内容显示 - 支持错误处理和重试
This commit is contained in:
parent
3a244eb989
commit
e4cdcc5141
190
src/hooks/useConversations.ts
Normal file
190
src/hooks/useConversations.ts
Normal file
@ -0,0 +1,190 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useState, useEffect, useCallback } from 'react';
|
||||||
|
import type { Conversation, Message } from '@/drizzle/schema';
|
||||||
|
|
||||||
|
export interface ConversationWithMessages extends Conversation {
|
||||||
|
messages: Message[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useConversations() {
|
||||||
|
const [conversations, setConversations] = useState<Conversation[]>([]);
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
|
||||||
|
const fetchConversations = useCallback(async () => {
|
||||||
|
try {
|
||||||
|
setLoading(true);
|
||||||
|
setError(null);
|
||||||
|
const response = await fetch('/api/conversations');
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Failed to fetch conversations');
|
||||||
|
}
|
||||||
|
const data = await response.json();
|
||||||
|
setConversations(data);
|
||||||
|
} catch (err) {
|
||||||
|
setError(err instanceof Error ? err.message : 'Unknown error');
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const createConversation = useCallback(async (data: {
|
||||||
|
title?: string;
|
||||||
|
model: string;
|
||||||
|
tools?: string[];
|
||||||
|
enableThinking?: boolean;
|
||||||
|
}) => {
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/conversations', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify(data),
|
||||||
|
});
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Failed to create conversation');
|
||||||
|
}
|
||||||
|
const newConversation = await response.json();
|
||||||
|
setConversations((prev) => [newConversation, ...prev]);
|
||||||
|
return newConversation;
|
||||||
|
} catch (err) {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const updateConversation = useCallback(async (
|
||||||
|
conversationId: string,
|
||||||
|
data: { title?: string; isPinned?: boolean; isArchived?: boolean }
|
||||||
|
) => {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`/api/conversations/${conversationId}`, {
|
||||||
|
method: 'PUT',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify(data),
|
||||||
|
});
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Failed to update conversation');
|
||||||
|
}
|
||||||
|
const updated = await response.json();
|
||||||
|
setConversations((prev) =>
|
||||||
|
prev.map((c) => (c.conversationId === conversationId ? updated : c))
|
||||||
|
);
|
||||||
|
return updated;
|
||||||
|
} catch (err) {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const deleteConversation = useCallback(async (conversationId: string) => {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`/api/conversations/${conversationId}`, {
|
||||||
|
method: 'DELETE',
|
||||||
|
});
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Failed to delete conversation');
|
||||||
|
}
|
||||||
|
setConversations((prev) =>
|
||||||
|
prev.filter((c) => c.conversationId !== conversationId)
|
||||||
|
);
|
||||||
|
} catch (err) {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const clearAllConversations = useCallback(async () => {
|
||||||
|
try {
|
||||||
|
const ids = conversations.map((c) => c.conversationId);
|
||||||
|
const response = await fetch('/api/conversations', {
|
||||||
|
method: 'DELETE',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ conversationIds: ids }),
|
||||||
|
});
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Failed to clear conversations');
|
||||||
|
}
|
||||||
|
setConversations([]);
|
||||||
|
} catch (err) {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}, [conversations]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchConversations();
|
||||||
|
}, [fetchConversations]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
conversations,
|
||||||
|
loading,
|
||||||
|
error,
|
||||||
|
refetch: fetchConversations,
|
||||||
|
createConversation,
|
||||||
|
updateConversation,
|
||||||
|
deleteConversation,
|
||||||
|
clearAllConversations,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useConversation(conversationId: string | null) {
|
||||||
|
const [conversation, setConversation] = useState<ConversationWithMessages | null>(null);
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
|
||||||
|
const fetchConversation = useCallback(async () => {
|
||||||
|
if (!conversationId) {
|
||||||
|
setConversation(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
setLoading(true);
|
||||||
|
setError(null);
|
||||||
|
const response = await fetch(`/api/conversations/${conversationId}`);
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Failed to fetch conversation');
|
||||||
|
}
|
||||||
|
const data = await response.json();
|
||||||
|
setConversation(data);
|
||||||
|
} catch (err) {
|
||||||
|
setError(err instanceof Error ? err.message : 'Unknown error');
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
}, [conversationId]);
|
||||||
|
|
||||||
|
const addMessage = useCallback((message: Message) => {
|
||||||
|
setConversation((prev) => {
|
||||||
|
if (!prev) return prev;
|
||||||
|
return {
|
||||||
|
...prev,
|
||||||
|
messages: [...prev.messages, message],
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const updateLastMessage = useCallback((content: string, thinkingContent?: string) => {
|
||||||
|
setConversation((prev) => {
|
||||||
|
if (!prev || prev.messages.length === 0) return prev;
|
||||||
|
const messages = [...prev.messages];
|
||||||
|
const lastIndex = messages.length - 1;
|
||||||
|
messages[lastIndex] = {
|
||||||
|
...messages[lastIndex],
|
||||||
|
content,
|
||||||
|
thinkingContent: thinkingContent || messages[lastIndex].thinkingContent,
|
||||||
|
};
|
||||||
|
return { ...prev, messages };
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchConversation();
|
||||||
|
}, [fetchConversation]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
conversation,
|
||||||
|
loading,
|
||||||
|
error,
|
||||||
|
refetch: fetchConversation,
|
||||||
|
addMessage,
|
||||||
|
updateLastMessage,
|
||||||
|
};
|
||||||
|
}
|
||||||
178
src/hooks/useSettings.ts
Normal file
178
src/hooks/useSettings.ts
Normal file
@ -0,0 +1,178 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useState, useEffect, useCallback } from 'react';
|
||||||
|
|
||||||
|
export interface Settings {
|
||||||
|
cchUrl: string;
|
||||||
|
cchApiKeyConfigured: boolean;
|
||||||
|
defaultModel: string;
|
||||||
|
defaultTools: string[];
|
||||||
|
systemPrompt: string;
|
||||||
|
temperature: string;
|
||||||
|
theme: string;
|
||||||
|
language: string;
|
||||||
|
enableThinking: boolean;
|
||||||
|
saveChatHistory: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Tool {
|
||||||
|
id: number;
|
||||||
|
toolId: string;
|
||||||
|
name: string;
|
||||||
|
displayName: string;
|
||||||
|
description: string | null;
|
||||||
|
icon: string | null;
|
||||||
|
inputSchema: Record<string, unknown>;
|
||||||
|
isEnabled: boolean;
|
||||||
|
isDefault: boolean;
|
||||||
|
sortOrder: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Model {
|
||||||
|
id: number;
|
||||||
|
modelId: string;
|
||||||
|
name: string;
|
||||||
|
displayName: string;
|
||||||
|
description: string | null;
|
||||||
|
supportsTools: boolean;
|
||||||
|
supportsThinking: boolean;
|
||||||
|
supportsVision: boolean;
|
||||||
|
maxTokens: number;
|
||||||
|
contextWindow: number;
|
||||||
|
isEnabled: boolean;
|
||||||
|
isDefault: boolean;
|
||||||
|
sortOrder: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultSettings: Settings = {
|
||||||
|
cchUrl: 'http://localhost:13500',
|
||||||
|
cchApiKeyConfigured: false,
|
||||||
|
defaultModel: 'claude-sonnet-4-20250514',
|
||||||
|
defaultTools: ['web_search', 'code_execution', 'web_fetch'],
|
||||||
|
systemPrompt: '',
|
||||||
|
temperature: '0.7',
|
||||||
|
theme: 'light',
|
||||||
|
language: 'zh-CN',
|
||||||
|
enableThinking: false,
|
||||||
|
saveChatHistory: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
export function useSettings() {
|
||||||
|
const [settings, setSettings] = useState<Settings>(defaultSettings);
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
const [saving, setSaving] = useState(false);
|
||||||
|
|
||||||
|
// 获取设置
|
||||||
|
const fetchSettings = useCallback(async () => {
|
||||||
|
try {
|
||||||
|
setLoading(true);
|
||||||
|
setError(null);
|
||||||
|
const response = await fetch('/api/settings');
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Failed to fetch settings');
|
||||||
|
}
|
||||||
|
const data = await response.json();
|
||||||
|
setSettings(data);
|
||||||
|
} catch (err) {
|
||||||
|
setError(err instanceof Error ? err.message : 'Unknown error');
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// 更新设置
|
||||||
|
const updateSettings = useCallback(async (updates: Partial<Settings & { cchApiKey?: string }>) => {
|
||||||
|
try {
|
||||||
|
setSaving(true);
|
||||||
|
setError(null);
|
||||||
|
const response = await fetch('/api/settings', {
|
||||||
|
method: 'PUT',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify(updates),
|
||||||
|
});
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Failed to update settings');
|
||||||
|
}
|
||||||
|
const data = await response.json();
|
||||||
|
setSettings(data);
|
||||||
|
return data;
|
||||||
|
} catch (err) {
|
||||||
|
const message = err instanceof Error ? err.message : 'Unknown error';
|
||||||
|
setError(message);
|
||||||
|
throw err;
|
||||||
|
} finally {
|
||||||
|
setSaving(false);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// 初始加载
|
||||||
|
useEffect(() => {
|
||||||
|
fetchSettings();
|
||||||
|
}, [fetchSettings]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
settings,
|
||||||
|
loading,
|
||||||
|
error,
|
||||||
|
saving,
|
||||||
|
updateSettings,
|
||||||
|
refetch: fetchSettings,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useTools() {
|
||||||
|
const [tools, setTools] = useState<Tool[]>([]);
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
async function fetchTools() {
|
||||||
|
try {
|
||||||
|
setLoading(true);
|
||||||
|
const response = await fetch('/api/tools');
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Failed to fetch tools');
|
||||||
|
}
|
||||||
|
const data = await response.json();
|
||||||
|
setTools(data);
|
||||||
|
} catch (err) {
|
||||||
|
setError(err instanceof Error ? err.message : 'Unknown error');
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fetchTools();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return { tools, loading, error };
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useModels() {
|
||||||
|
const [models, setModels] = useState<Model[]>([]);
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
async function fetchModels() {
|
||||||
|
try {
|
||||||
|
setLoading(true);
|
||||||
|
const response = await fetch('/api/models');
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Failed to fetch models');
|
||||||
|
}
|
||||||
|
const data = await response.json();
|
||||||
|
setModels(data);
|
||||||
|
} catch (err) {
|
||||||
|
setError(err instanceof Error ? err.message : 'Unknown error');
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fetchModels();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return { models, loading, error };
|
||||||
|
}
|
||||||
233
src/hooks/useStreamChat.ts
Normal file
233
src/hooks/useStreamChat.ts
Normal file
@ -0,0 +1,233 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useState, useCallback, useRef } from 'react';
|
||||||
|
|
||||||
|
export interface StreamMessage {
|
||||||
|
type: 'thinking' | 'text' | 'tool_use_start' | 'done' | 'error';
|
||||||
|
content?: string;
|
||||||
|
id?: string;
|
||||||
|
name?: string;
|
||||||
|
messageId?: string;
|
||||||
|
inputTokens?: number;
|
||||||
|
outputTokens?: number;
|
||||||
|
error?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ChatMessage {
|
||||||
|
id: string;
|
||||||
|
role: 'user' | 'assistant';
|
||||||
|
content: string;
|
||||||
|
thinkingContent?: string;
|
||||||
|
status: 'pending' | 'streaming' | 'completed' | 'error';
|
||||||
|
error?: string;
|
||||||
|
inputTokens?: number;
|
||||||
|
outputTokens?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useStreamChat() {
|
||||||
|
const [messages, setMessages] = useState<ChatMessage[]>([]);
|
||||||
|
const [isStreaming, setIsStreaming] = useState(false);
|
||||||
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
const abortControllerRef = useRef<AbortController | null>(null);
|
||||||
|
|
||||||
|
// 发送消息
|
||||||
|
const sendMessage = useCallback(async (options: {
|
||||||
|
conversationId: string;
|
||||||
|
message: string;
|
||||||
|
model?: string;
|
||||||
|
tools?: string[];
|
||||||
|
enableThinking?: boolean;
|
||||||
|
}) => {
|
||||||
|
const { conversationId, message, model, tools, enableThinking } = options;
|
||||||
|
|
||||||
|
// 添加用户消息
|
||||||
|
const userMessage: ChatMessage = {
|
||||||
|
id: `user-${Date.now()}`,
|
||||||
|
role: 'user',
|
||||||
|
content: message,
|
||||||
|
status: 'completed',
|
||||||
|
};
|
||||||
|
|
||||||
|
// 添加 AI 消息占位
|
||||||
|
const assistantMessage: ChatMessage = {
|
||||||
|
id: `assistant-${Date.now()}`,
|
||||||
|
role: 'assistant',
|
||||||
|
content: '',
|
||||||
|
thinkingContent: '',
|
||||||
|
status: 'streaming',
|
||||||
|
};
|
||||||
|
|
||||||
|
setMessages((prev) => [...prev, userMessage, assistantMessage]);
|
||||||
|
setIsStreaming(true);
|
||||||
|
setError(null);
|
||||||
|
|
||||||
|
// 创建 AbortController
|
||||||
|
abortControllerRef.current = new AbortController();
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/chat', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
conversationId,
|
||||||
|
message,
|
||||||
|
model,
|
||||||
|
tools,
|
||||||
|
enableThinking,
|
||||||
|
}),
|
||||||
|
signal: abortControllerRef.current.signal,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const errorData = await response.json();
|
||||||
|
throw new Error(errorData.error || 'Failed to send message');
|
||||||
|
}
|
||||||
|
|
||||||
|
const reader = response.body?.getReader();
|
||||||
|
if (!reader) {
|
||||||
|
throw new Error('No response body');
|
||||||
|
}
|
||||||
|
|
||||||
|
const decoder = new TextDecoder();
|
||||||
|
let buffer = '';
|
||||||
|
let fullContent = '';
|
||||||
|
let thinkingContent = '';
|
||||||
|
|
||||||
|
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);
|
||||||
|
if (data === '[DONE]') continue;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const event: StreamMessage = JSON.parse(data);
|
||||||
|
|
||||||
|
if (event.type === 'thinking') {
|
||||||
|
thinkingContent += event.content || '';
|
||||||
|
setMessages((prev) => {
|
||||||
|
const updated = [...prev];
|
||||||
|
const lastIndex = updated.length - 1;
|
||||||
|
if (updated[lastIndex]?.role === 'assistant') {
|
||||||
|
updated[lastIndex] = {
|
||||||
|
...updated[lastIndex],
|
||||||
|
thinkingContent,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return updated;
|
||||||
|
});
|
||||||
|
} else if (event.type === 'text') {
|
||||||
|
fullContent += event.content || '';
|
||||||
|
setMessages((prev) => {
|
||||||
|
const updated = [...prev];
|
||||||
|
const lastIndex = updated.length - 1;
|
||||||
|
if (updated[lastIndex]?.role === 'assistant') {
|
||||||
|
updated[lastIndex] = {
|
||||||
|
...updated[lastIndex],
|
||||||
|
content: fullContent,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return updated;
|
||||||
|
});
|
||||||
|
} else if (event.type === 'done') {
|
||||||
|
setMessages((prev) => {
|
||||||
|
const updated = [...prev];
|
||||||
|
const lastIndex = updated.length - 1;
|
||||||
|
if (updated[lastIndex]?.role === 'assistant') {
|
||||||
|
updated[lastIndex] = {
|
||||||
|
...updated[lastIndex],
|
||||||
|
id: event.messageId || updated[lastIndex].id,
|
||||||
|
status: 'completed',
|
||||||
|
inputTokens: event.inputTokens,
|
||||||
|
outputTokens: event.outputTokens,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return updated;
|
||||||
|
});
|
||||||
|
} else if (event.type === 'error') {
|
||||||
|
throw new Error(event.error || 'Unknown error');
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
if (e instanceof SyntaxError) {
|
||||||
|
// 忽略 JSON 解析错误
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof Error && err.name === 'AbortError') {
|
||||||
|
// 用户取消
|
||||||
|
setMessages((prev) => {
|
||||||
|
const updated = [...prev];
|
||||||
|
const lastIndex = updated.length - 1;
|
||||||
|
if (updated[lastIndex]?.role === 'assistant') {
|
||||||
|
updated[lastIndex] = {
|
||||||
|
...updated[lastIndex],
|
||||||
|
status: 'completed',
|
||||||
|
content: updated[lastIndex].content || '(已取消)',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return updated;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
const errorMessage = err instanceof Error ? err.message : 'Unknown error';
|
||||||
|
setError(errorMessage);
|
||||||
|
setMessages((prev) => {
|
||||||
|
const updated = [...prev];
|
||||||
|
const lastIndex = updated.length - 1;
|
||||||
|
if (updated[lastIndex]?.role === 'assistant') {
|
||||||
|
updated[lastIndex] = {
|
||||||
|
...updated[lastIndex],
|
||||||
|
status: 'error',
|
||||||
|
error: errorMessage,
|
||||||
|
content: updated[lastIndex].content || '',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return updated;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
setIsStreaming(false);
|
||||||
|
abortControllerRef.current = null;
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// 停止生成
|
||||||
|
const stopGeneration = useCallback(() => {
|
||||||
|
if (abortControllerRef.current) {
|
||||||
|
abortControllerRef.current.abort();
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// 清空消息
|
||||||
|
const clearMessages = useCallback(() => {
|
||||||
|
setMessages([]);
|
||||||
|
setError(null);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// 设置初始消息
|
||||||
|
const setInitialMessages = useCallback((initialMessages: ChatMessage[]) => {
|
||||||
|
setMessages(initialMessages);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return {
|
||||||
|
messages,
|
||||||
|
isStreaming,
|
||||||
|
error,
|
||||||
|
sendMessage,
|
||||||
|
stopGeneration,
|
||||||
|
clearMessages,
|
||||||
|
setInitialMessages,
|
||||||
|
};
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user