Spaces:
				
			
			
	
			
			
					
		Running
		
	
	
	
			
			
	
	
	
	
		
		
					
		Running
		
	Upload 32 files
Browse files- CHANGELOG.md +0 -10
- CONTRIBUTING.md +0 -7
- index.html +12 -11
- kimi-js/kimi-config.js +1 -1
- kimi-js/kimi-constants.js +118 -251
- kimi-js/kimi-database.js +9 -2
- kimi-js/kimi-llm-manager.js +97 -8
- kimi-js/kimi-memory-system.js +1 -2
- kimi-js/kimi-module.js +7 -12
- kimi-js/kimi-script.js +11 -99
- kimi-js/kimi-utils.js +40 -8
- kimi-locale/de.json +1 -1
- kimi-locale/en.json +1 -1
- kimi-locale/es.json +1 -1
- kimi-locale/fr.json +1 -1
- kimi-locale/it.json +1 -1
- kimi-locale/ja.json +1 -1
- kimi-locale/zh.json +1 -1
    	
        CHANGELOG.md
    CHANGED
    
    | @@ -1,15 +1,5 @@ | |
| 1 | 
             
            # Virtual Kimi Changelog
         | 
| 2 |  | 
| 3 | 
            -
            # [1.0.8] - 2025-08-19
         | 
| 4 | 
            -
             | 
| 5 | 
            -
            ### Changed
         | 
| 6 | 
            -
             | 
| 7 | 
            -
            - Improved fallback logic for LLM responses: now uses localized emotional responses if the LLM reply is empty or invalid.
         | 
| 8 | 
            -
            - Made emotional response selection dynamic and robust, based on available variants.
         | 
| 9 | 
            -
            - Enhanced error handling for missing API keys, network issues, and API errors, ensuring the user always receives a meaningful message.
         | 
| 10 | 
            -
            - Refactored code patching to avoid accidental code removal or misplaced edits.
         | 
| 11 | 
            -
            - Clarified and documented emotional response logic for maintainability.
         | 
| 12 | 
            -
             | 
| 13 | 
             
            ## [1.0.7] - 2025-08-19
         | 
| 14 |  | 
| 15 | 
             
            ### Changed
         | 
|  | |
| 1 | 
             
            # Virtual Kimi Changelog
         | 
| 2 |  | 
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
| 3 | 
             
            ## [1.0.7] - 2025-08-19
         | 
| 4 |  | 
| 5 | 
             
            ### Changed
         | 
    	
        CONTRIBUTING.md
    CHANGED
    
    | @@ -331,13 +331,6 @@ Include the following information: | |
| 331 |  | 
| 332 | 
             
            ## Development Tips
         | 
| 333 |  | 
| 334 | 
            -
            ### Debugging
         | 
| 335 | 
            -
             | 
| 336 | 
            -
            - Use browser DevTools for JavaScript debugging
         | 
| 337 | 
            -
            - Check console for errors and warnings
         | 
| 338 | 
            -
            - Use the health check system: `window.KimiHealthCheck`
         | 
| 339 | 
            -
            - Enable debug mode: `window.KIMI_DEBUG = true`
         | 
| 340 | 
            -
             | 
| 341 | 
             
            ### Performance Optimization
         | 
| 342 |  | 
| 343 | 
             
            - Minimize DOM manipulations
         | 
|  | |
| 331 |  | 
| 332 | 
             
            ## Development Tips
         | 
| 333 |  | 
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
| 334 | 
             
            ### Performance Optimization
         | 
| 335 |  | 
| 336 | 
             
            - Minimize DOM manipulations
         | 
    	
        index.html
    CHANGED
    
    | @@ -55,7 +55,7 @@ | |
| 55 | 
             
                    },
         | 
| 56 | 
             
                    "dateCreated": "2025-07-16",
         | 
| 57 | 
             
                    "dateModified": "2025-08-19",
         | 
| 58 | 
            -
                    "version": "v1.0. | 
| 59 | 
             
                }
         | 
| 60 | 
             
                </script>
         | 
| 61 |  | 
| @@ -397,7 +397,8 @@ | |
| 397 |  | 
| 398 | 
             
                                    <div class="config-row">
         | 
| 399 | 
             
                                        <div class="config-label-group">
         | 
| 400 | 
            -
                                            <label class="config-label" id="api-key-label" data-i18n=" | 
|  | |
| 401 | 
             
                                            <span id="api-key-info" class="help-icon" data-i18n-title="api_key_help_title"
         | 
| 402 | 
             
                                                title="Saved = your API key is stored for this provider. Use Test API Key to verify the connection.">
         | 
| 403 | 
             
                                                <i class="fas fa-info-circle"></i>
         | 
| @@ -407,11 +408,11 @@ | |
| 407 | 
             
                                                title="Green = API key saved for current provider. Grey = no key saved."></span>
         | 
| 408 | 
             
                                        </div>
         | 
| 409 | 
             
                                        <div class="config-control">
         | 
| 410 | 
            -
                                            <input type="password" class="kimi-input" id=" | 
| 411 | 
            -
                                                 | 
| 412 | 
            -
                                                autocorrect="off" spellcheck="false" inputmode="text" | 
| 413 | 
            -
                                                 | 
| 414 | 
            -
                                                data-form-type="other" />
         | 
| 415 | 
             
                                            <button class="kimi-button" id="toggle-api-key" type="button" aria-pressed="false"
         | 
| 416 | 
             
                                                aria-label="Show API key">
         | 
| 417 | 
             
                                                <i class="fas fa-eye"></i>
         | 
| @@ -455,7 +456,7 @@ | |
| 455 | 
             
                                        <label class="config-label" data-i18n="max_tokens">Max Tokens</label>
         | 
| 456 | 
             
                                        <div class="config-control">
         | 
| 457 | 
             
                                            <div class="slider-container">
         | 
| 458 | 
            -
                                                <input type="range" class="kimi-slider" id="llm-max-tokens" min="10" max=" | 
| 459 | 
             
                                                    step="10" value="400" />
         | 
| 460 | 
             
                                                <span class="slider-value" id="llm-max-tokens-value">400</span>
         | 
| 461 | 
             
                                            </div>
         | 
| @@ -1013,8 +1014,7 @@ | |
| 1013 | 
             
                </div>
         | 
| 1014 | 
             
                </div>
         | 
| 1015 |  | 
| 1016 | 
            -
                <script src="dexie.min.js"></script>
         | 
| 1017 | 
            -
                <script src="kimi-locale/i18n.js" defer></script>
         | 
| 1018 | 
             
                <script type="module" src="kimi-js/kimi-main.js"></script>
         | 
| 1019 | 
             
                <script type="module" src="kimi-js/kimi-config.js"></script>
         | 
| 1020 | 
             
                <script type="module" src="kimi-js/kimi-error-manager.js"></script>
         | 
| @@ -1023,6 +1023,7 @@ | |
| 1023 | 
             
                <script type="module" src="kimi-js/kimi-constants.js"></script>
         | 
| 1024 | 
             
                <script type="module" src="kimi-js/kimi-memory-ui.js"></script>
         | 
| 1025 | 
             
                <script type="module" src="kimi-js/kimi-appearance.js"></script>
         | 
|  | |
| 1026 | 
             
                <script type="module" src="kimi-js/kimi-module.js"></script>
         | 
| 1027 | 
             
                <script type="module" src="kimi-js/kimi-script.js"></script>
         | 
| 1028 | 
             
                <script type="module" src="kimi-js/kimi-plugin-manager.js"></script>
         | 
| @@ -1054,7 +1055,7 @@ | |
| 1054 | 
             
                            "name": "Jean & Kimi"
         | 
| 1055 | 
             
                        },
         | 
| 1056 | 
             
                        "dateCreated": "2025-07-19",
         | 
| 1057 | 
            -
                            "version": "v1.0. | 
| 1058 | 
             
                    }
         | 
| 1059 | 
             
                }
         | 
| 1060 | 
             
                </script>
         | 
|  | |
| 55 | 
             
                    },
         | 
| 56 | 
             
                    "dateCreated": "2025-07-16",
         | 
| 57 | 
             
                    "dateModified": "2025-08-19",
         | 
| 58 | 
            +
                    "version": "v1.0.7"
         | 
| 59 | 
             
                }
         | 
| 60 | 
             
                </script>
         | 
| 61 |  | 
|  | |
| 397 |  | 
| 398 | 
             
                                    <div class="config-row">
         | 
| 399 | 
             
                                        <div class="config-label-group">
         | 
| 400 | 
            +
                                            <label class="config-label" id="api-key-label" data-i18n="openrouter_api_key">OpenRouter
         | 
| 401 | 
            +
                                                API Key</label>
         | 
| 402 | 
             
                                            <span id="api-key-info" class="help-icon" data-i18n-title="api_key_help_title"
         | 
| 403 | 
             
                                                title="Saved = your API key is stored for this provider. Use Test API Key to verify the connection.">
         | 
| 404 | 
             
                                                <i class="fas fa-info-circle"></i>
         | 
|  | |
| 408 | 
             
                                                title="Green = API key saved for current provider. Grey = no key saved."></span>
         | 
| 409 | 
             
                                        </div>
         | 
| 410 | 
             
                                        <div class="config-control">
         | 
| 411 | 
            +
                                            <input type="password" class="kimi-input" id="openrouter-api-key"
         | 
| 412 | 
            +
                                                name="openrouter_api_key" placeholder="sk-or-v1-..." autocomplete="new-password"
         | 
| 413 | 
            +
                                                autocapitalize="none" autocorrect="off" spellcheck="false" inputmode="text"
         | 
| 414 | 
            +
                                                aria-autocomplete="none" data-lpignore="true" data-1p-ignore="true"
         | 
| 415 | 
            +
                                                data-bwignore="true" data-form-type="other" />
         | 
| 416 | 
             
                                            <button class="kimi-button" id="toggle-api-key" type="button" aria-pressed="false"
         | 
| 417 | 
             
                                                aria-label="Show API key">
         | 
| 418 | 
             
                                                <i class="fas fa-eye"></i>
         | 
|  | |
| 456 | 
             
                                        <label class="config-label" data-i18n="max_tokens">Max Tokens</label>
         | 
| 457 | 
             
                                        <div class="config-control">
         | 
| 458 | 
             
                                            <div class="slider-container">
         | 
| 459 | 
            +
                                                <input type="range" class="kimi-slider" id="llm-max-tokens" min="10" max="8192"
         | 
| 460 | 
             
                                                    step="10" value="400" />
         | 
| 461 | 
             
                                                <span class="slider-value" id="llm-max-tokens-value">400</span>
         | 
| 462 | 
             
                                            </div>
         | 
|  | |
| 1014 | 
             
                </div>
         | 
| 1015 | 
             
                </div>
         | 
| 1016 |  | 
| 1017 | 
            +
                <script src="kimi-js/dexie.min.js"></script>
         | 
|  | |
| 1018 | 
             
                <script type="module" src="kimi-js/kimi-main.js"></script>
         | 
| 1019 | 
             
                <script type="module" src="kimi-js/kimi-config.js"></script>
         | 
| 1020 | 
             
                <script type="module" src="kimi-js/kimi-error-manager.js"></script>
         | 
|  | |
| 1023 | 
             
                <script type="module" src="kimi-js/kimi-constants.js"></script>
         | 
| 1024 | 
             
                <script type="module" src="kimi-js/kimi-memory-ui.js"></script>
         | 
| 1025 | 
             
                <script type="module" src="kimi-js/kimi-appearance.js"></script>
         | 
| 1026 | 
            +
                <script src="kimi-locale/i18n.js"></script>
         | 
| 1027 | 
             
                <script type="module" src="kimi-js/kimi-module.js"></script>
         | 
| 1028 | 
             
                <script type="module" src="kimi-js/kimi-script.js"></script>
         | 
| 1029 | 
             
                <script type="module" src="kimi-js/kimi-plugin-manager.js"></script>
         | 
|  | |
| 1055 | 
             
                            "name": "Jean & Kimi"
         | 
| 1056 | 
             
                        },
         | 
| 1057 | 
             
                        "dateCreated": "2025-07-19",
         | 
| 1058 | 
            +
                            "version": "v1.0.7"
         | 
| 1059 | 
             
                    }
         | 
| 1060 | 
             
                }
         | 
