Spaces:
Running
Running
Upload 29 files
Browse files- index.html +1 -0
- kimi-js/kimi-appearance.js +4 -6
- kimi-js/kimi-database.js +15 -15
- kimi-js/kimi-emotion-system.js +6 -6
- kimi-js/kimi-llm-manager.js +67 -30
- kimi-js/kimi-main.js +0 -3
- kimi-js/kimi-memory-ui.js +0 -6
- kimi-js/kimi-memory.js +0 -2
- kimi-js/kimi-module.js +41 -4
- kimi-js/kimi-script.js +38 -61
- kimi-js/kimi-utils.js +12 -2
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 |
-
//
|
| 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 |
-
//
|
| 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
|
| 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
|
| 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 |
-
//
|
| 68 |
}
|
| 69 |
});
|
| 70 |
|
|
@@ -90,7 +90,7 @@ class KimiDatabase {
|
|
| 90 |
if (!rec.lastAccess) rec.lastAccess = rec.timestamp || now;
|
| 91 |
});
|
| 92 |
} catch (e) {
|
| 93 |
-
//
|
| 94 |
}
|
| 95 |
});
|
| 96 |
}
|
|
@@ -283,7 +283,7 @@ class KimiDatabase {
|
|
| 283 |
}
|
| 284 |
}
|
| 285 |
|
| 286 |
-
//
|
| 287 |
const convCount = await this.db.conversations.count();
|
| 288 |
if (convCount === 0) {
|
| 289 |
}
|
|
@@ -369,8 +369,8 @@ class KimiDatabase {
|
|
| 369 |
}
|
| 370 |
}
|
| 371 |
|
| 372 |
-
//
|
| 373 |
-
// This
|
| 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 |
-
//
|
| 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 |
-
//
|
| 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 |
-
//
|
| 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 |
-
//
|
| 438 |
-
// WARNING: This is destructive
|
| 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:
|
| 583 |
let value = record.value;
|
| 584 |
if (record.encrypted && window.KimiSecurityUtils) {
|
| 585 |
try {
|
| 586 |
-
|
| 587 |
-
//
|
| 588 |
try {
|
| 589 |
await this.db.preferences.put({ key: key, value, updated: new Date().toISOString() });
|
| 590 |
} catch (mErr) {}
|
| 591 |
} catch (e) {
|
| 592 |
-
// If
|
| 593 |
-
console.warn("Failed to
|
| 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))); //
|
| 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))); //
|
| 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)));
|
| 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:
|
|
|
|
| 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 |
-
//
|
| 290 |
const totalInteractions = Number(await this.db.getPreference(`totalInteractions_${character}`, 0)) || 0;
|
| 291 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 292 |
const lastInteraction = await this.db.getPreference(`lastInteraction_${character}`, "First time");
|
| 293 |
-
//
|
| 294 |
-
let
|
| 295 |
-
|
| 296 |
-
|
| 297 |
-
|
| 298 |
-
|
| 299 |
-
|
| 300 |
-
favoriteWords = favoriteWordsPref.length ? favoriteWordsPref.split(/,\s*/) : [];
|
| 301 |
}
|
| 302 |
-
}
|
| 303 |
-
|
| 304 |
-
} else {
|
| 305 |
-
favoriteWords = [];
|
| 306 |
}
|
| 307 |
|
| 308 |
-
// Use unified emotion system defaults
|
| 309 |
const getUnifiedDefaults = () =>
|
| 310 |
window.getTraitDefaults
|
| 311 |
? window.getTraitDefaults()
|
|
@@ -378,7 +381,7 @@ class KimiLLMManager {
|
|
| 378 |
}
|
| 379 |
|
| 380 |
const personalityPrompt = [
|
| 381 |
-
// Language directive
|
| 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 |
-
`-
|
| 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 |
-
|
| 528 |
-
const provider = await this.db.getPreference("llmProvider", "
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
|
| 643 |
-
let languageInstruction = ""; //
|
| 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 = ""; //
|
| 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
|
| 1081 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
//
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 129 |
-
|
| 130 |
-
|
| 131 |
-
|
| 132 |
-
|
| 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
|
| 217 |
-
|
| 218 |
-
|
| 219 |
-
|
| 220 |
-
|
| 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 |
-
|
|
|
|
| 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
|
| 262 |
-
|
|
|
|
|
|
|
| 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
|
| 328 |
-
|
| 329 |
-
|
| 330 |
-
|
| 331 |
-
|
| 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 |
-
|
| 365 |
-
|
| 366 |
-
|
| 367 |
-
|
| 368 |
-
|
| 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)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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);
|