VirtualKimi commited on
Commit
28be518
·
verified ·
1 Parent(s): a9d1590

Upload 22 files

Browse files
CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
  # Virtual Kimi Changelog
2
 
 
 
 
 
 
 
 
 
 
 
 
 
3
  # [1.1.2] - 2025-08-30
4
 
5
  ### Improvements
 
1
  # Virtual Kimi Changelog
2
 
3
+ # [1.1.3] - 2025-08-30
4
+
5
+ ### Improvements
6
+
7
+ - Refined personality trait handling and synchronization per character for more consistent and predictable behavior across modules (emotion, video, voice, memory).
8
+
9
+ ### Fixed / Improved
10
+
11
+ - Improved conversation deletion logic: deleting chat now only affects the selected character and prevents stale asynchronous saves from re-adding removed messages.
12
+ - Added a lightweight BroadcastChannel announcement so clears propagate to other open tabs, avoiding cross-tab resurrection of deleted conversations.
13
+ - Centralized conversation clearing logic in the database layer to ensure consistent behavior (use `clearConversations()` across imports and operations).
14
+
15
  # [1.1.2] - 2025-08-30
16
 
17
  ### Improvements
index.html CHANGED
@@ -57,7 +57,7 @@
57
  },
58
  "dateCreated": "2025-07-16",
59
  "dateModified": "2025-08-30",
60
- "version": "v1.1.2"
61
  }
62
  </script>
63
 
@@ -1072,7 +1072,7 @@
1072
  <h3><i class="fas fa-code"></i> Technical Information</h3>
1073
  <div class="tech-info">
1074
  <p><strong>Created date :</strong> July 16, 2025</p>
1075
- <p><strong>Version :</strong> v1.1.2</p>
1076
  <p><strong>Last update :</strong> August 30, 2025</p>
1077
  <p><strong>Technologies :</strong> HTML5, CSS3, JavaScript ES6+, IndexedDB, Web Speech
1078
  API</p>
@@ -1127,7 +1127,7 @@
1127
  "name": "Jean & Kimi"
1128
  },
1129
  "dateCreated": "2025-07-16",
1130
- "version": "v1.1.2"
1131
  }
1132
  }
1133
  </script>
 
57
  },
58
  "dateCreated": "2025-07-16",
59
  "dateModified": "2025-08-30",
60
+ "version": "v1.1.3"
61
  }
62
  </script>
63
 
 
1072
  <h3><i class="fas fa-code"></i> Technical Information</h3>
1073
  <div class="tech-info">
1074
  <p><strong>Created date :</strong> July 16, 2025</p>
1075
+ <p><strong>Version :</strong> v1.1.3</p>
1076
  <p><strong>Last update :</strong> August 30, 2025</p>
1077
  <p><strong>Technologies :</strong> HTML5, CSS3, JavaScript ES6+, IndexedDB, Web Speech
1078
  API</p>
 
1127
  "name": "Jean & Kimi"
1128
  },
1129
  "dateCreated": "2025-07-16",
1130
+ "version": "v1.1.3"
1131
  }
1132
  }
1133
  </script>
kimi-js/kimi-constants.js CHANGED
@@ -344,11 +344,36 @@ window.KIMI_PERSONALITY_KEYWORDS = {
344
  negative: ["stupid", "dumb", "foolish", "slow", "naive", "ignorant", "simple"]
345
  },
346
  romance: {
347
- positive: ["cuddle", "love", "romantic", "kiss", "tenderness", "passion", "charming", "adorable", "sweet"],
 
 
 
 
 
 
 
 
 
 
 
 
348
  negative: ["cold", "distant", "indifferent", "rejection", "loneliness", "breakup", "sad"]
349
  },
