Fix AI rule drift and add auto-save during streaming

- Add rule reminders after 10+ messages to prevent AI drift
- Add auto-save every 5 seconds during streaming
- Add beforeunload warning for unsaved changes
- Save user message immediately before generating AI response
- Use refs for latest session data in async operations
- Reduce summary threshold from 20 to 15 messages
This commit is contained in:
Alexej Wolff
2026-05-04 18:44:10 +02:00
parent 5e98c60e3e
commit f73a218745
2 changed files with 124 additions and 21 deletions
+16 -7
View File
@@ -11,7 +11,7 @@ const DEEPSEEK_API_URL = "https://api.deepseek.com/v1/chat/completions";
// Context settings
const RECENT_MESSAGES_COUNT = 6; // Last N messages for context
const SUMMARY_THRESHOLD = 20; // After how many messages to generate summary
const SUMMARY_THRESHOLD = 15; // After how many messages to generate summary
// API key should be stored in environment variables
const getApiKey = () => import.meta.env.VITE_DEEPSEEK_API_KEY || "";
@@ -269,15 +269,24 @@ ${story.plot}`;
}
/**
* Builds dynamic context (state + summary)
* Builds dynamic context (state + summary + rule reminders)
*/
export function buildDynamicContext(session: GameSession): string {
export function buildDynamicContext(session: GameSession, messageCount?: number): string {
const state = session.currentState;
const summary = session.storySummary || "The story just began.";
const keyEvents = session.keyEvents?.length
? session.keyEvents.slice(-5).join("\n- ")
: "No significant events yet.";
// Add rule reminders after 10+ messages to prevent drift
const ruleReminder = (messageCount && messageCount >= 10) ? `
=== REMINDER ===
• Do NOT act for the player — only describe reactions and consequences
• Do NOT ask "What do you do?" — end with atmosphere, not questions
• Format dialogue: **"text"** (double asterisks = bold)
• React to player's words explicitly` : '';
return `
=== CURRENT STATE ===
Location: ${state.location}
@@ -288,7 +297,7 @@ Inventory: ${state.inventory.length > 0 ? state.inventory.join(", ") : "Empty"}
${summary}
=== KEY EVENTS ===
- ${keyEvents}`;
- ${keyEvents}${ruleReminder}`;
}
/**
@@ -317,8 +326,8 @@ export async function generateStoryResponse(
// 2. World context (cached by DeepSeek)
const worldContext = buildWorldContext(story);
// 3. Dynamic context (state + summary)
const dynamicContext = session ? buildDynamicContext(session) : "";
// 3. Dynamic context (state + summary + rule reminders after 10+ messages)
const dynamicContext = session ? buildDynamicContext(session, chatHistory.length) : "";
// 4. Last N messages (not the full history!)
const recentMessages = chatHistory.slice(-RECENT_MESSAGES_COUNT);
@@ -353,7 +362,7 @@ export async function generateStoryResponseStream(
): Promise<string> {
const styleRules = buildStyleRules(story, player);
const worldContext = buildWorldContext(story);
const dynamicContext = session ? buildDynamicContext(session) : "";
const dynamicContext = session ? buildDynamicContext(session, chatHistory.length) : "";
const recentMessages = chatHistory.slice(-RECENT_MESSAGES_COUNT);
const systemPrompt = styleRules + "\n" + worldContext + "\n" + dynamicContext;