1
This commit is contained in:
68
prisma/hooks/useChatSessions.ts
Normal file
68
prisma/hooks/useChatSessions.ts
Normal 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
|
||||
};
|
||||
};
|
||||
282
prisma/hooks/useDeepThink.ts
Normal file
282
prisma/hooks/useDeepThink.ts
Normal 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
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user