This commit is contained in:
从何开始123
2026-01-08 02:16:42 +08:00
parent 83b4df1167
commit 54e9bf5906
31 changed files with 2201 additions and 0 deletions

View File

@@ -0,0 +1,68 @@
import { useState, useEffect, useCallback } from 'react';
import { ChatSession, ChatMessage, ModelOption } from '../types';
export const useChatSessions = () => {
const [sessions, setSessions] = useState<ChatSession[]>(() => {
try {
// Migrate from old key (deepthink-sessions) if present, otherwise use new key (prisma-sessions)
const saved = localStorage.getItem('prisma-sessions') || localStorage.getItem('deepthink-sessions');
return saved ? JSON.parse(saved) : [];
} catch (e) {
return [];
}
});
const [currentSessionId, setCurrentSessionId] = useState<string | null>(null);
useEffect(() => {
localStorage.setItem('prisma-sessions', JSON.stringify(sessions));
}, [sessions]);
const getSession = useCallback((id: string) => {
return sessions.find(s => s.id === id);
}, [sessions]);
const createSession = useCallback((initialMessages: ChatMessage[], model: ModelOption) => {
const newId = Date.now().toString();
const title = initialMessages[0].content.slice(0, 40) + (initialMessages[0].content.length > 40 ? '...' : '');
const newSession: ChatSession = {
id: newId,
title,
messages: initialMessages,
createdAt: Date.now(),
model
};
setSessions(prev => [newSession, ...prev]);
setCurrentSessionId(newId);
return newId;
}, []);
const updateSessionMessages = useCallback((sessionId: string, messages: ChatMessage[]) => {
setSessions(prev => prev.map(s =>
s.id === sessionId ? { ...s, messages } : s
));
}, []);
const deleteSession = useCallback((id: string) => {
setSessions(prev => prev.filter(s => s.id !== id));
if (currentSessionId === id) {
setCurrentSessionId(null);
}
}, [currentSessionId]);
const clearCurrentSession = useCallback(() => {
setCurrentSessionId(null);
}, []);
return {
sessions,
currentSessionId,
setCurrentSessionId,
createSession,
updateSessionMessages,
deleteSession,
clearCurrentSession,
getSession
};
};

View File

