This commit is contained in:
从何开始123
2026-01-08 11:56:00 +08:00
parent 54e9bf5906
commit 1561c054b7
24 changed files with 1105 additions and 449 deletions

228
prisma/hooks/useAppLogic.ts Normal file
View File

@@ -0,0 +1,228 @@
import { useState, useEffect, useCallback } from 'react';
import { ModelOption, AppConfig, ChatMessage } from '../types';
import { STORAGE_KEYS, DEFAULT_CONFIG, getValidThinkingLevels } from '../config';
import { useDeepThink } from './useDeepThink';
import { useChatSessions } from './useChatSessions';
import { setInterceptorUrl } from '../interceptor';
export const useAppLogic = () => {
// Session Management
const {
sessions,
currentSessionId,
setCurrentSessionId,
createSession,
updateSessionMessages,
deleteSession,
getSession
} = useChatSessions();
// UI State
const [isSidebarOpen, setIsSidebarOpen] = useState(true);
const [isSettingsOpen, setIsSettingsOpen] = useState(false);
// Active Chat State
const [messages, setMessages] = useState<ChatMessage[]>([]);
const [query, setQuery] = useState('');
// App Configuration with Persistence
const [selectedModel, setSelectedModel] = useState<ModelOption>(() => {
const cached = localStorage.getItem(STORAGE_KEYS.MODEL);
return (cached as ModelOption) || 'gemini-3-flash-preview';
});
const [config, setConfig] = useState<AppConfig>(() => {
const cached = localStorage.getItem(STORAGE_KEYS.SETTINGS);
if (cached) {
try {
return { ...DEFAULT_CONFIG, ...JSON.parse(cached) };
} catch (e) {
return DEFAULT_CONFIG;
}
}
return DEFAULT_CONFIG;
});
// Deep Think Engine
const {
appState,
managerAnalysis,
experts,
finalOutput,
synthesisThoughts,
runDynamicDeepThink,
stopDeepThink,
resetDeepThink,
processStartTime,
processEndTime
} = useDeepThink();
// Network Interceptor Sync
useEffect(() => {
if (config.enableCustomApi && config.customBaseUrl) {
setInterceptorUrl(config.customBaseUrl);
} else {
setInterceptorUrl(null);
}
return () => setInterceptorUrl(null);
}, [config.enableCustomApi, config.customBaseUrl]);
// Persistence Effects
useEffect(() => {
localStorage.setItem(STORAGE_KEYS.SETTINGS, JSON.stringify(config));
}, [config]);
useEffect(() => {
localStorage.setItem(STORAGE_KEYS.MODEL, selectedModel);
}, [selectedModel]);
useEffect(() => {
const cachedSessionId = localStorage.getItem(STORAGE_KEYS.SESSION_ID);
if (cachedSessionId && sessions.some(s => s.id === cachedSessionId)) {
setCurrentSessionId(cachedSessionId);
}
}, [sessions, setCurrentSessionId]);
useEffect(() => {
if (currentSessionId) {
localStorage.setItem(STORAGE_KEYS.SESSION_ID, currentSessionId);
} else {
localStorage.removeItem(STORAGE_KEYS.SESSION_ID);
}
}, [currentSessionId]);
// Handle Model Constraints
useEffect(() => {
const validLevels = getValidThinkingLevels(selectedModel);
setConfig(prev => {
const newPlanning = validLevels.includes(prev.planningLevel) ? prev.planningLevel : 'low';
const newExpert = validLevels.includes(prev.expertLevel) ? prev.expertLevel : 'low';
const newSynthesis = validLevels.includes(prev.synthesisLevel) ? prev.synthesisLevel : 'high';
if (newPlanning !== prev.planningLevel || newExpert !== prev.expertLevel || newSynthesis !== prev.synthesisLevel) {
return {
...prev,
planningLevel: newPlanning as any,
expertLevel: newExpert as any,
synthesisLevel: newSynthesis as any,
};
}
return prev;
});
}, [selectedModel]);
// Sync Messages when switching sessions
useEffect(() => {
if (currentSessionId) {
const session = getSession(currentSessionId);
if (session) {
setMessages(session.messages);
setSelectedModel(session.model || 'gemini-3-flash-preview');
}
} else {
setMessages([]);
}
}, [currentSessionId, getSession]);
// Handle AI Completion
useEffect(() => {
if (appState === 'completed') {
const finalizedMessage: ChatMessage = {
id: `ai-${Date.now()}`,
role: 'model',
content: finalOutput,
analysis: managerAnalysis,
experts: experts,
synthesisThoughts: synthesisThoughts,
isThinking: false,
totalDuration: (processStartTime && processEndTime) ? (processEndTime - processStartTime) : undefined
};
const newMessages = [...messages, finalizedMessage];
setMessages(newMessages);
if (currentSessionId) {
updateSessionMessages(currentSessionId, newMessages);
} else {
createSession(newMessages, selectedModel);
}
resetDeepThink();
}
}, [appState, finalOutput, managerAnalysis, experts, synthesisThoughts, resetDeepThink, processStartTime, processEndTime, currentSessionId, messages, selectedModel, createSession, updateSessionMessages]);
const handleRun = useCallback(() => {
if (!query.trim()) return;
const userMsg: ChatMessage = {
id: `user-${Date.now()}`,
role: 'user',
content: query
};
const newMessages = [...messages, userMsg];
setMessages(newMessages);
let activeSessionId = currentSessionId;
if (!activeSessionId) {
activeSessionId = createSession(newMessages, selectedModel);
} else {
updateSessionMessages(activeSessionId, newMessages);
}
runDynamicDeepThink(query, messages, selectedModel, config);
setQuery('');
}, [query, messages, currentSessionId, selectedModel, config, createSession, updateSessionMessages, runDynamicDeepThink]);
const handleNewChat = useCallback(() => {
stopDeepThink();
setCurrentSessionId(null);
setMessages([]);
setQuery('');
resetDeepThink();
if (window.innerWidth < 1024) setIsSidebarOpen(false);
}, [stopDeepThink, setCurrentSessionId, resetDeepThink]);
const handleSelectSession = useCallback((id: string) => {
stopDeepThink();
resetDeepThink();
setCurrentSessionId(id);
if (window.innerWidth < 1024) setIsSidebarOpen(false);
}, [stopDeepThink, resetDeepThink, setCurrentSessionId]);
const handleDeleteSession = useCallback((id: string, e: React.MouseEvent) => {
e.stopPropagation();
deleteSession(id);
if (currentSessionId === id) {
handleNewChat();
}
}, [deleteSession, currentSessionId, handleNewChat]);
return {
sessions,
currentSessionId,
messages,
query,
setQuery,
selectedModel,
setSelectedModel,
config,
setConfig,
isSidebarOpen,
setIsSidebarOpen,
isSettingsOpen,
setIsSettingsOpen,
appState,
managerAnalysis,
experts,
finalOutput,
processStartTime,
processEndTime,
handleRun,
handleNewChat,
handleSelectSession,
handleDeleteSession,
stopDeepThink
};
};