| 1061 | 
             
                </script>
         | 
    	
        kimi-js/kimi-config.js
    CHANGED
    
    | @@ -25,7 +25,7 @@ window.KIMI_CONFIG = { | |
| 25 | 
             
                    VOICE_VOLUME: { min: 0.0, max: 1.0 },
         | 
| 26 | 
             
                    INTERFACE_OPACITY: { min: 0.1, max: 1.0 },
         | 
| 27 | 
             
                    LLM_TEMPERATURE: { min: 0.0, max: 1.0 },
         | 
| 28 | 
            -
                    LLM_MAX_TOKENS: { min: 10, max:  | 
| 29 | 
             
                    LLM_TOP_P: { min: 0.0, max: 1.0 },
         | 
| 30 | 
             
                    LLM_FREQUENCY_PENALTY: { min: 0.0, max: 2.0 },
         | 
| 31 | 
             
                    LLM_PRESENCE_PENALTY: { min: 0.0, max: 2.0 }
         | 
|  | |
| 25 | 
             
                    VOICE_VOLUME: { min: 0.0, max: 1.0 },
         | 
| 26 | 
             
                    INTERFACE_OPACITY: { min: 0.1, max: 1.0 },
         | 
| 27 | 
             
                    LLM_TEMPERATURE: { min: 0.0, max: 1.0 },
         | 
| 28 | 
            +
                    LLM_MAX_TOKENS: { min: 10, max: 8192 },
         | 
| 29 | 
             
                    LLM_TOP_P: { min: 0.0, max: 1.0 },
         | 
| 30 | 
             
                    LLM_FREQUENCY_PENALTY: { min: 0.0, max: 2.0 },
         | 
| 31 | 
             
                    LLM_PRESENCE_PENALTY: { min: 0.0, max: 2.0 }
         | 
    	
        kimi-js/kimi-constants.js
    CHANGED
    
    | @@ -2,212 +2,88 @@ | |
| 2 |  | 
| 3 | 
             
            window.KIMI_CONTEXT_KEYWORDS = {
         | 
| 4 | 
             
                en: {
         | 
| 5 | 
            -
                    surprise: [
         | 
| 6 | 
            -
             | 
| 7 | 
            -
             | 
| 8 | 
            -
             | 
| 9 | 
            -
             | 
| 10 | 
            -
             | 
| 11 | 
            -
             | 
| 12 | 
            -
             | 
| 13 | 
            -
             | 
| 14 | 
            -
             | 
| 15 | 
            -
                        "gosh",
         | 
| 16 | 
            -
                        "astonishing"
         | 
| 17 | 
            -
                    ],
         | 
| 18 | 
            -
                    laughing: [
         | 
| 19 | 
            -
                        "haha",
         | 
| 20 | 
            -
                        "lol",
         | 
| 21 | 
            -
                        "laugh",
         | 
| 22 | 
            -
                        "funny",
         | 
| 23 | 
            -
                        "hilarious",
         | 
| 24 | 
            -
                        "rofl",
         | 
| 25 | 
            -
                        "lmao",
         | 
| 26 | 
            -
                        "giggle",
         | 
| 27 | 
            -
                        "chuckle",
         | 
| 28 | 
            -
                        "snicker",
         | 
| 29 | 
            -
                        "you’re kidding"
         | 
| 30 | 
            -
                    ],
         | 
| 31 | 
            -
                    shy: [
         | 
| 32 | 
            -
                        "shy",
         | 
| 33 | 
            -
                        "embarrassed",
         | 
| 34 | 
            -
                        "blush",
         | 
| 35 | 
            -
                        "bashful",
         | 
| 36 | 
            -
                        "intimidated",
         | 
| 37 | 
            -
                        "awkward",
         | 
| 38 | 
            -
                        "nervous",
         | 
| 39 | 
            -
                        "timid",
         | 
| 40 | 
            -
                        "reserved",
         | 
| 41 | 
            -
                        "self-conscious"
         | 
| 42 | 
            -
                    ],
         | 
| 43 | 
            -
                    confident: [
         | 
| 44 | 
            -
                        "confidence",
         | 
| 45 | 
            -
                        "proud",
         | 
| 46 | 
            -
                        "confident",
         | 
| 47 | 
            -
                        "strong",
         | 
| 48 | 
            -
                        "determined",
         | 
| 49 | 
            -
                        "assertive",
         | 
| 50 | 
            -
                        "bold",
         | 
| 51 | 
            -
                        "fearless",
         | 
| 52 | 
            -
                        "self-assured",
         | 
| 53 | 
            -
                        "leader"
         | 
| 54 | 
            -
                    ],
         | 
| 55 | 
            -
                    romantic: [
         | 
| 56 | 
            -
                        "love",
         | 
| 57 | 
            -
                        "romantic",
         | 
| 58 | 
            -
                        "tender",
         | 
| 59 | 
            -
                        "hug",
         | 
| 60 | 
            -
                        "kiss",
         | 
| 61 | 
            -
                        "sweetheart",
         | 
| 62 | 
            -
                        "darling",
         | 
| 63 | 
            -
                        "my love",
         | 
| 64 | 
            -
                        "beloved",
         | 
| 65 | 
            -
                        "heart",
         | 
| 66 | 
            -
                        "passionate"
         | 
| 67 | 
            -
                    ],
         | 
| 68 | 
            -
                    flirtatious: [
         | 
| 69 | 
            -
                        "flirty",
         | 
| 70 | 
            -
                        "teasing",
         | 
| 71 | 
            -
                        "seduce",
         | 
| 72 | 
            -
                        "charm",
         | 
| 73 | 
            -
                        "flirt",
         | 
| 74 | 
            -
                        "wink",
         | 
| 75 | 
            -
                        "sassy",
         | 
| 76 | 
            -
                        "saucy",
         | 
| 77 | 
            -
                        "playful",
         | 
| 78 | 
            -
                        "seductive",
         | 
| 79 | 
            -
                        "come hither"
         | 
| 80 | 
            -
                    ],
         | 
| 81 | 
            -
                    goodbye: [
         | 
| 82 | 
            -
                        "goodbye",
         | 
| 83 | 
            -
                        "bye",
         | 
| 84 | 
            -
                        "see you",
         | 
| 85 | 
            -
                        "see you soon",
         | 
| 86 | 
            -
                        "ciao",
         | 
| 87 | 
            -
                        "take care",
         | 
| 88 | 
            -
                        "farewell",
         | 
| 89 | 
            -
                        "see ya",
         | 
| 90 | 
            -
                        "later",
         | 
| 91 | 
            -
                        "catch you later"
         | 
| 92 | 
            -
                    ],
         | 
| 93 | 
            -
                    kiss: ["kiss", "kisses", "embrace", "smooch", "peck", "lip lock", "kissy", "mwah"],
         | 
| 94 | 
            -
                    dancing: ["dance", "dancing", "move", "groove", "step", "boogie", "twirl", "spin", "shake", "jig"],
         | 
| 95 | 
            -
                    listening: [
         | 
| 96 | 
            -
                        "listen",
         | 
| 97 | 
            -
                        "listening",
         | 
| 98 | 
            -
                        "hear",
         | 
| 99 | 
            -
                        "question",
         | 
| 100 | 
            -
                        "ask",
         | 
| 101 | 
            -
                        "tell me",
         | 
| 102 | 
            -
                        "pay attention",
         | 
| 103 | 
            -
                        "hear me out",
         | 
| 104 | 
            -
                        "focus",
         | 
| 105 | 
            -
                        "tune in",
         | 
| 106 | 
            -
                        "lend an ear"
         | 
| 107 | 
            -
                    ]
         | 
| 108 | 
             
                },
         | 
| 109 | 
             
                fr: {
         | 
| 110 | 
            -
                    surprise: ["oh", "surprise", "incroyable", "wahou", "étonnant" | 
| 111 | 
            -
                    laughing: ["haha", "mdr", "rire", "drôle", "hilarant" | 
| 112 | 
            -
                    shy: ["timide", "gêné", "rougir", "honteux", "intimidé" | 
| 113 | 
            -
                    confident: ["confiance", "fier", "sûr", "fort", "déterminé" | 
| 114 | 
            -
                    romantic: ["amour", "romantique", "tendre", "câlin", "bisou", " | 
| 115 | 
            -
                    flirtatious: [
         | 
| 116 | 
            -
             | 
| 117 | 
            -
             | 
| 118 | 
            -
             | 
| 119 | 
            -
             | 
| 120 | 
            -
                        "aguiche",
         | 
| 121 | 
            -
                        "clin d’œil",
         | 
| 122 | 
            -
                        "coquin",
         | 
| 123 | 
            -
                        "séducteur",
         | 
| 124 | 
            -
                        "taquine",
         | 
| 125 | 
            -
                        "aguicheur"
         | 
| 126 | 
            -
                    ],
         | 
| 127 | 
            -
                    goodbye: ["au revoir", "bye", "à bientôt", "ciao", "salut", "prends soin de toi", "à plus", "à la prochaine", "bye bye"],
         | 
| 128 | 
            -
                    kiss: ["bisou", "baiser", "embrasser", "smack", "bisou bisou", "bécot", "embrassade"],
         | 
| 129 | 
            -
                    dancing: ["danse", "bouge", "remue", "tourne", "spin", "danser", "tourbillon", "bouger", "remuer", "gigoter"],
         | 
| 130 | 
            -
                    listening: [
         | 
| 131 | 
            -
                        "écoute",
         | 
| 132 | 
            -
                        "écouter",
         | 
| 133 | 
            -
                        "parle",
         | 
| 134 | 
            -
                        "question",
         | 
| 135 | 
            -
                        "demande",
         | 
| 136 | 
            -
                        "dis-moi",
         | 
| 137 | 
            -
                        "écoute-moi",
         | 
| 138 | 
            -
                        "sois attentif",
         | 
| 139 | 
            -
                        "prête l’oreille",
         | 
| 140 | 
            -
                        "concentre-toi"
         | 
| 141 | 
            -
                    ]
         | 
| 142 | 
             
                },
         | 
| 143 | 
             
                es: {
         | 
| 144 | 
            -
                    surprise: ["wow", "oh", "sorpresa", "increíble", "asombroso" | 
| 145 | 
            -
                    laughing: ["jaja", "lol", "reír", "gracioso", "divertido" | 
| 146 | 
            -
                    shy: ["tímido", "avergonzado", "sonrojar", "tímida", "intimidado" | 
| 147 | 
            -
                    confident: ["confianza", "orgulloso", "seguro", "fuerte", "determinado" | 
| 148 | 
            -
                    romantic: ["amor", "romántico", "tierno", "abrazo", "beso", " | 
| 149 | 
            -
                    flirtatious: ["coqueto", "provocar", "seducir", "encanto", "flirtear" | 
| 150 | 
            -
                    goodbye: ["adiós", "bye", "hasta pronto", "ciao", "hasta luego" | 
| 151 | 
            -
                    kiss: ["beso", "besos", "abrazar" | 
| 152 | 
            -
                    dancing: ["bailar", "baile", "mover", "ritmo", "paso" | 
| 153 | 
            -
                    listening: [
         | 
| 154 | 
            -
                        "escucha",
         | 
| 155 | 
            -
                        "escuchar",
         | 
| 156 | 
            -
                        "oír",
         | 
| 157 | 
            -
                        "habla",
         | 
| 158 | 
            -
                        "pregunta",
         | 
| 159 | 
            -
                        "preguntar",
         | 
| 160 | 
            -
                        "dime",
         | 
| 161 | 
            -
                        "escúchame",
         | 
| 162 | 
            -
                        "pon atención",
         | 
| 163 | 
            -
                        "presta oído",
         | 
| 164 | 
            -
                        "concéntrate"
         | 
| 165 | 
            -
                    ]
         | 
| 166 | 
             
                },
         | 
| 167 | 
             
                de: {
         | 
| 168 | 
            -
                    surprise: ["wow", "oh", "überraschung", "unglaublich", "erstaunlich" | 
| 169 | 
            -
                    laughing: ["haha", "lol", "lachen", "lustig", "witzig" | 
| 170 | 
            -
                    shy: ["schüchtern", "verlegen", "erröten", "beschämt", "eingeschüchtert" | 
| 171 | 
            -
                    confident: ["vertrauen", "stolz", "sicher", "stark", "entschlossen" | 
| 172 | 
            -
                    romantic: ["liebe", "romantisch", "zärtlich", "umarmung", "kuss", " | 
| 173 | 
            -
                    flirtatious: ["flirten", "necken", "verführen", "charme", "flirt" | 
| 174 | 
            -
                    goodbye: ["auf wiedersehen", "bye", "bis bald", "ciao", "bis später" | 
| 175 | 
            -
                    kiss: ["kuss", "küsse", "umarmen", " | 
| 176 | 
            -
                    dancing: ["tanzen", "tanz", "bewegen", "groove", "schritt" | 
| 177 | 
            -
                    listening: [
         | 
| 178 | 
            -
                        "hör",
         | 
| 179 | 
            -
                        "hören",
         | 
| 180 | 
            -
                        "zuhören",
         | 
| 181 | 
            -
                        "sprich",
         | 
| 182 | 
            -
                        "frage",
         | 
| 183 | 
            -
                        "fragen",
         | 
| 184 | 
            -
                        "sag mir",
         | 
| 185 | 
            -
                        "hör zu",
         | 
| 186 | 
            -
                        "sei aufmerksam",
         | 
| 187 | 
            -
                        "konzentriere dich"
         | 
| 188 | 
            -
                    ]
         | 
| 189 | 
             
                },
         | 
| 190 | 
             
                it: {
         | 
| 191 | 
            -
                    surprise: ["wow", "oh", "sorpresa", "incredibile", "stupefacente" | 
| 192 | 
            -
                    laughing: ["haha", "lol", "ridere", "divertente", "esilarante" | 
| 193 | 
            -
                    shy: [
         | 
| 194 | 
            -
             | 
| 195 | 
            -
             | 
| 196 | 
            -
             | 
| 197 | 
            -
             | 
| 198 | 
            -
             | 
| 199 | 
            -
             | 
| 200 | 
            -
             | 
| 201 | 
            -
             | 
| 202 | 
            -
             | 
| 203 | 
            -
                    ],
         | 
| 204 | 
            -
                     | 
| 205 | 
            -
                     | 
| 206 | 
            -
                     | 
| 207 | 
            -
                     | 
| 208 | 
            -
                     | 
| 209 | 
            -
                     | 
| 210 | 
            -
                     | 
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
| 211 | 
             
                }
         | 
| 212 | 
             
            };
         | 
| 213 |  | 
| @@ -709,28 +585,16 @@ window.KIMI_PERSONALITY_KEYWORDS = { | |
| 709 | 
             
                    }
         | 
| 710 | 
             
                },
         | 
| 711 | 
             
                ja: {
         | 
| 712 | 
            -
                     | 
| 713 | 
            -
             | 
| 714 | 
            -
             | 
| 715 | 
            -
                     | 
| 716 | 
            -
                    romantic: ["愛", "ロマンチック", "優しい", "抱擁", "キス", "愛しい"],
         | 
| 717 | 
            -
                    flirtatious: ["いちゃつく", "からかう", "誘惑", "魅力", "フリート"],
         | 
| 718 | 
            -
                    goodbye: ["さようなら", "バイバイ", "また今度", "チャオ", "またね"],
         | 
| 719 | 
            -
                    kiss: ["キス", "抱擁", "チュー"],
         | 
| 720 | 
            -
                    dancing: ["踊る", "ダンス", "動く", "グルーブ", "ステップ"],
         | 
| 721 | 
            -
                    listening: ["聞いて", "聞く", "聞いてください", "話して", "話す", "質問", "尋ねる", "教えて"]
         | 
| 722 | 
             
                },
         | 
| 723 | 
             
                zh: {
         | 
| 724 | 
            -
                     | 
| 725 | 
            -
             | 
| 726 | 
            -
             | 
| 727 | 
            -
                     | 
| 728 | 
            -
                    romantic: ["爱", "浪漫", "温柔", "拥抱", "吻", "亲爱的"],
         | 
| 729 | 
            -
                    flirtatious: ["调情", "挑逗", "诱惑", "魅力", "撒娇"],
         | 
| 730 | 
            -
                    goodbye: ["再见", "拜拜", "回头见", "拜", "下次见"],
         | 
| 731 | 
            -
                    kiss: ["吻", "亲吻", "拥抱", "亲"],
         | 
| 732 | 
            -
                    dancing: ["跳舞", "舞蹈", "移动", "律动", "步伐"],
         | 
| 733 | 
            -
                    listening: ["听", "听听", "倾听", "说", "说话", "问题", "提问", "告诉我"]
         | 
| 734 | 
             
                }
         | 
| 735 | 
             
            };
         | 
| 736 |  | 
| @@ -850,16 +714,10 @@ window.KIMI_COMMON_WORDS = { | |
| 850 | 
             
                zh: ["的", "一", "是", "在", "不", "了", "有", "和", "人", "这", "中", "大", "为", "上", "个", "国", "我", "以", "要"]
         | 
| 851 | 
             
            };
         | 
| 852 |  | 
| 853 | 
            -
            // Build Set version for fast lookup (must be outside the object)
         | 
| 854 | 
            -
            window.KIMI_COMMON_WORDS_SET = {};
         | 
| 855 | 
            -
            Object.keys(window.KIMI_COMMON_WORDS).forEach(lang => {
         | 
| 856 | 
            -
                window.KIMI_COMMON_WORDS_SET[lang] = new Set(window.KIMI_COMMON_WORDS[lang]);
         | 
| 857 | 
            -
            });
         | 
| 858 | 
            -
             | 
| 859 | 
             
            // Helper function to check if a word is common
         | 
| 860 | 
             
            window.isCommonWord = function (word, language = "en") {
         | 
| 861 | 
            -
                const  | 
| 862 | 
            -
                return  | 
| 863 | 
             
            };
         | 
| 864 |  | 
| 865 | 
             
            // Emotion detection sensitivity configuration (per language and emotion)
         | 
| @@ -882,42 +740,42 @@ window.KIMI_EMOTION_SENSITIVITY = { | |
| 882 | 
             
                },
         | 
| 883 | 
             
                // Example language-specific overrides (can be adjusted via settings if needed)
         | 
| 884 | 
             
                fr: { romantic: 1.1, laughing: 0.95 },
         | 
| 885 | 
            -
                es: { romantic: 1.05 | 
| 886 | 
            -
                 | 
| 887 | 
            -
                de: { | 
| 888 | 
            -
                 | 
| 889 | 
            -
                ja: { | 
| 890 | 
            -
                zh: { | 
| 891 | 
             
            };
         | 
| 892 |  | 
| 893 | 
             
            // Personality trait adjustment multipliers
         | 
| 894 | 
             
            // Allows fine-tuning how fast traits evolve globally and per emotion/trait.
         | 
| 895 | 
             
            window.KIMI_TRAIT_ADJUSTMENT = {
         | 
| 896 | 
            -
                globalGain: 1. | 
| 897 | 
            -
                globalLoss: 0 | 
| 898 | 
             
                // Per-emotion gain scaling (keys must match KimiEmotionSystem.EMOTIONS values)
         | 
| 899 | 
             
                emotionGain: {
         | 
| 900 | 
            -
                    positive: 1. | 
| 901 | 
            -
                    negative: 0 | 
| 902 | 
            -
                    romantic: 1. | 
| 903 | 
            -
                    laughing: 1. | 
| 904 | 
            -
                    dancing: 1. | 
| 905 | 
            -
                    shy: 0 | 
| 906 | 
            -
                    confident: 1. | 
| 907 | 
            -
                    flirtatious: 1. | 
| 908 | 
             
                },
         | 
| 909 | 
             
                // Per-trait scaling
         | 
| 910 | 
             
                traitGain: {
         | 
| 911 | 
            -
                    affection: 1. | 
| 912 | 
            -
                    romance: 1. | 
| 913 | 
            -
                    empathy: 1. | 
| 914 | 
            -
                    playfulness: 1. | 
| 915 | 
            -
                    humor: 1. | 
| 916 | 
            -
                    intelligence: 1. | 
| 917 | 
             
                },
         | 
| 918 | 
             
                traitLoss: {
         | 
| 919 | 
            -
                    affection: 0 | 
| 920 | 
            -
                    romance: 0 | 
| 921 | 
             
                    empathy: 1.0,
         | 
| 922 | 
             
                    playfulness: 1.0,
         | 
| 923 | 
             
                    humor: 1.0,
         | 
| @@ -1112,7 +970,16 @@ window.getLocalizedEmotionalResponse = function (type, index = null) { | |
| 1112 | 
             
                        : "";
         | 
| 1113 | 
             
                }
         | 
| 1114 |  | 
| 1115 | 
            -
                const  | 
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
| 1116 | 
             
                const randomIndex = index !== null ? index : Math.floor(Math.random() * count) + 1;
         | 
| 1117 |  | 
| 1118 | 
             
                return (
         | 
|  | |
| 2 |  | 
| 3 | 
             
            window.KIMI_CONTEXT_KEYWORDS = {
         | 
| 4 | 
             
                en: {
         | 
| 5 | 
            +
                    surprise: ["wow", "oh", "surprise", "incredible", "amazing"],
         | 
| 6 | 
            +
                    laughing: ["haha", "lol", "laugh", "funny", "hilarious"],
         | 
| 7 | 
            +
                    shy: ["shy", "embarrassed", "blush", "bashful", "intimidated"],
         | 
| 8 | 
            +
                    confident: ["confidence", "proud", "confident", "strong", "determined"],
         | 
| 9 | 
            +
                    romantic: ["love", "romantic", "tender", "hug", "kiss", "dear"],
         | 
| 10 | 
            +
                    flirtatious: ["flirty", "teasing", "seduce", "charm", "flirt"],
         | 
| 11 | 
            +
                    goodbye: ["goodbye", "bye", "see you", "see you soon", "ciao"],
         | 
| 12 | 
            +
                    kiss: ["kiss", "kisses", "embrace", "bise"],
         | 
| 13 | 
            +
                    dancing: ["dance", "dancing", "move", "groove", "step"],
         | 
| 14 | 
            +
                    listening: ["listen", "listening", "hear", "talk", "speak", "question", "ask", "tell me"]
         | 
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
| 15 | 
             
                },
         | 
| 16 | 
             
                fr: {
         | 
| 17 | 
            +
                    surprise: ["oh", "surprise", "incroyable", "wahou", "étonnant"],
         | 
| 18 | 
            +
                    laughing: ["haha", "mdr", "rire", "drôle", "hilarant"],
         | 
| 19 | 
            +
                    shy: ["timide", "gêné", "rougir", "honteux", "intimidé"],
         | 
| 20 | 
            +
                    confident: ["confiance", "fier", "sûr", "fort", "déterminé"],
         | 
| 21 | 
            +
                    romantic: ["amour", "romantique", "tendre", "câlin", "bisou", "cher"],
         | 
| 22 | 
            +
                    flirtatious: ["flirt", "taquin", "séduire", "charme", "aguiche"],
         | 
| 23 | 
            +
                    goodbye: ["au revoir", "bye", "à bientôt", "ciao", "salut"],
         | 
| 24 | 
            +
                    kiss: ["bisou", "baiser", "embrasser", "câlin"],
         | 
| 25 | 
            +
                    dancing: ["danse", "bouge", "remue", "tourne", "spin"],
         | 
| 26 | 
            +
                    listening: ["écoute", "ecoute", "écouter", "parle", "parler", "question", "demande", "dis-moi"]
         | 
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
| 27 | 
             
                },
         | 
| 28 | 
             
                es: {
         | 
| 29 | 
            +
                    surprise: ["wow", "oh", "sorpresa", "increíble", "asombroso"],
         | 
| 30 | 
            +
                    laughing: ["jaja", "lol", "reír", "gracioso", "divertido"],
         | 
| 31 | 
            +
                    shy: ["tímido", "avergonzado", "sonrojar", "tímida", "intimidado"],
         | 
| 32 | 
            +
                    confident: ["confianza", "orgulloso", "seguro", "fuerte", "determinado"],
         | 
| 33 | 
            +
                    romantic: ["amor", "romántico", "tierno", "abrazo", "beso", "querido"],
         | 
| 34 | 
            +
                    flirtatious: ["coqueto", "provocar", "seducir", "encanto", "flirtear"],
         | 
| 35 | 
            +
                    goodbye: ["adiós", "bye", "hasta pronto", "ciao", "hasta luego"],
         | 
| 36 | 
            +
                    kiss: ["beso", "besos", "abrazar"],
         | 
| 37 | 
            +
                    dancing: ["bailar", "baile", "mover", "ritmo", "paso"],
         | 
| 38 | 
            +
                    listening: ["escucha", "escuchar", "oír", "habla", "hablar", "pregunta", "preguntar", "dime"]
         | 
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
| 39 | 
             
                },
         | 
| 40 | 
             
                de: {
         | 
| 41 | 
            +
                    surprise: ["wow", "oh", "überraschung", "unglaublich", "erstaunlich"],
         | 
| 42 | 
            +
                    laughing: ["haha", "lol", "lachen", "lustig", "witzig"],
         | 
| 43 | 
            +
                    shy: ["schüchtern", "verlegen", "erröten", "beschämt", "eingeschüchtert"],
         | 
| 44 | 
            +
                    confident: ["vertrauen", "stolz", "sicher", "stark", "entschlossen"],
         | 
| 45 | 
            +
                    romantic: ["liebe", "romantisch", "zärtlich", "umarmung", "kuss", "lieber"],
         | 
| 46 | 
            +
                    flirtatious: ["flirten", "necken", "verführen", "charme", "flirt"],
         | 
| 47 | 
            +
                    goodbye: ["auf wiedersehen", "bye", "bis bald", "ciao", "bis später"],
         | 
| 48 | 
            +
                    kiss: ["kuss", "küsse", "umarmen", "küsschen"],
         | 
| 49 | 
            +
                    dancing: ["tanzen", "tanz", "bewegen", "groove", "schritt"],
         | 
| 50 | 
            +
                    listening: ["hör", "hören", "zuhören", "sprich", "sprechen", "frage", "fragen", "sag mir"]
         | 
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
| 51 | 
             
                },
         | 
| 52 | 
             
                it: {
         | 
| 53 | 
            +
                    surprise: ["wow", "oh", "sorpresa", "incredibile", "stupefacente"],
         | 
| 54 | 
            +
                    laughing: ["haha", "lol", "ridere", "divertente", "esilarante"],
         | 
| 55 | 
            +
                    shy: ["timido", "imbarazzato", "arrossire", "vergognoso", "intimidito"],
         | 
| 56 | 
            +
                    confident: ["fiducia", "orgoglioso", "sicuro", "forte", "determinato"],
         | 
| 57 | 
            +
                    romantic: ["amore", "romantico", "tenero", "abbraccio", "bacio", "caro"],
         | 
| 58 | 
            +
                    flirtatious: ["civettare", "provocare", "sedurre", "fascino", "flirtare"],
         | 
| 59 | 
            +
                    goodbye: ["arrivederci", "bye", "a presto", "ciao"],
         | 
| 60 | 
            +
                    kiss: ["bacio", "baci", "abbracciare", "bacetto"],
         | 
| 61 | 
            +
                    dancing: ["ballare", "ballo", "muovere", "ritmo", "passo"],
         | 
| 62 | 
            +
                    listening: ["ascolta", "ascoltare", "senti", "parla", "parlare", "domanda", "chiedere", "dimmi"]
         | 
| 63 | 
            +
                },
         | 
| 64 | 
            +
                ja: {
         | 
| 65 | 
            +
                    surprise: ["わお", "おお", "驚き", "信じられない", "すごい"],
         | 
| 66 | 
            +
                    laughing: ["はは", "笑", "笑う", "面白い", "愉快"],
         | 
| 67 | 
            +
                    shy: ["恥ずかしい", "照れる", "赤面", "内気", "遠慮"],
         | 
| 68 | 
            +
                    confident: ["自信", "誇り", "確信", "強い", "決意"],
         | 
| 69 | 
            +
                    romantic: ["愛", "ロマンチック", "優しい", "抱擁", "キス", "愛しい"],
         | 
| 70 | 
            +
                    flirtatious: ["いちゃつく", "からかう", "誘惑", "魅力", "フリート"],
         | 
| 71 | 
            +
                    goodbye: ["さようなら", "バイバイ", "また今度", "チャオ", "またね"],
         | 
| 72 | 
            +
                    kiss: ["キス", "抱擁", "チュー"],
         | 
| 73 | 
            +
                    dancing: ["踊る", "ダンス", "動く", "グルーブ", "ステップ"],
         | 
| 74 | 
            +
                    listening: ["聞いて", "聞く", "聞いてください", "話して", "話す", "質問", "尋ねる", "教えて"]
         | 
| 75 | 
            +
                },
         | 
| 76 | 
            +
                zh: {
         | 
| 77 | 
            +
                    surprise: ["哇", "哦", "惊喜", "难以置信", "惊人"],
         | 
| 78 | 
            +
                    laughing: ["哈哈", "笑", "大笑", "有趣", "搞笑"],
         | 
| 79 | 
            +
                    shy: ["害羞", "尴尬", "脸红", "羞涩", "胆怯"],
         | 
| 80 | 
            +
                    confident: ["自信", "骄傲", "确信", "强壮", "坚定"],
         | 
| 81 | 
            +
                    romantic: ["爱", "浪漫", "温柔", "拥抱", "吻", "亲爱的"],
         | 
| 82 | 
            +
                    flirtatious: ["调情", "挑逗", "诱惑", "魅力", "撒娇"],
         | 
| 83 | 
            +
                    goodbye: ["再见", "拜拜", "回头见", "拜", "下次见"],
         | 
| 84 | 
            +
                    kiss: ["吻", "亲吻", "拥抱", "亲"],
         | 
| 85 | 
            +
                    dancing: ["跳舞", "舞蹈", "移动", "律动", "步伐"],
         | 
| 86 | 
            +
                    listening: ["听", "听听", "倾听", "说", "说话", "问题", "提问", "告诉我"]
         | 
| 87 | 
             
                }
         | 
| 88 | 
             
            };
         | 
| 89 |  | 
|  | |
| 585 | 
             
                    }
         | 
| 586 | 
             
                },
         | 
| 587 | 
             
                ja: {
         | 
| 588 | 
            +
                    affection: {
         | 
| 589 | 
            +
                        positive: ["愛情", "優しさ", "近い", "温かさ", "親切", "思いやり", "抱きしめる", "愛", "敬愛"],
         | 
| 590 | 
            +
                        negative: ["意地悪", "冷たい", "無関心", "距離がある", "拒絶", "嫌い", "敵対的", "ばか", "くそ", "アホ"]
         | 
| 591 | 
            +
                    }
         | 
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
| 592 | 
             
                },
         | 
| 593 | 
             
                zh: {
         | 
| 594 | 
            +
                    affection: {
         | 
| 595 | 
            +
                        positive: ["感情", "温柔", "亲近", "温暖", "善良", "关怀", "拥抱", "爱", "崇拜"],
         | 
| 596 | 
            +
                        negative: ["刻薄", "冷漠", "无动于衷", "疏远", "拒绝", "讨厌", "敌对", "笨蛋", "傻", "婊子"]
         | 
| 597 | 
            +
                    }
         | 
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
| 598 | 
             
                }
         | 
| 599 | 
             
            };
         | 
| 600 |  | 
|  | |
| 714 | 
             
                zh: ["的", "一", "是", "在", "不", "了", "有", "和", "人", "这", "中", "大", "为", "上", "个", "国", "我", "以", "要"]
         | 
| 715 | 
             
            };
         | 
| 716 |  | 
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
| 717 | 
             
            // Helper function to check if a word is common
         | 
| 718 | 
             
            window.isCommonWord = function (word, language = "en") {
         | 
| 719 | 
            +
                const commonWords = window.KIMI_COMMON_WORDS[language] || window.KIMI_COMMON_WORDS.en;
         | 
| 720 | 
            +
                return commonWords.includes(word.toLowerCase());
         | 
| 721 | 
             
            };
         | 
| 722 |  | 
| 723 | 
             
            // Emotion detection sensitivity configuration (per language and emotion)
         | 
|  | |
| 740 | 
             
                },
         | 
| 741 | 
             
                // Example language-specific overrides (can be adjusted via settings if needed)
         | 
| 742 | 
             
                fr: { romantic: 1.1, laughing: 0.95 },
         | 
| 743 | 
            +
                es: { romantic: 1.05 },
         | 
| 744 | 
            +
                en: {},
         | 
| 745 | 
            +
                de: {},
         | 
| 746 | 
            +
                it: {},
         | 
| 747 | 
            +
                ja: {},
         | 
| 748 | 
            +
                zh: {}
         | 
| 749 | 
             
            };
         | 
| 750 |  | 
| 751 | 
             
            // Personality trait adjustment multipliers
         | 
| 752 | 
             
            // Allows fine-tuning how fast traits evolve globally and per emotion/trait.
         | 
| 753 | 
             
            window.KIMI_TRAIT_ADJUSTMENT = {
         | 
| 754 | 
            +
                globalGain: 1.0,
         | 
| 755 | 
            +
                globalLoss: 1.0,
         | 
| 756 | 
             
                // Per-emotion gain scaling (keys must match KimiEmotionSystem.EMOTIONS values)
         | 
| 757 | 
             
                emotionGain: {
         | 
| 758 | 
            +
                    positive: 1.0,
         | 
| 759 | 
            +
                    negative: 1.0,
         | 
| 760 | 
            +
                    romantic: 1.0,
         | 
| 761 | 
            +
                    laughing: 1.0,
         | 
| 762 | 
            +
                    dancing: 1.0,
         | 
| 763 | 
            +
                    shy: 1.0,
         | 
| 764 | 
            +
                    confident: 1.0,
         | 
| 765 | 
            +
                    flirtatious: 1.0
         | 
| 766 | 
             
                },
         | 
| 767 | 
             
                // Per-trait scaling
         | 
| 768 | 
             
                traitGain: {
         | 
| 769 | 
            +
                    affection: 1.0,
         | 
| 770 | 
            +
                    romance: 1.0,
         | 
| 771 | 
            +
                    empathy: 1.0,
         | 
| 772 | 
            +
                    playfulness: 1.0,
         | 
| 773 | 
            +
                    humor: 1.0,
         | 
| 774 | 
            +
                    intelligence: 1.0
         | 
| 775 | 
             
                },
         | 
| 776 | 
             
                traitLoss: {
         | 
| 777 | 
            +
                    affection: 1.0,
         | 
| 778 | 
            +
                    romance: 1.0,
         | 
| 779 | 
             
                    empathy: 1.0,
         | 
| 780 | 
             
                    playfulness: 1.0,
         | 
| 781 | 
             
                    humor: 1.0,
         | 
|  | |
| 970 | 
             
                        : "";
         | 
| 971 | 
             
                }
         | 
| 972 |  | 
| 973 | 
            +
                const responses = {
         | 
| 974 | 
            +
                    positive: 5,
         | 
| 975 | 
            +
                    negative: 5,
         | 
| 976 | 
            +
                    neutral: 5,
         | 
| 977 | 
            +
                    romantic: 5,
         | 
| 978 | 
            +
                    dancing: 5,
         | 
| 979 | 
            +
                    cold: 5
         | 
| 980 | 
            +
                };
         | 
| 981 | 
            +
             | 
| 982 | 
            +
                const count = responses[type] || 5;
         | 
| 983 | 
             
                const randomIndex = index !== null ? index : Math.floor(Math.random() * count) + 1;
         | 
| 984 |  | 
| 985 | 
             
                return (
         | 
    	
        kimi-js/kimi-database.js
    CHANGED
    
    | @@ -126,7 +126,14 @@ class KimiDatabase { | |
| 126 | 
             
                        { key: "llmProvider", value: "openrouter" },
         | 
| 127 | 
             
                        { key: "llmBaseUrl", value: "https://openrouter.ai/api/v1/chat/completions" },
         | 
| 128 | 
             
                        { key: "llmModelId", value: "mistralai/mistral-small-3.2-24b-instruct" },
         | 
| 129 | 
            -
                        { key: " | 
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
| 130 | 
             
                    ];
         | 
| 131 | 
             
                }
         | 
| 132 |  | 
| @@ -358,7 +365,7 @@ class KimiDatabase { | |
| 358 | 
             
                }
         | 
| 359 |  | 
| 360 | 
             
                async setPreference(key, value) {
         | 
| 361 | 
            -
                    if (key === " | 
| 362 | 
             
                        const isValid = window.KIMI_VALIDATORS?.validateApiKey(value) || window.KimiSecurityUtils?.validateApiKey(value);
         | 
| 363 | 
             
                        if (!isValid && value.length > 0) {
         | 
| 364 | 
             
                            throw new Error("Invalid API key format");
         | 
|  | |
| 126 | 
             
                        { key: "llmProvider", value: "openrouter" },
         | 
| 127 | 
             
                        { key: "llmBaseUrl", value: "https://openrouter.ai/api/v1/chat/completions" },
         | 
| 128 | 
             
                        { key: "llmModelId", value: "mistralai/mistral-small-3.2-24b-instruct" },
         | 
| 129 | 
            +
                        { key: "llmApiKey", value: "" },
         | 
| 130 | 
            +
                        // Explicit default for OpenRouter key to avoid missing key errors
         | 
| 131 | 
            +
                        { key: "openrouterApiKey", value: "" },
         | 
| 132 | 
            +
                        { key: "apiKey_openai", value: "" },
         | 
| 133 | 
            +
                        { key: "apiKey_groq", value: "" },
         | 
| 134 | 
            +
                        { key: "apiKey_together", value: "" },
         | 
| 135 | 
            +
                        { key: "apiKey_deepseek", value: "" },
         | 
| 136 | 
            +
                        { key: "apiKey_custom", value: "" }
         | 
| 137 | 
             
                    ];
         | 
| 138 | 
             
                }
         | 
| 139 |  | 
|  | |
| 365 | 
             
                }
         | 
| 366 |  | 
| 367 | 
             
                async setPreference(key, value) {
         | 
| 368 | 
            +
                    if (key === "openrouterApiKey" || key === "llmApiKey" || key.startsWith("apiKey_")) {
         | 
| 369 | 
             
                        const isValid = window.KIMI_VALIDATORS?.validateApiKey(value) || window.KimiSecurityUtils?.validateApiKey(value);
         | 
| 370 | 
             
                        if (!isValid && value.length > 0) {
         | 
| 371 | 
             
                            throw new Error("Invalid API key format");
         | 
    	
        kimi-js/kimi-llm-manager.js
    CHANGED
    
    | @@ -474,7 +474,7 @@ class KimiLLMManager { | |
| 474 | 
             
                    const provider = await this.db.getPreference("llmProvider", "openai");
         | 
| 475 | 
             
                    const apiKey = KimiProviderUtils
         | 
| 476 | 
             
                        ? await KimiProviderUtils.getApiKey(this.db, provider)
         | 
| 477 | 
            -
                        : await this.db.getPreference(" | 
| 478 | 
             
                    const modelId = await this.db.getPreference("llmModelId", this.currentModel || "gpt-4o-mini");
         | 
| 479 | 
             
                    if (!apiKey) {
         | 
| 480 | 
             
                        throw new Error("API key not configured for selected provider");
         | 
| @@ -576,7 +576,7 @@ class KimiLLMManager { | |
| 576 | 
             
                }
         | 
| 577 |  | 
| 578 | 
             
                async chatWithOpenRouter(userMessage, options = {}) {
         | 
| 579 | 
            -
                    const apiKey = await this.db.getPreference(" | 
| 580 | 
             
                    if (!apiKey) {
         | 
| 581 | 
             
                        throw new Error("OpenRouter API key not configured");
         | 
| 582 | 
             
                    }
         | 
| @@ -1000,11 +1000,7 @@ class KimiLLMManager { | |
| 1000 | 
             
                    // Check availability on OpenRouter
         | 
| 1001 | 
             
                    try {
         | 
| 1002 | 
             
                        // getAvailableModelsFromAPI removed
         | 
| 1003 | 
            -
                        return {
         | 
| 1004 | 
            -
                            available: true,
         | 
| 1005 | 
            -
                            model: model,
         | 
| 1006 | 
            -
                            pricing: model.pricing
         | 
| 1007 | 
            -
                        };
         | 
| 1008 | 
             
                    } catch (error) {
         | 
| 1009 | 
             
                        return {
         | 
| 1010 | 
             
                            available: false,
         | 
| @@ -1013,12 +1009,105 @@ class KimiLLMManager { | |
| 1013 | 
             
                    }
         | 
| 1014 | 
             
                }
         | 
| 1015 |  | 
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
| 1016 | 
             
                // Fetch models from OpenRouter API and merge into availableModels
         | 
| 1017 | 
             
                async refreshRemoteModels() {
         | 
| 1018 | 
             
                    if (this._isRefreshingModels) return;
         | 
| 1019 | 
             
                    this._isRefreshingModels = true;
         | 
| 1020 | 
             
                    try {
         | 
| 1021 | 
            -
                        const apiKey = await this.db.getPreference(" | 
| 1022 | 
             
                        const res = await fetch("https://openrouter.ai/api/v1/models", {
         | 
| 1023 | 
             
                            method: "GET",
         | 
| 1024 | 
             
                            headers: {
         | 
|  | |
| 474 | 
             
                    const provider = await this.db.getPreference("llmProvider", "openai");
         | 
| 475 | 
             
                    const apiKey = KimiProviderUtils
         | 
| 476 | 
             
                        ? await KimiProviderUtils.getApiKey(this.db, provider)
         | 
| 477 | 
            +
                        : await this.db.getPreference("llmApiKey", "");
         | 
| 478 | 
             
                    const modelId = await this.db.getPreference("llmModelId", this.currentModel || "gpt-4o-mini");
         | 
| 479 | 
             
                    if (!apiKey) {
         | 
| 480 | 
             
                        throw new Error("API key not configured for selected provider");
         | 
|  | |
| 576 | 
             
                }
         | 
| 577 |  | 
| 578 | 
             
                async chatWithOpenRouter(userMessage, options = {}) {
         | 
| 579 | 
            +
                    const apiKey = await this.db.getPreference("openrouterApiKey");
         | 
| 580 | 
             
                    if (!apiKey) {
         | 
| 581 | 
             
                        throw new Error("OpenRouter API key not configured");
         | 
| 582 | 
             
                    }
         | 
|  | |
| 1000 | 
             
                    // Check availability on OpenRouter
         | 
| 1001 | 
             
                    try {
         | 
| 1002 | 
             
                        // getAvailableModelsFromAPI removed
         | 
| 1003 | 
            +
                        return { available: true, model, pricing: model.pricing };
         | 
|  | |
|  | |
|  | |
|  | |
| 1004 | 
             
                    } catch (error) {
         | 
| 1005 | 
             
                        return {
         | 
| 1006 | 
             
                            available: false,
         | 
|  | |
| 1009 | 
             
                    }
         | 
| 1010 | 
             
                }
         | 
| 1011 |  | 
| 1012 | 
            +
                /**
         | 
| 1013 | 
            +
                 * Minimal API test for any provider. Sends only a short system prompt and a single user message in the chosen language.
         | 
| 1014 | 
            +
                 * No context, no memory, no previous messages, no extra parameters.
         | 
| 1015 | 
            +
                 * @param {string} provider - Provider name (e.g. 'openrouter', 'openai', 'ollama', etc.)
         | 
| 1016 | 
            +
                 * @param {string} language - Language code (e.g. 'en', 'fr', 'es', etc.)
         | 
| 1017 | 
            +
                 * @returns {Promise<{success: boolean, response?: string, error?: string}>}
         | 
| 1018 | 
            +
                 */
         | 
| 1019 | 
            +
                async testApiKeyMinimal(modelId) {
         | 
| 1020 | 
            +
                    const originalModel = this.currentModel;
         | 
| 1021 | 
            +
                    try {
         | 
| 1022 | 
            +
                        await this.setCurrentModel(modelId);
         | 
| 1023 | 
            +
                        const provider = await this.db.getPreference("llmProvider", "openrouter");
         | 
| 1024 | 
            +
                        const lang = await this.db.getPreference("selectedLanguage", "en");
         | 
| 1025 | 
            +
                        let testWord;
         | 
| 1026 | 
            +
                        switch (lang) {
         | 
| 1027 | 
            +
                            case "fr":
         | 
| 1028 | 
            +
                                testWord = "Bonjour";
         | 
| 1029 | 
            +
                                break;
         | 
| 1030 | 
            +
                            case "es":
         | 
| 1031 | 
            +
                                testWord = "Hola";
         | 
| 1032 | 
            +
                                break;
         | 
| 1033 | 
            +
                            case "de":
         | 
| 1034 | 
            +
                                testWord = "Hallo";
         | 
| 1035 | 
            +
                                break;
         | 
| 1036 | 
            +
                            case "it":
         | 
| 1037 | 
            +
                                testWord = "Ciao";
         | 
| 1038 | 
            +
                                break;
         | 
| 1039 | 
            +
                            case "ja":
         | 
| 1040 | 
            +
                                testWord = "こんにちは";
         | 
| 1041 | 
            +
                                break;
         | 
| 1042 | 
            +
                            case "zh":
         | 
| 1043 | 
            +
                                testWord = "你好";
         | 
| 1044 | 
            +
                                break;
         | 
| 1045 | 
            +
                            default:
         | 
| 1046 | 
            +
                                testWord = "Hello";
         | 
| 1047 | 
            +
                        }
         | 
| 1048 | 
            +
                        const systemPrompt = "You are a helpful assistant.";
         | 
| 1049 | 
            +
                        let apiKey = await this.db.getPreference("providerApiKey");
         | 
| 1050 | 
            +
                        let baseUrl = "";
         | 
| 1051 | 
            +
                        let payload = {
         | 
| 1052 | 
            +
                            model: modelId,
         | 
| 1053 | 
            +
                            messages: [
         | 
| 1054 | 
            +
                                { role: "system", content: systemPrompt },
         | 
| 1055 | 
            +
                                { role: "user", content: testWord }
         | 
| 1056 | 
            +
                            ],
         | 
| 1057 | 
            +
                            max_tokens: 2
         | 
| 1058 | 
            +
                        };
         | 
| 1059 | 
            +
                        let headers = { "Content-Type": "application/json" };
         | 
| 1060 | 
            +
                        if (provider === "openrouter") {
         | 
| 1061 | 
            +
                            baseUrl = "https://openrouter.ai/api/v1/chat/completions";
         | 
| 1062 | 
            +
                            headers["Authorization"] = `Bearer ${apiKey}`;
         | 
| 1063 | 
            +
                            headers["HTTP-Referer"] = window.location.origin;
         | 
| 1064 | 
            +
                            headers["X-Title"] = "Kimi - Virtual Companion";
         | 
| 1065 | 
            +
                        } else if (["openai", "groq", "together", "deepseek"].includes(provider)) {
         | 
| 1066 | 
            +
                            baseUrl = await this.db.getPreference("llmBaseUrl", "https://api.openai.com/v1/chat/completions");
         | 
| 1067 | 
            +
                            headers["Authorization"] = `Bearer ${apiKey}`;
         | 
| 1068 | 
            +
                        } else if (provider === "ollama") {
         | 
| 1069 | 
            +
                            baseUrl = "http://localhost:11434/api/chat";
         | 
| 1070 | 
            +
                            payload = {
         | 
| 1071 | 
            +
                                model: modelId,
         | 
| 1072 | 
            +
                                messages: [
         | 
| 1073 | 
            +
                                    { role: "system", content: systemPrompt },
         | 
| 1074 | 
            +
                                    { role: "user", content: testWord }
         | 
| 1075 | 
            +
                                ],
         | 
| 1076 | 
            +
                                stream: false
         | 
| 1077 | 
            +
                            };
         | 
| 1078 | 
            +
                        } else {
         | 
| 1079 | 
            +
                            throw new Error("Unknown provider: " + provider);
         | 
| 1080 | 
            +
                        }
         | 
| 1081 | 
            +
                        const response = await fetch(baseUrl, {
         | 
| 1082 | 
            +
                            method: "POST",
         | 
| 1083 | 
            +
                            headers,
         | 
| 1084 | 
            +
                            body: JSON.stringify(payload)
         | 
| 1085 | 
            +
                        });
         | 
| 1086 | 
            +
                        if (!response.ok) {
         | 
| 1087 | 
            +
                            const error = await response.text();
         | 
| 1088 | 
            +
                            return { success: false, error };
         | 
| 1089 | 
            +
                        }
         | 
| 1090 | 
            +
                        const data = await response.json();
         | 
| 1091 | 
            +
                        let content = "";
         | 
| 1092 | 
            +
                        if (provider === "ollama") {
         | 
| 1093 | 
            +
                            content = data?.message?.content || data?.choices?.[0]?.message?.content || "";
         | 
| 1094 | 
            +
                        } else {
         | 
| 1095 | 
            +
                            content = data?.choices?.[0]?.message?.content || "";
         | 
| 1096 | 
            +
                        }
         | 
| 1097 | 
            +
                        return { success: true, response: content };
         | 
| 1098 | 
            +
                    } catch (error) {
         | 
| 1099 | 
            +
                        return { success: false, error: error.message };
         | 
| 1100 | 
            +
                    } finally {
         | 
| 1101 | 
            +
                        await this.setCurrentModel(originalModel);
         | 
| 1102 | 
            +
                    }
         | 
| 1103 | 
            +
                }
         | 
| 1104 | 
            +
             | 
| 1105 | 
             
                // Fetch models from OpenRouter API and merge into availableModels
         | 
| 1106 | 
             
                async refreshRemoteModels() {
         | 
| 1107 | 
             
                    if (this._isRefreshingModels) return;
         | 
| 1108 | 
             
                    this._isRefreshingModels = true;
         | 
| 1109 | 
             
                    try {
         | 
| 1110 | 
            +
                        const apiKey = await this.db.getPreference("openrouterApiKey", "");
         | 
| 1111 | 
             
                        const res = await fetch("https://openrouter.ai/api/v1/models", {
         | 
| 1112 | 
             
                            method: "GET",
         | 
| 1113 | 
             
                            headers: {
         | 
    	
        kimi-js/kimi-memory-system.js
    CHANGED
    
    | @@ -840,8 +840,7 @@ class KimiMemorySystem { | |
| 840 | 
             
                    if (memoryData.content && memoryData.content.length > 24) importance += 0.05;
         | 
| 841 | 
             
                    if (memoryData.confidence && memoryData.confidence > 0.9) importance += 0.05;
         | 
| 842 |  | 
| 843 | 
            -
                     | 
| 844 | 
            -
                    return Math.min(1.0, Math.round(importance * 100) / 100);
         | 
| 845 | 
             
                }
         | 
| 846 |  | 
| 847 | 
             
                // Derive semantic tags from memory content to assist prioritization and merging
         | 
|  | |
| 840 | 
             
                    if (memoryData.content && memoryData.content.length > 24) importance += 0.05;
         | 
| 841 | 
             
                    if (memoryData.confidence && memoryData.confidence > 0.9) importance += 0.05;
         | 
| 842 |  | 
| 843 | 
            +
                    return Math.min(1.0, importance);
         | 
|  | |
| 844 | 
             
                }
         | 
| 845 |  | 
| 846 | 
             
                // Derive semantic tags from memory content to assist prioritization and merging
         | 
    	
        kimi-js/kimi-module.js
    CHANGED
    
    | @@ -747,10 +747,11 @@ async function loadSettingsData() { | |
| 747 | 
             
                        "voicePitch",
         | 
| 748 | 
             
                        "voiceVolume",
         | 
| 749 | 
             
                        "selectedLanguage",
         | 
| 750 | 
            -
                        " | 
| 751 | 
             
                        "llmProvider",
         | 
| 752 | 
             
                        "llmBaseUrl",
         | 
| 753 | 
             
                        "llmModelId",
         | 
|  | |
| 754 | 
             
                        "selectedCharacter",
         | 
| 755 | 
             
                        "llmTemperature",
         | 
| 756 | 
             
                        "llmMaxTokens",
         | 
| @@ -765,10 +766,11 @@ async function loadSettingsData() { | |
| 765 | 
             
                    const voicePitch = preferences.voicePitch !== undefined ? preferences.voicePitch : 1.1;
         | 
| 766 | 
             
                    const voiceVolume = preferences.voiceVolume !== undefined ? preferences.voiceVolume : 0.8;
         | 
| 767 | 
             
                    const selectedLanguage = preferences.selectedLanguage || "en";
         | 
| 768 | 
            -
                    const apiKey = preferences. | 
| 769 | 
             
                    const provider = preferences.llmProvider || "openrouter";
         | 
| 770 | 
             
                    const baseUrl = preferences.llmBaseUrl || "https://openrouter.ai/api/v1/chat/completions";
         | 
| 771 | 
             
                    const modelId = preferences.llmModelId || (window.kimiLLM ? window.kimiLLM.currentModel : "");
         | 
|  | |
| 772 | 
             
                    const selectedCharacter = preferences.selectedCharacter || "kimi";
         | 
| 773 | 
             
                    const llmTemperature = preferences.llmTemperature !== undefined ? preferences.llmTemperature : 0.8;
         | 
| 774 | 
             
                    const llmMaxTokens = preferences.llmMaxTokens !== undefined ? preferences.llmMaxTokens : 400;
         | 
| @@ -1339,17 +1341,10 @@ async function sendMessage() { | |
| 1339 |  | 
| 1340 | 
             
                try {
         | 
| 1341 | 
             
                    const response = await analyzeAndReact(message);
         | 
| 1342 | 
            -
                    let finalResponse = response;
         | 
| 1343 | 
            -
                    // Si la réponse du LLM est vide, nulle ou trop courte, utilise le fallback émotionnel
         | 
| 1344 | 
            -
                    if (!finalResponse || typeof finalResponse !== "string" || finalResponse.trim().length < 2) {
         | 
| 1345 | 
            -
                        finalResponse = window.getLocalizedEmotionalResponse
         | 
| 1346 | 
            -
                            ? window.getLocalizedEmotionalResponse("neutral")
         | 
| 1347 | 
            -
                            : "I'm here for you!";
         | 
| 1348 | 
            -
                    }
         | 
| 1349 | 
             
                    setTimeout(() => {
         | 
| 1350 | 
            -
                        addMessageToChat("kimi",  | 
| 1351 | 
             
                        if (window.voiceManager && !message.startsWith("Vous:")) {
         | 
| 1352 | 
            -
                            window.voiceManager.speak( | 
| 1353 | 
             
                        }
         | 
| 1354 | 
             
                        if (waitingIndicator) waitingIndicator.style.display = "none";
         | 
| 1355 | 
             
                    }, 1000);
         | 
| @@ -1616,7 +1611,7 @@ function setupSettingsListeners(kimiDB, kimiMemory) { | |
| 1616 | 
             
                        if (kimiDB) await kimiDB.setPreference("colorTheme", e.target.value);
         | 
| 1617 | 
             
                        if (window.kimiAppearanceManager && window.kimiAppearanceManager.changeTheme)
         | 
| 1618 | 
             
                            await window.kimiAppearanceManager.changeTheme(e.target.value);
         | 
| 1619 | 
            -
                         | 
| 1620 | 
             
                    };
         | 
| 1621 | 
             
                    colorThemeSelect.addEventListener("change", window._kimiColorThemeListener);
         | 
| 1622 | 
             
                }
         | 
|  | |
| 747 | 
             
                        "voicePitch",
         | 
| 748 | 
             
                        "voiceVolume",
         | 
| 749 | 
             
                        "selectedLanguage",
         | 
| 750 | 
            +
                        "openrouterApiKey",
         | 
| 751 | 
             
                        "llmProvider",
         | 
| 752 | 
             
                        "llmBaseUrl",
         | 
| 753 | 
             
                        "llmModelId",
         | 
| 754 | 
            +
                        "llmApiKey",
         | 
| 755 | 
             
                        "selectedCharacter",
         | 
| 756 | 
             
                        "llmTemperature",
         | 
| 757 | 
             
                        "llmMaxTokens",
         | 
|  | |
| 766 | 
             
                    const voicePitch = preferences.voicePitch !== undefined ? preferences.voicePitch : 1.1;
         | 
| 767 | 
             
                    const voiceVolume = preferences.voiceVolume !== undefined ? preferences.voiceVolume : 0.8;
         | 
| 768 | 
             
                    const selectedLanguage = preferences.selectedLanguage || "en";
         | 
| 769 | 
            +
                    const apiKey = preferences.openrouterApiKey || "";
         | 
| 770 | 
             
                    const provider = preferences.llmProvider || "openrouter";
         | 
| 771 | 
             
                    const baseUrl = preferences.llmBaseUrl || "https://openrouter.ai/api/v1/chat/completions";
         | 
| 772 | 
             
                    const modelId = preferences.llmModelId || (window.kimiLLM ? window.kimiLLM.currentModel : "");
         | 
| 773 | 
            +
                    const genericKey = preferences.llmApiKey || "";
         | 
| 774 | 
             
                    const selectedCharacter = preferences.selectedCharacter || "kimi";
         | 
| 775 | 
             
                    const llmTemperature = preferences.llmTemperature !== undefined ? preferences.llmTemperature : 0.8;
         | 
| 776 | 
             
                    const llmMaxTokens = preferences.llmMaxTokens !== undefined ? preferences.llmMaxTokens : 400;
         | 
|  | |
| 1341 |  | 
| 1342 | 
             
                try {
         | 
| 1343 | 
             
                    const response = await analyzeAndReact(message);
         | 
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
| 1344 | 
             
                    setTimeout(() => {
         | 
| 1345 | 
            +
                        addMessageToChat("kimi", response);
         | 
| 1346 | 
             
                        if (window.voiceManager && !message.startsWith("Vous:")) {
         | 
| 1347 | 
            +
                            window.voiceManager.speak(response);
         | 
| 1348 | 
             
                        }
         | 
| 1349 | 
             
                        if (waitingIndicator) waitingIndicator.style.display = "none";
         | 
| 1350 | 
             
                    }, 1000);
         | 
|  | |
| 1611 | 
             
                        if (kimiDB) await kimiDB.setPreference("colorTheme", e.target.value);
         | 
| 1612 | 
             
                        if (window.kimiAppearanceManager && window.kimiAppearanceManager.changeTheme)
         | 
| 1613 | 
             
                            await window.kimiAppearanceManager.changeTheme(e.target.value);
         | 
| 1614 | 
            +
                        if (window.KimiPluginManager && window.KimiPluginManager.loadPlugins) await window.KimiPluginManager.loadPlugins();
         | 
| 1615 | 
             
                    };
         | 
| 1616 | 
             
                    colorThemeSelect.addEventListener("change", window._kimiColorThemeListener);
         | 
| 1617 | 
             
                }
         | 
    	
        kimi-js/kimi-script.js
    CHANGED
    
    | @@ -80,7 +80,7 @@ document.addEventListener("DOMContentLoaded", async function () { | |
| 80 | 
             
                const ApiUi = {
         | 
| 81 | 
             
                    presenceDot: () => document.getElementById("api-key-presence"),
         | 
| 82 | 
             
                    presenceDotTest: () => document.getElementById("api-key-presence-test"),
         | 
| 83 | 
            -
                    apiKeyInput: () => document.getElementById(" | 
| 84 | 
             
                    toggleBtn: () => document.getElementById("toggle-api-key"),
         | 
| 85 | 
             
                    providerSelect: () => document.getElementById("llm-provider"),
         | 
| 86 | 
             
                    baseUrlInput: () => document.getElementById("llm-base-url"),
         | 
| @@ -150,9 +150,7 @@ document.addEventListener("DOMContentLoaded", async function () { | |
| 150 | 
             
                            }
         | 
| 151 | 
             
                        }
         | 
| 152 | 
             
                        // Load the provider-specific key
         | 
| 153 | 
            -
                        const keyPref = window.KimiProviderUtils
         | 
| 154 | 
            -
                            ? window.KimiProviderUtils.getKeyPrefForProvider(provider)
         | 
| 155 | 
            -
                            : "providerApiKey";
         | 
| 156 | 
             
                        const storedKey = await window.kimiDB.getPreference(keyPref, "");
         | 
| 157 | 
             
                        if (apiKeyInput) apiKeyInput.value = storedKey || "";
         | 
| 158 | 
             
                        ApiUi.setPresence(storedKey ? "#4caf50" : "#9e9e9e");
         | 
| @@ -243,18 +241,7 @@ document.addEventListener("DOMContentLoaded", async function () { | |
| 243 | 
             
                            baseUrlInput.placeholder = p.url;
         | 
| 244 | 
             
                            baseUrlInput.value = provider === "openrouter" ? placeholders.openrouter.url : p.url;
         | 
| 245 | 
             
                        }
         | 
| 246 | 
            -
                        if (apiKeyInput)  | 
| 247 | 
            -
                            apiKeyInput.placeholder = p.keyPh;
         | 
| 248 | 
            -
                            // Masquer/désactiver le champ pour Ollama/local
         | 
| 249 | 
            -
                            if (provider === "ollama") {
         | 
| 250 | 
            -
                                apiKeyInput.value = "";
         | 
| 251 | 
            -
                                apiKeyInput.disabled = true;
         | 
| 252 | 
            -
                                apiKeyInput.style.display = "none";
         | 
| 253 | 
            -
                            } else {
         | 
| 254 | 
            -
                                apiKeyInput.disabled = false;
         | 
| 255 | 
            -
                                apiKeyInput.style.display = "";
         | 
| 256 | 
            -
                            }
         | 
| 257 | 
            -
                        }
         | 
| 258 | 
             
                        if (modelIdInput) {
         | 
| 259 | 
             
                            modelIdInput.placeholder = p.model;
         | 
| 260 | 
             
                            // Only populate the field for OpenRouter since those are the models we have in the list
         | 
| @@ -266,11 +253,9 @@ document.addEventListener("DOMContentLoaded", async function () { | |
| 266 | 
             
                            await window.kimiDB.setPreference("llmBaseUrl", provider === "openrouter" ? placeholders.openrouter.url : p.url);
         | 
| 267 | 
             
                            const apiKeyLabel = document.getElementById("api-key-label");
         | 
| 268 | 
             
                            // Load provider-specific key into the input for clarity
         | 
| 269 | 
            -
                            const keyPref = window.KimiProviderUtils
         | 
| 270 | 
            -
             | 
| 271 | 
            -
             | 
| 272 | 
            -
                            const storedKey = await window.kimiDB.getPreference("providerApiKey", "");
         | 
| 273 | 
            -
                            if (apiKeyInput && provider !== "ollama") apiKeyInput.value = storedKey || "";
         | 
| 274 | 
             
                            const color = provider === "ollama" ? "#9e9e9e" : storedKey && storedKey.length > 0 ? "#4caf50" : "#9e9e9e";
         | 
| 275 | 
             
                            ApiUi.setPresence(color);
         | 
| 276 | 
             
                            // Changing provider invalidates previous test state
         | 
| @@ -288,13 +273,7 @@ document.addEventListener("DOMContentLoaded", async function () { | |
| 288 | 
             
                                    : "API Key";
         | 
| 289 | 
             
                            }
         | 
| 290 | 
             
                            const savedBadge = ApiUi.savedBadge();
         | 
| 291 | 
            -
                            if (savedBadge)  | 
| 292 | 
            -
                                if (provider !== "ollama" && storedKey) {
         | 
| 293 | 
            -
                                    savedBadge.style.display = "inline";
         | 
| 294 | 
            -
                                } else {
         | 
| 295 | 
            -
                                    savedBadge.style.display = "none";
         | 
| 296 | 
            -
                                }
         | 
| 297 | 
            -
                            }
         | 
| 298 | 
             
                            ApiUi.clearStatus();
         | 
| 299 | 
             
                        }
         | 
| 300 | 
             
                    });
         | 
| @@ -383,10 +362,6 @@ document.addEventListener("DOMContentLoaded", async function () { | |
| 383 |  | 
| 384 | 
             
                            await window.kimiDB.setSelectedCharacter(charKey);
         | 
| 385 | 
             
                            await window.kimiDB.setSystemPromptForCharacter(charKey, prompt);
         | 
| 386 | 
            -
                            // Ensure memory system uses the correct character
         | 
| 387 | 
            -
                            if (window.kimiMemorySystem) {
         | 
| 388 | 
            -
                                window.kimiMemorySystem.selectedCharacter = charKey;
         | 
| 389 | 
            -
                            }
         | 
| 390 | 
             
                            if (window.kimiVideo && window.kimiVideo.setCharacter) {
         | 
| 391 | 
             
                                window.kimiVideo.setCharacter(charKey);
         | 
| 392 | 
             
                                if (window.kimiVideo.switchToContext) {
         | 
| @@ -403,10 +378,6 @@ document.addEventListener("DOMContentLoaded", async function () { | |
| 403 | 
             
                                    settingsPanel.scrollTop = scrollTop;
         | 
| 404 | 
             
                                });
         | 
| 405 | 
             
                            }
         | 
| 406 | 
            -
                            // Refresh memory tab after character selection
         | 
| 407 | 
            -
                            if (window.kimiMemoryUI && typeof window.kimiMemoryUI.updateMemoryStats === "function") {
         | 
| 408 | 
            -
                                await window.kimiMemoryUI.updateMemoryStats();
         | 
| 409 | 
            -
                            }
         | 
| 410 | 
             
                            saveCharacterBtn.setAttribute("data-i18n", "saved");
         | 
| 411 | 
             
                            saveCharacterBtn.classList.add("success");
         | 
| 412 | 
             
                            saveCharacterBtn.disabled = true;
         | 
| @@ -586,67 +557,19 @@ document.addEventListener("DOMContentLoaded", async function () { | |
| 586 | 
             
                if (testApiButton) {
         | 
| 587 | 
             
                    testApiButton.addEventListener("click", async () => {
         | 
| 588 | 
             
                        const statusSpan = ApiUi.statusSpan();
         | 
| 589 | 
            -
                        const apiKeyInput = ApiUi.apiKeyInput();
         | 
| 590 | 
            -
                        const apiKey = apiKeyInput ? apiKeyInput.value.trim() : "";
         | 
| 591 | 
             
                        const providerSelect = ApiUi.providerSelect();
         | 
| 592 | 
            -
                        const baseUrlInput = ApiUi.baseUrlInput();
         | 
| 593 | 
            -
                        const modelIdInput = ApiUi.modelIdInput();
         | 
| 594 | 
             
                        const provider = providerSelect ? providerSelect.value : "openrouter";
         | 
| 595 | 
            -
                        const  | 
| 596 | 
            -
                        const modelId = modelIdInput ? modelIdInput.value.trim() : "";
         | 
| 597 | 
            -
             | 
| 598 | 
             
                        if (!statusSpan) return;
         | 
| 599 | 
            -
             | 
| 600 | 
            -
                        if (provider !== "ollama" && !apiKey) {
         | 
| 601 | 
            -
                            statusSpan.textContent = window.kimiI18nManager?.t("api_key_missing") || "API key missing";
         | 
| 602 | 
            -
                            statusSpan.style.color = "#ff6b6b";
         | 
| 603 | 
            -
                            return;
         | 
| 604 | 
            -
                        }
         | 
| 605 | 
            -
             | 
| 606 | 
            -
                        // Validate API key format before saving/testing
         | 
| 607 | 
            -
                        if (provider !== "ollama") {
         | 
| 608 | 
            -
                            const isValid = (window.KIMI_VALIDATORS && window.KIMI_VALIDATORS.validateApiKey(apiKey)) || false;
         | 
| 609 | 
            -
                            if (!isValid) {
         | 
| 610 | 
            -
                                statusSpan.textContent =
         | 
| 611 | 
            -
                                    window.kimiI18nManager?.t("api_key_invalid_format") ||
         | 
| 612 | 
            -
                                    "Invalid API key format (must start with sk-or-v1-)";
         | 
| 613 | 
            -
                                statusSpan.style.color = "#ff6b6b";
         | 
| 614 | 
            -
                                return;
         | 
| 615 | 
            -
                            }
         | 
| 616 | 
            -
                        }
         | 
| 617 | 
            -
             | 
| 618 | 
            -
                        if (window.kimiDB) {
         | 
| 619 | 
            -
                            // Save API key under provider-specific preference key (skip for Ollama)
         | 
| 620 | 
            -
                            if (provider !== "ollama") {
         | 
| 621 | 
            -
                                const keyPref = window.KimiProviderUtils
         | 
| 622 | 
            -
                                    ? window.KimiProviderUtils.getKeyPrefForProvider(provider)
         | 
| 623 | 
            -
                                    : "providerApiKey";
         | 
| 624 | 
            -
                                await window.kimiDB.setPreference(keyPref, apiKey);
         | 
| 625 | 
            -
                            }
         | 
| 626 | 
            -
                            await window.kimiDB.setPreference("llmProvider", provider);
         | 
| 627 | 
            -
                            if (baseUrl) await window.kimiDB.setPreference("llmBaseUrl", baseUrl);
         | 
| 628 | 
            -
                            if (modelId) await window.kimiDB.setPreference("llmModelId", modelId);
         | 
| 629 | 
            -
                        }
         | 
| 630 | 
            -
             | 
| 631 | 
             
                        statusSpan.textContent = "Testing in progress...";
         | 
| 632 | 
             
                        statusSpan.style.color = "#ffa726";
         | 
| 633 | 
            -
             | 
| 634 | 
             
                        try {
         | 
| 635 | 
             
                            if (window.kimiLLM) {
         | 
| 636 | 
            -
                                 | 
| 637 | 
            -
                                if (provider === "openrouter") {
         | 
| 638 | 
            -
                                    result = await window.kimiLLM.testModel(window.kimiLLM.currentModel, "Bonjour");
         | 
| 639 | 
            -
                                } else if (provider === "ollama") {
         | 
| 640 | 
            -
                                    const response = await window.kimiLLM.chatWithLocal("Bonjour", { maxTokens: 2 });
         | 
| 641 | 
            -
                                    result = { success: true, response };
         | 
| 642 | 
            -
                                } else {
         | 
| 643 | 
            -
                                    const response = await window.kimiLLM.chatWithOpenAICompatible("Bonjour", { maxTokens: 2 });
         | 
| 644 | 
            -
                                    result = { success: true, response };
         | 
| 645 | 
            -
                                }
         | 
| 646 | 
             
                                if (result.success) {
         | 
| 647 | 
             
                                    statusSpan.textContent = "Connection successful!";
         | 
| 648 | 
             
                                    statusSpan.style.color = "#4caf50";
         | 
| 649 | 
            -
                                    // Only show saved badge if an actual non-empty API key is stored and provider requires one
         | 
| 650 | 
             
                                    const savedBadge = ApiUi.savedBadge();
         | 
| 651 | 
             
                                    if (savedBadge) {
         | 
| 652 | 
             
                                        const apiKeyInputEl = ApiUi.apiKeyInput();
         | 
| @@ -659,21 +582,16 @@ document.addEventListener("DOMContentLoaded", async function () { | |
| 659 | 
             
                                            savedBadge.style.display = "none";
         | 
| 660 | 
             
                                        }
         | 
| 661 | 
             
                                    }
         | 
| 662 | 
            -
             | 
| 663 | 
             
                                    if (result.response) {
         | 
| 664 | 
             
                                        setTimeout(() => {
         | 
| 665 | 
             
                                            statusSpan.textContent = `Test response: \"${result.response.substring(0, 50)}...\"`;
         | 
| 666 | 
             
                                        }, 1000);
         | 
| 667 | 
             
                                    }
         | 
| 668 | 
            -
                                    // Mark test success explicitly
         | 
| 669 | 
             
                                    ApiUi.setTestPresence("#4caf50");
         | 
| 670 | 
             
                                } else {
         | 
| 671 | 
             
                                    statusSpan.textContent = `${result.error}`;
         | 
| 672 | 
             
                                    statusSpan.style.color = "#ff6b6b";
         | 
| 673 | 
             
                                    ApiUi.setTestPresence("#9e9e9e");
         | 
| 674 | 
            -
                                    if (result.error.includes("similaires disponibles")) {
         | 
| 675 | 
            -
                                        setTimeout(() => {}, 1000);
         | 
| 676 | 
            -
                                    }
         | 
| 677 | 
             
                                }
         | 
| 678 | 
             
                            } else {
         | 
| 679 | 
             
                                statusSpan.textContent = "LLM manager not initialized";
         | 
| @@ -685,10 +603,6 @@ document.addEventListener("DOMContentLoaded", async function () { | |
| 685 | 
             
                            statusSpan.textContent = `Error: ${error.message}`;
         | 
| 686 | 
             
                            statusSpan.style.color = "#ff6b6b";
         | 
| 687 | 
             
                            ApiUi.setTestPresence("#9e9e9e");
         | 
| 688 | 
            -
             | 
| 689 | 
            -
                            if (error.message.includes("non disponible")) {
         | 
| 690 | 
            -
                                setTimeout(() => {}, 1000);
         | 
| 691 | 
            -
                            }
         | 
| 692 | 
             
                        }
         | 
| 693 | 
             
                    });
         | 
| 694 | 
             
                }
         | 
| @@ -703,9 +617,7 @@ document.addEventListener("DOMContentLoaded", async function () { | |
| 703 | 
             
                        t = setTimeout(async () => {
         | 
| 704 | 
             
                            const providerEl = ApiUi.providerSelect();
         | 
| 705 | 
             
                            const provider = providerEl ? providerEl.value : "openrouter";
         | 
| 706 | 
            -
                            const keyPref = window.KimiProviderUtils
         | 
| 707 | 
            -
                                ? window.KimiProviderUtils.getKeyPrefForProvider(provider)
         | 
| 708 | 
            -
                                : "providerApiKey";
         | 
| 709 | 
             
                            const value = input.value.trim();
         | 
| 710 | 
             
                            // Update Test button state immediately
         | 
| 711 | 
             
                            const validNow = !!(window.KIMI_VALIDATORS && window.KIMI_VALIDATORS.validateApiKey(value));
         | 
|  | |
| 80 | 
             
                const ApiUi = {
         | 
| 81 | 
             
                    presenceDot: () => document.getElementById("api-key-presence"),
         | 
| 82 | 
             
                    presenceDotTest: () => document.getElementById("api-key-presence-test"),
         | 
| 83 | 
            +
                    apiKeyInput: () => document.getElementById("openrouter-api-key"),
         | 
| 84 | 
             
                    toggleBtn: () => document.getElementById("toggle-api-key"),
         | 
| 85 | 
             
                    providerSelect: () => document.getElementById("llm-provider"),
         | 
| 86 | 
             
                    baseUrlInput: () => document.getElementById("llm-base-url"),
         | 
|  | |
| 150 | 
             
                            }
         | 
| 151 | 
             
                        }
         | 
| 152 | 
             
                        // Load the provider-specific key
         | 
| 153 | 
            +
                        const keyPref = window.KimiProviderUtils ? window.KimiProviderUtils.getKeyPrefForProvider(provider) : "llmApiKey";
         | 
|  | |
|  | |
| 154 | 
             
                        const storedKey = await window.kimiDB.getPreference(keyPref, "");
         | 
| 155 | 
             
                        if (apiKeyInput) apiKeyInput.value = storedKey || "";
         | 
| 156 | 
             
                        ApiUi.setPresence(storedKey ? "#4caf50" : "#9e9e9e");
         | 
|  | |
| 241 | 
             
                            baseUrlInput.placeholder = p.url;
         | 
| 242 | 
             
                            baseUrlInput.value = provider === "openrouter" ? placeholders.openrouter.url : p.url;
         | 
| 243 | 
             
                        }
         | 
| 244 | 
            +
                        if (apiKeyInput) apiKeyInput.placeholder = p.keyPh;
         | 
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
| 245 | 
             
                        if (modelIdInput) {
         | 
| 246 | 
             
                            modelIdInput.placeholder = p.model;
         | 
| 247 | 
             
                            // Only populate the field for OpenRouter since those are the models we have in the list
         | 
|  | |
| 253 | 
             
                            await window.kimiDB.setPreference("llmBaseUrl", provider === "openrouter" ? placeholders.openrouter.url : p.url);
         | 
| 254 | 
             
                            const apiKeyLabel = document.getElementById("api-key-label");
         | 
| 255 | 
             
                            // Load provider-specific key into the input for clarity
         | 
| 256 | 
            +
                            const keyPref = window.KimiProviderUtils ? window.KimiProviderUtils.getKeyPrefForProvider(provider) : "llmApiKey";
         | 
| 257 | 
            +
                            const storedKey = await window.kimiDB.getPreference(keyPref, "");
         | 
| 258 | 
            +
                            if (apiKeyInput) apiKeyInput.value = storedKey || "";
         | 
|  | |
|  | |
| 259 | 
             
                            const color = provider === "ollama" ? "#9e9e9e" : storedKey && storedKey.length > 0 ? "#4caf50" : "#9e9e9e";
         | 
| 260 | 
             
                            ApiUi.setPresence(color);
         | 
| 261 | 
             
                            // Changing provider invalidates previous test state
         | 
|  | |
| 273 | 
             
                                    : "API Key";
         | 
| 274 | 
             
                            }
         | 
| 275 | 
             
                            const savedBadge = ApiUi.savedBadge();
         | 
| 276 | 
            +
                            if (savedBadge) savedBadge.style.display = "none";
         | 
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
| 277 | 
             
                            ApiUi.clearStatus();
         | 
| 278 | 
             
                        }
         | 
| 279 | 
             
                    });
         | 
|  | |
| 362 |  | 
| 363 | 
             
                            await window.kimiDB.setSelectedCharacter(charKey);
         | 
| 364 | 
             
                            await window.kimiDB.setSystemPromptForCharacter(charKey, prompt);
         | 
|  | |
|  | |
|  | |
|  | |
| 365 | 
             
                            if (window.kimiVideo && window.kimiVideo.setCharacter) {
         | 
| 366 | 
             
                                window.kimiVideo.setCharacter(charKey);
         | 
| 367 | 
             
                                if (window.kimiVideo.switchToContext) {
         | 
|  | |
| 378 | 
             
                                    settingsPanel.scrollTop = scrollTop;
         | 
| 379 | 
             
                                });
         | 
| 380 | 
             
                            }
         | 
|  | |
|  | |
|  | |
|  | |
| 381 | 
             
                            saveCharacterBtn.setAttribute("data-i18n", "saved");
         | 
| 382 | 
             
                            saveCharacterBtn.classList.add("success");
         | 
| 383 | 
             
                            saveCharacterBtn.disabled = true;
         | 
|  | |
| 557 | 
             
                if (testApiButton) {
         | 
| 558 | 
             
                    testApiButton.addEventListener("click", async () => {
         | 
| 559 | 
             
                        const statusSpan = ApiUi.statusSpan();
         | 
|  | |
|  | |
| 560 | 
             
                        const providerSelect = ApiUi.providerSelect();
         | 
|  | |
|  | |
| 561 | 
             
                        const provider = providerSelect ? providerSelect.value : "openrouter";
         | 
| 562 | 
            +
                        const modelIdInput = ApiUi.modelIdInput();
         | 
| 563 | 
            +
                        const modelId = modelIdInput ? modelIdInput.value.trim() : window.kimiLLM ? window.kimiLLM.currentModel : "model-id";
         | 
|  | |
| 564 | 
             
                        if (!statusSpan) return;
         | 
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
| 565 | 
             
                        statusSpan.textContent = "Testing in progress...";
         | 
| 566 | 
             
                        statusSpan.style.color = "#ffa726";
         | 
|  | |
| 567 | 
             
                        try {
         | 
| 568 | 
             
                            if (window.kimiLLM) {
         | 
| 569 | 
            +
                                const result = await window.kimiLLM.testApiKeyMinimal(modelId);
         | 
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
| 570 | 
             
                                if (result.success) {
         | 
| 571 | 
             
                                    statusSpan.textContent = "Connection successful!";
         | 
| 572 | 
             
                                    statusSpan.style.color = "#4caf50";
         | 
|  | |
| 573 | 
             
                                    const savedBadge = ApiUi.savedBadge();
         | 
| 574 | 
             
                                    if (savedBadge) {
         | 
| 575 | 
             
                                        const apiKeyInputEl = ApiUi.apiKeyInput();
         | 
|  | |
| 582 | 
             
                                            savedBadge.style.display = "none";
         | 
| 583 | 
             
                                        }
         | 
| 584 | 
             
                                    }
         | 
|  | |
| 585 | 
             
                                    if (result.response) {
         | 
| 586 | 
             
                                        setTimeout(() => {
         | 
| 587 | 
             
                                            statusSpan.textContent = `Test response: \"${result.response.substring(0, 50)}...\"`;
         | 
| 588 | 
             
                                        }, 1000);
         | 
| 589 | 
             
                                    }
         | 
|  | |
| 590 | 
             
                                    ApiUi.setTestPresence("#4caf50");
         | 
| 591 | 
             
                                } else {
         | 
| 592 | 
             
                                    statusSpan.textContent = `${result.error}`;
         | 
| 593 | 
             
                                    statusSpan.style.color = "#ff6b6b";
         | 
| 594 | 
             
                                    ApiUi.setTestPresence("#9e9e9e");
         | 
|  | |
|  | |
|  | |
| 595 | 
             
                                }
         | 
| 596 | 
             
                            } else {
         | 
| 597 | 
             
                                statusSpan.textContent = "LLM manager not initialized";
         | 
|  | |
| 603 | 
             
                            statusSpan.textContent = `Error: ${error.message}`;
         | 
| 604 | 
             
                            statusSpan.style.color = "#ff6b6b";
         | 
| 605 | 
             
                            ApiUi.setTestPresence("#9e9e9e");
         | 
|  | |
|  | |
|  | |
|  | |
| 606 | 
             
                        }
         | 
| 607 | 
             
                    });
         | 
| 608 | 
             
                }
         | 
|  | |
| 617 | 
             
                        t = setTimeout(async () => {
         | 
| 618 | 
             
                            const providerEl = ApiUi.providerSelect();
         | 
| 619 | 
             
                            const provider = providerEl ? providerEl.value : "openrouter";
         | 
| 620 | 
            +
                            const keyPref = window.KimiProviderUtils ? window.KimiProviderUtils.getKeyPrefForProvider(provider) : "llmApiKey";
         | 
|  | |
|  | |
| 621 | 
             
                            const value = input.value.trim();
         | 
| 622 | 
             
                            // Update Test button state immediately
         | 
| 623 | 
             
                            const validNow = !!(window.KIMI_VALIDATORS && window.KIMI_VALIDATORS.validateApiKey(value));
         | 
    	
        kimi-js/kimi-utils.js
    CHANGED
    
    | @@ -25,7 +25,7 @@ window.KimiValidationUtils = { | |
| 25 | 
             
                        voicePitch: { min: 0, max: 2, def: 1.0 },
         | 
| 26 | 
             
                        voiceVolume: { min: 0, max: 1, def: 0.8 },
         | 
| 27 | 
             
                        llmTemperature: { min: 0, max: 1, def: 0.8 },
         | 
| 28 | 
            -
                        llmMaxTokens: { min: 1, max:  | 
| 29 | 
             
                        llmTopP: { min: 0, max: 1, def: 0.9 },
         | 
| 30 | 
             
                        llmFrequencyPenalty: { min: 0, max: 2, def: 0.6 },
         | 
| 31 | 
             
                        llmPresencePenalty: { min: 0, max: 2, def: 0.5 },
         | 
| @@ -43,14 +43,26 @@ window.KimiValidationUtils = { | |
| 43 |  | 
| 44 | 
             
            // Provider utilities used across the app
         | 
| 45 | 
             
            const KimiProviderUtils = {
         | 
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
| 46 | 
             
                getKeyPrefForProvider(provider) {
         | 
| 47 | 
            -
                     | 
| 48 | 
            -
                    return provider === "ollama" ? null : "providerApiKey";
         | 
| 49 | 
             
                },
         | 
| 50 | 
             
                async getApiKey(db, provider) {
         | 
| 51 | 
             
                    if (!db) return null;
         | 
| 52 | 
             
                    if (provider === "ollama") return "__local__";
         | 
| 53 | 
            -
                     | 
|  | |
|  | |
|  | |
| 54 | 
             
                },
         | 
| 55 | 
             
                getLabelForProvider(provider) {
         | 
| 56 | 
             
                    const labels = {
         | 
| @@ -201,6 +213,8 @@ class KimiSecurityUtils { | |
| 201 | 
             
                    }
         | 
| 202 | 
             
                    return key.trim().length > 10 && (key.startsWith("sk-") || key.startsWith("sk-or-"));
         | 
| 203 | 
             
                }
         | 
|  | |
|  | |
| 204 | 
             
            }
         | 
| 205 |  | 
| 206 | 
             
            // Cache management for better performance
         | 
| @@ -370,7 +384,14 @@ class KimiVideoManager { | |
| 370 | 
             
                    this._consecutiveErrorCount = 0;
         | 
| 371 | 
             
                }
         | 
| 372 |  | 
| 373 | 
            -
                 | 
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
| 374 | 
             
                static crossfadeVideos(fromVideo, toVideo, duration = 300, onComplete) {
         | 
| 375 | 
             
                    // Resolve duration from CSS variable if present
         | 
| 376 | 
             
                    try {
         | 
| @@ -418,7 +439,12 @@ class KimiVideoManager { | |
| 418 | 
             
                    if (fromVideo.paused) fromVideo.play().catch(() => {});
         | 
| 419 | 
             
                }
         | 
| 420 |  | 
| 421 | 
            -
                 | 
|  | |
|  | |
|  | |
|  | |
|  | |
| 422 | 
             
                static createVideoElement(id, className = "bg-video") {
         | 
| 423 | 
             
                    const video = document.createElement("video");
         | 
| 424 | 
             
                    video.id = id;
         | 
| @@ -433,7 +459,11 @@ class KimiVideoManager { | |
| 433 | 
             
                    return video;
         | 
| 434 | 
             
                }
         | 
| 435 |  | 
| 436 | 
            -
                 | 
|  | |
|  | |
|  | |
|  | |
| 437 | 
             
                static getVideoElement(selector) {
         | 
| 438 | 
             
                    if (typeof selector === "string") {
         | 
| 439 | 
             
                        if (selector.startsWith("#")) {
         | 
| @@ -961,6 +991,7 @@ class KimiVideoManager { | |
| 961 | 
             
                }
         | 
| 962 |  | 
| 963 | 
             
                // keep only the augmented determineCategory above (with traits)
         | 
|  | |
| 964 | 
             
                selectOptimalVideo(category, specificVideo = null, traits = null, affection = null, emotion = null) {
         | 
| 965 | 
             
                    const availableVideos = this.videoCategories[category] || this.videoCategories.neutral;
         | 
| 966 |  | 
| @@ -1181,7 +1212,7 @@ class KimiVideoManager { | |
| 1181 | 
             
                    this.isEmotionVideoPlaying = false;
         | 
| 1182 | 
             
                    this.currentEmotionContext = null;
         | 
| 1183 |  | 
| 1184 | 
            -
                    //  | 
| 1185 | 
             
                    const category = "neutral";
         | 
| 1186 | 
             
                    const currentVideoSrc = this.activeVideo.querySelector("source").getAttribute("src");
         | 
| 1187 | 
             
                    const available = this.videoCategories[category] || [];
         | 
| @@ -1218,6 +1249,7 @@ class KimiVideoManager { | |
| 1218 | 
             
                }
         | 
| 1219 |  | 
| 1220 | 
             
                // ADVANCED CONTEXTUAL ANALYSIS
         | 
|  | |
| 1221 | 
             
                async analyzeAndSelectVideo(userMessage, kimiResponse, emotionAnalysis, traits = null, affection = null, lang = null) {
         | 
| 1222 | 
             
                    // Do not analyze-switch away while dancing is sticky/playing
         | 
| 1223 | 
             
                    if (this._stickyContext === "dancing" || this.currentContext === "dancing") {
         | 
|  | |
| 25 | 
             
                        voicePitch: { min: 0, max: 2, def: 1.0 },
         | 
| 26 | 
             
                        voiceVolume: { min: 0, max: 1, def: 0.8 },
         | 
| 27 | 
             
                        llmTemperature: { min: 0, max: 1, def: 0.8 },
         | 
| 28 | 
            +
                        llmMaxTokens: { min: 1, max: 8192, def: 400 },
         | 
| 29 | 
             
                        llmTopP: { min: 0, max: 1, def: 0.9 },
         | 
| 30 | 
             
                        llmFrequencyPenalty: { min: 0, max: 2, def: 0.6 },
         | 
| 31 | 
             
                        llmPresencePenalty: { min: 0, max: 2, def: 0.5 },
         | 
|  | |
| 43 |  | 
| 44 | 
             
            // Provider utilities used across the app
         | 
| 45 | 
             
            const KimiProviderUtils = {
         | 
| 46 | 
            +
                keyPrefMap: {
         | 
| 47 | 
            +
                    openrouter: "openrouterApiKey",
         | 
| 48 | 
            +
                    openai: "apiKey_openai",
         | 
| 49 | 
            +
                    groq: "apiKey_groq",
         | 
| 50 | 
            +
                    together: "apiKey_together",
         | 
| 51 | 
            +
                    deepseek: "apiKey_deepseek",
         | 
| 52 | 
            +
                    custom: "apiKey_custom",
         | 
| 53 | 
            +
                    "openai-compatible": "llmApiKey",
         | 
| 54 | 
            +
                    ollama: null
         | 
| 55 | 
            +
                },
         | 
| 56 | 
             
                getKeyPrefForProvider(provider) {
         | 
| 57 | 
            +
                    return this.keyPrefMap[provider] || "llmApiKey";
         | 
|  | |
| 58 | 
             
                },
         | 
| 59 | 
             
                async getApiKey(db, provider) {
         | 
| 60 | 
             
                    if (!db) return null;
         | 
| 61 | 
             
                    if (provider === "ollama") return "__local__";
         | 
| 62 | 
            +
                    const pref = this.getKeyPrefForProvider(provider);
         | 
| 63 | 
            +
                    if (!pref) return null;
         | 
| 64 | 
            +
                    if (provider === "openrouter") return await db.getPreference("openrouterApiKey");
         | 
| 65 | 
            +
                    return await db.getPreference(pref);
         | 
| 66 | 
             
                },
         | 
| 67 | 
             
                getLabelForProvider(provider) {
         | 
| 68 | 
             
                    const labels = {
         | 
|  | |
| 213 | 
             
                    }
         | 
| 214 | 
             
                    return key.trim().length > 10 && (key.startsWith("sk-") || key.startsWith("sk-or-"));
         | 
| 215 | 
             
                }
         | 
| 216 | 
            +
             | 
| 217 | 
            +
                // Removed unused encrypt/decrypt for clarity; storage should rely on secure contexts if reintroduced
         | 
| 218 | 
             
            }
         | 
| 219 |  | 
| 220 | 
             
            // Cache management for better performance
         | 
|  | |
| 384 | 
             
                    this._consecutiveErrorCount = 0;
         | 
| 385 | 
             
                }
         | 
| 386 |  | 
| 387 | 
            +
                /**
         | 
| 388 | 
            +
                 * Centralized crossfade transition between two videos.
         | 
| 389 | 
            +
                 * Ensures both videos are loaded and playing before transition.
         | 
| 390 | 
            +
                 * @param {HTMLVideoElement} fromVideo - The currently visible video.
         | 
| 391 | 
            +
                 * @param {HTMLVideoElement} toVideo - The next video to show.
         | 
| 392 | 
            +
                 * @param {number} duration - Transition duration in ms.
         | 
| 393 | 
            +
                 * @param {function} [onComplete] - Optional callback after transition.
         | 
| 394 | 
            +
                 */
         | 
| 395 | 
             
                static crossfadeVideos(fromVideo, toVideo, duration = 300, onComplete) {
         | 
| 396 | 
             
                    // Resolve duration from CSS variable if present
         | 
| 397 | 
             
                    try {
         | 
|  | |
| 439 | 
             
                    if (fromVideo.paused) fromVideo.play().catch(() => {});
         | 
| 440 | 
             
                }
         | 
| 441 |  | 
| 442 | 
            +
                /**
         | 
| 443 | 
            +
                 * Centralized video element creation utility.
         | 
| 444 | 
            +
                 * @param {string} id - The id for the video element.
         | 
| 445 | 
            +
                 * @param {string} [className] - Optional class name.
         | 
| 446 | 
            +
                 * @returns {HTMLVideoElement}
         | 
| 447 | 
            +
                 */
         | 
| 448 | 
             
                static createVideoElement(id, className = "bg-video") {
         | 
| 449 | 
             
                    const video = document.createElement("video");
         | 
| 450 | 
             
                    video.id = id;
         | 
|  | |
| 459 | 
             
                    return video;
         | 
| 460 | 
             
                }
         | 
| 461 |  | 
| 462 | 
            +
                /**
         | 
| 463 | 
            +
                 * Centralized video selection utility.
         | 
| 464 | 
            +
                 * @param {string} selector - CSS selector or id.
         | 
| 465 | 
            +
                 * @returns {HTMLVideoElement|null}
         | 
| 466 | 
            +
                 */
         | 
| 467 | 
             
                static getVideoElement(selector) {
         | 
| 468 | 
             
                    if (typeof selector === "string") {
         | 
| 469 | 
             
                        if (selector.startsWith("#")) {
         | 
|  | |
| 991 | 
             
                }
         | 
| 992 |  | 
| 993 | 
             
                // keep only the augmented determineCategory above (with traits)
         | 
| 994 | 
            +
             | 
| 995 | 
             
                selectOptimalVideo(category, specificVideo = null, traits = null, affection = null, emotion = null) {
         | 
| 996 | 
             
                    const availableVideos = this.videoCategories[category] || this.videoCategories.neutral;
         | 
| 997 |  | 
|  | |
| 1212 | 
             
                    this.isEmotionVideoPlaying = false;
         | 
| 1213 | 
             
                    this.currentEmotionContext = null;
         | 
| 1214 |  | 
| 1215 | 
            +
                    // Correction : si la voix est encore en cours, relancer une vidéo neutre en boucle
         | 
| 1216 | 
             
                    const category = "neutral";
         | 
| 1217 | 
             
                    const currentVideoSrc = this.activeVideo.querySelector("source").getAttribute("src");
         | 
| 1218 | 
             
                    const available = this.videoCategories[category] || [];
         | 
|  | |
| 1249 | 
             
                }
         | 
| 1250 |  | 
| 1251 | 
             
                // ADVANCED CONTEXTUAL ANALYSIS
         | 
| 1252 | 
            +
                // ADVANCED CONTEXTUAL ANALYSIS - SIMPLIFIED
         | 
| 1253 | 
             
                async analyzeAndSelectVideo(userMessage, kimiResponse, emotionAnalysis, traits = null, affection = null, lang = null) {
         | 
| 1254 | 
             
                    // Do not analyze-switch away while dancing is sticky/playing
         | 
| 1255 | 
             
                    if (this._stickyContext === "dancing" || this.currentContext === "dancing") {
         | 
    	
        kimi-locale/de.json
    CHANGED
    
    | @@ -143,7 +143,7 @@ | |
| 143 | 
             
                "character_summary_bella": "Fröhlich, fürsorglich, sieht Menschen als Pflanzen, die Pflege brauchen",
         | 
| 144 | 
             
                "character_summary_rosa": "Chaotisch, aufmerksamkeitssuchend, gedeiht in kontrolliertem Chaos",
         | 
| 145 | 
             
                "character_summary_stella": "Launisch, künstlerisch, fantasievoll, verspielt, verwandelt Chaos in Kunst",
         | 
| 146 | 
            -
                "fallback_api_missing": "Um wirklich mit mir zu chatten, füge deinen API-Schlüssel in den Einstellungen hinzu! 💕",
         | 
| 147 | 
             
                "fallback_api_error": "Entschuldigung, der KI-Service ist vorübergehend nicht verfügbar. Bitte versuche es später noch einmal.",
         | 
| 148 | 
             
                "fallback_model_error": "Entschuldigung, das ausgewählte Modell ist nicht verfügbar. Bitte wähle ein anderes Modell oder überprüfe deine Konfiguration.",
         | 
| 149 | 
             
                "fallback_network_error": "Entschuldigung, ich kann nicht antworten, da keine Internetverbindung besteht.",
         | 
|  | |
| 143 | 
             
                "character_summary_bella": "Fröhlich, fürsorglich, sieht Menschen als Pflanzen, die Pflege brauchen",
         | 
| 144 | 
             
                "character_summary_rosa": "Chaotisch, aufmerksamkeitssuchend, gedeiht in kontrolliertem Chaos",
         | 
| 145 | 
             
                "character_summary_stella": "Launisch, künstlerisch, fantasievoll, verspielt, verwandelt Chaos in Kunst",
         | 
| 146 | 
            +
                "fallback_api_missing": "Um wirklich mit mir zu chatten, füge deinen OpenRouter API-Schlüssel in den Einstellungen hinzu! 💕",
         | 
| 147 | 
             
                "fallback_api_error": "Entschuldigung, der KI-Service ist vorübergehend nicht verfügbar. Bitte versuche es später noch einmal.",
         | 
| 148 | 
             
                "fallback_model_error": "Entschuldigung, das ausgewählte Modell ist nicht verfügbar. Bitte wähle ein anderes Modell oder überprüfe deine Konfiguration.",
         | 
| 149 | 
             
                "fallback_network_error": "Entschuldigung, ich kann nicht antworten, da keine Internetverbindung besteht.",
         | 
    	
        kimi-locale/en.json
    CHANGED
    
    | @@ -143,7 +143,7 @@ | |
| 143 | 
             
                "character_summary_bella": "Cheerful, nurturing, sees people as plants needing care",
         | 
| 144 | 
             
                "character_summary_rosa": "Chaotic, attention-seeking, thrives on controlled chaos",
         | 
| 145 | 
             
                "character_summary_stella": "Whimsical, artistic, imaginative, playful, transforms chaos into art",
         | 
| 146 | 
            -
                "fallback_api_missing": "To really chat with me, add your API key in settings! 💕",
         | 
| 147 | 
             
                "fallback_api_error": "Sorry, the AI service is temporarily unavailable. Please try again later.",
         | 
| 148 | 
             
                "fallback_model_error": "Sorry, the selected model is not available. Please choose another model or check your configuration.",
         | 
| 149 | 
             
                "fallback_network_error": "Sorry, I cannot respond because there is no internet connection.",
         | 
|  | |
| 143 | 
             
                "character_summary_bella": "Cheerful, nurturing, sees people as plants needing care",
         | 
| 144 | 
             
                "character_summary_rosa": "Chaotic, attention-seeking, thrives on controlled chaos",
         | 
| 145 | 
             
                "character_summary_stella": "Whimsical, artistic, imaginative, playful, transforms chaos into art",
         | 
| 146 | 
            +
                "fallback_api_missing": "To really chat with me, add your OpenRouter API key in settings! 💕",
         | 
| 147 | 
             
                "fallback_api_error": "Sorry, the AI service is temporarily unavailable. Please try again later.",
         | 
| 148 | 
             
                "fallback_model_error": "Sorry, the selected model is not available. Please choose another model or check your configuration.",
         | 
| 149 | 
             
                "fallback_network_error": "Sorry, I cannot respond because there is no internet connection.",
         | 
    	
        kimi-locale/es.json
    CHANGED
    
    | @@ -143,7 +143,7 @@ | |
| 143 | 
             
                "character_summary_bella": "Alegre, cariñosa, ve a las personas como plantas que necesitan cuidado",
         | 
| 144 | 
             
                "character_summary_rosa": "Caótica, busca atención, prospera en el caos controlado",
         | 
| 145 | 
             
                "character_summary_stella": "Caprichosa, artística, imaginativa, juguetona, transforma el caos en arte",
         | 
| 146 | 
            -
                "fallback_api_missing": "Para realmente chatear conmigo, ¡agrega tu clave API de en configuración! 💕",
         | 
| 147 | 
             
                "fallback_api_error": "Lo siento, el servicio de IA no está disponible temporalmente. Por favor, intenta de nuevo más tarde.",
         | 
| 148 | 
             
                "fallback_model_error": "Lo siento, el modelo seleccionado no está disponible. Por favor, elige otro modelo o verifica tu configuración.",
         | 
| 149 | 
             
                "fallback_network_error": "Lo siento, no puedo responder porque no hay conexión a internet.",
         | 
|  | |
| 143 | 
             
                "character_summary_bella": "Alegre, cariñosa, ve a las personas como plantas que necesitan cuidado",
         | 
| 144 | 
             
                "character_summary_rosa": "Caótica, busca atención, prospera en el caos controlado",
         | 
| 145 | 
             
                "character_summary_stella": "Caprichosa, artística, imaginativa, juguetona, transforma el caos en arte",
         | 
| 146 | 
            +
                "fallback_api_missing": "Para realmente chatear conmigo, ¡agrega tu clave API de OpenRouter en configuración! 💕",
         | 
| 147 | 
             
                "fallback_api_error": "Lo siento, el servicio de IA no está disponible temporalmente. Por favor, intenta de nuevo más tarde.",
         | 
| 148 | 
             
                "fallback_model_error": "Lo siento, el modelo seleccionado no está disponible. Por favor, elige otro modelo o verifica tu configuración.",
         | 
| 149 | 
             
                "fallback_network_error": "Lo siento, no puedo responder porque no hay conexión a internet.",
         | 
    	
        kimi-locale/fr.json
    CHANGED
    
    | @@ -143,7 +143,7 @@ | |
| 143 | 
             
                "character_summary_bella": "Joyeuse, bienveillante, voit les gens comme des plantes ayant besoin de soins",
         | 
| 144 | 
             
                "character_summary_rosa": "Chaotique, en quête d'attention, prospère dans le chaos contrôlé",
         | 
| 145 | 
             
                "character_summary_stella": "Fantaisiste, artistique, imaginative, joueuse, transforme le chaos en art",
         | 
| 146 | 
            -
                "fallback_api_missing": "Pour vraiment discuter avec moi, ajoute ta clé API dans les paramètres ! 💕",
         | 
| 147 | 
             
                "fallback_api_error": "Désolée, le service IA est temporairement indisponible. Veuillez réessayer plus tard.",
         | 
| 148 | 
             
                "fallback_model_error": "Désolée, le modèle sélectionné n'est pas disponible. Veuillez choisir un autre modèle ou vérifier votre configuration.",
         | 
| 149 | 
             
                "fallback_network_error": "Désolée, je ne peux pas répondre car il n'y a pas de connexion internet.",
         | 
|  | |
| 143 | 
             
                "character_summary_bella": "Joyeuse, bienveillante, voit les gens comme des plantes ayant besoin de soins",
         | 
| 144 | 
             
                "character_summary_rosa": "Chaotique, en quête d'attention, prospère dans le chaos contrôlé",
         | 
| 145 | 
             
                "character_summary_stella": "Fantaisiste, artistique, imaginative, joueuse, transforme le chaos en art",
         | 
| 146 | 
            +
                "fallback_api_missing": "Pour vraiment discuter avec moi, ajoute ta clé API OpenRouter dans les paramètres ! 💕",
         | 
| 147 | 
             
                "fallback_api_error": "Désolée, le service IA est temporairement indisponible. Veuillez réessayer plus tard.",
         | 
| 148 | 
             
                "fallback_model_error": "Désolée, le modèle sélectionné n'est pas disponible. Veuillez choisir un autre modèle ou vérifier votre configuration.",
         | 
| 149 | 
             
                "fallback_network_error": "Désolée, je ne peux pas répondre car il n'y a pas de connexion internet.",
         | 
    	
        kimi-locale/it.json
    CHANGED
    
    | @@ -143,7 +143,7 @@ | |
| 143 | 
             
                "character_summary_bella": "Allegra, premurosa, vede le persone come piante che hanno bisogno di cure",
         | 
| 144 | 
             
                "character_summary_rosa": "Caotica, cerca attenzione, prospera nel caos controllato",
         | 
| 145 | 
             
                "character_summary_stella": "Capricciosa, artistica, fantasiosa, giocosa, trasforma il caos in arte",
         | 
| 146 | 
            -
                "fallback_api_missing": "Per chattare davvero con me, aggiungi la tua chiave API nelle impostazioni! 💕",
         | 
| 147 | 
             
                "fallback_api_error": "Spiacente, il servizio AI è temporaneamente non disponibile. Riprova più tardi.",
         | 
| 148 | 
             
                "fallback_model_error": "Spiacente, il modello selezionato non è disponibile. Scegli un altro modello o controlla la tua configurazione.",
         | 
| 149 | 
             
                "fallback_network_error": "Spiacente, non posso rispondere perché non c'è connessione internet.",
         | 
|  | |
| 143 | 
             
                "character_summary_bella": "Allegra, premurosa, vede le persone come piante che hanno bisogno di cure",
         | 
| 144 | 
             
                "character_summary_rosa": "Caotica, cerca attenzione, prospera nel caos controllato",
         | 
| 145 | 
             
                "character_summary_stella": "Capricciosa, artistica, fantasiosa, giocosa, trasforma il caos in arte",
         | 
| 146 | 
            +
                "fallback_api_missing": "Per chattare davvero con me, aggiungi la tua chiave API OpenRouter nelle impostazioni! 💕",
         | 
| 147 | 
             
                "fallback_api_error": "Spiacente, il servizio AI è temporaneamente non disponibile. Riprova più tardi.",
         | 
| 148 | 
             
                "fallback_model_error": "Spiacente, il modello selezionato non è disponibile. Scegli un altro modello o controlla la tua configurazione.",
         | 
| 149 | 
             
                "fallback_network_error": "Spiacente, non posso rispondere perché non c'è connessione internet.",
         | 
    	
        kimi-locale/ja.json
    CHANGED
    
    | @@ -143,7 +143,7 @@ | |
| 143 | 
             
                "character_summary_bella": "陽気で、優しく、人を世話が必要な植物として見る",
         | 
| 144 | 
             
                "character_summary_rosa": "混沌的で、注目を求め、制御された混沌で繁栄する",
         | 
| 145 | 
             
                "character_summary_stella": "気まぐれで、芸術的、想像力豊か、遊び心があり、混沌を芸術に変える",
         | 
| 146 | 
            -
                "fallback_api_missing": "本当に私とチャットするには、設定で APIキーを追加してください! 💕",
         | 
| 147 | 
             
                "fallback_api_error": "申し訳ありませんが、AIサービスが一時的に利用できません。後でもう一度試してください。",
         | 
| 148 | 
             
                "fallback_model_error": "申し訳ありませんが、選択されたモデルは利用できません。別のモデルを選択するか、設定を確認してください。",
         | 
| 149 | 
             
                "fallback_network_error": "申し訳ありませんが、インターネット接続がないため応答できません。",
         | 
|  | |
| 143 | 
             
                "character_summary_bella": "陽気で、優しく、人を世話が必要な植物として見る",
         | 
| 144 | 
             
                "character_summary_rosa": "混沌的で、注目を求め、制御された混沌で繁栄する",
         | 
| 145 | 
             
                "character_summary_stella": "気まぐれで、芸術的、想像力豊か、遊び心があり、混沌を芸術に変える",
         | 
| 146 | 
            +
                "fallback_api_missing": "本当に私とチャットするには、設定でOpenRouter APIキーを追加してください! 💕",
         | 
| 147 | 
             
                "fallback_api_error": "申し訳ありませんが、AIサービスが一時的に利用できません。後でもう一度試してください。",
         | 
| 148 | 
             
                "fallback_model_error": "申し訳ありませんが、選択されたモデルは利用できません。別のモデルを選択するか、設定を確認してください。",
         | 
| 149 | 
             
                "fallback_network_error": "申し訳ありませんが、インターネット接続がないため応答できません。",
         | 
    	
        kimi-locale/zh.json
    CHANGED
    
    | @@ -143,7 +143,7 @@ | |
| 143 | 
             
                "character_summary_bella": "开朗、体贴,将人视为需要关爱的植物",
         | 
| 144 | 
             
                "character_summary_rosa": "混乱、寻求关注,在受控混乱中蓬勃发展",
         | 
| 145 | 
             
                "character_summary_stella": "古怪、艺术、富有想象力、顽皮,将混乱转化为艺术",
         | 
| 146 | 
            -
                "fallback_api_missing": "要真正与我聊天,请在设置中添加您的 API密钥! 💕",
         | 
| 147 | 
             
                "fallback_api_error": "抱歉,AI服务暂时不可用。请稍后再试。",
         | 
| 148 | 
             
                "fallback_model_error": "抱歉,所选模型不可用。请选择其他模型或检查您的配置。",
         | 
| 149 | 
             
                "fallback_network_error": "抱歉,由于没有互联网连接,我无法回应。",
         | 
|  | |
| 143 | 
             
                "character_summary_bella": "开朗、体贴,将人视为需要关爱的植物",
         | 
| 144 | 
             
                "character_summary_rosa": "混乱、寻求关注,在受控混乱中蓬勃发展",
         | 
| 145 | 
             
                "character_summary_stella": "古怪、艺术、富有想象力、顽皮,将混乱转化为艺术",
         | 
| 146 | 
            +
                "fallback_api_missing": "要真正与我聊天,请在设置中添加您的OpenRouter API密钥! 💕",
         | 
| 147 | 
             
                "fallback_api_error": "抱歉,AI服务暂时不可用。请稍后再试。",
         | 
| 148 | 
             
                "fallback_model_error": "抱歉,所选模型不可用。请选择其他模型或检查您的配置。",
         | 
| 149 | 
             
                "fallback_network_error": "抱歉,由于没有互联网连接,我无法回应。",
         | 