@@ -0,0 +1,282 @@
import { useState, useRef, useCallback } from 'react';
import { getAI } from '../api';
import { getThinkingBudget } from '../config';
import { AppConfig, ModelOption, AppState, AnalysisResult, ExpertResult, ChatMessage } from '../types';
import { executeManagerAnalysis } from '../services/deepThink/manager';
import { streamExpertResponse } from '../services/deepThink/expert';
import { streamSynthesisResponse } from '../services/deepThink/synthesis';
export const useDeepThink = () => {
const [appState, setAppState] = useState<AppState>('idle');
const [managerAnalysis, setManagerAnalysis] = useState<AnalysisResult | null>(null);
const [experts, setExperts] = useState<ExpertResult[]>([]);
const [finalOutput, setFinalOutput] = useState('');
const [synthesisThoughts, setSynthesisThoughts] = useState('');
// Timing state
const [processStartTime, setProcessStartTime] = useState<number | null>(null);
const [processEndTime, setProcessEndTime] = useState<number | null>(null);
// Refs for data consistency during high-frequency streaming updates
const expertsDataRef = useRef<ExpertResult[]>([]);
const abortControllerRef = useRef<AbortController | null>(null);
const stopDeepThink = useCallback(() => {
if (abortControllerRef.current) {
abortControllerRef.current.abort();
abortControllerRef.current = null;
}
setAppState('idle');
setProcessEndTime(Date.now());
}, []);
const resetDeepThink = useCallback(() => {
setAppState('idle');
setManagerAnalysis(null);
setExperts([]);
expertsDataRef.current = [];
setFinalOutput('');
setSynthesisThoughts('');
setProcessStartTime(null);
setProcessEndTime(null);
abortControllerRef.current = null;
}, []);
// Helper: Orchestrate a single expert's lifecycle (Start -> Stream -> End)
const runExpertLifecycle = async (
expert: ExpertResult,
index: number,
ai: any,
model: ModelOption,
context: string,
budget: number,
signal: AbortSignal
): Promise<ExpertResult> => {
if (signal.aborted) return expert;
// 1. Mark as thinking
const startTime = Date.now();
expertsDataRef.current[index] = {
...expert,
status: 'thinking',
startTime
};
setExperts([...expertsDataRef.current]);
try {
// 2. Stream execution via service
let fullContent = "";
let fullThoughts = "";
await streamExpertResponse(
ai,
model,
expert,
context,
budget,
signal,
(textChunk, thoughtChunk) => {
fullContent += textChunk;
fullThoughts += thoughtChunk;
// Update Ref & State live
expertsDataRef.current[index] = {
...expertsDataRef.current[index],
thoughts: fullThoughts,
content: fullContent
};
setExperts([...expertsDataRef.current]);
}
);
if (signal.aborted) return expertsDataRef.current[index];
// 3. Mark as completed
expertsDataRef.current[index] = {
...expertsDataRef.current[index],
status: 'completed',
endTime: Date.now()
};
setExperts([...expertsDataRef.current]);
return expertsDataRef.current[index];
} catch (error) {
console.error(`Expert ${expert.role} error:`, error);
if (!signal.aborted) {
expertsDataRef.current[index] = {
...expertsDataRef.current[index],
status: 'error',
content: "Failed to generate response.",
endTime: Date.now()
};
setExperts([...expertsDataRef.current]);
}
return expertsDataRef.current[index];
}
};
const runDynamicDeepThink = async (
query: string,
history: ChatMessage[],
model: ModelOption,
config: AppConfig
) => {
if (!query.trim()) return;
// Reset previous run
if (abortControllerRef.current) {
abortControllerRef.current.abort();
}
abortControllerRef.current = new AbortController();
const signal = abortControllerRef.current.signal;
setAppState('analyzing');
setManagerAnalysis(null);
setExperts([]);
expertsDataRef.current = [];
setFinalOutput('');
setSynthesisThoughts('');
setProcessStartTime(Date.now());
setProcessEndTime(null);
const ai = getAI({
apiKey: config.enableCustomApi ? config.customApiKey : undefined,
baseUrl: (config.enableCustomApi && config.customBaseUrl) ? config.customBaseUrl : undefined
});
try {
const recentHistory = history.slice(-5).map(msg =>
`${msg.role === 'user' ? 'User' : 'Model'}: ${msg.content}`
).join('\n');
// --- 1. Initialize Primary Expert IMMEDIATELY ---
const primaryExpert: ExpertResult = {
id: 'expert-0',
role: "Primary Responder",
description: "Directly addresses the user's original query.",
temperature: 1,
prompt: query,
status: 'pending'
};
expertsDataRef.current = [primaryExpert];
setExperts([primaryExpert]);
// --- 2. Start Parallel Execution ---
// Task A: Run Primary Expert (Index 0)
const primaryExpertTask = runExpertLifecycle(
primaryExpert,
0,
ai,
model,
recentHistory,
getThinkingBudget(config.expertLevel, model),
signal
);
// Task B: Run Manager Analysis via Service
const managerTask = executeManagerAnalysis(
ai,
model,
query,
recentHistory,
getThinkingBudget(config.planningLevel, model)
);
// Wait for Manager Analysis
const analysisJson = await managerTask;
if (signal.aborted) return;
setManagerAnalysis(analysisJson);
// --- 3. Initialize & Run Supplementary Experts ---
const generatedExperts: ExpertResult[] = analysisJson.experts.map((exp, idx) => ({
...exp,
id: `expert-${idx + 1}`,
status: 'pending'
}));
// Update state: Keep Primary (0) and append new ones
const currentPrimary = expertsDataRef.current[0];
const allExperts = [currentPrimary, ...generatedExperts];
expertsDataRef.current = allExperts;
setExperts([...allExperts]);
setAppState('experts_working');
// Task C: Run Supplementary Experts (Offset indices by 1)
const supplementaryTasks = generatedExperts.map((exp, idx) =>
runExpertLifecycle(
exp,
idx + 1,
ai,
model,
recentHistory,
getThinkingBudget(config.expertLevel, model),
signal
)
);
// --- 4. Wait for ALL Experts ---
const allResults = await Promise.all([primaryExpertTask, ...supplementaryTasks]);
if (signal.aborted) return;
// --- 5. Synthesis ---
setAppState('synthesizing');
let fullFinalText = '';
let fullFinalThoughts = '';
await streamSynthesisResponse(
ai,
model,
query,
recentHistory,
allResults,
getThinkingBudget(config.synthesisLevel, model),
signal,
(textChunk, thoughtChunk) => {
fullFinalText += textChunk;
fullFinalThoughts += thoughtChunk;
setFinalOutput(fullFinalText);
setSynthesisThoughts(fullFinalThoughts);
}
);
if (!signal.aborted) {
setAppState('completed');
setProcessEndTime(Date.now());
}
} catch (e: any) {
if (signal.aborted) {
console.log('Operation aborted by user');
} else {
console.error(e);
setAppState('idle');
setProcessEndTime(Date.now());
}
} finally {
abortControllerRef.current = null;
}
};
return {
appState,
managerAnalysis,
experts,
finalOutput,
synthesisThoughts,
runDynamicDeepThink,
stopDeepThink,
resetDeepThink,
processStartTime,
processEndTime
};
};