View File

@@ -1,52 +1,37 @@
import { useState, useRef, useCallback } from 'react';
import { useCallback } from 'react';
import { getAI } from '../api';
import { getThinkingBudget } from '../config';
import { AppConfig, ModelOption, AppState, AnalysisResult, ExpertResult, ChatMessage } from '../types';
import { AppConfig, ModelOption, ExpertResult, ChatMessage } from '../types';
import { executeManagerAnalysis } from '../services/deepThink/manager';
import { executeManagerAnalysis, executeManagerReview } from '../services/deepThink/manager';
import { streamExpertResponse } from '../services/deepThink/expert';
import { streamSynthesisResponse } from '../services/deepThink/synthesis';
import { useDeepThinkState } from './useDeepThinkState';
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);
const {
appState, setAppState,
managerAnalysis, setManagerAnalysis,
experts, expertsDataRef,
finalOutput, setFinalOutput,
synthesisThoughts, setSynthesisThoughts,
processStartTime, setProcessStartTime,
processEndTime, setProcessEndTime,
abortControllerRef,
resetDeepThink,
stopDeepThink,
updateExpertAt,
setInitialExperts,
appendExperts
} = useDeepThinkState();
// 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)
/**
* Orchestrates a single expert's lifecycle (Start -> Stream -> End)
*/
const runExpertLifecycle = async (
expert: ExpertResult,
index: number,
globalIndex: number,
ai: any,
model: ModelOption,
context: string,
@@ -55,17 +40,10 @@ export const useDeepThink = () => {
): 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]);
updateExpertAt(globalIndex, { status: 'thinking', startTime });
try {
// 2. Stream execution via service
let fullContent = "";
let fullThoughts = "";
@@ -79,44 +57,27 @@ export const useDeepThink = () => {
(textChunk, thoughtChunk) => {
fullContent += textChunk;
fullThoughts += thoughtChunk;
// Update Ref & State live
expertsDataRef.current[index] = {
...expertsDataRef.current[index],
thoughts: fullThoughts,
content: fullContent
};
setExperts([...expertsDataRef.current]);
updateExpertAt(globalIndex, { thoughts: fullThoughts, content: fullContent });
}
);
if (signal.aborted) return expertsDataRef.current[index];
if (signal.aborted) return expertsDataRef.current[globalIndex];
// 3. Mark as completed
expertsDataRef.current[index] = {
...expertsDataRef.current[index],
status: 'completed',
endTime: Date.now()
};
setExperts([...expertsDataRef.current]);
return expertsDataRef.current[index];
updateExpertAt(globalIndex, { status: 'completed', endTime: Date.now() });
return expertsDataRef.current[globalIndex];
} 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]);
updateExpertAt(globalIndex, { status: 'error', content: "Failed to generate response.", endTime: Date.now() });
}
return expertsDataRef.current[index];
return expertsDataRef.current[globalIndex];
}
};
/**
* Main Orchestration logic
*/
const runDynamicDeepThink = async (
query: string,
history: ChatMessage[],
@@ -125,20 +86,16 @@ export const useDeepThink = () => {
) => {
if (!query.trim()) return;
// Reset previous run
if (abortControllerRef.current) {
abortControllerRef.current.abort();
}
if (abortControllerRef.current) abortControllerRef.current.abort();
abortControllerRef.current = new AbortController();
const signal = abortControllerRef.current.signal;
// Reset UI state
setAppState('analyzing');
setManagerAnalysis(null);
setExperts([]);
expertsDataRef.current = [];
setInitialExperts([]);
setFinalOutput('');
setSynthesisThoughts('');
setProcessStartTime(Date.now());
setProcessEndTime(null);
@@ -152,33 +109,8 @@ export const useDeepThink = () => {
`${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 ---
// --- Phase 1: Planning & Initial Experts ---
// 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,
@@ -187,60 +119,97 @@ export const useDeepThink = () => {
getThinkingBudget(config.planningLevel, model)
);
// Wait for Manager Analysis
const analysisJson = await managerTask;
const primaryExpert: ExpertResult = {
id: 'expert-0',
role: "Primary Responder",
description: "Directly addresses the user's original query.",
temperature: 1,
prompt: query,
status: 'pending',
round: 1
};
setInitialExperts([primaryExpert]);
const primaryTask = runExpertLifecycle(
primaryExpert, 0, ai, model, recentHistory,
getThinkingBudget(config.expertLevel, model), signal
);
const analysisJson = await managerTask;
if (signal.aborted) return;
setManagerAnalysis(analysisJson);
// --- 3. Initialize & Run Supplementary Experts ---
const generatedExperts: ExpertResult[] = analysisJson.experts.map((exp, idx) => ({
const round1Experts: ExpertResult[] = analysisJson.experts.map((exp, idx) => ({
...exp,
id: `expert-${idx + 1}`,
status: 'pending'
id: `expert-r1-${idx + 1}`,
status: 'pending',
round: 1
}));
// Update state: Keep Primary (0) and append new ones
const currentPrimary = expertsDataRef.current[0];
const allExperts = [currentPrimary, ...generatedExperts];
expertsDataRef.current = allExperts;
setExperts([...allExperts]);
appendExperts(round1Experts);
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
)
const round1Tasks = round1Experts.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]);
await Promise.all([primaryTask, ...round1Tasks]);
if (signal.aborted) return;
// --- Phase 2: Recursive Loop (Optional) ---
let roundCounter = 1;
const MAX_ROUNDS = 3;
let loopActive = config.enableRecursiveLoop ?? false;
while (loopActive && roundCounter < MAX_ROUNDS) {
if (signal.aborted) return;
setAppState('reviewing');
const reviewResult = await executeManagerReview(
ai, model, query, expertsDataRef.current,
getThinkingBudget(config.planningLevel, model)
);
if (signal.aborted) return;
if (reviewResult.satisfied) {
loopActive = false;
} else {
roundCounter++;
const nextRoundExperts = (reviewResult.refined_experts || []).map((exp, idx) => ({
...exp, id: `expert-r${roundCounter}-${idx}`, status: 'pending' as const, round: roundCounter
}));
if (nextRoundExperts.length === 0) {
loopActive = false;
break;
}
const startIndex = expertsDataRef.current.length;
appendExperts(nextRoundExperts);
setAppState('experts_working');
const nextRoundTasks = nextRoundExperts.map((exp, idx) =>
runExpertLifecycle(exp, startIndex + idx, ai, model, recentHistory,
getThinkingBudget(config.expertLevel, model), signal)
);
await Promise.all(nextRoundTasks);
}
}
if (signal.aborted) return;
// --- 5. Synthesis ---
// --- Phase 3: Synthesis ---
setAppState('synthesizing');
let fullFinalText = '';
let fullFinalThoughts = '';
await streamSynthesisResponse(
ai,
model,
query,
recentHistory,
allResults,
getThinkingBudget(config.synthesisLevel, model),
signal,
ai, model, query, recentHistory, expertsDataRef.current,
getThinkingBudget(config.synthesisLevel, model), signal,
(textChunk, thoughtChunk) => {
fullFinalText += textChunk;
fullFinalThoughts += thoughtChunk;
@@ -255,9 +224,7 @@ export const useDeepThink = () => {
}
} catch (e: any) {
if (signal.aborted) {
console.log('Operation aborted by user');
} else {
if (!signal.aborted) {
console.error(e);
setAppState('idle');
setProcessEndTime(Date.now());

View File

@@ -0,0 +1,73 @@
import { useState, useRef, useCallback } from 'react';
import { AppState, AnalysisResult, ExpertResult } from '../types';
export const useDeepThinkState = () => {
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
const expertsDataRef = useRef<ExpertResult[]>([]);
const abortControllerRef = useRef<AbortController | null>(null);
const resetDeepThink = useCallback(() => {
setAppState('idle');
setManagerAnalysis(null);
setExperts([]);
expertsDataRef.current = [];
setFinalOutput('');
setSynthesisThoughts('');
setProcessStartTime(null);
setProcessEndTime(null);
abortControllerRef.current = null;
}, []);
const stopDeepThink = useCallback(() => {
if (abortControllerRef.current) {
abortControllerRef.current.abort();
abortControllerRef.current = null;
}
setAppState('idle');
setProcessEndTime(Date.now());
}, []);
const updateExpertAt = useCallback((index: number, update: Partial<ExpertResult> | ((prev: ExpertResult) => ExpertResult)) => {
const current = expertsDataRef.current[index];
const next = typeof update === 'function' ? update(current) : { ...current, ...update };
expertsDataRef.current[index] = next;
setExperts([...expertsDataRef.current]);
}, []);
const setInitialExperts = useCallback((initialList: ExpertResult[]) => {
expertsDataRef.current = initialList;
setExperts(initialList);
}, []);
const appendExperts = useCallback((newList: ExpertResult[]) => {
expertsDataRef.current = [...expertsDataRef.current, ...newList];
setExperts([...expertsDataRef.current]);
}, []);
return {
appState, setAppState,
managerAnalysis, setManagerAnalysis,
experts, setExperts, expertsDataRef,
finalOutput, setFinalOutput,
synthesisThoughts, setSynthesisThoughts,
processStartTime, setProcessStartTime,
processEndTime, setProcessEndTime,
abortControllerRef,
resetDeepThink,
stopDeepThink,
updateExpertAt,
setInitialExperts,
appendExperts
};
};