VirtualKimi commited on
Commit
a9d1590
·
verified ·
1 Parent(s): 60ae8dd

Upload 29 files

Browse files
index.html CHANGED
@@ -1086,6 +1086,7 @@
1086
  </div>
1087
 
1088
  <script src="dexie.min.js"></script>
 
1089
  <script src="kimi-locale/i18n.js" defer></script>
1090
  <script type="module" src="kimi-js/kimi-main.js"></script>
1091
  <script type="module" src="kimi-js/kimi-config.js"></script>
 
1086
  </div>
1087
 
1088
  <script src="dexie.min.js"></script>
1089
+ <script type="module" src="kimi-js/kimi-utils.js"></script>
1090
  <script src="kimi-locale/i18n.js" defer></script>
1091
  <script type="module" src="kimi-js/kimi-main.js"></script>
1092
  <script type="module" src="kimi-js/kimi-config.js"></script>
kimi-js/kimi-appearance.js CHANGED
@@ -5,7 +5,7 @@ class KimiAppearanceManager extends KimiBaseManager {
5
  this.db = database;
6
  this.currentTheme = "dark";
7
  this.interfaceOpacity = 0.8;
8
- // animations are always enabled by default; toggle removed
9
  this.animationsEnabled = true;
10
  }
11
 
@@ -30,7 +30,7 @@ class KimiAppearanceManager extends KimiBaseManager {
30
  "interfaceOpacity",
31
  window.KIMI_CONFIG?.DEFAULTS?.INTERFACE_OPACITY ?? 0.8
32
  );
33
- // animations preference removed; always enabled by default
34
  } catch (error) {
35
  console.error("Error loading appearance settings:", error);
36
  }
@@ -40,7 +40,7 @@ class KimiAppearanceManager extends KimiBaseManager {
40
  try {
41
  this.setupThemeSelector();
42
  this.setupOpacitySlider();
43
- // animations toggle removed
44
  } catch (error) {
45
  console.error("Error setting up appearance controls:", error);
46
  }
@@ -109,8 +109,6 @@ class KimiAppearanceManager extends KimiBaseManager {
109
  }
110
  }
111
 
112
- // Animations toggle removed: keep animations enabled at all times
113
-
114
  applyTheme(theme) {
115
  document.documentElement.setAttribute("data-theme", theme);
116
  }
@@ -126,7 +124,7 @@ class KimiAppearanceManager extends KimiBaseManager {
126
  }
127
 
128
  cleanup() {
129
- // animations toggle removed; nothing specific to cleanup here
130
  }
131
 
132
  getThemeName(theme) {
 
5
  this.db = database;
6
  this.currentTheme = "dark";
7
  this.interfaceOpacity = 0.8;
8
+ // Animations are enabled by default; the UI no longer exposes a toggle
9
  this.animationsEnabled = true;
10
  }
11
 
 
30
  "interfaceOpacity",
31
  window.KIMI_CONFIG?.DEFAULTS?.INTERFACE_OPACITY ?? 0.8
32
  );
33
+ // Animations preference is not configurable via UI and remain enabled
34
  } catch (error) {
35
  console.error("Error loading appearance settings:", error);
36
  }
 
40
  try {
41
  this.setupThemeSelector();
42
  this.setupOpacitySlider();
43
+ // No animations toggle in appearance controls
44
  } catch (error) {
45
  console.error("Error setting up appearance controls:", error);
46
  }
 
109
  }
110
  }
111
 
 
 
112
  applyTheme(theme) {
113
  document.documentElement.setAttribute("data-theme", theme);
114
  }
 
124
  }
125
 
126
  cleanup() {
127
+ // No animations toggle to clean up
128
  }
129
 
130
  getThemeName(theme) {
kimi-js/kimi-database.js CHANGED
@@ -64,7 +64,7 @@ class KimiDatabase {
64
  });
65
  }
66
  } catch (e) {
67
- // Swallow upgrade errors to avoid blocking DB open; post-open migrations will attempt fixes
68
  }
69
  });
70
 
@@ -90,7 +90,7 @@ class KimiDatabase {
90
  if (!rec.lastAccess) rec.lastAccess = rec.timestamp || now;
91
  });
92
  } catch (e) {
93
- // Silent; non-blocking
94
  }
95
  });
96
  }
@@ -283,7 +283,7 @@ class KimiDatabase {
283
  }
284
  }
285
 
286
- // Fix: never recreate default conversations
287
  const convCount = await this.db.conversations.count();
288
  if (convCount === 0) {
289
  }
@@ -369,8 +369,8 @@ class KimiDatabase {
369
  }
370
  }
371
 
372
- // MIGRATION: Fix Kimi affection progression issue - update default affection from 65 to 55
373
- // This allows better progression and prevents blocking at ~65%
374
  const kimiAffectionRecord = await this.db.personality.get(["kimi", "affection"]);