350
  affection: {
351
- positive: ["affection", "tenderness", "close", "warmth", "kind", "caring", "cuddle", "love", "adore", "lovely"],
 
 
 
 
 
 
 
 
 
 
 
 
 
352
  negative: [
353
  "mean",
354
  "cold",
@@ -410,7 +435,21 @@ window.KIMI_PERSONALITY_KEYWORDS = {
410
  ]
411
  },
412
  romance: {
413
- positive: ["câlin", "amour", "romantique", "bisou", "tendresse", "passion", "séduisant", "charmant", "adorable"],
 
 
 
 
 
 
 
 
 
 
 
 
 
 
414
  negative: [
415
  "froid",
416
  "froide",
@@ -434,6 +473,11 @@ window.KIMI_PERSONALITY_KEYWORDS = {
434
  "attentionné",
435
  "câlin",
436
  "aimer",
 
 
 
 
 
437
  "adorer",
438
  "adorable"
439
  ],
@@ -521,11 +565,35 @@ window.KIMI_PERSONALITY_KEYWORDS = {
521
  ]
522
  },
523
  romance: {
524
- positive: ["abrazo", "amor", "romántico", "beso", "ternura", "pasión", "encantador", "adorable", "dulce"],
 
 
 
 
 
 
 
 
 
 
 
 
525
  negative: ["frío", "fría", "distante", "indiferente", "rechazo", "soledad", "ruptura", "triste"]
526
  },
527
  affection: {
528
- positive: ["afecto", "ternura", "cerca", "calidez", "amable", "cariño", "abrazar", "amor", "adorar"],
 
 
 
 
 
 
 
 
 
 
 
 
529
  negative: [
530
  "malo",
531
  "mala",
@@ -595,7 +663,8 @@ window.KIMI_PERSONALITY_KEYWORDS = {
595
  "leidenschaft",
596
  "charmant",
597
  "liebenswert",
598
- "süß"
 
599
  ],
600
  negative: [
601
  "kalt",
@@ -612,7 +681,18 @@ window.KIMI_PERSONALITY_KEYWORDS = {
612
  ]
613
  },
614
  affection: {
615
- positive: ["zuneigung", "zärtlichkeit", "nah", "wärme", "freundlich", "fürsorglich", "umarmen", "liebe", "anbeten"],
 
 
 
 
 
 
 
 
 
 
 
616
  negative: [
617
  "gemein",
618
  "gemeine",
@@ -674,11 +754,33 @@ window.KIMI_PERSONALITY_KEYWORDS = {
674
  negative: ["stupido", "stupida", "sciocco", "sciocca", "lento", "lenta", "ingenuo", "ingenua", "ignorante"]
675
  },
676
  romance: {
677
- positive: ["abbraccio", "amore", "romantico", "bacio", "tenerezza", "passione", "affascinante", "adorabile", "dolce"],
 
 
 
 
 
 
 
 
 
 
 
678
  negative: ["freddo", "fredda", "distante", "indifferente", "rifiuto", "solitudine", "rottura", "triste"]
679
  },
680
  affection: {
681
- positive: ["affetto", "tenerezza", "vicino", "calore", "gentile", "premuroso", "abbraccio", "amore", "adorare"],
 
 
 
 
 
 
 
 
 
 
 
682
  negative: [
683
  "cattivo",
684
  "cattiva",
@@ -722,7 +824,7 @@ window.KIMI_PERSONALITY_KEYWORDS = {
722
  laughing: ["はは", "笑", "笑う", "面白い", "愉快"],
723
  shy: ["恥ずかしい", "照れる", "赤面", "内気", "遠慮"],
724
  confident: ["自信", "誇り", "確信", "強い", "決意"],
725
- romantic: ["愛", "ロマンチック", "優しい", "抱擁", "キス", "愛しい"],
726
  flirtatious: ["いちゃつく", "からかう", "誘惑", "魅力", "フリート"],
727
  goodbye: ["さようなら", "バイバイ", "また今度", "チャオ", "またね"],
728
  kiss: ["キス", "抱擁", "チュー"],
@@ -734,7 +836,7 @@ window.KIMI_PERSONALITY_KEYWORDS = {
734
  laughing: ["哈哈", "笑", "大笑", "有趣", "搞笑"],
735
  shy: ["害羞", "尴尬", "脸红", "羞涩", "胆怯"],
736
  confident: ["自信", "骄傲", "确信", "强壮", "坚定"],
737
- romantic: ["爱", "浪漫", "温柔", "拥抱", "吻", "亲爱的"],
738
  flirtatious: ["调情", "挑逗", "诱惑", "魅力", "撒娇"],
739
  goodbye: ["再见", "拜拜", "回头见", "拜", "下次见"],
740
  kiss: ["吻", "亲吻", "拥抱", "亲"],
@@ -808,6 +910,9 @@ window.KIMI_NEGATORS = window.KIMI_NEGATORS || {
808
  window.KIMI_NEGATION_WINDOW = window.KIMI_NEGATION_WINDOW || 3; // tokens to look back for negation
809
  window.KIMI_SMOOTHING_ALPHA = window.KIMI_SMOOTHING_ALPHA || 0.3;
810
  window.KIMI_PERSIST_THRESHOLD = window.KIMI_PERSIST_THRESHOLD || 0.1; // absolute percent (slightly higher to slow small visible jumps)
 
 
 
811
 
812
  // Memory system knobs
813
  window.KIMI_MAX_MEMORIES = window.KIMI_MAX_MEMORIES || 100; // default max memory entries per character
 
344
  negative: ["stupid", "dumb", "foolish", "slow", "naive", "ignorant", "simple"]
345
  },
346
  romance: {
347
+ positive: [
348
+ "cuddle",
349
+ "love",
350
+ "romantic",
351
+ "kiss",
352
+ "tenderness",
353
+ "passion",
354
+ "charming",
355
+ "adorable",
356
+ "sweet",
357
+ "i love you",
358
+ "love you"
359
+ ],
360
  negative: ["cold", "distant", "indifferent", "rejection", "loneliness", "breakup", "sad"]
361
  },
362
  affection: {
363
+ positive: [
364
+ "affection",
365
+ "tenderness",
366
+ "close",
367
+ "warmth",
368
+ "kind",
369
+ "caring",
370
+ "cuddle",
371
+ "love",
372
+ "adore",
373
+ "lovely",
374
+ "i love you",
375
+ "love you"
376
+ ],
377
  negative: [
378
  "mean",
379
  "cold",
 
435
  ]
436
  },
437
  romance: {
438
+ positive: [
439
+ "câlin",
440
+ "amour",
441
+ "romantique",
442
+ "bisou",
443
+ "tendresse",
444
+ "passion",
445
+ "séduisant",
446
+ "charmant",
447
+ "adorable",
448
+ "je t'aime",
449
+ "je taime",
450
+ "t'aime",
451
+ "taime"
452
+ ],
453
  negative: [
454
  "froid",
455
  "froide",
 
473
  "attentionné",
474
  "câlin",
475
  "aimer",
476
+ "aime",
477
+ "je t'aime",
478
+ "je taime",
479
+ "t'aime",
480
+ "taime",
481
  "adorer",
482
  "adorable"
483
  ],
 
565
  ]
566
  },
567
  romance: {
568
+ positive: [
569
+ "abrazo",
570
+ "amor",
571
+ "romántico",
572
+ "beso",
573
+ "ternura",
574
+ "pasión",
575
+ "encantador",
576
+ "adorable",
577
+ "dulce",
578
+ "te quiero",
579
+ "te amo"
580
+ ],
581
  negative: ["frío", "fría", "distante", "indiferente", "rechazo", "soledad", "ruptura", "triste"]
582
  },
583
  affection: {
584
+ positive: [
585
+ "afecto",
586
+ "ternura",
587
+ "cerca",
588
+ "calidez",
589
+ "amable",
590
+ "cariño",
591
+ "abrazar",
592
+ "amor",
593
+ "adorar",
594
+ "te quiero",
595
+ "te amo"
596
+ ],
597
  negative: [
598
  "malo",
599
  "mala",
 
663
  "leidenschaft",
664
  "charmant",
665
  "liebenswert",
666
+ "süß",
667
+ "ich liebe dich"
668
  ],
669
  negative: [
670
  "kalt",
 
681
  ]
682
  },
683
  affection: {
684
+ positive: [
685
+ "zuneigung",
686
+ "zärtlichkeit",
687
+ "nah",
688
+ "wärme",
689
+ "freundlich",
690
+ "fürsorglich",
691
+ "umarmen",
692
+ "liebe",
693
+ "anbeten",
694
+ "ich liebe dich"
695
+ ],
696
  negative: [
697
  "gemein",
698
  "gemeine",
 
754
  negative: ["stupido", "stupida", "sciocco", "sciocca", "lento", "lenta", "ingenuo", "ingenua", "ignorante"]
755
  },
756
  romance: {
757
+ positive: [
758
+ "abbraccio",
759
+ "amore",
760
+ "romantico",
761
+ "bacio",
762
+ "tenerezza",
763
+ "passione",
764
+ "affascinante",
765
+ "adorabile",
766
+ "dolce",
767
+ "ti amo"
768
+ ],
769
  negative: ["freddo", "fredda", "distante", "indifferente", "rifiuto", "solitudine", "rottura", "triste"]
770
  },
771
  affection: {
772
+ positive: [
773
+ "affetto",
774
+ "tenerezza",
775
+ "vicino",
776
+ "calore",
777
+ "gentile",
778
+ "premuroso",
779
+ "abbraccio",
780
+ "amore",
781
+ "adorare",
782
+ "ti amo"
783
+ ],
784
  negative: [
785
  "cattivo",
786
  "cattiva",
 
824
  laughing: ["はは", "笑", "笑う", "面白い", "愉快"],
825
  shy: ["恥ずかしい", "照れる", "赤面", "内気", "遠慮"],
826
  confident: ["自信", "誇り", "確信", "強い", "決意"],
827
+ romantic: ["愛", "ロマンチック", "優しい", "抱擁", "キス", "愛しい", "愛してる", "好き"],
828
  flirtatious: ["いちゃつく", "からかう", "誘惑", "魅力", "フリート"],
829
  goodbye: ["さようなら", "バイバイ", "また今度", "チャオ", "またね"],
830
  kiss: ["キス", "抱擁", "チュー"],
 
836
  laughing: ["哈哈", "笑", "大笑", "有趣", "搞笑"],
837
  shy: ["害羞", "尴尬", "脸红", "羞涩", "胆怯"],
838
  confident: ["自信", "骄傲", "确信", "强壮", "坚定"],
839
+ romantic: ["爱", "浪漫", "温柔", "拥抱", "吻", "亲爱的", "我爱你"],
840
  flirtatious: ["调情", "挑逗", "诱惑", "魅力", "撒娇"],
841
  goodbye: ["再见", "拜拜", "回头见", "拜", "下次见"],
842
  kiss: ["吻", "亲吻", "拥抱", "亲"],
 
910
  window.KIMI_NEGATION_WINDOW = window.KIMI_NEGATION_WINDOW || 3; // tokens to look back for negation
911
  window.KIMI_SMOOTHING_ALPHA = window.KIMI_SMOOTHING_ALPHA || 0.3;
912
  window.KIMI_PERSIST_THRESHOLD = window.KIMI_PERSIST_THRESHOLD || 0.1; // absolute percent (slightly higher to slow small visible jumps)
913
+ // Weight applied to counts found in the LLM/Kimi response when updating personality traits.
914
+ // You can override at runtime by setting window.KIMI_LLM_RESPONSE_WEIGHT (e.g. 0.2)
915
+ window.KIMI_LLM_RESPONSE_WEIGHT = window.KIMI_LLM_RESPONSE_WEIGHT || 0.2;
916
 
917
  // Memory system knobs
918
  window.KIMI_MAX_MEMORIES = window.KIMI_MAX_MEMORIES || 100; // default max memory entries per character
kimi-js/kimi-database.js CHANGED
@@ -98,7 +98,7 @@ class KimiDatabase {
98
  async setConversationsBatch(conversationsArray) {
99
  if (!Array.isArray(conversationsArray)) return;
100
  try {
101
- await this.db.conversations.clear();
102
  if (conversationsArray.length) {
103
  await this.db.conversations.bulkPut(conversationsArray);
104
  }
@@ -107,6 +107,18 @@ class KimiDatabase {
107
  }
108
  }
109
 
 
 
 
 
 
 
 
 
 
 
 
 
110
  async setLLMModelsBatch(modelsArray) {
111
  if (!Array.isArray(modelsArray)) return;
112
  try {
@@ -465,6 +477,24 @@ class KimiDatabase {
465
 
466
  async saveConversation(userText, kimiResponse, favorability, timestamp = new Date(), character = null) {
467
  if (!character) character = await this.getSelectedCharacter();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
468
  const conversation = {
469
  user: userText,
470
  kimi: kimiResponse,
 
98
  async setConversationsBatch(conversationsArray) {
99
  if (!Array.isArray(conversationsArray)) return;
100
  try {
101
+ await this.clearConversations(null);
102
  if (conversationsArray.length) {
103
  await this.db.conversations.bulkPut(conversationsArray);
104
  }
 
107
  }
108
  }
109
 
110
+ // Clear conversations helper - centralizes clearing logic
111
+ async clearConversations(character = null) {
112
+ if (character) {
113
+ // Delete only conversations for a given character
114
+ const all = await this.db.conversations.where("character").equals(character).toArray();
115
+ const ids = all.map(item => item.id);
116
+ if (ids.length) return this.db.conversations.bulkDelete(ids);
117
+ return Promise.resolve();
118
+ }
119
+ return this.db.conversations.clear();
120
+ }
121
+
122
  async setLLMModelsBatch(modelsArray) {
123
  if (!Array.isArray(modelsArray)) return;
124
  try {
 
477
 
478
  async saveConversation(userText, kimiResponse, favorability, timestamp = new Date(), character = null) {
479
  if (!character) character = await this.getSelectedCharacter();
480
+ // If a global cleared timestamp exists and this conversation is older or equal, skip saving
481
+ try {
482
+ const clearedMap = window.kimiConversationsClearedAt;
483
+ if (clearedMap && typeof clearedMap === "object") {
484
+ const clearedAtForChar = clearedMap[character];
485
+ if (clearedAtForChar) {
486
+ const clearedTime = new Date(clearedAtForChar).getTime();
487
+ const convTime = new Date(timestamp).getTime();
488
+ if (!isNaN(clearedTime) && convTime <= clearedTime) {
489
+ // Skip saving to avoid resurrecting cleared messages for this character
490
+ return null;
491
+ }
492
+ }
493
+ }
494
+ } catch (e) {
495
+ // ignore parsing errors and fall through to save
496
+ }
497
+
498
  const conversation = {
499
  user: userText,
500
  kimi: kimiResponse,
kimi-js/kimi-emotion-system.js CHANGED
@@ -357,13 +357,14 @@ class KimiEmotionSystem {
357
  let posCount = 0;
358
  let negCount = 0;
359
 
 
360
  for (const w of posWords) {
361
  posCount += this.countTokenMatches(lowerUser, String(w)) * 1.0;
362
- posCount += this.countTokenMatches(lowerKimi, String(w)) * 0.5;
363
  }
364
  for (const w of negWords) {
365
  negCount += this.countTokenMatches(lowerUser, String(w)) * 1.0;
366
- negCount += this.countTokenMatches(lowerKimi, String(w)) * 0.5;
367
  }
368
 
369
  const delta = (posCount - negCount) * 0.8; // softened multiplier to 0.8 for gentler progression
 
357
  let posCount = 0;
358
  let negCount = 0;
359
 
360
+ const llmWeight = Number(window.KIMI_LLM_RESPONSE_WEIGHT) || 0.5;
361
  for (const w of posWords) {
362
  posCount += this.countTokenMatches(lowerUser, String(w)) * 1.0;
363
+ posCount += this.countTokenMatches(lowerKimi, String(w)) * llmWeight;
364
  }
365
  for (const w of negWords) {
366
  negCount += this.countTokenMatches(lowerUser, String(w)) * 1.0;
367
+ negCount += this.countTokenMatches(lowerKimi, String(w)) * llmWeight;
368
  }
369
 
370
  const delta = (posCount - negCount) * 0.8; // softened multiplier to 0.8 for gentler progression
kimi-js/kimi-module.js CHANGED
@@ -41,7 +41,7 @@ class KimiDataManager extends KimiBaseManager {
41
 
42
  try {
43
  // Clear all conversations directly
44
- await this.db.db.conversations.clear();
45
 
46
  // Clear chat UI
47
  const chatMessages = document.getElementById("chat-messages");
@@ -822,6 +822,23 @@ async function loadChatHistory() {
822
  chatMessages.removeChild(chatMessages.firstChild);
823
  }
824
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
825
  if (kimiDB) {
826
  try {
827
  const recent = await kimiDB.getRecentConversations(10);
@@ -1545,6 +1562,37 @@ async function sendMessage() {
1545
  message = validation.sanitized || message.trim();
1546
  if (!message) return;
1547
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1548
  addMessageToChat("user", message);
1549
  chatInput.value = "";
1550
  if (waitingIndicator) waitingIndicator.style.display = "inline-block";
 
41
 
42
  try {
43
  // Clear all conversations directly
44
+ await this.db.clearConversations();
45
 
46
  // Clear chat UI
47
  const chatMessages = document.getElementById("chat-messages");
 
822
  chatMessages.removeChild(chatMessages.firstChild);
823
  }
824
 
825
+ // Ensure i18n manager has loaded translations to avoid raw keys appearing (e.g., "greeting_high")
826
+ if (window.kimiI18nManager && typeof window.kimiI18nManager.applyTranslations === "function") {
827
+ // give i18n a short moment to apply if still loading
828
+ const start = Date.now();
829
+ while (
830
+ (window.kimiI18nManager.translations == null || Object.keys(window.kimiI18nManager.translations).length === 0) &&
831
+ Date.now() - start < 500
832
+ ) {
833
+ // small delay
834
+ // eslint-disable-next-line no-await-in-loop
835
+ await new Promise(r => setTimeout(r, 50));
836
+ }
837
+ try {
838
+ window.kimiI18nManager.applyTranslations();
839
+ } catch (e) {}
840
+ }
841
+
842
  if (kimiDB) {
843
  try {
844
  const recent = await kimiDB.getRecentConversations(10);
 
1562
  message = validation.sanitized || message.trim();
1563
  if (!message) return;
1564
 
1565
+ // Force persist current personality sliders to avoid UI/DB desync
1566
+ try {
1567
+ const kimiDB = window.kimiDB;
1568
+ if (kimiDB && typeof kimiDB.setPersonalityBatch === "function") {
1569
+ const traitIds = [
1570
+ "trait-affection",
1571
+ "trait-playfulness",
1572
+ "trait-intelligence",
1573
+ "trait-empathy",
1574
+ "trait-humor",
1575
+ "trait-romance"
1576
+ ];
1577
+ const pending = {};
1578
+ traitIds.forEach(id => {
1579
+ const el = document.getElementById(id);
1580
+ if (el && el.value !== undefined) {
1581
+ const trait = id.replace(/^trait-/, "");
1582
+ const v = Number(el.value);
1583
+ if (isFinite(v)) pending[trait] = v;
1584
+ }
1585
+ });
1586
+ // Only write when there is at least one trait
1587
+ if (Object.keys(pending).length > 0) {
1588
+ // small timeout to ensure UI changes settled (rare) - use direct write
1589
+ await kimiDB.setPersonalityBatch(pending);
1590
+ }
1591
+ }
1592
+ } catch (e) {
1593
+ console.warn("Failed to persist personality sliders before send:", e);
1594
+ }
1595
+
1596
  addMessageToChat("user", message);
1597
  chatInput.value = "";
1598
  if (waitingIndicator) waitingIndicator.style.display = "inline-block";
kimi-js/kimi-script.js CHANGED
@@ -58,6 +58,24 @@ document.addEventListener("DOMContentLoaded", async function () {
58
  await kimiMemory.init();
59
  window.kimiMemory = kimiMemory;
60
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
61
  // Expose globally (already set before init)
62
 
63
  // Load available models now that LLM is ready
@@ -409,57 +427,82 @@ document.addEventListener("DOMContentLoaded", async function () {
409
  async function attachCharacterSection() {
410
  let saveCharacterBtn = window.KimiDOMUtils.get("#save-character-btn");
411
  if (saveCharacterBtn) {
412
- saveCharacterBtn.addEventListener("click", async e => {
413
- const settingsPanel = window.KimiDOMUtils.get(".settings-panel");
414
- let scrollTop = settingsPanel ? settingsPanel.scrollTop : null;
415
- const characterGrid = window.KimiDOMUtils.get("#character-grid");
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 : "";
422
-
423
- await window.kimiDB.setSelectedCharacter(charKey);
424
- await window.kimiDB.setSystemPromptForCharacter(charKey, prompt);
425
- // Ensure memory system uses the correct character
426
- if (window.kimiMemorySystem) {
427
- window.kimiMemorySystem.selectedCharacter = charKey;
428
- }
429
- if (window.kimiVideo && window.kimiVideo.setCharacter) {
430
- window.kimiVideo.setCharacter(charKey);
431
- if (window.kimiVideo.switchToContext) {
432
- window.kimiVideo.switchToContext("neutral");
 
 
 
 
 
 
 
 
 
433
  }
434
- }
435
- if (window.voiceManager && window.voiceManager.updateSelectedCharacter) {
436
- await window.voiceManager.updateSelectedCharacter();
437
- }
438
 
439
- await window.loadCharacterSection();
440
- if (settingsPanel && scrollTop !== null) {
441
- requestAnimationFrame(() => {
442
- settingsPanel.scrollTop = scrollTop;
443
- });
444
- }
445
- // Refresh memory tab after character selection
446
- if (window.kimiMemoryUI && typeof window.kimiMemoryUI.updateMemoryStats === "function") {
447
- await window.kimiMemoryUI.updateMemoryStats();
448
- }
449
- saveCharacterBtn.setAttribute("data-i18n", "saved");
450
- saveCharacterBtn.classList.add("success");
451
- saveCharacterBtn.disabled = true;
452
-
453
- setTimeout(() => {
454
- saveCharacterBtn.setAttribute("data-i18n", "save");
455
- saveCharacterBtn.classList.remove("success");
456
- saveCharacterBtn.disabled = false;
457
- }, 1500);
458
- });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
459
  }
460
  let settingsButton2 = window.KimiDOMUtils.get("#settings-button");
461
  if (settingsButton2) {
462
- settingsButton2.addEventListener("click", window.loadCharacterSection);
 
 
 
463
  }
464
  }
465
  await attachCharacterSection();
@@ -901,7 +944,29 @@ document.addEventListener("DOMContentLoaded", async function () {
901
  }
902
  if (window.kimiDB && window.kimiDB.db) {
903
  try {
904
- await window.kimiDB.db.conversations.clear();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
905
  } catch (error) {
906
  console.error("Error deleting conversations:", error);
907
  }
 
58
  await kimiMemory.init();
59
  window.kimiMemory = kimiMemory;
60
 
61
+ // Setup BroadcastChannel to announce conversation clears across tabs (very small, optional)
62
+ try {
63
+ if (typeof BroadcastChannel !== "undefined") {
64
+ window.kimiBroadcast = new BroadcastChannel("kimi-db-events");
65
+ window.kimiBroadcast.addEventListener("message", ev => {
66
+ try {
67
+ const data = ev.data || {};
68
+ if (data && data.type === "conversations:cleared" && data.character) {
69
+ window.kimiConversationsClearedAt = window.kimiConversationsClearedAt || {};
70
+ window.kimiConversationsClearedAt[data.character] = data.timestamp || new Date().toISOString();
71
+ }
72
+ } catch (e) {
73
+ // ignore
74
+ }
75
+ });
76
+ }
77
+ } catch (e) {}
78
+
79
  // Expose globally (already set before init)
80
 
81
  // Load available models now that LLM is ready
 
427
  async function attachCharacterSection() {
428
  let saveCharacterBtn = window.KimiDOMUtils.get("#save-character-btn");
429
  if (saveCharacterBtn) {
430
+ // Prevent attaching the same listener multiple times
431
+ if (saveCharacterBtn.dataset.kimiSaveListenerAttached === "1") {
432
+ // already attached
433
+ } else {
434
+ saveCharacterBtn.dataset.kimiSaveListenerAttached = "1";
435
+ saveCharacterBtn.addEventListener("click", async e => {
436
+ const settingsPanel = window.KimiDOMUtils.get(".settings-panel");
437
+ let scrollTop = settingsPanel ? settingsPanel.scrollTop : null;
438
+ const characterGrid = window.KimiDOMUtils.get("#character-grid");
439
+ const selectedCard = characterGrid ? characterGrid.querySelector(".character-card.selected") : null;
440
+ if (!selectedCard) return;
441
+ const charKey = selectedCard.dataset.character;
442
+ // Character save should not toggle the API key saved indicator.
443
+ const promptInput = window.KimiDOMUtils.get(`#prompt-${charKey}`);
444
+ const prompt = promptInput ? promptInput.value : "";
445
+
446
+ await window.kimiDB.setSelectedCharacter(charKey);
447
+ await window.kimiDB.setSystemPromptForCharacter(charKey, prompt);
448
+ // Ensure memory system uses the correct character
449
+ if (window.kimiMemorySystem) {
450
+ window.kimiMemorySystem.selectedCharacter = charKey;
451
+ }
452
+ if (window.kimiVideo && window.kimiVideo.setCharacter) {
453
+ window.kimiVideo.setCharacter(charKey);
454
+ if (window.kimiVideo.switchToContext) {
455
+ window.kimiVideo.switchToContext("neutral");
456
+ }
457
+ }
458
+ if (window.voiceManager && window.voiceManager.updateSelectedCharacter) {
459
+ await window.voiceManager.updateSelectedCharacter();
460
  }
 
 
 
 
461
 
462
+ await window.loadCharacterSection();
463
+ if (settingsPanel && scrollTop !== null) {
464
+ requestAnimationFrame(() => {
465
+ settingsPanel.scrollTop = scrollTop;
466
+ });
467
+ }
468
+ // Refresh memory tab after character selection
469
+ if (window.kimiMemoryUI && typeof window.kimiMemoryUI.updateMemoryStats === "function") {
470
+ await window.kimiMemoryUI.updateMemoryStats();
471
+ }
472
+ // Refresh chat history immediately so the chat-messages container shows the selected character's history
473
+ if (typeof window.loadChatHistory === "function") {
474
+ try {
475
+ await window.loadChatHistory();
476
+ } catch (e) {
477
+ console.warn("Failed to refresh chat history after character change:", e);
478
+ }
479
+ }
480
+ saveCharacterBtn.setAttribute("data-i18n", "saved");
481
+ saveCharacterBtn.classList.add("success");
482
+ saveCharacterBtn.disabled = true;
483
+
484
+ setTimeout(() => {
485
+ saveCharacterBtn.setAttribute("data-i18n", "save");
486
+ saveCharacterBtn.classList.remove("success");
487
+ saveCharacterBtn.disabled = false;
488
+ }, 900);
489
+ // Also reload the page shortly after restoring the button so the user sees feedback
490
+ setTimeout(() => {
491
+ try {
492
+ location.reload();
493
+ } catch (e) {
494
+ console.warn("Failed to reload after save feedback:", e);
495
+ }
496
+ }, 1100);
497
+ });
498
+ }
499
  }
500
  let settingsButton2 = window.KimiDOMUtils.get("#settings-button");
501
  if (settingsButton2) {
502
+ if (!settingsButton2.dataset.kimiSettingsListenerAttached) {
503
+ settingsButton2.dataset.kimiSettingsListenerAttached = "1";
504
+ settingsButton2.addEventListener("click", window.loadCharacterSection);
505
+ }
506
  }
507
  }
508
  await attachCharacterSection();
 
944
  }
945
  if (window.kimiDB && window.kimiDB.db) {
946
  try {
947
+ const selectedCharacter = await window.kimiDB.getSelectedCharacter();
948
+ // Mark cleared timestamp per-character to prevent pending async saves from re-adding old messages
949
+ window.kimiConversationsClearedAt = window.kimiConversationsClearedAt || {};
950
+ const ts = new Date().toISOString();
951
+ window.kimiConversationsClearedAt[selectedCharacter] = ts;
952
+ await window.kimiDB.clearConversations(selectedCharacter);
953
+ // Broadcast the clear to other tabs so they set their cleared markers too
954
+ try {
955
+ if (window.kimiBroadcast && typeof window.kimiBroadcast.postMessage === "function") {
956
+ window.kimiBroadcast.postMessage({
957
+ type: "conversations:cleared",
958
+ character: selectedCharacter,
959
+ timestamp: ts
960
+ });
961
+ }
962
+ } catch (e) {}
963
+ // Clear the cleared marker for this character shortly after to allow new conversations again
964
+ setTimeout(() => {
965
+ try {
966
+ if (window.kimiConversationsClearedAt)
967
+ delete window.kimiConversationsClearedAt[selectedCharacter];
968
+ } catch (e) {}
969
+ }, 5000);
970
  } catch (error) {
971
  console.error("Error deleting conversations:", error);
972
  }