feat: Add chat session to analysis page
- Import ChatSession from @google/generative-ai - Add chatSession state to maintain conversation history - Initialize chat session with personality and language context - Replace stateless generateContent() with chatSession.sendMessage() - Add FEN before/after to move analysis prompts - Chat history is preserved when navigating between moves - LLM can now provide context-aware commentary across the game
This commit is contained in:
@@ -14,6 +14,7 @@ import { detectChessFormat, ChessFormat } from "@/lib/chessFormatDetector";
|
|||||||
import { detectMissedTactics, DetectedTactic, uciToSan } from "@/lib/tacticDetection";
|
import { detectMissedTactics, DetectedTactic, uciToSan } from "@/lib/tacticDetection";
|
||||||
import { lookupOpening } from "@/lib/openings";
|
import { lookupOpening } from "@/lib/openings";
|
||||||
import { getGenAIModel } from "@/lib/gemini";
|
import { getGenAIModel } from "@/lib/gemini";
|
||||||
|
import { ChatSession } from "@google/generative-ai";
|
||||||
import ReactMarkdown from "react-markdown";
|
import ReactMarkdown from "react-markdown";
|
||||||
|
|
||||||
interface MoveStep {
|
interface MoveStep {
|
||||||
@@ -56,6 +57,7 @@ export default function AnalysisPage() {
|
|||||||
const [stepDetails, setStepDetails] = useState<Record<number, StepDetails>>({});
|
const [stepDetails, setStepDetails] = useState<Record<number, StepDetails>>({});
|
||||||
const [isCommenting, setIsCommenting] = useState(false);
|
const [isCommenting, setIsCommenting] = useState(false);
|
||||||
const [comments, setComments] = useState<Record<number, string>>({});
|
const [comments, setComments] = useState<Record<number, string>>({});
|
||||||
|
const [chatSession, setChatSession] = useState<ChatSession | null>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const storedKey = localStorage.getItem("gemini_api_key");
|
const storedKey = localStorage.getItem("gemini_api_key");
|
||||||
@@ -70,6 +72,40 @@ export default function AnalysisPage() {
|
|||||||
return () => sf.terminate();
|
return () => sf.terminate();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
// Initialize chat session for conversational analysis
|
||||||
|
useEffect(() => {
|
||||||
|
if (apiKey) {
|
||||||
|
const model = getGenAIModel(apiKey, "gemini-2.5-flash");
|
||||||
|
const session = model.startChat({
|
||||||
|
history: [
|
||||||
|
{
|
||||||
|
role: "user",
|
||||||
|
parts: [{
|
||||||
|
text: `You are ${selectedPersonality.name}. You will analyze a chess game move by move.
|
||||||
|
Stay in character and maintain your personality throughout the analysis.
|
||||||
|
Language: ${language.toUpperCase()}.
|
||||||
|
|
||||||
|
IMPORTANT:
|
||||||
|
- You are analyzing moves sequentially
|
||||||
|
- Each move you analyze builds on the previous context
|
||||||
|
- If the user navigates backwards or forwards, you will see the move number
|
||||||
|
- Provide educational commentary in your characteristic style
|
||||||
|
- Be concise (3-4 sentences per move)
|
||||||
|
- Focus on what the move accomplishes and what was missed`
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
role: "model",
|
||||||
|
parts: [{
|
||||||
|
text: `Understood. I am ${selectedPersonality.name}, and I will analyze this game move by move in ${language}, maintaining my personality while providing educational insights. I'll keep track of the game's progression and provide context-aware commentary.`
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
setChatSession(session);
|
||||||
|
}
|
||||||
|
}, [apiKey, selectedPersonality, language]);
|
||||||
|
|
||||||
const currentFen = useMemo(() => {
|
const currentFen = useMemo(() => {
|
||||||
if (currentIndex === 0) return initialFen;
|
if (currentIndex === 0) return initialFen;
|
||||||
return steps[currentIndex - 1]?.fenAfter || initialFen;
|
return steps[currentIndex - 1]?.fenAfter || initialFen;
|
||||||
@@ -199,7 +235,7 @@ export default function AnalysisPage() {
|
|||||||
}, [currentIndex, steps, evaluationVersion]);
|
}, [currentIndex, steps, evaluationVersion]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!apiKey) return;
|
if (!chatSession) return;
|
||||||
if (currentIndex === 0) return;
|
if (currentIndex === 0) return;
|
||||||
const step = steps[currentIndex - 1];
|
const step = steps[currentIndex - 1];
|
||||||
const details = stepDetails[currentIndex];
|
const details = stepDetails[currentIndex];
|
||||||
@@ -210,7 +246,6 @@ export default function AnalysisPage() {
|
|||||||
setIsCommenting(true);
|
setIsCommenting(true);
|
||||||
const timeout = setTimeout(async () => {
|
const timeout = setTimeout(async () => {
|
||||||
try {
|
try {
|
||||||
const model = getGenAIModel(apiKey, "gemini-2.5-flash");
|
|
||||||
const delta = details.cpLoss ?? 0;
|
const delta = details.cpLoss ?? 0;
|
||||||
const evalBefore = details.evalBefore!.score / 100;
|
const evalBefore = details.evalBefore!.score / 100;
|
||||||
const evalAfter = details.evalAfter!.score / 100;
|
const evalAfter = details.evalAfter!.score / 100;
|
||||||
@@ -221,14 +256,14 @@ export default function AnalysisPage() {
|
|||||||
.join("; ") || "None";
|
.join("; ") || "None";
|
||||||
|
|
||||||
const prompt = `
|
const prompt = `
|
||||||
You are ${selectedPersonality.name}. Stay in character.
|
Analyze this move:
|
||||||
Language: ${language.toUpperCase()}.
|
|
||||||
Explain the move that was just played.
|
|
||||||
|
|
||||||
DATA:
|
DATA:
|
||||||
- Move number: ${step.moveNumber}
|
- Move number: ${step.moveNumber}
|
||||||
- Side to move: ${step.color}
|
- Side to move: ${step.color}
|
||||||
- Move played (SAN): ${step.san}
|
- Move played (SAN): ${step.san}
|
||||||
|
- FEN before move: ${step.fenBefore}
|
||||||
|
- FEN after move: ${step.fenAfter}
|
||||||
- Evaluation before move: ${evalBefore.toFixed(2)} pawns
|
- Evaluation before move: ${evalBefore.toFixed(2)} pawns
|
||||||
- Evaluation after move: ${evalAfter.toFixed(2)} pawns
|
- Evaluation after move: ${evalAfter.toFixed(2)} pawns
|
||||||
- Best move suggestion: ${details.bestMoveSan ?? details.evalBefore!.bestMove}
|
- Best move suggestion: ${details.bestMoveSan ?? details.evalBefore!.bestMove}
|
||||||
@@ -244,7 +279,7 @@ INSTRUCTIONS:
|
|||||||
- Refer to the player's side as ${step.color}.
|
- Refer to the player's side as ${step.color}.
|
||||||
- Keep it educational and stay true to your personality tone.`;
|
- Keep it educational and stay true to your personality tone.`;
|
||||||
|
|
||||||
const result = await model.generateContent(prompt);
|
const result = await chatSession.sendMessage(prompt);
|
||||||
if (!cancelled) {
|
if (!cancelled) {
|
||||||
setComments(prev => ({ ...prev, [currentIndex]: result.response.text() }));
|
setComments(prev => ({ ...prev, [currentIndex]: result.response.text() }));
|
||||||
}
|
}
|
||||||
@@ -258,8 +293,9 @@ INSTRUCTIONS:
|
|||||||
return () => {
|
return () => {
|
||||||
cancelled = true;
|
cancelled = true;
|
||||||
clearTimeout(timeout);
|
clearTimeout(timeout);
|
||||||
|
setIsCommenting(false);
|
||||||
};
|
};
|
||||||
}, [apiKey, currentIndex, stepDetails, steps, comments, selectedPersonality, language, openingInfo]);
|
}, [chatSession, currentIndex, stepDetails, steps, comments, openingInfo]);
|
||||||
|
|
||||||
const formatEval = (evaluation?: StockfishEvaluation) => {
|
const formatEval = (evaluation?: StockfishEvaluation) => {
|
||||||
if (!evaluation) return t.analysis.enginePending;
|
if (!evaluation) return t.analysis.enginePending;
|
||||||
|
|||||||
Reference in New Issue
Block a user