feat(Hooks): 支持秘塔配置和图片搜索结果处理
useSettings: - 添加 metasoApiKeyConfigured 状态 - 支持秘塔API Key的更新操作 useStreamChat: - 添加 SearchImageData 类型定义 - 处理 tool_search_images 事件,实时展示搜索图片 - 处理 tool_used 事件,追踪使用的工具 - 添加搜索图片的数据库持久化逻辑 - ChatMessage 接口添加 searchImages 和 usedTools 字段
This commit is contained in:
parent
5a6a147bd8
commit
3459f3821f
@ -5,6 +5,7 @@ import { useState, useEffect, useCallback } from 'react';
|
||||
export interface Settings {
|
||||
cchUrl: string;
|
||||
cchApiKeyConfigured: boolean;
|
||||
metasoApiKeyConfigured: boolean;
|
||||
apiFormat: 'claude' | 'openai';
|
||||
defaultModel: string;
|
||||
defaultTools: string[];
|
||||
@ -51,9 +52,10 @@ export interface Model {
|
||||
const defaultSettings: Settings = {
|
||||
cchUrl: 'http://localhost:13500',
|
||||
cchApiKeyConfigured: false,
|
||||
metasoApiKeyConfigured: false,
|
||||
apiFormat: 'claude',
|
||||
defaultModel: 'claude-sonnet-4-5-20250929',
|
||||
defaultTools: ['web_search', 'web_fetch'],
|
||||
defaultTools: ['web_search', 'web_fetch', 'mita_search', 'mita_reader'],
|
||||
systemPrompt: '',
|
||||
temperature: '0.7',
|
||||
theme: 'light',
|
||||
@ -88,7 +90,7 @@ export function useSettings() {
|
||||
}, []);
|
||||
|
||||
// 更新设置
|
||||
const updateSettings = useCallback(async (updates: Partial<Settings & { cchApiKey?: string }>) => {
|
||||
const updateSettings = useCallback(async (updates: Partial<Settings & { cchApiKey?: string; metasoApiKey?: string }>) => {
|
||||
try {
|
||||
setSaving(true);
|
||||
setError(null);
|
||||
|
||||
@ -4,7 +4,7 @@ import { useState, useCallback, useRef } from 'react';
|
||||
import { executePythonInPyodide, type LoadingCallback } from '@/services/tools/pyodideRunner';
|
||||
|
||||
export interface StreamMessage {
|
||||
type: 'thinking' | 'text' | 'tool_use_start' | 'tool_execution_result' | 'pyodide_execution_required' | 'done' | 'error';
|
||||
type: 'thinking' | 'text' | 'tool_use_start' | 'tool_execution_result' | 'tool_search_images' | 'pyodide_execution_required' | 'tool_used' | 'done' | 'error';
|
||||
content?: string;
|
||||
id?: string;
|
||||
name?: string;
|
||||
@ -19,6 +19,22 @@ export interface StreamMessage {
|
||||
success?: boolean;
|
||||
result?: string;
|
||||
images?: string[];
|
||||
// 搜索到的图片
|
||||
searchImages?: SearchImageData[];
|
||||
// 工具使用相关
|
||||
toolName?: string;
|
||||
usedTools?: string[];
|
||||
}
|
||||
|
||||
// 搜索图片数据类型
|
||||
export interface SearchImageData {
|
||||
title: string;
|
||||
imageUrl: string;
|
||||
width: number;
|
||||
height: number;
|
||||
score: string;
|
||||
position: number;
|
||||
sourceUrl?: string;
|
||||
}
|
||||
|
||||
export interface ChatMessage {
|
||||
@ -32,10 +48,14 @@ export interface ChatMessage {
|
||||
outputTokens?: number;
|
||||
// 工具执行产生的图片
|
||||
images?: string[];
|
||||
// 搜索到的图片
|
||||
searchImages?: SearchImageData[];
|
||||
// 用户上传的图片(Base64)
|
||||
uploadedImages?: string[];
|
||||
// 用户上传的文档
|
||||
uploadedDocuments?: UploadedDocument[];
|
||||
// 使用的工具列表
|
||||
usedTools?: string[];
|
||||
// Pyodide 加载状态
|
||||
pyodideStatus?: {
|
||||
stage: 'loading' | 'ready' | 'error';
|
||||
@ -67,6 +87,29 @@ async function saveMessageImages(messageId: string, images: string[]): Promise<v
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存搜索到的图片到数据库
|
||||
*/
|
||||
async function saveMessageSearchImages(messageId: string, searchImages: SearchImageData[]): Promise<void> {
|
||||
if (!messageId || !searchImages || searchImages.length === 0) return;
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/messages/${messageId}`, {
|
||||
method: 'PATCH',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ searchImages }),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
console.error('Failed to save search images:', await response.text());
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error saving search images:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将文件转换为 Base64
|
||||
*/
|
||||
@ -159,6 +202,8 @@ export function useStreamChat() {
|
||||
const abortControllerRef = useRef<AbortController | null>(null);
|
||||
// 临时存储 Pyodide 执行产生的图片,等待 messageId
|
||||
const pendingImagesRef = useRef<string[]>([]);
|
||||
// 临时存储搜索到的图片,等待 messageId
|
||||
const pendingSearchImagesRef = useRef<SearchImageData[]>([]);
|
||||
|
||||
// 发送消息
|
||||
const sendMessage = useCallback(async (options: {
|
||||
@ -371,6 +416,47 @@ export function useStreamChat() {
|
||||
return updated;
|
||||
});
|
||||
}
|
||||
} else if (event.type === 'tool_search_images') {
|
||||
// 处理图片搜索结果
|
||||
if (event.searchImages && event.searchImages.length > 0) {
|
||||
// 存储到临时变量,等待 messageId 后保存到数据库
|
||||
pendingSearchImagesRef.current = [
|
||||
...pendingSearchImagesRef.current,
|
||||
...event.searchImages,
|
||||
];
|
||||
// 更新 UI
|
||||
setMessages((prev) => {
|
||||
const updated = [...prev];
|
||||
const lastIndex = updated.length - 1;
|
||||
if (updated[lastIndex]?.role === 'assistant') {
|
||||
const existingSearchImages = updated[lastIndex].searchImages || [];
|
||||
updated[lastIndex] = {
|
||||
...updated[lastIndex],
|
||||
searchImages: [...existingSearchImages, ...event.searchImages!],
|
||||
};
|
||||
}
|
||||
return updated;
|
||||
});
|
||||
}
|
||||
} else if (event.type === 'tool_used') {
|
||||
// 实时工具使用事件
|
||||
if (event.toolName) {
|
||||
setMessages((prev) => {
|
||||
const updated = [...prev];
|
||||
const lastIndex = updated.length - 1;
|
||||
if (updated[lastIndex]?.role === 'assistant') {
|
||||
const existingTools = updated[lastIndex].usedTools || [];
|
||||
// 避免重复添加
|
||||
if (!existingTools.includes(event.toolName!)) {
|
||||
updated[lastIndex] = {
|
||||
...updated[lastIndex],
|
||||
usedTools: [...existingTools, event.toolName!],
|
||||
};
|
||||
}
|
||||
}
|
||||
return updated;
|
||||
});
|
||||
}
|
||||
} else if (event.type === 'pyodide_execution_required') {
|
||||
// 需要在浏览器端执行 Python 图形代码
|
||||
const code = event.code || '';
|
||||
@ -444,16 +530,25 @@ export function useStreamChat() {
|
||||
pendingImagesRef.current = []; // 清空临时存储
|
||||
}
|
||||
|
||||
// 如果有待保存的搜索图片,保存到数据库
|
||||
if (event.messageId && pendingSearchImagesRef.current.length > 0) {
|
||||
saveMessageSearchImages(event.messageId, pendingSearchImagesRef.current);
|
||||
pendingSearchImagesRef.current = []; // 清空临时存储
|
||||
}
|
||||
|
||||
setMessages((prev) => {
|
||||
const updated = [...prev];
|
||||
const lastIndex = updated.length - 1;
|
||||
if (updated[lastIndex]?.role === 'assistant') {
|
||||
// 如果 done 事件包含 usedTools,使用它(保证完整性)
|
||||
const finalUsedTools = event.usedTools || updated[lastIndex].usedTools;
|
||||
updated[lastIndex] = {
|
||||
...updated[lastIndex],
|
||||
id: event.messageId || updated[lastIndex].id,
|
||||
status: 'completed',
|
||||
inputTokens: event.inputTokens,
|
||||
outputTokens: event.outputTokens,
|
||||
usedTools: finalUsedTools,
|
||||
};
|
||||
}
|
||||
return updated;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user