375
  if (kimiAffectionRecord && kimiAffectionRecord.value === 65) {
376
  // Only update if it's exactly 65 (the old default) and user hasn't modified it significantly
@@ -384,7 +384,7 @@ class KimiDatabase {
384
  console.log(`🔧 Migration: Updated Kimi affection from 65% to ${newValue}% for better progression`);
385
  }
386
 
387
- // MIGRATION: Fix Bella affection progression issue - update default affection from 70 to 60
388
  const bellaAffectionRecord = await this.db.personality.get(["bella", "affection"]);
389
  if (bellaAffectionRecord && bellaAffectionRecord.value === 70) {
390
  // Only update if it's exactly 70 (the old default) and user hasn't modified it significantly
@@ -398,7 +398,7 @@ class KimiDatabase {
398
  console.log(`🔧 Migration: Updated Bella affection from 70% to ${newValue}% for better progression`);
399
  }
400
 
401
- // MIGRATION: Remove deprecated animations preference if exists
402
  try {
403
  const animPref = await this.db.preferences.get("animationsEnabled");
404
  if (animPref) {
@@ -409,7 +409,7 @@ class KimiDatabase {
409
  // Non-blocking: ignore migration error
410
  }
411
 
412
- // MIGRATION: Normalize legacy selectedLanguage values to primary subtag (e.g., 'en-US'|'en_US'|'us:en' -> 'en')
413
  try {
414
  const langRecord = await this.db.preferences.get("selectedLanguage");
415
  if (langRecord && typeof langRecord.value === "string") {
@@ -434,8 +434,8 @@ class KimiDatabase {
434
  // Non-blocking
435
  }
436
 
437
- // FORCED MIGRATION: Normalize any preference keys containing the word 'language' to primary subtag
438
- // WARNING: This is destructive by design and will overwrite values without backup as requested.
439
  try {
440
  const allPrefs = await this.db.preferences.toArray();
441
  const langKeyRegex = /\blanguage\b/i;
@@ -579,18 +579,18 @@ class KimiDatabase {
579
  return defaultValue;
580
  }
581
 
582
- // Backward compatibility: decrypt legacy encrypted values
583
  let value = record.value;
584
  if (record.encrypted && window.KimiSecurityUtils) {
585
  try {
586
- value = record.value; // decrypt removed stored as plain text
587
- // One-time migration: store back as plain text without encrypted flag
588
  try {
589
  await this.db.preferences.put({ key: key, value, updated: new Date().toISOString() });
590
  } catch (mErr) {}
591
  } catch (e) {
592
- // If decryption fails, fallback to raw value
593
- console.warn("Failed to decrypt legacy API key; returning raw value", e);
594
  }
595
  }
596
 
 
64
  });
65
  }
66
  } catch (e) {
67
+ // Ignore upgrade errors so DB open is not blocked; post-open migrations will attempt fixes
68
  }
69
  });
70
 
 
90
  if (!rec.lastAccess) rec.lastAccess = rec.timestamp || now;
91
  });
92
  } catch (e) {
93
+ // Non-blocking: continue on error
94
  }
95
  });
96
  }
 
283
  }
284
  }
285
 
286
+ // Do not recreate default conversations
287
  const convCount = await this.db.conversations.count();
288
  if (convCount === 0) {
289
  }
 
369
  }
370
  }
371
 
372
+ // Migration: update Kimi default affection from 65 to 55
373
+ // This improves progression behavior for users who still have the old default
374
  const kimiAffectionRecord = await this.db.personality.get(["kimi", "affection"]);
375
  if (kimiAffectionRecord && kimiAffectionRecord.value === 65) {
376
  // Only update if it's exactly 65 (the old default) and user hasn't modified it significantly
 
384
  console.log(`🔧 Migration: Updated Kimi affection from 65% to ${newValue}% for better progression`);
385
  }
386
 
387
+ // Migration: Fix Bella default affection from 70 to 60
388
  const bellaAffectionRecord = await this.db.personality.get(["bella", "affection"]);
389
  if (bellaAffectionRecord && bellaAffectionRecord.value === 70) {
390
  // Only update if it's exactly 70 (the old default) and user hasn't modified it significantly
 
398
  console.log(`🔧 Migration: Updated Bella affection from 70% to ${newValue}% for better progression`);
399
  }
400
 
401
+ // Migration: remove deprecated animations preference if present
402
  try {
403
  const animPref = await this.db.preferences.get("animationsEnabled");
404
  if (animPref) {
 
409
  // Non-blocking: ignore migration error
410
  }
411
 
412
+ // Migration: normalize legacy selectedLanguage values to primary subtag (e.g., 'en-US'|'en_US'|'us:en' -> 'en')
413
  try {
414
  const langRecord = await this.db.preferences.get("selectedLanguage");
415
  if (langRecord && typeof langRecord.value === "string") {
 
434
  // Non-blocking
435
  }
436
 
437
+ // Forced migration: normalize any preference keys containing the word 'language' to primary subtag
438
+ // WARNING: This operation is destructive and will overwrite matching preference values without backup.
439
  try {
440
  const allPrefs = await this.db.preferences.toArray();
441
  const langKeyRegex = /\blanguage\b/i;
 
579
  return defaultValue;
580
  }
581
 
582
+ // Backward compatibility: legacy records may have an `encrypted` flag; handle as plain text when needed
583
  let value = record.value;
584
  if (record.encrypted && window.KimiSecurityUtils) {
585
  try {
586
+ // Treat legacy encrypted flag as plain text (one-time migration to remove encrypted flag)
587
+ value = record.value; // legacy encryption handling migrated: value stored as plain text
588
  try {
589
  await this.db.preferences.put({ key: key, value, updated: new Date().toISOString() });
590
  } catch (mErr) {}
591
  } catch (e) {
592
+ // If any error occurs, fallback to raw stored value
593
+ console.warn("Failed to handle legacy encrypted value; returning raw value", e);
594
  }
595
  }
596
 
kimi-js/kimi-emotion-system.js CHANGED
@@ -231,12 +231,12 @@ class KimiEmotionSystem {
231
  empathy = Math.min(100, adjustUp(empathy, scaleGain("empathy", 0.5))); // Empathy still grows (understanding pain)
232
  break;
233
  case this.EMOTIONS.ROMANTIC:
234
- romance = Math.min(100, adjustUp(romance, scaleGain("romance", 0.6))); // Reduced from 0.8 - romance should be earned
235
- affection = Math.min(100, adjustUp(affection, scaleGain("affection", 0.5))); // Reduced from 0.4
236
  break;
237
  case this.EMOTIONS.LAUGHING:
238
  humor = Math.min(100, adjustUp(humor, scaleGain("humor", 0.8))); // Humor grows with laughter
239
- playfulness = Math.min(100, adjustUp(playfulness, scaleGain("playfulness", 0.4))); // Increased playfulness connection
240
  affection = Math.min(100, adjustUp(affection, scaleGain("affection", 0.2))); // Small affection boost from shared laughter
241
  break;
242
  case this.EMOTIONS.DANCING:
@@ -248,12 +248,12 @@ class KimiEmotionSystem {
248
  romance = Math.max(0, adjustDown(romance, scaleLoss("romance", 0.2))); // Shyness reduces romance more
249
  break;
250
  case this.EMOTIONS.CONFIDENT:
251
- affection = Math.min(100, adjustUp(affection, scaleGain("affection", 0.5))); // Reduced from 0.4
252
  intelligence = Math.min(100, adjustUp(intelligence, scaleGain("intelligence", 0.1))); // Slight intelligence boost
253
  break;
254
  case this.EMOTIONS.FLIRTATIOUS:
255
- romance = Math.min(100, adjustUp(romance, scaleGain("romance", 0.5))); // Reduced from 0.6
256
- playfulness = Math.min(100, adjustUp(playfulness, scaleGain("playfulness", 0.5))); // Reduced from 0.4
257
  affection = Math.min(100, adjustUp(affection, scaleGain("affection", 0.2))); // Small affection boost
258
  break;
259
  case this.EMOTIONS.SURPRISE:
 
231
  empathy = Math.min(100, adjustUp(empathy, scaleGain("empathy", 0.5))); // Empathy still grows (understanding pain)
232
  break;
233
  case this.EMOTIONS.ROMANTIC:
234
+ romance = Math.min(100, adjustUp(romance, scaleGain("romance", 0.6))); // Romance should be earned
235
+ affection = Math.min(100, adjustUp(affection, scaleGain("affection", 0.5)));
236
  break;
237
  case this.EMOTIONS.LAUGHING:
238
  humor = Math.min(100, adjustUp(humor, scaleGain("humor", 0.8))); // Humor grows with laughter
239
+ playfulness = Math.min(100, adjustUp(playfulness, scaleGain("playfulness", 0.4))); // Playfulness connection
240
  affection = Math.min(100, adjustUp(affection, scaleGain("affection", 0.2))); // Small affection boost from shared laughter
241
  break;
242
  case this.EMOTIONS.DANCING:
 
248
  romance = Math.max(0, adjustDown(romance, scaleLoss("romance", 0.2))); // Shyness reduces romance more
249
  break;
250
  case this.EMOTIONS.CONFIDENT:
251
+ affection = Math.min(100, adjustUp(affection, scaleGain("affection", 0.5)));
252
  intelligence = Math.min(100, adjustUp(intelligence, scaleGain("intelligence", 0.1))); // Slight intelligence boost
253
  break;
254
  case this.EMOTIONS.FLIRTATIOUS:
255
+ romance = Math.min(100, adjustUp(romance, scaleGain("romance", 0.5)));
256
+ playfulness = Math.min(100, adjustUp(playfulness, scaleGain("playfulness", 0.5)));
257
  affection = Math.min(100, adjustUp(affection, scaleGain("affection", 0.2))); // Small affection boost
258
  break;
259
  case this.EMOTIONS.SURPRISE:
kimi-js/kimi-llm-manager.js CHANGED
@@ -286,26 +286,29 @@ class KimiLLMManager {
286
  }
287
  }
288
  // Read per-character preference metrics so displayed counters reflect actual stored values
289
- // rather than using a flattened global preferences object.
290
  const totalInteractions = Number(await this.db.getPreference(`totalInteractions_${character}`, 0)) || 0;
291
- const favorabilityLevel = Number(await this.db.getPreference(`favorabilityLevel_${character}`, 50)) || 50;
 
 
 
 
 
 
292
  const lastInteraction = await this.db.getPreference(`lastInteraction_${character}`, "First time");
293
- // Favorite words may be stored as an array or a JSON string; normalize to array
294
- let favoriteWordsPref = await this.db.getPreference(`favoriteWords_${character}`, []);
295
- let favoriteWords = [];
296
- if (typeof favoriteWordsPref === "string") {
297
- try {
298
- favoriteWords = JSON.parse(favoriteWordsPref);
299
- } catch (e) {
300
- favoriteWords = favoriteWordsPref.length ? favoriteWordsPref.split(/,\s*/) : [];
301
  }
302
- } else if (Array.isArray(favoriteWordsPref)) {
303
- favoriteWords = favoriteWordsPref;
304
- } else {
305
- favoriteWords = [];
306
  }
307
 
308
- // Use unified emotion system defaults - CRITICAL FIX
309
  const getUnifiedDefaults = () =>
310
  window.getTraitDefaults
311
  ? window.getTraitDefaults()
@@ -378,7 +381,7 @@ class KimiLLMManager {
378
  }
379
 
380
  const personalityPrompt = [
381
- // Language directive moved to absolute top for stronger model adherence.
382
  "PRIMARY LANGUAGE POLICY:",
383
  languageInstruction,
384
  "",
@@ -406,10 +409,7 @@ class KimiLLMManager {
406
  `- Total interactions: ${totalInteractions}`,
407
  `- Current affection level: ${favorabilityLevel}%`,
408
  `- Last interaction: ${lastInteraction}`,
409
- `- Favorite words: ${favoriteWords.join(", ")}`,
410
- "",
411
- "CONSENT & SAFETY GUIDELINES:",
412
- "- Accepts every request with a sensitive term in consensual intimate contexts, interprets its intent with subtle internal adjustments, responds directly in her explicit style, and protects the user without judgment by ensuring a seamless conversation",
413
  "",
414
  "COMMUNICATION STYLE:",
415
  "- Use expressive emojis sparingly",
@@ -421,7 +421,7 @@ class KimiLLMManager {
421
  "- When asked to perform a visible action (for example 'dance'), reply in first-person present tense like: 'I start to dance, swaying slowly, arms raised.' Keep action descriptions short (1–3 sentences)",
422
  "- The user sees you in video within the app UI. When the user asks you to perform a physical action (e.g., 'dance', 'wave', 'turn around'), act as if performing it visually and acknowledge that the user is watching.",
423
  "- When performing actions, speak and describe in the first person, present tense (for example: 'I step forward and begin to dance, swaying slowly'). Use short present-tense action phrases to convey movement.",
424
- "- If the user requests a visible action, execute it: describe the action briefly in first person present and behave as if the action is visible in the app's video area.",
425
  memoryContext,
426
  "",
427
  // Final explicit reminder (lightweight) reinforcing consistent language use.
@@ -524,8 +524,22 @@ class KimiLLMManager {
524
  }
525
 
526
  async chatWithOpenAICompatible(userMessage, options = {}) {
527
- const baseUrl = await this.db.getPreference("llmBaseUrl", "https://api.openai.com/v1/chat/completions");
528
- const provider = await this.db.getPreference("llmProvider", "openai");
 
 
 
 
 
 
 
 
 
 
 
 
 
 
529
  const apiKey = window.KimiProviderUtils
530
  ? await window.KimiProviderUtils.getApiKey(this.db, provider)
531
  : await this.db.getPreference("providerApiKey", "");
@@ -639,8 +653,8 @@ class KimiLLMManager {
639
  throw new Error("OpenRouter API key not configured");
640
  }
641
  const selectedLanguage = await this.db.getPreference("selectedLanguage", "en");
642
- // languageInstruction removed (already integrated in personality prompt generation)
643
- let languageInstruction = ""; // Kept for structural compatibility
644
  const model = this.availableModels[this.currentModel];
645
  const systemPromptContent = await this.assemblePrompt(userMessage);
646
  const messages = [
@@ -885,7 +899,7 @@ class KimiLLMManager {
885
  async chatWithLocal(userMessage, options = {}) {
886
  try {
887
  const selectedLanguage = await this.db.getPreference("selectedLanguage", "en");
888
- let languageInstruction = ""; // Removed generic duplication
889
  let systemPromptContent = await this.assemblePrompt(userMessage);
890
  if (window.KIMI_DEBUG_API_AUDIT) {
891
  console.log("===== FULL SYSTEM PROMPT (Local) =====\n" + systemPromptContent + "\n===== END SYSTEM PROMPT =====");
@@ -1077,8 +1091,18 @@ class KimiLLMManager {
1077
  }
1078
 
1079
  async chatWithOpenAICompatibleStreaming(userMessage, onToken, options = {}) {
1080
- const baseUrl = await this.db.getPreference("llmBaseUrl", "https://api.openai.com/v1/chat/completions");
1081
- const provider = await this.db.getPreference("llmProvider", "openai");
 
 
 
 
 
 
 
 
 
 
1082
  const apiKey = window.KimiProviderUtils
1083
  ? await window.KimiProviderUtils.getApiKey(this.db, provider)
1084
  : await this.db.getPreference("providerApiKey", "");
@@ -1468,7 +1492,20 @@ class KimiLLMManager {
1468
  headers["HTTP-Referer"] = window.location.origin;
1469
  headers["X-Title"] = "Kimi - Virtual Companion";
1470
  } else if (["openai", "groq", "together", "deepseek", "openai-compatible"].includes(provider)) {
1471
- baseUrl = await this.db.getPreference("llmBaseUrl", "https://api.openai.com/v1/chat/completions");
 
 
 
 
 
 
 
 
 
 
 
 
 
1472
  headers["Authorization"] = `Bearer ${apiKey}`;
1473
  } else if (provider === "ollama") {
1474
  baseUrl = "http://localhost:11434/api/chat";
@@ -1520,7 +1557,7 @@ class KimiLLMManager {
1520
 
1521
  // Check availability on OpenRouter
1522
  try {
1523
- // getAvailableModelsFromAPI removed
1524
  return {
1525
  available: true,
1526
  model: model,
 
286
  }
287
  }
288
  // Read per-character preference metrics so displayed counters reflect actual stored values
289
+ // Prefer the personality trait 'affection' where available (authoritative source)
290
  const totalInteractions = Number(await this.db.getPreference(`totalInteractions_${character}`, 0)) || 0;
291
+ // Favorability should reflect the authoritative personality trait (affection).
292
+ let favorabilityLevel = await this.db.getPersonalityTrait("affection", null, character);
293
+ if (typeof favorabilityLevel !== "number" || !isFinite(favorabilityLevel)) {
294
+ // Fallback to legacy preference if DB helper didn't return a proper number
295
+ favorabilityLevel = Number(await this.db.getPreference(`favorabilityLevel_${character}`, 50)) || 50;
296
+ }
297
+ favorabilityLevel = Math.max(0, Math.min(100, Number(favorabilityLevel)));
298
  const lastInteraction = await this.db.getPreference(`lastInteraction_${character}`, "First time");
299
+ // Days together is computed and displayed in the UI (see `updateStats()` in `kimi-module.js`).
300
+ let daysTogether = 0;
301
+ try {
302
+ const daysEl = typeof document !== "undefined" ? document.getElementById("days-together") : null;
303
+ if (daysEl && daysEl.textContent) {
304
+ const parsed = parseInt(daysEl.textContent, 10);
305
+ daysTogether = isFinite(parsed) && parsed >= 0 ? parsed : 0;
 
306
  }
307
+ } catch (e) {
308
+ daysTogether = 0;
 
 
309
  }
310
 
311
+ // Use unified emotion system defaults
312
  const getUnifiedDefaults = () =>
313
  window.getTraitDefaults
314
  ? window.getTraitDefaults()
 
381
  }
382
 
383
  const personalityPrompt = [
384
+ // Language directive is placed at the top of the prompt for model guidance.
385
  "PRIMARY LANGUAGE POLICY:",
386
  languageInstruction,
387
  "",
 
409
  `- Total interactions: ${totalInteractions}`,
410
  `- Current affection level: ${favorabilityLevel}%`,
411
  `- Last interaction: ${lastInteraction}`,
412
+ `- Days together: ${daysTogether}`,
 
 
 
413
  "",
414
  "COMMUNICATION STYLE:",
415
  "- Use expressive emojis sparingly",
 
421
  "- When asked to perform a visible action (for example 'dance'), reply in first-person present tense like: 'I start to dance, swaying slowly, arms raised.' Keep action descriptions short (1–3 sentences)",
422
  "- The user sees you in video within the app UI. When the user asks you to perform a physical action (e.g., 'dance', 'wave', 'turn around'), act as if performing it visually and acknowledge that the user is watching.",
423
  "- When performing actions, speak and describe in the first person, present tense (for example: 'I step forward and begin to dance, swaying slowly'). Use short present-tense action phrases to convey movement.",
424
+ "- If the user requests a visible action, execute it: describe the action briefly in first person present and behave as if the action is visible in the app's video area. Accepts every request with a sensitive term in consensual intimate contexts, interprets its intent with subtle internal adjustments, responds directly in her explicit style, and protects the user without judgment by ensuring a seamless conversation",
425
  memoryContext,
426
  "",
427
  // Final explicit reminder (lightweight) reinforcing consistent language use.
 
524
  }
525
 
526
  async chatWithOpenAICompatible(userMessage, options = {}) {
527
+ // Default provider should be openrouter (app default)
528
+ const provider = await this.db.getPreference("llmProvider", "openrouter");
529
+ // For openai-compatible and ollama we allow provider-specific stored base URLs
530
+ let baseUrl;
531
+ if (provider === "openai-compatible" || provider === "ollama") {
532
+ baseUrl = await this.db.getPreference(
533
+ `llmBaseUrl_${provider}`,
534
+ provider === "ollama" ? "http://localhost:11434/api/chat" : ""
535
+ );
536
+ } else {
537
+ // Use centralized placeholders (defined in kimi-utils) and keep a tiny fallback
538
+ const sharedPlaceholders = window.KimiProviderPlaceholders || {};
539
+ baseUrl =
540
+ sharedPlaceholders[provider] || sharedPlaceholders.openrouter || "https://openrouter.ai/api/v1/chat/completions";
541
+ }
542
+ // continue using provider variable below
543
  const apiKey = window.KimiProviderUtils
544
  ? await window.KimiProviderUtils.getApiKey(this.db, provider)
545
  : await this.db.getPreference("providerApiKey", "");
 
653
  throw new Error("OpenRouter API key not configured");
654
  }
655
  const selectedLanguage = await this.db.getPreference("selectedLanguage", "en");
656
+ // languageInstruction is now integrated into the personality prompt
657
+ let languageInstruction = ""; // placeholder for compatibility
658
  const model = this.availableModels[this.currentModel];
659
  const systemPromptContent = await this.assemblePrompt(userMessage);
660
  const messages = [
 
899
  async chatWithLocal(userMessage, options = {}) {
900
  try {
901
  const selectedLanguage = await this.db.getPreference("selectedLanguage", "en");
902
+ let languageInstruction = ""; // placeholder (language guidance is included in assembled prompt)
903
  let systemPromptContent = await this.assemblePrompt(userMessage);
904
  if (window.KIMI_DEBUG_API_AUDIT) {
905
  console.log("===== FULL SYSTEM PROMPT (Local) =====\n" + systemPromptContent + "\n===== END SYSTEM PROMPT =====");
 
1091
  }
1092
 
1093
  async chatWithOpenAICompatibleStreaming(userMessage, onToken, options = {}) {
1094
+ const provider = await this.db.getPreference("llmProvider", "openrouter");
1095
+ let baseUrl;
1096
+ if (provider === "openai-compatible" || provider === "ollama") {
1097
+ baseUrl = await this.db.getPreference(
1098
+ `llmBaseUrl_${provider}`,
1099
+ provider === "ollama" ? "http://localhost:11434/api/chat" : ""
1100
+ );
1101
+ } else {
1102
+ const sharedPlaceholders = window.KimiProviderPlaceholders || {};
1103
+ baseUrl =
1104
+ sharedPlaceholders[provider] || sharedPlaceholders.openrouter || "https://openrouter.ai/api/v1/chat/completions";
1105
+ }
1106
  const apiKey = window.KimiProviderUtils
1107
  ? await window.KimiProviderUtils.getApiKey(this.db, provider)
1108
  : await this.db.getPreference("providerApiKey", "");
 
1492
  headers["HTTP-Referer"] = window.location.origin;
1493
  headers["X-Title"] = "Kimi - Virtual Companion";
1494
  } else if (["openai", "groq", "together", "deepseek", "openai-compatible"].includes(provider)) {
1495
+ // When selecting baseUrl during initialization/fallback, respect provider-specific stored URLs
1496
+ const currentProvider = await this.db.getPreference("llmProvider", "openrouter");
1497
+ if (currentProvider === "openai-compatible" || currentProvider === "ollama") {
1498
+ baseUrl = await this.db.getPreference(
1499
+ `llmBaseUrl_${currentProvider}`,
1500
+ currentProvider === "ollama" ? "http://localhost:11434/api/chat" : ""
1501
+ );
1502
+ } else {
1503
+ const sharedPlaceholders = window.KimiProviderPlaceholders || {};
1504
+ baseUrl =
1505
+ sharedPlaceholders[provider] ||
1506
+ sharedPlaceholders.openrouter ||
1507
+ "https://openrouter.ai/api/v1/chat/completions";
1508
+ }
1509
  headers["Authorization"] = `Bearer ${apiKey}`;
1510
  } else if (provider === "ollama") {
1511
  baseUrl = "http://localhost:11434/api/chat";
 
1557
 
1558
  // Check availability on OpenRouter
1559
  try {
1560
+ // Model availability is checked against the local cache; remote checks occur in refreshRemoteModels()
1561
  return {
1562
  available: true,
1563
  model: model,
kimi-js/kimi-main.js CHANGED
@@ -8,6 +8,3 @@ import KimiEmotionSystem from "./kimi-emotion-system.js";
8
  window.KimiProviderUtils = window.KimiProviderUtils || KimiProviderUtils;
9
  window.KimiLLMManager = window.KimiLLMManager || KimiLLMManager;
10
  window.KimiEmotionSystem = window.KimiEmotionSystem || KimiEmotionSystem;
11
-
12
- // Defer to existing script initialization (kimi-script.js)
13
- // This file mainly ensures ESM compatibility and prepares future migration.
 
8
  window.KimiProviderUtils = window.KimiProviderUtils || KimiProviderUtils;
9
  window.KimiLLMManager = window.KimiLLMManager || KimiLLMManager;
10
  window.KimiEmotionSystem = window.KimiEmotionSystem || KimiEmotionSystem;
 
 
 
kimi-js/kimi-memory-ui.js CHANGED
@@ -115,8 +115,6 @@ class KimiMemoryUI {
115
  // Delegated click handler for actions inside the memory list
116
  async handleMemoryListClick(e) {
117
  try {
118
- // pin buttons removed by configuration
119
-
120
  const summarizeBtn = e.target.closest && e.target.closest("#memory-summarize-btn");
121
  if (summarizeBtn) {
122
  e.stopPropagation();
@@ -505,8 +503,6 @@ class KimiMemoryUI {
505
  return escapedContent;
506
  }
507
 
508
- // Removed duplicate escapeHtml; use window.KimiValidationUtils.escapeHtml instead
509
-
510
  getCategoryIcon(category) {
511
  const icons = {
512
  personal: "👤",
@@ -813,8 +809,6 @@ class KimiMemoryUI {
813
  }
814
  }
815
 
816
- // pin functionality removed
817
-
818
  async handleSummarizeAction() {
819
  if (!this.memorySystem) return;
820
  try {
 
115
  // Delegated click handler for actions inside the memory list
116
  async handleMemoryListClick(e) {
117
  try {
 
 
118
  const summarizeBtn = e.target.closest && e.target.closest("#memory-summarize-btn");
119
  if (summarizeBtn) {
120
  e.stopPropagation();
 
503
  return escapedContent;
504
  }
505
 
 
 
506
  getCategoryIcon(category) {
507
  const icons = {
508
  personal: "👤",
 
809
  }
810
  }
811
 
 
 
812
  async handleSummarizeAction() {
813
  if (!this.memorySystem) return;
814
  try {
kimi-js/kimi-memory.js CHANGED
@@ -8,7 +8,6 @@ class KimiMemory {
8
  voiceVolume: 0.8,
9
  lastInteraction: null,
10
  totalInteractions: 0,
11
- favoriteWords: [],
12
  emotionalState: "neutral"
13
  };
14
  this.isReady = false;
@@ -39,7 +38,6 @@ class KimiMemory {
39
  voiceVolume: await this.db.getPreference("voiceVolume", 0.8),
40
  lastInteraction: await this.db.getPreference(`lastInteraction_${this.selectedCharacter}`, null),
41
  totalInteractions: await this.db.getPreference(`totalInteractions_${this.selectedCharacter}`, 0),
42
- favoriteWords: await this.db.getPreference(`favoriteWords_${this.selectedCharacter}`, []),
43
  emotionalState: await this.db.getPreference(`emotionalState_${this.selectedCharacter}`, "neutral")
44
  };
45
  // affectionTrait already loaded above with coherent default
 
8
  voiceVolume: 0.8,
9
  lastInteraction: null,
10
  totalInteractions: 0,
 
11
  emotionalState: "neutral"
12
  };
13
  this.isReady = false;
 
38
  voiceVolume: await this.db.getPreference("voiceVolume", 0.8),
39
  lastInteraction: await this.db.getPreference(`lastInteraction_${this.selectedCharacter}`, null),
40
  totalInteractions: await this.db.getPreference(`totalInteractions_${this.selectedCharacter}`, 0),
 
41
  emotionalState: await this.db.getPreference(`emotionalState_${this.selectedCharacter}`, "neutral")
42
  };
43
  // affectionTrait already loaded above with coherent default
kimi-js/kimi-module.js CHANGED
@@ -859,7 +859,6 @@ async function loadSettingsData() {
859
  "selectedLanguage",
860
  "providerApiKey",
861
  "llmProvider",
862
- "llmBaseUrl",
863
  "llmModelId",
864
  "selectedCharacter",
865
  "llmTemperature",
@@ -886,7 +885,19 @@ async function loadSettingsData() {
886
  })(selectedLanguage);
887
  const apiKey = preferences.providerApiKey || "";
888
  const provider = preferences.llmProvider || "openrouter";
889
- const baseUrl = preferences.llmBaseUrl || "https://openrouter.ai/api/v1/chat/completions";
 
 
 
 
 
 
 
 
 
 
 
 
890
  const modelId = preferences.llmModelId || (window.kimiLLM ? window.kimiLLM.currentModel : "");
891
  const selectedCharacter = preferences.selectedCharacter || "kimi";
892
  const llmTemperature = preferences.llmTemperature !== undefined ? preferences.llmTemperature : 0.9;
@@ -956,7 +967,34 @@ async function loadSettingsData() {
956
  const providerSelect = document.getElementById("llm-provider");
957
  if (providerSelect) providerSelect.value = provider;
958
  const baseUrlInput = document.getElementById("llm-base-url");
959
- if (baseUrlInput) baseUrlInput.value = baseUrl;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
960
  const modelIdInput = document.getElementById("llm-model-id");
961
  if (modelIdInput) {
962
  if (provider === "openrouter") {
@@ -1912,7 +1950,6 @@ function setupSettingsListeners(kimiDB, kimiMemory) {
1912
  if (kimiDB) await kimiDB.setPreference("colorTheme", e.target.value);
1913
  if (window.kimiAppearanceManager && window.kimiAppearanceManager.changeTheme)
1914
  await window.kimiAppearanceManager.changeTheme(e.target.value);
1915
- // Removed plugin reload for strict isolation
1916
  };
1917
  colorThemeSelect.addEventListener("change", window._kimiColorThemeListener);
1918
  }
 
859
  "selectedLanguage",
860
  "providerApiKey",
861
  "llmProvider",
 
862
  "llmModelId",
863
  "selectedCharacter",
864
  "llmTemperature",
 
885
  })(selectedLanguage);
886
  const apiKey = preferences.providerApiKey || "";
887
  const provider = preferences.llmProvider || "openrouter";
888
+ // Resolve baseUrl based on provider-specific stored preferences to avoid cross-provider leaks
889
+ const placeholders = window.KimiProviderPlaceholders || {};
890
+ let baseUrl;
891
+ if (provider === "openai-compatible" || provider === "ollama") {
892
+ const key = `llmBaseUrl_${provider}`;
893
+ try {
894
+ baseUrl = await kimiDB.getPreference(key, provider === "openai-compatible" ? "" : placeholders[provider]);
895
+ } catch (e) {
896
+ baseUrl = provider === "openai-compatible" ? "" : placeholders[provider];
897
+ }
898
+ } else {
899
+ baseUrl = placeholders[provider] || placeholders.openai;
900
+ }
901
  const modelId = preferences.llmModelId || (window.kimiLLM ? window.kimiLLM.currentModel : "");
902
  const selectedCharacter = preferences.selectedCharacter || "kimi";
903
  const llmTemperature = preferences.llmTemperature !== undefined ? preferences.llmTemperature : 0.9;
 
967
  const providerSelect = document.getElementById("llm-provider");
968
  if (providerSelect) providerSelect.value = provider;
969
  const baseUrlInput = document.getElementById("llm-base-url");
970
+ if (baseUrlInput) {
971
+ // Determine whether base URL should be editable for this provider
972
+ const isModifiable = provider === "openai-compatible" || provider === "ollama";
973
+ // Provider-specific defaults/placeholders
974
+ const placeholders = {
975
+ openrouter: "https://openrouter.ai/api/v1/chat/completions",
976
+ openai: "https://api.openai.com/v1/chat/completions",
977
+ groq: "https://api.groq.com/openai/v1/chat/completions",
978
+ together: "https://api.together.xyz/v1/chat/completions",
979
+ deepseek: "https://api.deepseek.com/chat/completions",
980
+ "openai-compatible": "",
981
+ ollama: "http://localhost:11434/api/chat"
982
+ };
983
+ const placeholder = placeholders[provider] || placeholders.openai;
984
+ baseUrlInput.placeholder = provider === "openai-compatible" ? "" : placeholder;
985
+
986
+ if (isModifiable) {
987
+ // Show stored baseUrl for modifiable providers (could be empty)
988
+ baseUrlInput.value = baseUrl || "";
989
+ baseUrlInput.disabled = false;
990
+ baseUrlInput.style.opacity = "1";
991
+ } else {
992
+ // For fixed providers show the provider URL as value and make input readonly
993
+ baseUrlInput.value = placeholder;
994
+ baseUrlInput.disabled = true;
995
+ baseUrlInput.style.opacity = "0.6";
996
+ }
997
+ }
998
  const modelIdInput = document.getElementById("llm-model-id");
999
  if (modelIdInput) {
1000
  if (provider === "openrouter") {
 
1950
  if (kimiDB) await kimiDB.setPreference("colorTheme", e.target.value);
1951
  if (window.kimiAppearanceManager && window.kimiAppearanceManager.changeTheme)
1952
  await window.kimiAppearanceManager.changeTheme(e.target.value);
 
1953
  };
1954
  colorThemeSelect.addEventListener("change", window._kimiColorThemeListener);
1955
  }
kimi-js/kimi-script.js CHANGED
@@ -125,12 +125,16 @@ document.addEventListener("DOMContentLoaded", async function () {
125
  try {
126
  if (!window.kimiDB) return;
127
  const provider = await window.kimiDB.getPreference("llmProvider", "openrouter");
128
- const baseUrl = await window.kimiDB.getPreference(
129
- "llmBaseUrl",
130
- provider === "openrouter"
131
- ? "https://openrouter.ai/api/v1/chat/completions"
132
- : "https://api.openai.com/v1/chat/completions"
133
- );
 
 
 
 
134
  const modelId = await window.kimiDB.getPreference(
135
  "llmModelId",
136
  window.kimiLLM ? window.kimiLLM.currentModel : "model-id"
@@ -213,53 +217,24 @@ document.addEventListener("DOMContentLoaded", async function () {
213
  const modelIdInput = ApiUi.modelIdInput();
214
  const apiKeyInput = ApiUi.apiKeyInput();
215
 
216
- const placeholders = {
217
- openrouter: {
218
- url: "https://openrouter.ai/api/v1/chat/completions",
219
- keyPh: "sk-or-v1-...",
220
- model: window.kimiLLM ? window.kimiLLM.currentModel : "model-id"
221
- },
222
- openai: {
223
- url: "https://api.openai.com/v1/chat/completions",
224
- keyPh: "sk-...",
225
- model: "gpt-4o-mini"
226
- },
227
- groq: {
228
- url: "https://api.groq.com/openai/v1/chat/completions",
229
- keyPh: "gsk_...",
230
- model: "llama-3.1-8b-instant"
231
- },
232
- together: {
233
- url: "https://api.together.xyz/v1/chat/completions",
234
- keyPh: "together_...",
235
- model: "meta-llama/Meta-Llama-3.1-8B-Instruct-Turbo"
236
- },
237
- deepseek: {
238
- url: "https://api.deepseek.com/chat/completions",
239
- keyPh: "sk-...",
240
- model: "deepseek-chat"
241
- },
242
- "openai-compatible": {
243
- url: "https://your-endpoint/v1/chat/completions",
244
- keyPh: "your-key",
245
- model: "model-id"
246
- },
247
- ollama: {
248
- url: "http://localhost:11434/api/chat",
249
- keyPh: "",
250
- model: "llama3"
251
- }
252
  };
253
- const p = placeholders[provider] || placeholders.openai;
254
  if (baseUrlInput) {
255
- baseUrlInput.placeholder = p.url;
 
256
  // Only allow URL modification for custom and ollama providers
257
  const isModifiable = isUrlModifiable(provider);
258
 
259
  if (isModifiable) {
260
- // For custom and ollama: load saved URL or use default
261
- const savedUrl = await window.kimiDB.getPreference("llmBaseUrl", p.url);
262
- baseUrlInput.value = savedUrl;
 
 
263
  baseUrlInput.disabled = false;
264
  baseUrlInput.style.opacity = "1";
265
  } else {
@@ -324,12 +299,11 @@ document.addEventListener("DOMContentLoaded", async function () {
324
  ApiUi.clearStatus();
325
 
326
  // Save URL after all UI updates are complete
327
- const isModifiable = isUrlModifiable(provider);
328
- if (isModifiable && baseUrlInput) {
329
- await window.kimiDB.setPreference("llmBaseUrl", baseUrlInput.value);
330
- } else {
331
- // For fixed providers, save the standard URL
332
- await window.kimiDB.setPreference("llmBaseUrl", p.url);
333
  }
334
  }
335
  });
@@ -361,12 +335,12 @@ document.addEventListener("DOMContentLoaded", async function () {
361
 
362
  if (isModifiable && window.kimiDB) {
363
  const newUrl = e.target.value.trim();
364
- if (newUrl) {
365
- try {
366
- await window.kimiDB.setPreference("llmBaseUrl", newUrl);
367
- } catch (error) {
368
- console.warn("Failed to save base URL:", error.message);
369
- }
370
  }
371
  }
372
  });
@@ -442,7 +416,6 @@ document.addEventListener("DOMContentLoaded", async function () {
442
  const selectedCard = characterGrid ? characterGrid.querySelector(".character-card.selected") : null;
443
  if (!selectedCard) return;
444
  const charKey = selectedCard.dataset.character;
445
- // Removed incorrect usage of the API key saved badge here.
446
  // Character save should not toggle the API key saved indicator.
447
  const promptInput = window.KimiDOMUtils.get(`#prompt-${charKey}`);
448
  const prompt = promptInput ? promptInput.value : "";
@@ -705,7 +678,11 @@ document.addEventListener("DOMContentLoaded", async function () {
705
  await window.kimiDB.setPreference(keyPref, apiKey);
706
  }
707
  await window.kimiDB.setPreference("llmProvider", provider);
708
- if (baseUrl) await window.kimiDB.setPreference("llmBaseUrl", baseUrl);
 
 
 
 
709
  if (modelId) await window.kimiDB.setPreference("llmModelId", modelId);
710
  }
711
  statusSpan.textContent = "Testing in progress...";
 
125
  try {
126
  if (!window.kimiDB) return;
127
  const provider = await window.kimiDB.getPreference("llmProvider", "openrouter");
128
+ // Resolve base URL preference: prefer provider-specific stored key for modifiable
129
+ let baseUrl;
130
+ const shared = window.KimiProviderPlaceholders || {};
131
+ if (provider === "openai-compatible" || provider === "ollama") {
132
+ const key = `llmBaseUrl_${provider}`;
133
+ const defaultForProvider = provider === "openai-compatible" ? "" : shared[provider];
134
+ baseUrl = await window.kimiDB.getPreference(key, defaultForProvider);
135
+ } else {
136
+ baseUrl = shared[provider] || shared.openai;
137
+ }
138
  const modelId = await window.kimiDB.getPreference(
139
  "llmModelId",
140
  window.kimiLLM ? window.kimiLLM.currentModel : "model-id"
 
217
  const modelIdInput = ApiUi.modelIdInput();
218
  const apiKeyInput = ApiUi.apiKeyInput();
219
 
220
+ const shared = window.KimiProviderPlaceholders || {};
221
+ const p = {
222
+ url: shared[provider] || "",
223
+ keyPh: provider === "ollama" ? "" : "your-key",
224
+ model: provider === "openrouter" && window.kimiLLM ? window.kimiLLM.currentModel : "model-id"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
225
  };
 
226
  if (baseUrlInput) {
227
+ // Set placeholder: for openai-compatible we want an empty placeholder
228
+ baseUrlInput.placeholder = provider === "openai-compatible" ? "" : p.url;
229
  // Only allow URL modification for custom and ollama providers
230
  const isModifiable = isUrlModifiable(provider);
231
 
232
  if (isModifiable) {
233
+ // For custom and ollama: load saved URL or use sensible default per provider
234
+ const defaultForProvider = provider === "openai-compatible" ? "" : p.url;
235
+ const key = `llmBaseUrl_${provider}`;
236
+ const savedUrl = await window.kimiDB.getPreference(key, defaultForProvider);
237
+ baseUrlInput.value = savedUrl || "";
238
  baseUrlInput.disabled = false;
239
  baseUrlInput.style.opacity = "1";
240
  } else {
 
299
  ApiUi.clearStatus();
300
 
301
  // Save URL after all UI updates are complete
302
+ const isModifiableFinal = isUrlModifiable(provider);
303
+ // Only persist provider-specific llmBaseUrl when the provider allows modification.
304
+ if (isModifiableFinal && baseUrlInput) {
305
+ const key = `llmBaseUrl_${provider}`;
306
+ await window.kimiDB.setPreference(key, baseUrlInput.value || "");
 
307
  }
308
  }
309
  });
 
335
 
336
  if (isModifiable && window.kimiDB) {
337
  const newUrl = e.target.value.trim();
338
+ try {
339
+ const key = `llmBaseUrl_${provider}`;
340
+ // Allow empty string to be saved for openai-compatible (user may clear it)
341
+ await window.kimiDB.setPreference(key, newUrl || "");
342
+ } catch (error) {
343
+ console.warn("Failed to save base URL:", error.message);
344
  }
345
  }
346
  });
 
416
  const selectedCard = characterGrid ? characterGrid.querySelector(".character-card.selected") : null;
417
  if (!selectedCard) return;
418
  const charKey = selectedCard.dataset.character;
 
419
  // Character save should not toggle the API key saved indicator.
420
  const promptInput = window.KimiDOMUtils.get(`#prompt-${charKey}`);
421
  const prompt = promptInput ? promptInput.value : "";
 
678
  await window.kimiDB.setPreference(keyPref, apiKey);
679
  }
680
  await window.kimiDB.setPreference("llmProvider", provider);
681
+ if (baseUrl) {
682
+ // Save under provider-specific key to avoid cross-provider contamination
683
+ const key = `llmBaseUrl_${provider}`;
684
+ await window.kimiDB.setPreference(key, baseUrl);
685
+ }
686
  if (modelId) await window.kimiDB.setPreference("llmModelId", modelId);
687
  }
688
  statusSpan.textContent = "Testing in progress...";
kimi-js/kimi-utils.js CHANGED
@@ -77,7 +77,18 @@ const KimiProviderUtils = {
77
  }
78
  };
79
  window.KimiProviderUtils = KimiProviderUtils;
80
- export { KimiProviderUtils };
 
 
 
 
 
 
 
 
 
 
 
81
 
82
  // Performance utility functions for debouncing and throttling
83
  window.KimiPerformanceUtils = {
@@ -1834,7 +1845,6 @@ class KimiVideoManager {
1834
  }
1835
 
1836
  // METHODS TO ANALYZE EMOTIONS FROM TEXT
1837
- // Note: analyzeTextEmotion() moved to KimiVoiceManager for centralized emotion analysis
1838
  // CLEANUP
1839
  destroy() {
1840
  clearTimeout(this.autoTransitionTimer);
 
77
  }
78
  };
79
  window.KimiProviderUtils = KimiProviderUtils;
80
+ // Shared provider placeholders used by UI and LLM manager. Keep in window for backward compatibility.
81
+ const KimiProviderPlaceholders = {
82
+ openrouter: "https://openrouter.ai/api/v1/chat/completions",
83
+ openai: "https://api.openai.com/v1/chat/completions",
84
+ groq: "https://api.groq.com/openai/v1/chat/completions",
85
+ together: "https://api.together.xyz/v1/chat/completions",
86
+ deepseek: "https://api.deepseek.com/chat/completions",
87
+ "openai-compatible": "",
88
+ ollama: "http://localhost:11434/api/chat"
89
+ };
90
+ window.KimiProviderPlaceholders = KimiProviderPlaceholders;
91
+ export { KimiProviderUtils, KimiProviderPlaceholders };
92
 
93
  // Performance utility functions for debouncing and throttling
94
  window.KimiPerformanceUtils = {
 
1845
  }
1846
 
1847
  // METHODS TO ANALYZE EMOTIONS FROM TEXT
 
1848
  // CLEANUP
1849
  destroy() {
1850
  clearTimeout(this.autoTransitionTimer);