Spaces:
Running
Running
Upload 69 files
Browse filesThis view is limited to 50 files because it contains too many changes.
See raw diff
- .gitattributes +10 -0
- CHANGELOG.md +487 -0
- CONTRIBUTING.md +364 -0
- LICENSE.md +23 -0
- Launch-local-kimi-app.bat +9 -0
- dexie-js/dexie.min.js +2 -0
- dexie-js/dexie.min.js.map +0 -0
- favicon.ico +0 -0
- index.html +1102 -0
- kimi-css/kimi-memory-styles.css +691 -0
- kimi-css/kimi-settings.css +1632 -0
- kimi-css/kimi-style.css +2075 -0
- kimi-icons/2blanche.jpg +3 -0
- kimi-icons/bella.jpg +3 -0
- kimi-icons/favicons/apple-touch-icon-180x180.png +0 -0
- kimi-icons/favicons/favicon-128x128.png +0 -0
- kimi-icons/favicons/favicon-16x16.png +0 -0
- kimi-icons/favicons/favicon-192x192.png +0 -0
- kimi-icons/favicons/favicon-32x32.png +0 -0
- kimi-icons/favicons/favicon-48x48.png +0 -0
- kimi-icons/favicons/favicon-64x64.png +0 -0
- kimi-icons/favicons/favicon-96x96.png +0 -0
- kimi-icons/jasmine.jpg +3 -0
- kimi-icons/july.jpg +3 -0
- kimi-icons/kimi-loading.png +3 -0
- kimi-icons/kimi.jpg +3 -0
- kimi-icons/rosa.jpg +3 -0
- kimi-icons/stella.jpg +3 -0
- kimi-icons/virtual-kimi-banners.jpg +3 -0
- kimi-icons/virtualkimi-logo.png +3 -0
- kimi-icons/virtualkimi-preview1.jpg +0 -0
- kimi-icons/virtualkimi-preview2.jpg +0 -0
- kimi-js/kimi-appearance.js +148 -0
- kimi-js/kimi-config.js +157 -0
- kimi-js/kimi-constants.js +1233 -0
- kimi-js/kimi-data-manager.js +318 -0
- kimi-js/kimi-database.js +1226 -0
- kimi-js/kimi-debug-utils.js +133 -0
- kimi-js/kimi-emotion-system.js +1060 -0
- kimi-js/kimi-error-manager.js +219 -0
- kimi-js/kimi-llm-manager.js +1729 -0
- kimi-js/kimi-main.js +11 -0
- kimi-js/kimi-memory-system.js +2257 -0
- kimi-js/kimi-memory-ui.js +966 -0
- kimi-js/kimi-memory.js +156 -0
- kimi-js/kimi-module.js +1949 -0
- kimi-js/kimi-plugin-manager.js +260 -0
- kimi-js/kimi-script.js +1250 -0
- kimi-js/kimi-security.js +163 -0
- kimi-js/kimi-utils.js +1118 -0
.gitattributes
CHANGED
|
@@ -33,3 +33,13 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 36 |
+
kimi-icons/2blanche.jpg filter=lfs diff=lfs merge=lfs -text
|
| 37 |
+
kimi-icons/bella.jpg filter=lfs diff=lfs merge=lfs -text
|
| 38 |
+
kimi-icons/jasmine.jpg filter=lfs diff=lfs merge=lfs -text
|
| 39 |
+
kimi-icons/july.jpg filter=lfs diff=lfs merge=lfs -text
|
| 40 |
+
kimi-icons/kimi-loading.png filter=lfs diff=lfs merge=lfs -text
|
| 41 |
+
kimi-icons/kimi.jpg filter=lfs diff=lfs merge=lfs -text
|
| 42 |
+
kimi-icons/rosa.jpg filter=lfs diff=lfs merge=lfs -text
|
| 43 |
+
kimi-icons/stella.jpg filter=lfs diff=lfs merge=lfs -text
|
| 44 |
+
kimi-icons/virtual-kimi-banners.jpg filter=lfs diff=lfs merge=lfs -text
|
| 45 |
+
kimi-icons/virtualkimi-logo.png filter=lfs diff=lfs merge=lfs -text
|
CHANGELOG.md
ADDED
|
@@ -0,0 +1,487 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Virtual Kimi App Changelog
|
| 2 |
+
|
| 3 |
+
# [1.1.7.1] - 2025-11-12 (HuggingFace and Github version)
|
| 4 |
+
|
| 5 |
+
### Added
|
| 6 |
+
|
| 7 |
+
- **New Characters**: Introduced 2 new AI personalities:
|
| 8 |
+
- **2Blanche**: Stoic YoRHa android combat unit with deep emotional vulnerability hidden beneath military protocols
|
| 9 |
+
- **Jasmine**: Divine goddess of love, sensuality, and Kamasutra; inspires passion, intimacy, and pleasure in every encounter
|
| 10 |
+
- **Character-Specific Emotions**: Added ANDROID, SENSUAL, and LOVE emotion types with specialized responses
|
| 11 |
+
- **Enhanced Emotion System**: New contextual keywords and emotional responses for android and sensual personas
|
| 12 |
+
- **Complete Internationalization**: Full translations for both new characters across all 8 supported languages (English, French, Spanish, German, Italian, Portuguese, Japanese, Chinese)
|
| 13 |
+
|
| 14 |
+
### Changed
|
| 15 |
+
|
| 16 |
+
- **Total Characters**: Expanded from 4 to 6 unique AI personalities
|
| 17 |
+
- **Emotion Mapping**: New emotions mapped to existing video categories for seamless integration
|
| 18 |
+
- **Character Variety**: Enhanced personality spectrum from cosmic/nurturing/chaotic/artistic to include technological/rebellious themes
|
| 19 |
+
|
| 20 |
+
### Technical Details
|
| 21 |
+
|
| 22 |
+
- Added comprehensive character profiles with unique trait progressions
|
| 23 |
+
- 2Blanche: Ultra-difficult progression (35% affection start) with loyalty bonuses
|
| 24 |
+
- July: Trust-based progression (45% affection start) with rebellion bonuses
|
| 25 |
+
- Maintained compatibility with existing video structure (no new categories required)
|
| 26 |
+
|
| 27 |
+
# [1.1.6.1] - 2025-09-05
|
| 28 |
+
|
| 29 |
+
### Changed
|
| 30 |
+
|
| 31 |
+
- Improved text formatting in the chat window.
|
| 32 |
+
|
| 33 |
+
### Bug Fixes
|
| 34 |
+
|
| 35 |
+
- Fixed some issues.
|
| 36 |
+
|
| 37 |
+
# [1.1.5.1] - 2025-09-04
|
| 38 |
+
|
| 39 |
+
### Bug Fixes
|
| 40 |
+
|
| 41 |
+
- Fixed a bug where sliders refused the value 0 (0 was treated as falsy and reset to defaults).
|
| 42 |
+
|
| 43 |
+
# [1.1.5] - 2025-09-03
|
| 44 |
+
|
| 45 |
+
### Bug Fixes
|
| 46 |
+
|
| 47 |
+
- Fixed some issues.
|
| 48 |
+
|
| 49 |
+
### Changed
|
| 50 |
+
|
| 51 |
+
- Separated the KimiDataManager class and moved logic into the new file `kimi-data-manager.js`.
|
| 52 |
+
|
| 53 |
+
# [1.1.4.1] - 2025-09-03
|
| 54 |
+
|
| 55 |
+
### Bug Fixes
|
| 56 |
+
|
| 57 |
+
- Fixed an issue with language selection and speech recognition that could prevent correct voice detection and audio input. Improved handling and fallbacks to ensure consistent behavior.
|
| 58 |
+
|
| 59 |
+
### Changed
|
| 60 |
+
|
| 61 |
+
- Separated the KimiVideoManager class and moved video management logic into the new file `kimi-videos.js`.
|
| 62 |
+
|
| 63 |
+
# [1.1.3] - 2025-09-01
|
| 64 |
+
|
| 65 |
+
### Bug Fixes
|
| 66 |
+
|
| 67 |
+
- Fixed an issue with language selection and speech recognition / text-to-speech playback that could prevent correct voice detection and audio output across browsers. Improved normalization and fallback handling.
|
| 68 |
+
|
| 69 |
+
### Changed
|
| 70 |
+
|
| 71 |
+
- Modified the calculations used for character personality trait processing to improve accuracy and consistency across modules.
|
| 72 |
+
|
| 73 |
+
# [1.1.2] - 2025-08-30
|
| 74 |
+
|
| 75 |
+
### Improvements
|
| 76 |
+
|
| 77 |
+
- Improved memory and prompt generation to avoid duplicate memory sections and display accurate per-character counters.
|
| 78 |
+
|
| 79 |
+
### Added
|
| 80 |
+
|
| 81 |
+
- A concise "7-day summary" feature that extracts high-signal conversation highlights for quick reference.
|
| 82 |
+
|
| 83 |
+
### Notes
|
| 84 |
+
|
| 85 |
+
- Voice UI and TTS: Only Microsoft Edge and Google Chrome will display the voice selection list and support voice playback of messages; other browsers may not expose compatible voices.
|
| 86 |
+
|
| 87 |
+
### Bug Fixes
|
| 88 |
+
|
| 89 |
+
- Fixed import/export functions for preferences and data to ensure exported files can be re-imported correctly.
|
| 90 |
+
|
| 91 |
+
- Fixed some small bugs related to memory, video playback, and preference import/export.
|
| 92 |
+
|
| 93 |
+
# [1.1.1] - 2025-08-29
|
| 94 |
+
|
| 95 |
+
### Improvements
|
| 96 |
+
|
| 97 |
+
- Microsoft Edge and Google Chrome Only : Improved language and voice selection logic: normalization, fallback, and robust preference management across all modules.
|
| 98 |
+
- Enhanced voice compatibility and ensured consistent language handling.
|
| 99 |
+
|
| 100 |
+
### Bug Fixes
|
| 101 |
+
|
| 102 |
+
- Fixed issue where videos could freeze after opening or closing the memory modal or changing memory sections.
|
| 103 |
+
- Added automatic reset to neutral video state after UI interactions to prevent stuck/frozen videos.
|
| 104 |
+
|
| 105 |
+
# [1.1.0] - 2025-08-28
|
| 106 |
+
|
| 107 |
+
### Changed
|
| 108 |
+
|
| 109 |
+
- **Recommended LLMs**: Updated the list of recommended LLM models to reflect current recommendations and improvements.
|
| 110 |
+
|
| 111 |
+
- **Settings modal UI/UX**: Updated tab layout and visual behavior in the settings modal for clearer navigation and improved usability.
|
| 112 |
+
|
| 113 |
+
### Fixed
|
| 114 |
+
|
| 115 |
+
- **Memory features UX**: Fixed multiple UI/UX issues in the memory system to ensure reliable capture, display, and management of remembered items.
|
| 116 |
+
- **Miscellaneous bug fixes**: Corrected various small bugs across the application.
|
| 117 |
+
|
| 118 |
+
### Internationalization
|
| 119 |
+
|
| 120 |
+
- **Interface translations**: Added new strings and translation keys to support the updated UI elements.
|
| 121 |
+
|
| 122 |
+
# [1.0.9] - 2025-08-23
|
| 123 |
+
|
| 124 |
+
### Major System Improvements
|
| 125 |
+
|
| 126 |
+
- **Personality trait system overhaul**: Rebalanced progression curves and multipliers for more natural character development.
|
| 127 |
+
- **Unified emotion system**: Centralized emotion-to-video mapping and fixed all 13 emotions to properly affect traits.
|
| 128 |
+
- **Intelligence trait integration**: Added intelligence to personality calculations and video selection algorithms.
|
| 129 |
+
- **Enhanced emotion detection**: Improved keyword detection with better priorities and reduced conflicts.
|
| 130 |
+
- **Video selection rebalancing**: Fixed positive/negative bias and made auto-triggers more accessible.
|
| 131 |
+
- **Complete codebase synchronization**: Eliminated inconsistencies and redundancies across all modules.
|
| 132 |
+
- **Text streaming implementation**: Added real-time text streaming in chat for better user experience.
|
| 133 |
+
|
| 134 |
+
### Language & Voice Improvements
|
| 135 |
+
|
| 136 |
+
- **Enhanced language and voice selection**: Fixed bugs and inconsistencies in language switching and voice preferences.
|
| 137 |
+
- **Improved voice synchronization**: Better coordination between selected language and available voice options.
|
| 138 |
+
|
| 139 |
+
### API Key Management Enhancements
|
| 140 |
+
|
| 141 |
+
- **Provider-specific API key storage**: Implemented separate storage for different LLM providers (OpenRouter, OpenAI, Groq, etc.).
|
| 142 |
+
- **Unified API key handling**: Consolidated all API key operations through a centralized utility system.
|
| 143 |
+
- **Enhanced settings UI**: Improved visual design and layout of API configuration section.
|
| 144 |
+
- **Comprehensive API audit**: Fixed inconsistencies across all chat, test, and model loading functions.
|
| 145 |
+
|
| 146 |
+
### Bug Fixes
|
| 147 |
+
|
| 148 |
+
- Fixed trait calculation inconsistencies between modules (INTELLIGENCE and others).
|
| 149 |
+
- Resolved emotion detection conflicts (LISTENING, ROMANTIC/KISS categories).
|
| 150 |
+
- Corrected fallback values causing progression issues.
|
| 151 |
+
- Fixed API key loading and display issues in settings modal.
|
| 152 |
+
|
| 153 |
+
# [1.0.8] - 2025-08-19
|
| 154 |
+
|
| 155 |
+
### Changed
|
| 156 |
+
|
| 157 |
+
- Improved fallback logic for LLM responses: now uses localized emotional responses if the LLM reply is empty or invalid.
|
| 158 |
+
- Made emotional response selection dynamic and robust, based on available variants.
|
| 159 |
+
- Enhanced error handling for missing API keys, network issues, and API errors, ensuring the user always receives a meaningful message.
|
| 160 |
+
- Refactored code patching to avoid accidental code removal or misplaced edits.
|
| 161 |
+
- Clarified and documented emotional response logic for maintainability.
|
| 162 |
+
|
| 163 |
+
## [1.0.7] - 2025-08-19
|
| 164 |
+
|
| 165 |
+
### Changed
|
| 166 |
+
|
| 167 |
+
- Removed the global system prompt that caused issues and implemented per-character system prompts for each character.
|
| 168 |
+
- Improved voice reading of messages for clearer and more natural audio playback.
|
| 169 |
+
- Fixed various small bugs related to characters' personality traits.
|
| 170 |
+
- Improved detection of words and phrases for memory recording to increase accuracy.
|
| 171 |
+
|
| 172 |
+
## [1.0.6] - 2025-08-15
|
| 173 |
+
|
| 174 |
+
### Added
|
| 175 |
+
|
| 176 |
+
- Added 100+ videos for various contexts.
|
| 177 |
+
|
| 178 |
+
### Changed
|
| 179 |
+
|
| 180 |
+
- Optimized video preloading to improve speed on slow web servers.
|
| 181 |
+
|
| 182 |
+
### Fixed
|
| 183 |
+
|
| 184 |
+
- Fixed various minor bugs.
|
| 185 |
+
|
| 186 |
+
## [1.0.5] - 2025-08-13 - "Personality & Language Sensitivity"
|
| 187 |
+
|
| 188 |
+
### Added
|
| 189 |
+
|
| 190 |
+
- Multilingual profanity/insult detection for negative context across 7 languages (en, fr, es, de, it, ja, zh)
|
| 191 |
+
- Gendered variants support in negative keywords (fr, es, it, de) to improve accuracy (e.g., sérieux/sérieuse)
|
| 192 |
+
- Extended personality keywords for Spanish and Italian (all traits) with gendered forms
|
| 193 |
+
|
| 194 |
+
### Changed
|
| 195 |
+
|
| 196 |
+
- Personality sync now completes missing values using character-specific defaults (with generic fallback)
|
| 197 |
+
- Centralized side-effects on personality updates (UI/memory/video/voice) behind a single `personality:updated` listener
|
| 198 |
+
- Sliders: generic handler only updates display; persistence and effects handled by specialized listeners
|
| 199 |
+
- Trait updates preserve fractional progress (2 decimals) for smoother affection changes
|
| 200 |
+
- Stats now use character-specific default for affection (with generic fallback) when missing
|
| 201 |
+
|
| 202 |
+
### Fixed
|
| 203 |
+
|
| 204 |
+
- Removed obsolete `personalityUpdated` listener to avoid duplicate processing
|
| 205 |
+
- Unified KimiMemory affection default loading (removed conflicting double assignment and legacy default 80)
|
| 206 |
+
- Minor cleanup and consistency improvements in utils and sync flows
|
| 207 |
+
|
| 208 |
+
## [1.0.4] - 2025-08-09 - "Emotion & Context Logic Upgrade"
|
| 209 |
+
|
| 210 |
+
### Added
|
| 211 |
+
|
| 212 |
+
- Major improvements to emotion, context, and personality logic:
|
| 213 |
+
- Enhanced emotion detection and mapping for more nuanced responses
|
| 214 |
+
- Contextual keyword analysis for better understanding of user intent
|
| 215 |
+
- Refined personality trait system with dynamic adaptation
|
| 216 |
+
- Video selection logic now adapts to both emotion and conversational context
|
| 217 |
+
- Improved handling of multi-layered context (emotion, keywords, personality, situation)
|
| 218 |
+
|
| 219 |
+
### Changed
|
| 220 |
+
|
| 221 |
+
- Video playback and character reactions are now more tightly coupled to detected context and personality traits
|
| 222 |
+
- Emotion and context logic refactored for clarity and maintainability
|
| 223 |
+
- Keyword extraction and context matching algorithms improved for accuracy
|
| 224 |
+
|
| 225 |
+
### Technical
|
| 226 |
+
|
| 227 |
+
- Refactored core logic in `kimi-emotion-system.js`, `kimi-logic.js`, and `kimi-memory-system.js`
|
| 228 |
+
- Updated video selection and playback logic in `kimi-memory.js` and `kimi-memory-ui.js`
|
| 229 |
+
- Improved context propagation between modules
|
| 230 |
+
|
| 231 |
+
## [1.0.3] - 2025-08-09 - "LLM multi-provider"
|
| 232 |
+
|
| 233 |
+
### Added
|
| 234 |
+
|
| 235 |
+
- LLM multi-provider UX enhancements:
|
| 236 |
+
- Dynamic API key label per provider (OpenRouter, OpenAI, Groq, Together, DeepSeek, Custom, Ollama)
|
| 237 |
+
- Visual "Saved" badge when a key is stored or after a successful test
|
| 238 |
+
- Localized tooltip explaining Saved vs connection test
|
| 239 |
+
|
| 240 |
+
### Changed
|
| 241 |
+
|
| 242 |
+
- OpenAI-compatible flow now reads llmBaseUrl/llmModelId and the correct provider key from KimiDB
|
| 243 |
+
- Clears connection status message when provider/Base URL/Model ID/key changes for clearer feedback
|
| 244 |
+
|
| 245 |
+
## [1.0.2] - 2025-08-09 - "Smoother Video"
|
| 246 |
+
|
| 247 |
+
### Changed
|
| 248 |
+
|
| 249 |
+
- Video playback and transition stability improvements:
|
| 250 |
+
- Lightweight MP4 prefetch queue (neutral + likely next clips) to reduce wait times during switches
|
| 251 |
+
- Earlier transition on `canplay` (instead of `canplaythrough`) for faster, smoother swaps
|
| 252 |
+
- Context-aware throttling to prevent rapid switching under load (speaking: ~200ms, listening: ~250ms, dancing: ~600ms, neutral: ~1200ms)
|
| 253 |
+
|
| 254 |
+
### Fixed
|
| 255 |
+
|
| 256 |
+
- Safe revert on failed `play()` during a switch to avoid frozen frames
|
| 257 |
+
- Aligned event listeners to `canplay` and ensured proper cleanup to prevent leaks
|
| 258 |
+
- Corrected prefetch cache initialization order (prevented `undefined.has` runtime error)
|
| 259 |
+
- Removed unsupported `<link rel="preload" as="video">` to eliminate console warnings
|
| 260 |
+
|
| 261 |
+
### Technical
|
| 262 |
+
|
| 263 |
+
- Front-end performance tweaks: GPU-accelerated fades with `will-change: opacity` and `backface-visibility: hidden`
|
| 264 |
+
- Connection warm-up: added `preconnect`/`dns-prefetch` to the origin for faster first video start
|
| 265 |
+
- Files updated: `index.html`, `kimi-css/kimi-style.css`, `kimi-js/kimi-utils.js`
|
| 266 |
+
|
| 267 |
+
## [1.0.1] - 2025-08-08
|
| 268 |
+
|
| 269 |
+
- Fixed an issue where the browser prompted to save the OpenRouter API key as a password. The input field is now properly configured to prevent password managers from interfering.
|
| 270 |
+
- Added a waiting animation that appears between the user's message submission and the LLM's response, improving user feedback during processing.
|
| 271 |
+
- Added a new section in the API tab: below the recommended LLM models, all available OpenRouter LLM models are now dynamically loaded and displayed for selection.
|
| 272 |
+
|
| 273 |
+
## [1.0.0] - 2025-08-07 - "Unified"
|
| 274 |
+
|
| 275 |
+
### Added
|
| 276 |
+
|
| 277 |
+
- **Intelligent Memory System**: Automatic extraction and categorization of memories from conversations
|
| 278 |
+
- **Multiple AI Characters**: 4 unique personalities (Kimi, Bella, Rosa, Stella) with distinct traits
|
| 279 |
+
- **Advanced Emotion Detection**: Real-time emotion analysis with cultural awareness
|
| 280 |
+
- **Plugin System**: Extensible architecture for themes, voices, and behaviors
|
| 281 |
+
- **Memory Management UI**: Complete interface for viewing, searching, and managing memories
|
| 282 |
+
- **Enhanced Personality System**: 6 dynamic traits that evolve based on interactions
|
| 283 |
+
- **Multilingual Support**: Full localization in 7 languages with auto-detection
|
| 284 |
+
- **Production Health Check**: Comprehensive system validation and monitoring
|
| 285 |
+
- **Performance Optimizations**: Batch database operations and improved loading times
|
| 286 |
+
- **Security Enhancements**: Input validation, sanitization, and secure API handling
|
| 287 |
+
|
| 288 |
+
### Changed
|
| 289 |
+
|
| 290 |
+
- **Unified Architecture**: Consolidated all emotion and personality systems
|
| 291 |
+
- **Improved Database**: Enhanced IndexedDB implementation with batch operations
|
| 292 |
+
- **Better Error Handling**: Centralized error management with fallback responses
|
| 293 |
+
- **Enhanced UI/UX**: More responsive and accessible interface design
|
| 294 |
+
- **Optimized Video System**: Smoother transitions and better emotion mapping
|
| 295 |
+
|
| 296 |
+
### Fixed
|
| 297 |
+
|
| 298 |
+
- Function export issues in module system
|
| 299 |
+
- Memory leaks in event listeners
|
| 300 |
+
- Cross-browser compatibility issues
|
| 301 |
+
- Voice recognition stability problems
|
| 302 |
+
- Database initialization race conditions
|
| 303 |
+
|
| 304 |
+
### Technical
|
| 305 |
+
|
| 306 |
+
- Migrated to unified emotion system
|
| 307 |
+
- Implemented comprehensive validation layer
|
| 308 |
+
- Added automated health monitoring
|
| 309 |
+
- Enhanced plugin security validation
|
| 310 |
+
- Improved mobile responsiveness
|
| 311 |
+
|
| 312 |
+
## [0.0.9] - 2025-08-04 - "Enhanced"
|
| 313 |
+
|
| 314 |
+
### Added
|
| 315 |
+
|
| 316 |
+
- Advanced LLM model selection interface
|
| 317 |
+
- Improved voice synthesis with better emotion mapping
|
| 318 |
+
- Enhanced personality trait visualization
|
| 319 |
+
- Better conversation export/import functionality
|
| 320 |
+
|
| 321 |
+
### Changed
|
| 322 |
+
|
| 323 |
+
- Upgraded database schema for better performance
|
| 324 |
+
- Improved theme system with more customization options
|
| 325 |
+
- Enhanced mobile interface responsiveness
|
| 326 |
+
|
| 327 |
+
### Fixed
|
| 328 |
+
|
| 329 |
+
- Various browser compatibility issues
|
| 330 |
+
- Voice recognition accuracy improvements
|
| 331 |
+
- Memory management optimizations
|
| 332 |
+
|
| 333 |
+
## [0.0.8] - 2025-08-01 - "Evolution"
|
| 334 |
+
|
| 335 |
+
### Added
|
| 336 |
+
|
| 337 |
+
- Dynamic personality trait evolution
|
| 338 |
+
- Enhanced emotion detection algorithms
|
| 339 |
+
- Improved conversation context awareness
|
| 340 |
+
- Better visual feedback systems
|
| 341 |
+
|
| 342 |
+
### Changed
|
| 343 |
+
|
| 344 |
+
- Redesigned settings interface
|
| 345 |
+
- Improved conversation flow management
|
| 346 |
+
- Enhanced error reporting system
|
| 347 |
+
|
| 348 |
+
### Fixed
|
| 349 |
+
|
| 350 |
+
- Database sync issues
|
| 351 |
+
- Voice recognition edge cases
|
| 352 |
+
- Theme switching problems
|
| 353 |
+
|
| 354 |
+
## [0.0.7] - 2025-07-29 - "Immersion"
|
| 355 |
+
|
| 356 |
+
### Added
|
| 357 |
+
|
| 358 |
+
- Real-time video emotion responses
|
| 359 |
+
- Enhanced voice interaction capabilities
|
| 360 |
+
- Improved conversation context retention
|
| 361 |
+
- Better visual theme system
|
| 362 |
+
|
| 363 |
+
### Changed
|
| 364 |
+
|
| 365 |
+
- Upgraded UI framework for better performance
|
| 366 |
+
- Improved data synchronization mechanisms
|
| 367 |
+
- Enhanced accessibility features
|
| 368 |
+
|
| 369 |
+
### Fixed
|
| 370 |
+
|
| 371 |
+
- Various stability improvements
|
| 372 |
+
- Better error handling
|
| 373 |
+
- Improved cross-platform compatibility
|
| 374 |
+
|
| 375 |
+
## [0.0.6] - 2025-07-26 - "Connection"
|
| 376 |
+
|
| 377 |
+
### Added
|
| 378 |
+
|
| 379 |
+
- Multi-language support system
|
| 380 |
+
- Enhanced conversation memory
|
| 381 |
+
- Improved personality customization
|
| 382 |
+
- Better audio/video synchronization
|
| 383 |
+
|
| 384 |
+
### Changed
|
| 385 |
+
|
| 386 |
+
- Redesigned conversation interface
|
| 387 |
+
- Improved data persistence layer
|
| 388 |
+
- Enhanced user experience flows
|
| 389 |
+
|
| 390 |
+
### Fixed
|
| 391 |
+
|
| 392 |
+
- Memory leak issues
|
| 393 |
+
- Browser compatibility problems
|
| 394 |
+
- Audio synchronization bugs
|
| 395 |
+
|
| 396 |
+
## [0.0.5] - 2025-07-23 - "Rebirth"
|
| 397 |
+
|
| 398 |
+
### Added
|
| 399 |
+
|
| 400 |
+
- Complete application rewrite
|
| 401 |
+
- Modern ES6+ JavaScript architecture
|
| 402 |
+
- Responsive design system
|
| 403 |
+
- Advanced AI integration capabilities
|
| 404 |
+
- Comprehensive settings system
|
| 405 |
+
|
| 406 |
+
### Changed
|
| 407 |
+
|
| 408 |
+
- Modernized codebase with current web standards
|
| 409 |
+
- Improved performance and reliability
|
| 410 |
+
- Enhanced user interface design
|
| 411 |
+
- Better data management system
|
| 412 |
+
|
| 413 |
+
### Removed
|
| 414 |
+
|
| 415 |
+
- Legacy jQuery dependencies
|
| 416 |
+
- Outdated browser support
|
| 417 |
+
|
| 418 |
+
## [0.0.4] - 2025-07-20 - "Stability"
|
| 419 |
+
|
| 420 |
+
### Added
|
| 421 |
+
|
| 422 |
+
- Enhanced voice recognition
|
| 423 |
+
- Improved conversation flow
|
| 424 |
+
- Better error handling
|
| 425 |
+
- Enhanced visual feedback
|
| 426 |
+
|
| 427 |
+
### Fixed
|
| 428 |
+
|
| 429 |
+
- Various stability issues
|
| 430 |
+
- Performance optimizations
|
| 431 |
+
- Browser compatibility improvements
|
| 432 |
+
|
| 433 |
+
## [0.0.3] - 2025-07-18 - "Polish"
|
| 434 |
+
|
| 435 |
+
### Added
|
| 436 |
+
|
| 437 |
+
- Improved user interface
|
| 438 |
+
- Better conversation management
|
| 439 |
+
- Enhanced customization options
|
| 440 |
+
|
| 441 |
+
### Fixed
|
| 442 |
+
|
| 443 |
+
- Various bugs and stability issues
|
| 444 |
+
- Performance improvements
|
| 445 |
+
|
| 446 |
+
## [0.0.2] - 2025-07-17 - "Improvements"
|
| 447 |
+
|
| 448 |
+
### Added
|
| 449 |
+
|
| 450 |
+
- Basic conversation memory
|
| 451 |
+
- Improved personality system
|
| 452 |
+
- Enhanced visual themes
|
| 453 |
+
|
| 454 |
+
### Fixed
|
| 455 |
+
|
| 456 |
+
- Initial bug fixes
|
| 457 |
+
- Performance optimizations
|
| 458 |
+
|
| 459 |
+
## [0.0.1] - 2025-07-16 - "Genesis"
|
| 460 |
+
|
| 461 |
+
### Added
|
| 462 |
+
|
| 463 |
+
- Initial release
|
| 464 |
+
- Basic AI conversation capabilities
|
| 465 |
+
- Voice recognition and synthesis
|
| 466 |
+
- Simple personality system
|
| 467 |
+
- Theme customization
|
| 468 |
+
- Local data storage
|
| 469 |
+
|
| 470 |
+
---
|
| 471 |
+
|
| 472 |
+
## Legend
|
| 473 |
+
|
| 474 |
+
- **Added**: New features
|
| 475 |
+
- **Changed**: Changes in existing functionality
|
| 476 |
+
- **Deprecated**: Soon-to-be removed features
|
| 477 |
+
- **Removed**: Removed features
|
| 478 |
+
- **Fixed**: Bug fixes
|
| 479 |
+
- **Security**: Security improvements
|
| 480 |
+
- **Technical**: Internal technical changes
|
| 481 |
+
|
| 482 |
+
---
|
| 483 |
+
|
| 484 |
+
All notable changes to Virtual Kimi will be documented in this file.
|
| 485 |
+
|
| 486 |
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
| 487 |
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
CONTRIBUTING.md
ADDED
|
@@ -0,0 +1,364 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Contributing to Virtual Kimi
|
| 2 |
+
|
| 3 |
+
Thank you for your interest in contributing to Virtual Kimi! This document provides guidelines and information for contributors.
|
| 4 |
+
|
| 5 |
+
## Table of Contents
|
| 6 |
+
|
| 7 |
+
- [Code of Conduct](#code-of-conduct)
|
| 8 |
+
- [Getting Started](#getting-started)
|
| 9 |
+
- [Development Setup](#development-setup)
|
| 10 |
+
- [Contribution Guidelines](#contribution-guidelines)
|
| 11 |
+
- [Project Structure](#project-structure)
|
| 12 |
+
- [Coding Standards](#coding-standards)
|
| 13 |
+
- [Testing](#testing)
|
| 14 |
+
- [Pull Request Process](#pull-request-process)
|
| 15 |
+
- [Issue Reporting](#issue-reporting)
|
| 16 |
+
|
| 17 |
+
## Code of Conduct
|
| 18 |
+
|
| 19 |
+
We are committed to providing a welcoming and inclusive environment for all contributors. Please be respectful and constructive in all interactions.
|
| 20 |
+
|
| 21 |
+
### Expected Behavior
|
| 22 |
+
|
| 23 |
+
- Use welcoming and inclusive language
|
| 24 |
+
- Be respectful of differing viewpoints and experiences
|
| 25 |
+
- Gracefully accept constructive criticism
|
| 26 |
+
- Focus on what is best for the community
|
| 27 |
+
- Show empathy towards other community members
|
| 28 |
+
|
| 29 |
+
## Getting Started
|
| 30 |
+
|
| 31 |
+
### Prerequisites
|
| 32 |
+
|
| 33 |
+
- Modern web browser (Chrome, Edge, Firefox recommended)
|
| 34 |
+
- Basic knowledge of JavaScript, HTML, and CSS
|
| 35 |
+
- Git for version control
|
| 36 |
+
- Text editor or IDE of your choice
|
| 37 |
+
|
| 38 |
+
### First Contribution
|
| 39 |
+
|
| 40 |
+
1. Fork the repository
|
| 41 |
+
2. Clone your fork locally
|
| 42 |
+
3. Create a new branch for your feature/fix
|
| 43 |
+
4. Make your changes
|
| 44 |
+
5. Test thoroughly
|
| 45 |
+
6. Submit a pull request
|
| 46 |
+
|
| 47 |
+
## Development Setup
|
| 48 |
+
|
| 49 |
+
### Local Environment
|
| 50 |
+
|
| 51 |
+
```bash
|
| 52 |
+
# Clone the repository
|
| 53 |
+
git clone https://github.com/virtualkimi/virtual-kimi.git
|
| 54 |
+
cd virtual-kimi
|
| 55 |
+
|
| 56 |
+
# Open in browser
|
| 57 |
+
# Option 1: Direct file access
|
| 58 |
+
open index.html
|
| 59 |
+
|
| 60 |
+
# Option 2: Local server (recommended)
|
| 61 |
+
python -m http.server 8000
|
| 62 |
+
# Navigate to http://localhost:8000
|
| 63 |
+
```
|
| 64 |
+
|
| 65 |
+
### Development Tools
|
| 66 |
+
|
| 67 |
+
- **Browser DevTools**: For debugging and testing
|
| 68 |
+
- **Live Server**: For hot reload during development
|
| 69 |
+
- **Lighthouse**: For performance auditing
|
| 70 |
+
- **Accessibility tools**: For ensuring inclusive design
|
| 71 |
+
|
| 72 |
+
## Contribution Guidelines
|
| 73 |
+
|
| 74 |
+
### Types of Contributions
|
| 75 |
+
|
| 76 |
+
- **Bug fixes**: Resolve existing issues
|
| 77 |
+
- **Feature additions**: New functionality
|
| 78 |
+
- **Performance improvements**: Optimization and efficiency
|
| 79 |
+
- **Documentation**: Improve guides and comments
|
| 80 |
+
- **Localization**: Translation and internationalization
|
| 81 |
+
- **Plugin development**: Extend functionality
|
| 82 |
+
- **Testing**: Add or improve test coverage
|
| 83 |
+
|
| 84 |
+
### Before You Start
|
| 85 |
+
|
| 86 |
+
1. Check existing issues and pull requests
|
| 87 |
+
2. Open an issue to discuss major changes
|
| 88 |
+
3. Ensure your idea aligns with the project goals
|
| 89 |
+
4. Consider the impact on existing functionality
|
| 90 |
+
|
| 91 |
+
## Project Structure
|
| 92 |
+
|
| 93 |
+
### Core Files
|
| 94 |
+
|
| 95 |
+
```
|
| 96 |
+
├── index.html # Main application
|
| 97 |
+
├── kimi-script.js # Primary initialization
|
| 98 |
+
├── kimi-database.js # Data persistence
|
| 99 |
+
├── kimi-llm-manager.js # AI integration
|
| 100 |
+
├── kimi-emotion-system.js # Emotion analysis
|
| 101 |
+
├── kimi-memory-system.js # Memory management
|
| 102 |
+
├── kimi-voices.js # Speech synthesis
|
| 103 |
+
├── kimi-appearance.js # Theme management
|
| 104 |
+
└── kimi-utils.js # Utility functions
|
| 105 |
+
```
|
| 106 |
+
|
| 107 |
+
### Module Dependencies
|
| 108 |
+
|
| 109 |
+
- **Core System**: Database → Security → Config
|
| 110 |
+
- **AI System**: LLM Manager → Emotion System → Memory System
|
| 111 |
+
- **UI System**: Appearance → Utils → Module functions
|
| 112 |
+
- **Localization**: i18n → All user-facing modules
|
| 113 |
+
|
| 114 |
+
### Adding New Features
|
| 115 |
+
|
| 116 |
+
#### New Memory Categories
|
| 117 |
+
|
| 118 |
+
```javascript
|
| 119 |
+
// In kimi-memory-system.js
|
| 120 |
+
const newCategory = {
|
| 121 |
+
name: "custom_category",
|
| 122 |
+
icon: "fas fa-custom-icon",
|
| 123 |
+
keywords: ["keyword1", "keyword2"],
|
| 124 |
+
confidence: 0.7
|
| 125 |
+
};
|
| 126 |
+
|
| 127 |
+
// Add to MEMORY_CATEGORIES constant
|
| 128 |
+
```
|
| 129 |
+
|
| 130 |
+
#### New Themes
|
| 131 |
+
|
| 132 |
+
```javascript
|
| 133 |
+
// Create plugin in kimi-plugins/custom-theme/
|
| 134 |
+
// manifest.json
|
| 135 |
+
{
|
| 136 |
+
"name": "Custom Theme",
|
| 137 |
+
"version": "1.0.0",
|
| 138 |
+
"type": "theme",
|
| 139 |
+
"style": "theme.css",
|
| 140 |
+
"enabled": true
|
| 141 |
+
}
|
| 142 |
+
```
|
| 143 |
+
|
| 144 |
+
#### New AI Models
|
| 145 |
+
|
| 146 |
+
```javascript
|
| 147 |
+
// In kimi-llm-manager.js
|
| 148 |
+
"custom/model-id": {
|
| 149 |
+
name: "Custom Model",
|
| 150 |
+
provider: "Custom Provider",
|
| 151 |
+
type: "openrouter",
|
| 152 |
+
contextWindow: 8000,
|
| 153 |
+
pricing: { input: 0.1, output: 0.2 },
|
| 154 |
+
strengths: ["Custom", "Feature"]
|
| 155 |
+
}
|
| 156 |
+
```
|
| 157 |
+
|
| 158 |
+
## Coding Standards
|
| 159 |
+
|
| 160 |
+
### JavaScript Style
|
| 161 |
+
|
| 162 |
+
- Use ES6+ features and modern syntax
|
| 163 |
+
- Prefer `const` and `let` over `var`
|
| 164 |
+
- Use meaningful variable and function names in English
|
| 165 |
+
- Follow camelCase for variables and functions
|
| 166 |
+
- Use PascalCase for classes and constructors
|
| 167 |
+
|
| 168 |
+
### Code Organization
|
| 169 |
+
|
| 170 |
+
- Keep functions focused and single-purpose
|
| 171 |
+
- Use async/await for asynchronous operations
|
| 172 |
+
- Handle errors gracefully with try/catch blocks
|
| 173 |
+
- Add JSDoc comments for complex functions
|
| 174 |
+
- Group related functionality in modules
|
| 175 |
+
|
| 176 |
+
### Example Code Style
|
| 177 |
+
|
| 178 |
+
```javascript
|
| 179 |
+
/**
|
| 180 |
+
* Analyzes user input for emotional content and updates personality traits
|
| 181 |
+
* @param {string} text - User input text
|
| 182 |
+
* @param {string} emotion - Detected emotion type
|
| 183 |
+
* @returns {Promise<Object>} Updated personality traits
|
| 184 |
+
*/
|
| 185 |
+
async function updatePersonalityFromEmotion(text, emotion) {
|
| 186 |
+
try {
|
| 187 |
+
// Validate input
|
| 188 |
+
if (!text || typeof text !== "string") {
|
| 189 |
+
throw new Error("Invalid input text");
|
| 190 |
+
}
|
| 191 |
+
|
| 192 |
+
// Process emotion
|
| 193 |
+
const traits = await this.processEmotionalContent(text, emotion);
|
| 194 |
+
|
| 195 |
+
// Update database
|
| 196 |
+
await this.db.setPersonalityBatch(traits);
|
| 197 |
+
|
| 198 |
+
return traits;
|
| 199 |
+
} catch (error) {
|
| 200 |
+
console.error("Error updating personality:", error);
|
| 201 |
+
throw error;
|
| 202 |
+
}
|
| 203 |
+
}
|
| 204 |
+
```
|
| 205 |
+
|
| 206 |
+
### CSS Guidelines
|
| 207 |
+
|
| 208 |
+
- Use CSS custom properties (variables) for theming
|
| 209 |
+
- Follow BEM methodology for class naming
|
| 210 |
+
- Ensure responsive design principles
|
| 211 |
+
- Maintain accessibility standards
|
| 212 |
+
- Use semantic HTML elements
|
| 213 |
+
|
| 214 |
+
### HTML Standards
|
| 215 |
+
|
| 216 |
+
- Use semantic HTML5 elements
|
| 217 |
+
- Include proper ARIA labels for accessibility
|
| 218 |
+
- Ensure proper heading hierarchy
|
| 219 |
+
- Add meaningful alt text for images
|
| 220 |
+
- Validate markup regularly
|
| 221 |
+
|
| 222 |
+
## Testing
|
| 223 |
+
|
| 224 |
+
### Manual Testing Checklist
|
| 225 |
+
|
| 226 |
+
- [ ] Application loads without errors
|
| 227 |
+
- [ ] All core features function correctly
|
| 228 |
+
- [ ] Voice recognition works (in supported browsers)
|
| 229 |
+
- [ ] Memory system stores and retrieves data
|
| 230 |
+
- [ ] Theme switching works properly
|
| 231 |
+
- [ ] Responsive design on mobile devices
|
| 232 |
+
- [ ] Cross-browser compatibility
|
| 233 |
+
- [ ] Accessibility with keyboard navigation
|
| 234 |
+
|
| 235 |
+
### Browser Testing
|
| 236 |
+
|
| 237 |
+
Test in the following browsers:
|
| 238 |
+
|
| 239 |
+
- Chrome (latest 2 versions)
|
| 240 |
+
- Edge (latest 2 versions)
|
| 241 |
+
- Firefox (latest 2 versions)
|
| 242 |
+
- Safari (latest version, if possible)
|
| 243 |
+
|
| 244 |
+
### Performance Testing
|
| 245 |
+
|
| 246 |
+
- Check loading times
|
| 247 |
+
- Monitor memory usage
|
| 248 |
+
- Test with large conversation histories
|
| 249 |
+
- Verify smooth animations
|
| 250 |
+
- Ensure responsive UI interactions
|
| 251 |
+
|
| 252 |
+
## Pull Request Process
|
| 253 |
+
|
| 254 |
+
### Before Submitting
|
| 255 |
+
|
| 256 |
+
1. **Test thoroughly**: Ensure your changes work as expected
|
| 257 |
+
2. **Check compatibility**: Test across different browsers
|
| 258 |
+
3. **Update documentation**: Modify README.md if needed
|
| 259 |
+
4. **Clean up code**: Remove debugging code and comments
|
| 260 |
+
5. **Commit messages**: Use clear, descriptive commit messages
|
| 261 |
+
|
| 262 |
+
### PR Template
|
| 263 |
+
|
| 264 |
+
```markdown
|
| 265 |
+
## Description
|
| 266 |
+
|
| 267 |
+
Brief description of changes made.
|
| 268 |
+
|
| 269 |
+
## Type of Change
|
| 270 |
+
|
| 271 |
+
- [ ] Bug fix
|
| 272 |
+
- [ ] New feature
|
| 273 |
+
- [ ] Performance improvement
|
| 274 |
+
- [ ] Documentation update
|
| 275 |
+
- [ ] Other: **\_**
|
| 276 |
+
|
| 277 |
+
## Testing
|
| 278 |
+
|
| 279 |
+
- [ ] Tested in Chrome
|
| 280 |
+
- [ ] Tested in Edge
|
| 281 |
+
- [ ] Tested in Firefox
|
| 282 |
+
- [ ] Tested on mobile
|
| 283 |
+
- [ ] No errors in console
|
| 284 |
+
|
| 285 |
+
## Screenshots (if applicable)
|
| 286 |
+
|
| 287 |
+
Add screenshots of UI changes.
|
| 288 |
+
|
| 289 |
+
## Additional Notes
|
| 290 |
+
|
| 291 |
+
Any additional context or considerations.
|
| 292 |
+
```
|
| 293 |
+
|
| 294 |
+
### Review Process
|
| 295 |
+
|
| 296 |
+
1. Maintainers review code for quality and functionality
|
| 297 |
+
2. Feedback provided through PR comments
|
| 298 |
+
3. Make requested changes and push updates
|
| 299 |
+
4. Final approval and merge
|
| 300 |
+
|
| 301 |
+
## Issue Reporting
|
| 302 |
+
|
| 303 |
+
### Bug Reports
|
| 304 |
+
|
| 305 |
+
Include the following information:
|
| 306 |
+
|
| 307 |
+
- Browser and version
|
| 308 |
+
- Operating system
|
| 309 |
+
- Steps to reproduce
|
| 310 |
+
- Expected behavior
|
| 311 |
+
- Actual behavior
|
| 312 |
+
- Console errors (if any)
|
| 313 |
+
- Screenshots (if applicable)
|
| 314 |
+
|
| 315 |
+
### Feature Requests
|
| 316 |
+
|
| 317 |
+
- Clear description of the feature
|
| 318 |
+
- Use case and benefits
|
| 319 |
+
- Possible implementation approach
|
| 320 |
+
- Any relevant examples or mockups
|
| 321 |
+
|
| 322 |
+
### Issue Labels
|
| 323 |
+
|
| 324 |
+
- `bug`: Something isn't working
|
| 325 |
+
- `enhancement`: New feature or improvement
|
| 326 |
+
- `documentation`: Documentation updates
|
| 327 |
+
- `good first issue`: Good for newcomers
|
| 328 |
+
- `help wanted`: Community assistance needed
|
| 329 |
+
- `plugin`: Related to plugin system
|
| 330 |
+
- `accessibility`: Accessibility improvements
|
| 331 |
+
|
| 332 |
+
## Development Tips
|
| 333 |
+
|
| 334 |
+
### Performance Optimization
|
| 335 |
+
|
| 336 |
+
- Minimize DOM manipulations
|
| 337 |
+
- Use event delegation for dynamic content
|
| 338 |
+
- Implement proper cleanup for event listeners
|
| 339 |
+
- Optimize database queries with batch operations
|
| 340 |
+
|
| 341 |
+
### Accessibility
|
| 342 |
+
|
| 343 |
+
- Test with keyboard navigation
|
| 344 |
+
- Verify screen reader compatibility
|
| 345 |
+
- Ensure sufficient color contrast
|
| 346 |
+
- Add appropriate ARIA labels
|
| 347 |
+
|
| 348 |
+
## Community
|
| 349 |
+
|
| 350 |
+
### Getting Help
|
| 351 |
+
|
| 352 |
+
- Open an issue for technical questions
|
| 353 |
+
- Check existing documentation first
|
| 354 |
+
- Be specific about your problem or question
|
| 355 |
+
|
| 356 |
+
### Communication
|
| 357 |
+
|
| 358 |
+
- Be respectful and professional
|
| 359 |
+
- Provide context and details
|
| 360 |
+
- Be patient with response times
|
| 361 |
+
- Help others when possible
|
| 362 |
+
|
| 363 |
+
Thank you for contributing to Virtual Kimi! Your efforts help create a better AI companion experience for everyone.
|
| 364 |
+
|
LICENSE.md
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Virtual Kimi Custom License
|
| 2 |
+
|
| 3 |
+
Copyright (c) 2025 Virtual Kimi Project
|
| 4 |
+
|
| 5 |
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
| 6 |
+
of this software and associated documentation files (the "Software"), to use,
|
| 7 |
+
copy, modify, and distribute the Software for personal, educational, or research purposes,
|
| 8 |
+
subject to the following conditions:
|
| 9 |
+
|
| 10 |
+
- **Commercial use, resale, or monetization of this application or any derivative work is strictly prohibited without the explicit written consent of the author.**
|
| 11 |
+
- The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
| 12 |
+
- You may not use the name, logo, or branding of Virtual Kimi for commercial purposes without explicit permission.
|
| 13 |
+
|
| 14 |
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
| 15 |
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
| 16 |
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
| 17 |
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
| 18 |
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
| 19 |
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
| 20 |
+
SOFTWARE.
|
| 21 |
+
|
| 22 |
+
For commercial licensing inquiries, please contact: [ijohn@virtualkimi.com](ijohn@virtualkimi.com)
|
| 23 |
+
[WebSite: https:/virtual-kimi.com](https:/virtual-kimi.com)
|
Launch-local-kimi-app.bat
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
@echo off
|
| 2 |
+
REM Starts a Python HTTP server on port 8080
|
| 3 |
+
start "" python -m http.server 8080
|
| 4 |
+
|
| 5 |
+
REM Pause 2 seconds to allow the server to start
|
| 6 |
+
timeout /t 2 >nul
|
| 7 |
+
|
| 8 |
+
REM Opens the homepage in the default browser
|
| 9 |
+
start "" http://localhost:8080/index.html
|
dexie-js/dexie.min.js
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
(function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e="undefined"!=typeof globalThis?globalThis:e||self).Dexie=t()})(this,function(){"use strict";var s=function(e,t){return(s=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var n in t)Object.prototype.hasOwnProperty.call(t,n)&&(e[n]=t[n])})(e,t)};var _=function(){return(_=Object.assign||function(e){for(var t,n=1,r=arguments.length;n<r;n++)for(var i in t=arguments[n])Object.prototype.hasOwnProperty.call(t,i)&&(e[i]=t[i]);return e}).apply(this,arguments)};function i(e,t,n){if(n||2===arguments.length)for(var r,i=0,o=t.length;i<o;i++)!r&&i in t||((r=r||Array.prototype.slice.call(t,0,i))[i]=t[i]);return e.concat(r||Array.prototype.slice.call(t))}var f="undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:"undefined"!=typeof window?window:global,x=Object.keys,k=Array.isArray;function a(t,n){return"object"!=typeof n||x(n).forEach(function(e){t[e]=n[e]}),t}"undefined"==typeof Promise||f.Promise||(f.Promise=Promise);var c=Object.getPrototypeOf,n={}.hasOwnProperty;function m(e,t){return n.call(e,t)}function r(t,n){"function"==typeof n&&(n=n(c(t))),("undefined"==typeof Reflect?x:Reflect.ownKeys)(n).forEach(function(e){l(t,e,n[e])})}var u=Object.defineProperty;function l(e,t,n,r){u(e,t,a(n&&m(n,"get")&&"function"==typeof n.get?{get:n.get,set:n.set,configurable:!0}:{value:n,configurable:!0,writable:!0},r))}function o(t){return{from:function(e){return t.prototype=Object.create(e.prototype),l(t.prototype,"constructor",t),{extend:r.bind(null,t.prototype)}}}}var h=Object.getOwnPropertyDescriptor;var d=[].slice;function b(e,t,n){return d.call(e,t,n)}function p(e,t){return t(e)}function y(e){if(!e)throw new Error("Assertion Failed")}function v(e){f.setImmediate?setImmediate(e):setTimeout(e,0)}function O(e,t){if("string"==typeof t&&m(e,t))return e[t];if(!t)return e;if("string"!=typeof t){for(var n=[],r=0,i=t.length;r<i;++r){var o=O(e,t[r]);n.push(o)}return n}var a=t.indexOf(".");if(-1!==a){var u=e[t.substr(0,a)];return null==u?void 0:O(u,t.substr(a+1))}}function P(e,t,n){if(e&&void 0!==t&&!("isFrozen"in Object&&Object.isFrozen(e)))if("string"!=typeof t&&"length"in t){y("string"!=typeof n&&"length"in n);for(var r=0,i=t.length;r<i;++r)P(e,t[r],n[r])}else{var o,a,u=t.indexOf(".");-1!==u?(o=t.substr(0,u),""===(a=t.substr(u+1))?void 0===n?k(e)&&!isNaN(parseInt(o))?e.splice(o,1):delete e[o]:e[o]=n:P(u=!(u=e[o])||!m(e,o)?e[o]={}:u,a,n)):void 0===n?k(e)&&!isNaN(parseInt(t))?e.splice(t,1):delete e[t]:e[t]=n}}function g(e){var t,n={};for(t in e)m(e,t)&&(n[t]=e[t]);return n}var t=[].concat;function w(e){return t.apply([],e)}var e="BigUint64Array,BigInt64Array,Array,Boolean,String,Date,RegExp,Blob,File,FileList,FileSystemFileHandle,FileSystemDirectoryHandle,ArrayBuffer,DataView,Uint8ClampedArray,ImageBitmap,ImageData,Map,Set,CryptoKey".split(",").concat(w([8,16,32,64].map(function(t){return["Int","Uint","Float"].map(function(e){return e+t+"Array"})}))).filter(function(e){return f[e]}),K=new Set(e.map(function(e){return f[e]}));var E=null;function S(e){E=new WeakMap;e=function e(t){if(!t||"object"!=typeof t)return t;var n=E.get(t);if(n)return n;if(k(t)){n=[],E.set(t,n);for(var r=0,i=t.length;r<i;++r)n.push(e(t[r]))}else if(K.has(t.constructor))n=t;else{var o,a=c(t);for(o in n=a===Object.prototype?{}:Object.create(a),E.set(t,n),t)m(t,o)&&(n[o]=e(t[o]))}return n}(e);return E=null,e}var j={}.toString;function A(e){return j.call(e).slice(8,-1)}var C="undefined"!=typeof Symbol?Symbol.iterator:"@@iterator",T="symbol"==typeof C?function(e){var t;return null!=e&&(t=e[C])&&t.apply(e)}:function(){return null};function q(e,t){t=e.indexOf(t);return 0<=t&&e.splice(t,1),0<=t}var D={};function I(e){var t,n,r,i;if(1===arguments.length){if(k(e))return e.slice();if(this===D&&"string"==typeof e)return[e];if(i=T(e)){for(n=[];!(r=i.next()).done;)n.push(r.value);return n}if(null==e)return[e];if("number"!=typeof(t=e.length))return[e];for(n=new Array(t);t--;)n[t]=e[t];return n}for(t=arguments.length,n=new Array(t);t--;)n[t]=arguments[t];return n}var B="undefined"!=typeof Symbol?function(e){return"AsyncFunction"===e[Symbol.toStringTag]}:function(){return!1},R=["Unknown","Constraint","Data","TransactionInactive","ReadOnly","Version","NotFound","InvalidState","InvalidAccess","Abort","Timeout","QuotaExceeded","Syntax","DataClone"],F=["Modify","Bulk","OpenFailed","VersionChange","Schema","Upgrade","InvalidTable","MissingAPI","NoSuchDatabase","InvalidArgument","SubTransaction","Unsupported","Internal","DatabaseClosed","PrematureCommit","ForeignAwait"].concat(R),M={VersionChanged:"Database version changed by other database connection",DatabaseClosed:"Database has been closed",Abort:"Transaction aborted",TransactionInactive:"Transaction has already completed or failed",MissingAPI:"IndexedDB API missing. Please visit https://tinyurl.com/y2uuvskb"};function N(e,t){this.name=e,this.message=t}function L(e,t){return e+". Errors: "+Object.keys(t).map(function(e){return t[e].toString()}).filter(function(e,t,n){return n.indexOf(e)===t}).join("\n")}function U(e,t,n,r){this.failures=t,this.failedKeys=r,this.successCount=n,this.message=L(e,t)}function V(e,t){this.name="BulkError",this.failures=Object.keys(t).map(function(e){return t[e]}),this.failuresByPos=t,this.message=L(e,this.failures)}o(N).from(Error).extend({toString:function(){return this.name+": "+this.message}}),o(U).from(N),o(V).from(N);var z=F.reduce(function(e,t){return e[t]=t+"Error",e},{}),W=N,Y=F.reduce(function(e,n){var r=n+"Error";function t(e,t){this.name=r,e?"string"==typeof e?(this.message="".concat(e).concat(t?"\n "+t:""),this.inner=t||null):"object"==typeof e&&(this.message="".concat(e.name," ").concat(e.message),this.inner=e):(this.message=M[n]||r,this.inner=null)}return o(t).from(W),e[n]=t,e},{});Y.Syntax=SyntaxError,Y.Type=TypeError,Y.Range=RangeError;var $=R.reduce(function(e,t){return e[t+"Error"]=Y[t],e},{});var Q=F.reduce(function(e,t){return-1===["Syntax","Type","Range"].indexOf(t)&&(e[t+"Error"]=Y[t]),e},{});function G(){}function X(e){return e}function H(t,n){return null==t||t===X?n:function(e){return n(t(e))}}function J(e,t){return function(){e.apply(this,arguments),t.apply(this,arguments)}}function Z(i,o){return i===G?o:function(){var e=i.apply(this,arguments);void 0!==e&&(arguments[0]=e);var t=this.onsuccess,n=this.onerror;this.onsuccess=null,this.onerror=null;var r=o.apply(this,arguments);return t&&(this.onsuccess=this.onsuccess?J(t,this.onsuccess):t),n&&(this.onerror=this.onerror?J(n,this.onerror):n),void 0!==r?r:e}}function ee(n,r){return n===G?r:function(){n.apply(this,arguments);var e=this.onsuccess,t=this.onerror;this.onsuccess=this.onerror=null,r.apply(this,arguments),e&&(this.onsuccess=this.onsuccess?J(e,this.onsuccess):e),t&&(this.onerror=this.onerror?J(t,this.onerror):t)}}function te(i,o){return i===G?o:function(e){var t=i.apply(this,arguments);a(e,t);var n=this.onsuccess,r=this.onerror;this.onsuccess=null,this.onerror=null;e=o.apply(this,arguments);return n&&(this.onsuccess=this.onsuccess?J(n,this.onsuccess):n),r&&(this.onerror=this.onerror?J(r,this.onerror):r),void 0===t?void 0===e?void 0:e:a(t,e)}}function ne(e,t){return e===G?t:function(){return!1!==t.apply(this,arguments)&&e.apply(this,arguments)}}function re(i,o){return i===G?o:function(){var e=i.apply(this,arguments);if(e&&"function"==typeof e.then){for(var t=this,n=arguments.length,r=new Array(n);n--;)r[n]=arguments[n];return e.then(function(){return o.apply(t,r)})}return o.apply(this,arguments)}}Q.ModifyError=U,Q.DexieError=N,Q.BulkError=V;var ie="undefined"!=typeof location&&/^(http|https):\/\/(localhost|127\.0\.0\.1)/.test(location.href);function oe(e){ie=e}var ae={},ue=100,e="undefined"==typeof Promise?[]:function(){var e=Promise.resolve();if("undefined"==typeof crypto||!crypto.subtle)return[e,c(e),e];var t=crypto.subtle.digest("SHA-512",new Uint8Array([0]));return[t,c(t),e]}(),R=e[0],F=e[1],e=e[2],F=F&&F.then,se=R&&R.constructor,ce=!!e;var le=function(e,t){be.push([e,t]),he&&(queueMicrotask(Se),he=!1)},fe=!0,he=!0,de=[],pe=[],ye=X,ve={id:"global",global:!0,ref:0,unhandleds:[],onunhandled:G,pgp:!1,env:{},finalize:G},me=ve,be=[],ge=0,we=[];function _e(e){if("object"!=typeof this)throw new TypeError("Promises must be constructed via new");this._listeners=[],this._lib=!1;var t=this._PSD=me;if("function"!=typeof e){if(e!==ae)throw new TypeError("Not a function");return this._state=arguments[1],this._value=arguments[2],void(!1===this._state&&Oe(this,this._value))}this._state=null,this._value=null,++t.ref,function t(r,e){try{e(function(n){if(null===r._state){if(n===r)throw new TypeError("A promise cannot be resolved with itself.");var e=r._lib&&je();n&&"function"==typeof n.then?t(r,function(e,t){n instanceof _e?n._then(e,t):n.then(e,t)}):(r._state=!0,r._value=n,Pe(r)),e&&Ae()}},Oe.bind(null,r))}catch(e){Oe(r,e)}}(this,e)}var xe={get:function(){var u=me,t=Fe;function e(n,r){var i=this,o=!u.global&&(u!==me||t!==Fe),a=o&&!Ue(),e=new _e(function(e,t){Ke(i,new ke(Qe(n,u,o,a),Qe(r,u,o,a),e,t,u))});return this._consoleTask&&(e._consoleTask=this._consoleTask),e}return e.prototype=ae,e},set:function(e){l(this,"then",e&&e.prototype===ae?xe:{get:function(){return e},set:xe.set})}};function ke(e,t,n,r,i){this.onFulfilled="function"==typeof e?e:null,this.onRejected="function"==typeof t?t:null,this.resolve=n,this.reject=r,this.psd=i}function Oe(e,t){var n,r;pe.push(t),null===e._state&&(n=e._lib&&je(),t=ye(t),e._state=!1,e._value=t,r=e,de.some(function(e){return e._value===r._value})||de.push(r),Pe(e),n&&Ae())}function Pe(e){var t=e._listeners;e._listeners=[];for(var n=0,r=t.length;n<r;++n)Ke(e,t[n]);var i=e._PSD;--i.ref||i.finalize(),0===ge&&(++ge,le(function(){0==--ge&&Ce()},[]))}function Ke(e,t){if(null!==e._state){var n=e._state?t.onFulfilled:t.onRejected;if(null===n)return(e._state?t.resolve:t.reject)(e._value);++t.psd.ref,++ge,le(Ee,[n,e,t])}else e._listeners.push(t)}function Ee(e,t,n){try{var r,i=t._value;!t._state&&pe.length&&(pe=[]),r=ie&&t._consoleTask?t._consoleTask.run(function(){return e(i)}):e(i),t._state||-1!==pe.indexOf(i)||function(e){var t=de.length;for(;t;)if(de[--t]._value===e._value)return de.splice(t,1)}(t),n.resolve(r)}catch(e){n.reject(e)}finally{0==--ge&&Ce(),--n.psd.ref||n.psd.finalize()}}function Se(){$e(ve,function(){je()&&Ae()})}function je(){var e=fe;return he=fe=!1,e}function Ae(){var e,t,n;do{for(;0<be.length;)for(e=be,be=[],n=e.length,t=0;t<n;++t){var r=e[t];r[0].apply(null,r[1])}}while(0<be.length);he=fe=!0}function Ce(){var e=de;de=[],e.forEach(function(e){e._PSD.onunhandled.call(null,e._value,e)});for(var t=we.slice(0),n=t.length;n;)t[--n]()}function Te(e){return new _e(ae,!1,e)}function qe(n,r){var i=me;return function(){var e=je(),t=me;try{return We(i,!0),n.apply(this,arguments)}catch(e){r&&r(e)}finally{We(t,!1),e&&Ae()}}}r(_e.prototype,{then:xe,_then:function(e,t){Ke(this,new ke(null,null,e,t,me))},catch:function(e){if(1===arguments.length)return this.then(null,e);var t=e,n=arguments[1];return"function"==typeof t?this.then(null,function(e){return(e instanceof t?n:Te)(e)}):this.then(null,function(e){return(e&&e.name===t?n:Te)(e)})},finally:function(t){return this.then(function(e){return _e.resolve(t()).then(function(){return e})},function(e){return _e.resolve(t()).then(function(){return Te(e)})})},timeout:function(r,i){var o=this;return r<1/0?new _e(function(e,t){var n=setTimeout(function(){return t(new Y.Timeout(i))},r);o.then(e,t).finally(clearTimeout.bind(null,n))}):this}}),"undefined"!=typeof Symbol&&Symbol.toStringTag&&l(_e.prototype,Symbol.toStringTag,"Dexie.Promise"),ve.env=Ye(),r(_e,{all:function(){var o=I.apply(null,arguments).map(Ve);return new _e(function(n,r){0===o.length&&n([]);var i=o.length;o.forEach(function(e,t){return _e.resolve(e).then(function(e){o[t]=e,--i||n(o)},r)})})},resolve:function(n){return n instanceof _e?n:n&&"function"==typeof n.then?new _e(function(e,t){n.then(e,t)}):new _e(ae,!0,n)},reject:Te,race:function(){var e=I.apply(null,arguments).map(Ve);return new _e(function(t,n){e.map(function(e){return _e.resolve(e).then(t,n)})})},PSD:{get:function(){return me},set:function(e){return me=e}},totalEchoes:{get:function(){return Fe}},newPSD:Ne,usePSD:$e,scheduler:{get:function(){return le},set:function(e){le=e}},rejectionMapper:{get:function(){return ye},set:function(e){ye=e}},follow:function(i,n){return new _e(function(e,t){return Ne(function(n,r){var e=me;e.unhandleds=[],e.onunhandled=r,e.finalize=J(function(){var t,e=this;t=function(){0===e.unhandleds.length?n():r(e.unhandleds[0])},we.push(function e(){t(),we.splice(we.indexOf(e),1)}),++ge,le(function(){0==--ge&&Ce()},[])},e.finalize),i()},n,e,t)})}}),se&&(se.allSettled&&l(_e,"allSettled",function(){var e=I.apply(null,arguments).map(Ve);return new _e(function(n){0===e.length&&n([]);var r=e.length,i=new Array(r);e.forEach(function(e,t){return _e.resolve(e).then(function(e){return i[t]={status:"fulfilled",value:e}},function(e){return i[t]={status:"rejected",reason:e}}).then(function(){return--r||n(i)})})})}),se.any&&"undefined"!=typeof AggregateError&&l(_e,"any",function(){var e=I.apply(null,arguments).map(Ve);return new _e(function(n,r){0===e.length&&r(new AggregateError([]));var i=e.length,o=new Array(i);e.forEach(function(e,t){return _e.resolve(e).then(function(e){return n(e)},function(e){o[t]=e,--i||r(new AggregateError(o))})})})}),se.withResolvers&&(_e.withResolvers=se.withResolvers));var De={awaits:0,echoes:0,id:0},Ie=0,Be=[],Re=0,Fe=0,Me=0;function Ne(e,t,n,r){var i=me,o=Object.create(i);o.parent=i,o.ref=0,o.global=!1,o.id=++Me,ve.env,o.env=ce?{Promise:_e,PromiseProp:{value:_e,configurable:!0,writable:!0},all:_e.all,race:_e.race,allSettled:_e.allSettled,any:_e.any,resolve:_e.resolve,reject:_e.reject}:{},t&&a(o,t),++i.ref,o.finalize=function(){--this.parent.ref||this.parent.finalize()};r=$e(o,e,n,r);return 0===o.ref&&o.finalize(),r}function Le(){return De.id||(De.id=++Ie),++De.awaits,De.echoes+=ue,De.id}function Ue(){return!!De.awaits&&(0==--De.awaits&&(De.id=0),De.echoes=De.awaits*ue,!0)}function Ve(e){return De.echoes&&e&&e.constructor===se?(Le(),e.then(function(e){return Ue(),e},function(e){return Ue(),Xe(e)})):e}function ze(){var e=Be[Be.length-1];Be.pop(),We(e,!1)}function We(e,t){var n,r=me;(t?!De.echoes||Re++&&e===me:!Re||--Re&&e===me)||queueMicrotask(t?function(e){++Fe,De.echoes&&0!=--De.echoes||(De.echoes=De.awaits=De.id=0),Be.push(me),We(e,!0)}.bind(null,e):ze),e!==me&&(me=e,r===ve&&(ve.env=Ye()),ce&&(n=ve.env.Promise,t=e.env,(r.global||e.global)&&(Object.defineProperty(f,"Promise",t.PromiseProp),n.all=t.all,n.race=t.race,n.resolve=t.resolve,n.reject=t.reject,t.allSettled&&(n.allSettled=t.allSettled),t.any&&(n.any=t.any))))}function Ye(){var e=f.Promise;return ce?{Promise:e,PromiseProp:Object.getOwnPropertyDescriptor(f,"Promise"),all:e.all,race:e.race,allSettled:e.allSettled,any:e.any,resolve:e.resolve,reject:e.reject}:{}}function $e(e,t,n,r,i){var o=me;try{return We(e,!0),t(n,r,i)}finally{We(o,!1)}}function Qe(t,n,r,i){return"function"!=typeof t?t:function(){var e=me;r&&Le(),We(n,!0);try{return t.apply(this,arguments)}finally{We(e,!1),i&&queueMicrotask(Ue)}}}function Ge(e){Promise===se&&0===De.echoes?0===Re?e():enqueueNativeMicroTask(e):setTimeout(e,0)}-1===(""+F).indexOf("[native code]")&&(Le=Ue=G);var Xe=_e.reject;var He=String.fromCharCode(65535),Je="Invalid key provided. Keys must be of type string, number, Date or Array<string | number | Date>.",Ze="String expected.",et=[],tt="__dbnames",nt="readonly",rt="readwrite";function it(e,t){return e?t?function(){return e.apply(this,arguments)&&t.apply(this,arguments)}:e:t}var ot={type:3,lower:-1/0,lowerOpen:!1,upper:[[]],upperOpen:!1};function at(t){return"string"!=typeof t||/\./.test(t)?function(e){return e}:function(e){return void 0===e[t]&&t in e&&delete(e=S(e))[t],e}}function ut(){throw Y.Type()}function st(e,t){try{var n=ct(e),r=ct(t);if(n!==r)return"Array"===n?1:"Array"===r?-1:"binary"===n?1:"binary"===r?-1:"string"===n?1:"string"===r?-1:"Date"===n?1:"Date"!==r?NaN:-1;switch(n){case"number":case"Date":case"string":return t<e?1:e<t?-1:0;case"binary":return function(e,t){for(var n=e.length,r=t.length,i=n<r?n:r,o=0;o<i;++o)if(e[o]!==t[o])return e[o]<t[o]?-1:1;return n===r?0:n<r?-1:1}(lt(e),lt(t));case"Array":return function(e,t){for(var n=e.length,r=t.length,i=n<r?n:r,o=0;o<i;++o){var a=st(e[o],t[o]);if(0!==a)return a}return n===r?0:n<r?-1:1}(e,t)}}catch(e){}return NaN}function ct(e){var t=typeof e;if("object"!=t)return t;if(ArrayBuffer.isView(e))return"binary";e=A(e);return"ArrayBuffer"===e?"binary":e}function lt(e){return e instanceof Uint8Array?e:ArrayBuffer.isView(e)?new Uint8Array(e.buffer,e.byteOffset,e.byteLength):new Uint8Array(e)}var ft=(ht.prototype._trans=function(e,r,t){var n=this._tx||me.trans,i=this.name,o=ie&&"undefined"!=typeof console&&console.createTask&&console.createTask("Dexie: ".concat("readonly"===e?"read":"write"," ").concat(this.name));function a(e,t,n){if(!n.schema[i])throw new Y.NotFound("Table "+i+" not part of transaction");return r(n.idbtrans,n)}var u=je();try{var s=n&&n.db._novip===this.db._novip?n===me.trans?n._promise(e,a,t):Ne(function(){return n._promise(e,a,t)},{trans:n,transless:me.transless||me}):function t(n,r,i,o){if(n.idbdb&&(n._state.openComplete||me.letThrough||n._vip)){var a=n._createTransaction(r,i,n._dbSchema);try{a.create(),n._state.PR1398_maxLoop=3}catch(e){return e.name===z.InvalidState&&n.isOpen()&&0<--n._state.PR1398_maxLoop?(console.warn("Dexie: Need to reopen db"),n.close({disableAutoOpen:!1}),n.open().then(function(){return t(n,r,i,o)})):Xe(e)}return a._promise(r,function(e,t){return Ne(function(){return me.trans=a,o(e,t,a)})}).then(function(e){if("readwrite"===r)try{a.idbtrans.commit()}catch(e){}return"readonly"===r?e:a._completion.then(function(){return e})})}if(n._state.openComplete)return Xe(new Y.DatabaseClosed(n._state.dbOpenError));if(!n._state.isBeingOpened){if(!n._state.autoOpen)return Xe(new Y.DatabaseClosed);n.open().catch(G)}return n._state.dbReadyPromise.then(function(){return t(n,r,i,o)})}(this.db,e,[this.name],a);return o&&(s._consoleTask=o,s=s.catch(function(e){return console.trace(e),Xe(e)})),s}finally{u&&Ae()}},ht.prototype.get=function(t,e){var n=this;return t&&t.constructor===Object?this.where(t).first(e):null==t?Xe(new Y.Type("Invalid argument to Table.get()")):this._trans("readonly",function(e){return n.core.get({trans:e,key:t}).then(function(e){return n.hook.reading.fire(e)})}).then(e)},ht.prototype.where=function(o){if("string"==typeof o)return new this.db.WhereClause(this,o);if(k(o))return new this.db.WhereClause(this,"[".concat(o.join("+"),"]"));var n=x(o);if(1===n.length)return this.where(n[0]).equals(o[n[0]]);var e=this.schema.indexes.concat(this.schema.primKey).filter(function(t){if(t.compound&&n.every(function(e){return 0<=t.keyPath.indexOf(e)})){for(var e=0;e<n.length;++e)if(-1===n.indexOf(t.keyPath[e]))return!1;return!0}return!1}).sort(function(e,t){return e.keyPath.length-t.keyPath.length})[0];if(e&&this.db._maxKey!==He){var t=e.keyPath.slice(0,n.length);return this.where(t).equals(t.map(function(e){return o[e]}))}!e&&ie&&console.warn("The query ".concat(JSON.stringify(o)," on ").concat(this.name," would benefit from a ")+"compound index [".concat(n.join("+"),"]"));var a=this.schema.idxByName;function u(e,t){return 0===st(e,t)}var r=n.reduce(function(e,t){var n=e[0],r=e[1],e=a[t],i=o[t];return[n||e,n||!e?it(r,e&&e.multi?function(e){e=O(e,t);return k(e)&&e.some(function(e){return u(i,e)})}:function(e){return u(i,O(e,t))}):r]},[null,null]),t=r[0],r=r[1];return t?this.where(t.name).equals(o[t.keyPath]).filter(r):e?this.filter(r):this.where(n).equals("")},ht.prototype.filter=function(e){return this.toCollection().and(e)},ht.prototype.count=function(e){return this.toCollection().count(e)},ht.prototype.offset=function(e){return this.toCollection().offset(e)},ht.prototype.limit=function(e){return this.toCollection().limit(e)},ht.prototype.each=function(e){return this.toCollection().each(e)},ht.prototype.toArray=function(e){return this.toCollection().toArray(e)},ht.prototype.toCollection=function(){return new this.db.Collection(new this.db.WhereClause(this))},ht.prototype.orderBy=function(e){return new this.db.Collection(new this.db.WhereClause(this,k(e)?"[".concat(e.join("+"),"]"):e))},ht.prototype.reverse=function(){return this.toCollection().reverse()},ht.prototype.mapToClass=function(r){var e,t=this.db,n=this.name;function i(){return null!==e&&e.apply(this,arguments)||this}(this.schema.mappedClass=r).prototype instanceof ut&&(function(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Class extends value "+String(t)+" is not a constructor or null");function n(){this.constructor=e}s(e,t),e.prototype=null===t?Object.create(t):(n.prototype=t.prototype,new n)}(i,e=r),Object.defineProperty(i.prototype,"db",{get:function(){return t},enumerable:!1,configurable:!0}),i.prototype.table=function(){return n},r=i);for(var o=new Set,a=r.prototype;a;a=c(a))Object.getOwnPropertyNames(a).forEach(function(e){return o.add(e)});function u(e){if(!e)return e;var t,n=Object.create(r.prototype);for(t in e)if(!o.has(t))try{n[t]=e[t]}catch(e){}return n}return this.schema.readHook&&this.hook.reading.unsubscribe(this.schema.readHook),this.schema.readHook=u,this.hook("reading",u),r},ht.prototype.defineClass=function(){return this.mapToClass(function(e){a(this,e)})},ht.prototype.add=function(t,n){var r=this,e=this.schema.primKey,i=e.auto,o=e.keyPath,a=t;return o&&i&&(a=at(o)(t)),this._trans("readwrite",function(e){return r.core.mutate({trans:e,type:"add",keys:null!=n?[n]:null,values:[a]})}).then(function(e){return e.numFailures?_e.reject(e.failures[0]):e.lastResult}).then(function(e){if(o)try{P(t,o,e)}catch(e){}return e})},ht.prototype.update=function(e,t){if("object"!=typeof e||k(e))return this.where(":id").equals(e).modify(t);e=O(e,this.schema.primKey.keyPath);return void 0===e?Xe(new Y.InvalidArgument("Given object does not contain its primary key")):this.where(":id").equals(e).modify(t)},ht.prototype.put=function(t,n){var r=this,e=this.schema.primKey,i=e.auto,o=e.keyPath,a=t;return o&&i&&(a=at(o)(t)),this._trans("readwrite",function(e){return r.core.mutate({trans:e,type:"put",values:[a],keys:null!=n?[n]:null})}).then(function(e){return e.numFailures?_e.reject(e.failures[0]):e.lastResult}).then(function(e){if(o)try{P(t,o,e)}catch(e){}return e})},ht.prototype.delete=function(t){var n=this;return this._trans("readwrite",function(e){return n.core.mutate({trans:e,type:"delete",keys:[t]})}).then(function(e){return e.numFailures?_e.reject(e.failures[0]):void 0})},ht.prototype.clear=function(){var t=this;return this._trans("readwrite",function(e){return t.core.mutate({trans:e,type:"deleteRange",range:ot})}).then(function(e){return e.numFailures?_e.reject(e.failures[0]):void 0})},ht.prototype.bulkGet=function(t){var n=this;return this._trans("readonly",function(e){return n.core.getMany({keys:t,trans:e}).then(function(e){return e.map(function(e){return n.hook.reading.fire(e)})})})},ht.prototype.bulkAdd=function(r,e,t){var o=this,a=Array.isArray(e)?e:void 0,u=(t=t||(a?void 0:e))?t.allKeys:void 0;return this._trans("readwrite",function(e){var t=o.schema.primKey,n=t.auto,t=t.keyPath;if(t&&a)throw new Y.InvalidArgument("bulkAdd(): keys argument invalid on tables with inbound keys");if(a&&a.length!==r.length)throw new Y.InvalidArgument("Arguments objects and keys must have the same length");var i=r.length,t=t&&n?r.map(at(t)):r;return o.core.mutate({trans:e,type:"add",keys:a,values:t,wantResults:u}).then(function(e){var t=e.numFailures,n=e.results,r=e.lastResult,e=e.failures;if(0===t)return u?n:r;throw new V("".concat(o.name,".bulkAdd(): ").concat(t," of ").concat(i," operations failed"),e)})})},ht.prototype.bulkPut=function(r,e,t){var o=this,a=Array.isArray(e)?e:void 0,u=(t=t||(a?void 0:e))?t.allKeys:void 0;return this._trans("readwrite",function(e){var t=o.schema.primKey,n=t.auto,t=t.keyPath;if(t&&a)throw new Y.InvalidArgument("bulkPut(): keys argument invalid on tables with inbound keys");if(a&&a.length!==r.length)throw new Y.InvalidArgument("Arguments objects and keys must have the same length");var i=r.length,t=t&&n?r.map(at(t)):r;return o.core.mutate({trans:e,type:"put",keys:a,values:t,wantResults:u}).then(function(e){var t=e.numFailures,n=e.results,r=e.lastResult,e=e.failures;if(0===t)return u?n:r;throw new V("".concat(o.name,".bulkPut(): ").concat(t," of ").concat(i," operations failed"),e)})})},ht.prototype.bulkUpdate=function(t){var h=this,n=this.core,r=t.map(function(e){return e.key}),i=t.map(function(e){return e.changes}),d=[];return this._trans("readwrite",function(e){return n.getMany({trans:e,keys:r,cache:"clone"}).then(function(c){var l=[],f=[];t.forEach(function(e,t){var n=e.key,r=e.changes,i=c[t];if(i){for(var o=0,a=Object.keys(r);o<a.length;o++){var u=a[o],s=r[u];if(u===h.schema.primKey.keyPath){if(0!==st(s,n))throw new Y.Constraint("Cannot update primary key in bulkUpdate()")}else P(i,u,s)}d.push(t),l.push(n),f.push(i)}});var s=l.length;return n.mutate({trans:e,type:"put",keys:l,values:f,updates:{keys:r,changeSpecs:i}}).then(function(e){var t=e.numFailures,n=e.failures;if(0===t)return s;for(var r=0,i=Object.keys(n);r<i.length;r++){var o,a=i[r],u=d[Number(a)];null!=u&&(o=n[a],delete n[a],n[u]=o)}throw new V("".concat(h.name,".bulkUpdate(): ").concat(t," of ").concat(s," operations failed"),n)})})})},ht.prototype.bulkDelete=function(t){var r=this,i=t.length;return this._trans("readwrite",function(e){return r.core.mutate({trans:e,type:"delete",keys:t})}).then(function(e){var t=e.numFailures,n=e.lastResult,e=e.failures;if(0===t)return n;throw new V("".concat(r.name,".bulkDelete(): ").concat(t," of ").concat(i," operations failed"),e)})},ht);function ht(){}function dt(i){function t(e,t){if(t){for(var n=arguments.length,r=new Array(n-1);--n;)r[n-1]=arguments[n];return a[e].subscribe.apply(null,r),i}if("string"==typeof e)return a[e]}var a={};t.addEventType=u;for(var e=1,n=arguments.length;e<n;++e)u(arguments[e]);return t;function u(e,n,r){if("object"!=typeof e){var i;n=n||ne;var o={subscribers:[],fire:r=r||G,subscribe:function(e){-1===o.subscribers.indexOf(e)&&(o.subscribers.push(e),o.fire=n(o.fire,e))},unsubscribe:function(t){o.subscribers=o.subscribers.filter(function(e){return e!==t}),o.fire=o.subscribers.reduce(n,r)}};return a[e]=t[e]=o}x(i=e).forEach(function(e){var t=i[e];if(k(t))u(e,i[e][0],i[e][1]);else{if("asap"!==t)throw new Y.InvalidArgument("Invalid event config");var n=u(e,X,function(){for(var e=arguments.length,t=new Array(e);e--;)t[e]=arguments[e];n.subscribers.forEach(function(e){v(function(){e.apply(null,t)})})})}})}}function pt(e,t){return o(t).from({prototype:e}),t}function yt(e,t){return!(e.filter||e.algorithm||e.or)&&(t?e.justLimit:!e.replayFilter)}function vt(e,t){e.filter=it(e.filter,t)}function mt(e,t,n){var r=e.replayFilter;e.replayFilter=r?function(){return it(r(),t())}:t,e.justLimit=n&&!r}function bt(e,t){if(e.isPrimKey)return t.primaryKey;var n=t.getIndexByKeyPath(e.index);if(!n)throw new Y.Schema("KeyPath "+e.index+" on object store "+t.name+" is not indexed");return n}function gt(e,t,n){var r=bt(e,t.schema);return t.openCursor({trans:n,values:!e.keysOnly,reverse:"prev"===e.dir,unique:!!e.unique,query:{index:r,range:e.range}})}function wt(e,o,t,n){var a=e.replayFilter?it(e.filter,e.replayFilter()):e.filter;if(e.or){var u={},r=function(e,t,n){var r,i;a&&!a(t,n,function(e){return t.stop(e)},function(e){return t.fail(e)})||("[object ArrayBuffer]"===(i=""+(r=t.primaryKey))&&(i=""+new Uint8Array(r)),m(u,i)||(u[i]=!0,o(e,t,n)))};return Promise.all([e.or._iterate(r,t),_t(gt(e,n,t),e.algorithm,r,!e.keysOnly&&e.valueMapper)])}return _t(gt(e,n,t),it(e.algorithm,a),o,!e.keysOnly&&e.valueMapper)}function _t(e,r,i,o){var a=qe(o?function(e,t,n){return i(o(e),t,n)}:i);return e.then(function(n){if(n)return n.start(function(){var t=function(){return n.continue()};r&&!r(n,function(e){return t=e},function(e){n.stop(e),t=G},function(e){n.fail(e),t=G})||a(n.value,n,function(e){return t=e}),t()})})}var xt=(kt.prototype.execute=function(e){var t=this["@@propmod"];if(void 0!==t.add){var n=t.add;if(k(n))return i(i([],k(e)?e:[],!0),n,!0).sort();if("number"==typeof n)return(Number(e)||0)+n;if("bigint"==typeof n)try{return BigInt(e)+n}catch(e){return BigInt(0)+n}throw new TypeError("Invalid term ".concat(n))}if(void 0!==t.remove){var r=t.remove;if(k(r))return k(e)?e.filter(function(e){return!r.includes(e)}).sort():[];if("number"==typeof r)return Number(e)-r;if("bigint"==typeof r)try{return BigInt(e)-r}catch(e){return BigInt(0)-r}throw new TypeError("Invalid subtrahend ".concat(r))}n=null===(n=t.replacePrefix)||void 0===n?void 0:n[0];return n&&"string"==typeof e&&e.startsWith(n)?t.replacePrefix[1]+e.substring(n.length):e},kt);function kt(e){this["@@propmod"]=e}var Ot=(Pt.prototype._read=function(e,t){var n=this._ctx;return n.error?n.table._trans(null,Xe.bind(null,n.error)):n.table._trans("readonly",e).then(t)},Pt.prototype._write=function(e){var t=this._ctx;return t.error?t.table._trans(null,Xe.bind(null,t.error)):t.table._trans("readwrite",e,"locked")},Pt.prototype._addAlgorithm=function(e){var t=this._ctx;t.algorithm=it(t.algorithm,e)},Pt.prototype._iterate=function(e,t){return wt(this._ctx,e,t,this._ctx.table.core)},Pt.prototype.clone=function(e){var t=Object.create(this.constructor.prototype),n=Object.create(this._ctx);return e&&a(n,e),t._ctx=n,t},Pt.prototype.raw=function(){return this._ctx.valueMapper=null,this},Pt.prototype.each=function(t){var n=this._ctx;return this._read(function(e){return wt(n,t,e,n.table.core)})},Pt.prototype.count=function(e){var i=this;return this._read(function(e){var t=i._ctx,n=t.table.core;if(yt(t,!0))return n.count({trans:e,query:{index:bt(t,n.schema),range:t.range}}).then(function(e){return Math.min(e,t.limit)});var r=0;return wt(t,function(){return++r,!1},e,n).then(function(){return r})}).then(e)},Pt.prototype.sortBy=function(e,t){var n=e.split(".").reverse(),r=n[0],i=n.length-1;function o(e,t){return t?o(e[n[t]],t-1):e[r]}var a="next"===this._ctx.dir?1:-1;function u(e,t){return st(o(e,i),o(t,i))*a}return this.toArray(function(e){return e.sort(u)}).then(t)},Pt.prototype.toArray=function(e){var o=this;return this._read(function(e){var t=o._ctx;if("next"===t.dir&&yt(t,!0)&&0<t.limit){var n=t.valueMapper,r=bt(t,t.table.core.schema);return t.table.core.query({trans:e,limit:t.limit,values:!0,query:{index:r,range:t.range}}).then(function(e){e=e.result;return n?e.map(n):e})}var i=[];return wt(t,function(e){return i.push(e)},e,t.table.core).then(function(){return i})},e)},Pt.prototype.offset=function(t){var e=this._ctx;return t<=0||(e.offset+=t,yt(e)?mt(e,function(){var n=t;return function(e,t){return 0===n||(1===n?--n:t(function(){e.advance(n),n=0}),!1)}}):mt(e,function(){var e=t;return function(){return--e<0}})),this},Pt.prototype.limit=function(e){return this._ctx.limit=Math.min(this._ctx.limit,e),mt(this._ctx,function(){var r=e;return function(e,t,n){return--r<=0&&t(n),0<=r}},!0),this},Pt.prototype.until=function(r,i){return vt(this._ctx,function(e,t,n){return!r(e.value)||(t(n),i)}),this},Pt.prototype.first=function(e){return this.limit(1).toArray(function(e){return e[0]}).then(e)},Pt.prototype.last=function(e){return this.reverse().first(e)},Pt.prototype.filter=function(t){var e;return vt(this._ctx,function(e){return t(e.value)}),(e=this._ctx).isMatch=it(e.isMatch,t),this},Pt.prototype.and=function(e){return this.filter(e)},Pt.prototype.or=function(e){return new this.db.WhereClause(this._ctx.table,e,this)},Pt.prototype.reverse=function(){return this._ctx.dir="prev"===this._ctx.dir?"next":"prev",this._ondirectionchange&&this._ondirectionchange(this._ctx.dir),this},Pt.prototype.desc=function(){return this.reverse()},Pt.prototype.eachKey=function(n){var e=this._ctx;return e.keysOnly=!e.isMatch,this.each(function(e,t){n(t.key,t)})},Pt.prototype.eachUniqueKey=function(e){return this._ctx.unique="unique",this.eachKey(e)},Pt.prototype.eachPrimaryKey=function(n){var e=this._ctx;return e.keysOnly=!e.isMatch,this.each(function(e,t){n(t.primaryKey,t)})},Pt.prototype.keys=function(e){var t=this._ctx;t.keysOnly=!t.isMatch;var n=[];return this.each(function(e,t){n.push(t.key)}).then(function(){return n}).then(e)},Pt.prototype.primaryKeys=function(e){var n=this._ctx;if("next"===n.dir&&yt(n,!0)&&0<n.limit)return this._read(function(e){var t=bt(n,n.table.core.schema);return n.table.core.query({trans:e,values:!1,limit:n.limit,query:{index:t,range:n.range}})}).then(function(e){return e.result}).then(e);n.keysOnly=!n.isMatch;var r=[];return this.each(function(e,t){r.push(t.primaryKey)}).then(function(){return r}).then(e)},Pt.prototype.uniqueKeys=function(e){return this._ctx.unique="unique",this.keys(e)},Pt.prototype.firstKey=function(e){return this.limit(1).keys(function(e){return e[0]}).then(e)},Pt.prototype.lastKey=function(e){return this.reverse().firstKey(e)},Pt.prototype.distinct=function(){var e=this._ctx,e=e.index&&e.table.schema.idxByName[e.index];if(!e||!e.multi)return this;var n={};return vt(this._ctx,function(e){var t=e.primaryKey.toString(),e=m(n,t);return n[t]=!0,!e}),this},Pt.prototype.modify=function(w){var n=this,r=this._ctx;return this._write(function(d){var a,u,p;p="function"==typeof w?w:(a=x(w),u=a.length,function(e){for(var t=!1,n=0;n<u;++n){var r=a[n],i=w[r],o=O(e,r);i instanceof xt?(P(e,r,i.execute(o)),t=!0):o!==i&&(P(e,r,i),t=!0)}return t});var y=r.table.core,e=y.schema.primaryKey,v=e.outbound,m=e.extractKey,b=200,e=n.db._options.modifyChunkSize;e&&(b="object"==typeof e?e[y.name]||e["*"]||200:e);function g(e,t){var n=t.failures,t=t.numFailures;c+=e-t;for(var r=0,i=x(n);r<i.length;r++){var o=i[r];s.push(n[o])}}var s=[],c=0,t=[];return n.clone().primaryKeys().then(function(l){function f(s){var c=Math.min(b,l.length-s);return y.getMany({trans:d,keys:l.slice(s,s+c),cache:"immutable"}).then(function(e){for(var n=[],t=[],r=v?[]:null,i=[],o=0;o<c;++o){var a=e[o],u={value:S(a),primKey:l[s+o]};!1!==p.call(u,u.value,u)&&(null==u.value?i.push(l[s+o]):v||0===st(m(a),m(u.value))?(t.push(u.value),v&&r.push(l[s+o])):(i.push(l[s+o]),n.push(u.value)))}return Promise.resolve(0<n.length&&y.mutate({trans:d,type:"add",values:n}).then(function(e){for(var t in e.failures)i.splice(parseInt(t),1);g(n.length,e)})).then(function(){return(0<t.length||h&&"object"==typeof w)&&y.mutate({trans:d,type:"put",keys:r,values:t,criteria:h,changeSpec:"function"!=typeof w&&w,isAdditionalChunk:0<s}).then(function(e){return g(t.length,e)})}).then(function(){return(0<i.length||h&&w===Kt)&&y.mutate({trans:d,type:"delete",keys:i,criteria:h,isAdditionalChunk:0<s}).then(function(e){return g(i.length,e)})}).then(function(){return l.length>s+c&&f(s+b)})})}var h=yt(r)&&r.limit===1/0&&("function"!=typeof w||w===Kt)&&{index:r.index,range:r.range};return f(0).then(function(){if(0<s.length)throw new U("Error modifying one or more objects",s,c,t);return l.length})})})},Pt.prototype.delete=function(){var i=this._ctx,n=i.range;return yt(i)&&(i.isPrimKey||3===n.type)?this._write(function(e){var t=i.table.core.schema.primaryKey,r=n;return i.table.core.count({trans:e,query:{index:t,range:r}}).then(function(n){return i.table.core.mutate({trans:e,type:"deleteRange",range:r}).then(function(e){var t=e.failures;e.lastResult,e.results;e=e.numFailures;if(e)throw new U("Could not delete some values",Object.keys(t).map(function(e){return t[e]}),n-e);return n-e})})}):this.modify(Kt)},Pt);function Pt(){}var Kt=function(e,t){return t.value=null};function Et(e,t){return e<t?-1:e===t?0:1}function St(e,t){return t<e?-1:e===t?0:1}function jt(e,t,n){e=e instanceof Dt?new e.Collection(e):e;return e._ctx.error=new(n||TypeError)(t),e}function At(e){return new e.Collection(e,function(){return qt("")}).limit(0)}function Ct(e,s,n,r){var i,c,l,f,h,d,p,y=n.length;if(!n.every(function(e){return"string"==typeof e}))return jt(e,Ze);function t(e){i="next"===e?function(e){return e.toUpperCase()}:function(e){return e.toLowerCase()},c="next"===e?function(e){return e.toLowerCase()}:function(e){return e.toUpperCase()},l="next"===e?Et:St;var t=n.map(function(e){return{lower:c(e),upper:i(e)}}).sort(function(e,t){return l(e.lower,t.lower)});f=t.map(function(e){return e.upper}),h=t.map(function(e){return e.lower}),p="next"===(d=e)?"":r}t("next");e=new e.Collection(e,function(){return Tt(f[0],h[y-1]+r)});e._ondirectionchange=function(e){t(e)};var v=0;return e._addAlgorithm(function(e,t,n){var r=e.key;if("string"!=typeof r)return!1;var i=c(r);if(s(i,h,v))return!0;for(var o=null,a=v;a<y;++a){var u=function(e,t,n,r,i,o){for(var a=Math.min(e.length,r.length),u=-1,s=0;s<a;++s){var c=t[s];if(c!==r[s])return i(e[s],n[s])<0?e.substr(0,s)+n[s]+n.substr(s+1):i(e[s],r[s])<0?e.substr(0,s)+r[s]+n.substr(s+1):0<=u?e.substr(0,u)+t[u]+n.substr(u+1):null;i(e[s],c)<0&&(u=s)}return a<r.length&&"next"===o?e+n.substr(e.length):a<e.length&&"prev"===o?e.substr(0,n.length):u<0?null:e.substr(0,u)+r[u]+n.substr(u+1)}(r,i,f[a],h[a],l,d);null===u&&null===o?v=a+1:(null===o||0<l(o,u))&&(o=u)}return t(null!==o?function(){e.continue(o+p)}:n),!1}),e}function Tt(e,t,n,r){return{type:2,lower:e,upper:t,lowerOpen:n,upperOpen:r}}function qt(e){return{type:1,lower:e,upper:e}}var Dt=(Object.defineProperty(It.prototype,"Collection",{get:function(){return this._ctx.table.db.Collection},enumerable:!1,configurable:!0}),It.prototype.between=function(e,t,n,r){n=!1!==n,r=!0===r;try{return 0<this._cmp(e,t)||0===this._cmp(e,t)&&(n||r)&&(!n||!r)?At(this):new this.Collection(this,function(){return Tt(e,t,!n,!r)})}catch(e){return jt(this,Je)}},It.prototype.equals=function(e){return null==e?jt(this,Je):new this.Collection(this,function(){return qt(e)})},It.prototype.above=function(e){return null==e?jt(this,Je):new this.Collection(this,function(){return Tt(e,void 0,!0)})},It.prototype.aboveOrEqual=function(e){return null==e?jt(this,Je):new this.Collection(this,function(){return Tt(e,void 0,!1)})},It.prototype.below=function(e){return null==e?jt(this,Je):new this.Collection(this,function(){return Tt(void 0,e,!1,!0)})},It.prototype.belowOrEqual=function(e){return null==e?jt(this,Je):new this.Collection(this,function(){return Tt(void 0,e)})},It.prototype.startsWith=function(e){return"string"!=typeof e?jt(this,Ze):this.between(e,e+He,!0,!0)},It.prototype.startsWithIgnoreCase=function(e){return""===e?this.startsWith(e):Ct(this,function(e,t){return 0===e.indexOf(t[0])},[e],He)},It.prototype.equalsIgnoreCase=function(e){return Ct(this,function(e,t){return e===t[0]},[e],"")},It.prototype.anyOfIgnoreCase=function(){var e=I.apply(D,arguments);return 0===e.length?At(this):Ct(this,function(e,t){return-1!==t.indexOf(e)},e,"")},It.prototype.startsWithAnyOfIgnoreCase=function(){var e=I.apply(D,arguments);return 0===e.length?At(this):Ct(this,function(t,e){return e.some(function(e){return 0===t.indexOf(e)})},e,He)},It.prototype.anyOf=function(){var t=this,i=I.apply(D,arguments),o=this._cmp;try{i.sort(o)}catch(e){return jt(this,Je)}if(0===i.length)return At(this);var e=new this.Collection(this,function(){return Tt(i[0],i[i.length-1])});e._ondirectionchange=function(e){o="next"===e?t._ascending:t._descending,i.sort(o)};var a=0;return e._addAlgorithm(function(e,t,n){for(var r=e.key;0<o(r,i[a]);)if(++a===i.length)return t(n),!1;return 0===o(r,i[a])||(t(function(){e.continue(i[a])}),!1)}),e},It.prototype.notEqual=function(e){return this.inAnyRange([[-1/0,e],[e,this.db._maxKey]],{includeLowers:!1,includeUppers:!1})},It.prototype.noneOf=function(){var e=I.apply(D,arguments);if(0===e.length)return new this.Collection(this);try{e.sort(this._ascending)}catch(e){return jt(this,Je)}var t=e.reduce(function(e,t){return e?e.concat([[e[e.length-1][1],t]]):[[-1/0,t]]},null);return t.push([e[e.length-1],this.db._maxKey]),this.inAnyRange(t,{includeLowers:!1,includeUppers:!1})},It.prototype.inAnyRange=function(e,t){var o=this,a=this._cmp,u=this._ascending,n=this._descending,s=this._min,c=this._max;if(0===e.length)return At(this);if(!e.every(function(e){return void 0!==e[0]&&void 0!==e[1]&&u(e[0],e[1])<=0}))return jt(this,"First argument to inAnyRange() must be an Array of two-value Arrays [lower,upper] where upper must not be lower than lower",Y.InvalidArgument);var r=!t||!1!==t.includeLowers,i=t&&!0===t.includeUppers;var l,f=u;function h(e,t){return f(e[0],t[0])}try{(l=e.reduce(function(e,t){for(var n=0,r=e.length;n<r;++n){var i=e[n];if(a(t[0],i[1])<0&&0<a(t[1],i[0])){i[0]=s(i[0],t[0]),i[1]=c(i[1],t[1]);break}}return n===r&&e.push(t),e},[])).sort(h)}catch(e){return jt(this,Je)}var d=0,p=i?function(e){return 0<u(e,l[d][1])}:function(e){return 0<=u(e,l[d][1])},y=r?function(e){return 0<n(e,l[d][0])}:function(e){return 0<=n(e,l[d][0])};var v=p,e=new this.Collection(this,function(){return Tt(l[0][0],l[l.length-1][1],!r,!i)});return e._ondirectionchange=function(e){f="next"===e?(v=p,u):(v=y,n),l.sort(h)},e._addAlgorithm(function(e,t,n){for(var r,i=e.key;v(i);)if(++d===l.length)return t(n),!1;return!p(r=i)&&!y(r)||(0===o._cmp(i,l[d][1])||0===o._cmp(i,l[d][0])||t(function(){f===u?e.continue(l[d][0]):e.continue(l[d][1])}),!1)}),e},It.prototype.startsWithAnyOf=function(){var e=I.apply(D,arguments);return e.every(function(e){return"string"==typeof e})?0===e.length?At(this):this.inAnyRange(e.map(function(e){return[e,e+He]})):jt(this,"startsWithAnyOf() only works with strings")},It);function It(){}function Bt(t){return qe(function(e){return Rt(e),t(e.target.error),!1})}function Rt(e){e.stopPropagation&&e.stopPropagation(),e.preventDefault&&e.preventDefault()}var Ft="storagemutated",Mt="x-storagemutated-1",Nt=dt(null,Ft),Lt=(Ut.prototype._lock=function(){return y(!me.global),++this._reculock,1!==this._reculock||me.global||(me.lockOwnerFor=this),this},Ut.prototype._unlock=function(){if(y(!me.global),0==--this._reculock)for(me.global||(me.lockOwnerFor=null);0<this._blockedFuncs.length&&!this._locked();){var e=this._blockedFuncs.shift();try{$e(e[1],e[0])}catch(e){}}return this},Ut.prototype._locked=function(){return this._reculock&&me.lockOwnerFor!==this},Ut.prototype.create=function(t){var n=this;if(!this.mode)return this;var e=this.db.idbdb,r=this.db._state.dbOpenError;if(y(!this.idbtrans),!t&&!e)switch(r&&r.name){case"DatabaseClosedError":throw new Y.DatabaseClosed(r);case"MissingAPIError":throw new Y.MissingAPI(r.message,r);default:throw new Y.OpenFailed(r)}if(!this.active)throw new Y.TransactionInactive;return y(null===this._completion._state),(t=this.idbtrans=t||(this.db.core||e).transaction(this.storeNames,this.mode,{durability:this.chromeTransactionDurability})).onerror=qe(function(e){Rt(e),n._reject(t.error)}),t.onabort=qe(function(e){Rt(e),n.active&&n._reject(new Y.Abort(t.error)),n.active=!1,n.on("abort").fire(e)}),t.oncomplete=qe(function(){n.active=!1,n._resolve(),"mutatedParts"in t&&Nt.storagemutated.fire(t.mutatedParts)}),this},Ut.prototype._promise=function(n,r,i){var o=this;if("readwrite"===n&&"readwrite"!==this.mode)return Xe(new Y.ReadOnly("Transaction is readonly"));if(!this.active)return Xe(new Y.TransactionInactive);if(this._locked())return new _e(function(e,t){o._blockedFuncs.push([function(){o._promise(n,r,i).then(e,t)},me])});if(i)return Ne(function(){var e=new _e(function(e,t){o._lock();var n=r(e,t,o);n&&n.then&&n.then(e,t)});return e.finally(function(){return o._unlock()}),e._lib=!0,e});var e=new _e(function(e,t){var n=r(e,t,o);n&&n.then&&n.then(e,t)});return e._lib=!0,e},Ut.prototype._root=function(){return this.parent?this.parent._root():this},Ut.prototype.waitFor=function(e){var t,r=this._root(),i=_e.resolve(e);r._waitingFor?r._waitingFor=r._waitingFor.then(function(){return i}):(r._waitingFor=i,r._waitingQueue=[],t=r.idbtrans.objectStore(r.storeNames[0]),function e(){for(++r._spinCount;r._waitingQueue.length;)r._waitingQueue.shift()();r._waitingFor&&(t.get(-1/0).onsuccess=e)}());var o=r._waitingFor;return new _e(function(t,n){i.then(function(e){return r._waitingQueue.push(qe(t.bind(null,e)))},function(e){return r._waitingQueue.push(qe(n.bind(null,e)))}).finally(function(){r._waitingFor===o&&(r._waitingFor=null)})})},Ut.prototype.abort=function(){this.active&&(this.active=!1,this.idbtrans&&this.idbtrans.abort(),this._reject(new Y.Abort))},Ut.prototype.table=function(e){var t=this._memoizedTables||(this._memoizedTables={});if(m(t,e))return t[e];var n=this.schema[e];if(!n)throw new Y.NotFound("Table "+e+" not part of transaction");n=new this.db.Table(e,n,this);return n.core=this.db.core.table(e),t[e]=n},Ut);function Ut(){}function Vt(e,t,n,r,i,o,a){return{name:e,keyPath:t,unique:n,multi:r,auto:i,compound:o,src:(n&&!a?"&":"")+(r?"*":"")+(i?"++":"")+zt(t)}}function zt(e){return"string"==typeof e?e:e?"["+[].join.call(e,"+")+"]":""}function Wt(e,t,n){return{name:e,primKey:t,indexes:n,mappedClass:null,idxByName:(r=function(e){return[e.name,e]},n.reduce(function(e,t,n){n=r(t,n);return n&&(e[n[0]]=n[1]),e},{}))};var r}var Yt=function(e){try{return e.only([[]]),Yt=function(){return[[]]},[[]]}catch(e){return Yt=function(){return He},He}};function $t(t){return null==t?function(){}:"string"==typeof t?1===(n=t).split(".").length?function(e){return e[n]}:function(e){return O(e,n)}:function(e){return O(e,t)};var n}function Qt(e){return[].slice.call(e)}var Gt=0;function Xt(e){return null==e?":id":"string"==typeof e?e:"[".concat(e.join("+"),"]")}function Ht(e,i,t){function _(e){if(3===e.type)return null;if(4===e.type)throw new Error("Cannot convert never type to IDBKeyRange");var t=e.lower,n=e.upper,r=e.lowerOpen,e=e.upperOpen;return void 0===t?void 0===n?null:i.upperBound(n,!!e):void 0===n?i.lowerBound(t,!!r):i.bound(t,n,!!r,!!e)}function n(e){var h,w=e.name;return{name:w,schema:e,mutate:function(e){var y=e.trans,v=e.type,m=e.keys,b=e.values,g=e.range;return new Promise(function(t,e){t=qe(t);var n=y.objectStore(w),r=null==n.keyPath,i="put"===v||"add"===v;if(!i&&"delete"!==v&&"deleteRange"!==v)throw new Error("Invalid operation type: "+v);var o,a=(m||b||{length:1}).length;if(m&&b&&m.length!==b.length)throw new Error("Given keys array must have same length as given values array.");if(0===a)return t({numFailures:0,failures:{},results:[],lastResult:void 0});function u(e){++l,Rt(e)}var s=[],c=[],l=0;if("deleteRange"===v){if(4===g.type)return t({numFailures:l,failures:c,results:[],lastResult:void 0});3===g.type?s.push(o=n.clear()):s.push(o=n.delete(_(g)))}else{var r=i?r?[b,m]:[b,null]:[m,null],f=r[0],h=r[1];if(i)for(var d=0;d<a;++d)s.push(o=h&&void 0!==h[d]?n[v](f[d],h[d]):n[v](f[d])),o.onerror=u;else for(d=0;d<a;++d)s.push(o=n[v](f[d])),o.onerror=u}function p(e){e=e.target.result,s.forEach(function(e,t){return null!=e.error&&(c[t]=e.error)}),t({numFailures:l,failures:c,results:"delete"===v?m:s.map(function(e){return e.result}),lastResult:e})}o.onerror=function(e){u(e),p(e)},o.onsuccess=p})},getMany:function(e){var f=e.trans,h=e.keys;return new Promise(function(t,e){t=qe(t);for(var n,r=f.objectStore(w),i=h.length,o=new Array(i),a=0,u=0,s=function(e){e=e.target;o[e._pos]=e.result,++u===a&&t(o)},c=Bt(e),l=0;l<i;++l)null!=h[l]&&((n=r.get(h[l]))._pos=l,n.onsuccess=s,n.onerror=c,++a);0===a&&t(o)})},get:function(e){var r=e.trans,i=e.key;return new Promise(function(t,e){t=qe(t);var n=r.objectStore(w).get(i);n.onsuccess=function(e){return t(e.target.result)},n.onerror=Bt(e)})},query:(h=s,function(f){return new Promise(function(n,e){n=qe(n);var r,i,o,t=f.trans,a=f.values,u=f.limit,s=f.query,c=u===1/0?void 0:u,l=s.index,s=s.range,t=t.objectStore(w),l=l.isPrimaryKey?t:t.index(l.name),s=_(s);if(0===u)return n({result:[]});h?((c=a?l.getAll(s,c):l.getAllKeys(s,c)).onsuccess=function(e){return n({result:e.target.result})},c.onerror=Bt(e)):(r=0,i=!a&&"openKeyCursor"in l?l.openKeyCursor(s):l.openCursor(s),o=[],i.onsuccess=function(e){var t=i.result;return t?(o.push(a?t.value:t.primaryKey),++r===u?n({result:o}):void t.continue()):n({result:o})},i.onerror=Bt(e))})}),openCursor:function(e){var c=e.trans,o=e.values,a=e.query,u=e.reverse,l=e.unique;return new Promise(function(t,n){t=qe(t);var e=a.index,r=a.range,i=c.objectStore(w),i=e.isPrimaryKey?i:i.index(e.name),e=u?l?"prevunique":"prev":l?"nextunique":"next",s=!o&&"openKeyCursor"in i?i.openKeyCursor(_(r),e):i.openCursor(_(r),e);s.onerror=Bt(n),s.onsuccess=qe(function(e){var r,i,o,a,u=s.result;u?(u.___id=++Gt,u.done=!1,r=u.continue.bind(u),i=(i=u.continuePrimaryKey)&&i.bind(u),o=u.advance.bind(u),a=function(){throw new Error("Cursor not stopped")},u.trans=c,u.stop=u.continue=u.continuePrimaryKey=u.advance=function(){throw new Error("Cursor not started")},u.fail=qe(n),u.next=function(){var e=this,t=1;return this.start(function(){return t--?e.continue():e.stop()}).then(function(){return e})},u.start=function(e){function t(){if(s.result)try{e()}catch(e){u.fail(e)}else u.done=!0,u.start=function(){throw new Error("Cursor behind last entry")},u.stop()}var n=new Promise(function(t,e){t=qe(t),s.onerror=Bt(e),u.fail=e,u.stop=function(e){u.stop=u.continue=u.continuePrimaryKey=u.advance=a,t(e)}});return s.onsuccess=qe(function(e){s.onsuccess=t,t()}),u.continue=r,u.continuePrimaryKey=i,u.advance=o,t(),n},t(u)):t(null)},n)})},count:function(e){var t=e.query,i=e.trans,o=t.index,a=t.range;return new Promise(function(t,e){var n=i.objectStore(w),r=o.isPrimaryKey?n:n.index(o.name),n=_(a),r=n?r.count(n):r.count();r.onsuccess=qe(function(e){return t(e.target.result)}),r.onerror=Bt(e)})}}}var r,o,a,u=(o=t,a=Qt((r=e).objectStoreNames),{schema:{name:r.name,tables:a.map(function(e){return o.objectStore(e)}).map(function(t){var e=t.keyPath,n=t.autoIncrement,r=k(e),i={},n={name:t.name,primaryKey:{name:null,isPrimaryKey:!0,outbound:null==e,compound:r,keyPath:e,autoIncrement:n,unique:!0,extractKey:$t(e)},indexes:Qt(t.indexNames).map(function(e){return t.index(e)}).map(function(e){var t=e.name,n=e.unique,r=e.multiEntry,e=e.keyPath,r={name:t,compound:k(e),keyPath:e,unique:n,multiEntry:r,extractKey:$t(e)};return i[Xt(e)]=r}),getIndexByKeyPath:function(e){return i[Xt(e)]}};return i[":id"]=n.primaryKey,null!=e&&(i[Xt(e)]=n.primaryKey),n})},hasGetAll:0<a.length&&"getAll"in o.objectStore(a[0])&&!("undefined"!=typeof navigator&&/Safari/.test(navigator.userAgent)&&!/(Chrome\/|Edge\/)/.test(navigator.userAgent)&&[].concat(navigator.userAgent.match(/Safari\/(\d*)/))[1]<604)}),t=u.schema,s=u.hasGetAll,u=t.tables.map(n),c={};return u.forEach(function(e){return c[e.name]=e}),{stack:"dbcore",transaction:e.transaction.bind(e),table:function(e){if(!c[e])throw new Error("Table '".concat(e,"' not found"));return c[e]},MIN_KEY:-1/0,MAX_KEY:Yt(i),schema:t}}function Jt(e,t,n,r){var i=n.IDBKeyRange;return n.indexedDB,{dbcore:(r=Ht(t,i,r),e.dbcore.reduce(function(e,t){t=t.create;return _(_({},e),t(e))},r))}}function Zt(n,e){var t=e.db,e=Jt(n._middlewares,t,n._deps,e);n.core=e.dbcore,n.tables.forEach(function(e){var t=e.name;n.core.schema.tables.some(function(e){return e.name===t})&&(e.core=n.core.table(t),n[t]instanceof n.Table&&(n[t].core=e.core))})}function en(i,e,t,o){t.forEach(function(n){var r=o[n];e.forEach(function(e){var t=function e(t,n){return h(t,n)||(t=c(t))&&e(t,n)}(e,n);(!t||"value"in t&&void 0===t.value)&&(e===i.Transaction.prototype||e instanceof i.Transaction?l(e,n,{get:function(){return this.table(n)},set:function(e){u(this,n,{value:e,writable:!0,configurable:!0,enumerable:!0})}}):e[n]=new i.Table(n,r))})})}function tn(n,e){e.forEach(function(e){for(var t in e)e[t]instanceof n.Table&&delete e[t]})}function nn(e,t){return e._cfg.version-t._cfg.version}function rn(n,r,i,e){var o=n._dbSchema;i.objectStoreNames.contains("$meta")&&!o.$meta&&(o.$meta=Wt("$meta",hn("")[0],[]),n._storeNames.push("$meta"));var a=n._createTransaction("readwrite",n._storeNames,o);a.create(i),a._completion.catch(e);var u=a._reject.bind(a),s=me.transless||me;Ne(function(){return me.trans=a,me.transless=s,0!==r?(Zt(n,i),t=r,((e=a).storeNames.includes("$meta")?e.table("$meta").get("version").then(function(e){return null!=e?e:t}):_e.resolve(t)).then(function(e){return c=e,l=a,f=i,t=[],e=(s=n)._versions,h=s._dbSchema=ln(0,s.idbdb,f),0!==(e=e.filter(function(e){return e._cfg.version>=c})).length?(e.forEach(function(u){t.push(function(){var t=h,e=u._cfg.dbschema;fn(s,t,f),fn(s,e,f),h=s._dbSchema=e;var n=an(t,e);n.add.forEach(function(e){un(f,e[0],e[1].primKey,e[1].indexes)}),n.change.forEach(function(e){if(e.recreate)throw new Y.Upgrade("Not yet support for changing primary key");var t=f.objectStore(e.name);e.add.forEach(function(e){return cn(t,e)}),e.change.forEach(function(e){t.deleteIndex(e.name),cn(t,e)}),e.del.forEach(function(e){return t.deleteIndex(e)})});var r=u._cfg.contentUpgrade;if(r&&u._cfg.version>c){Zt(s,f),l._memoizedTables={};var i=g(e);n.del.forEach(function(e){i[e]=t[e]}),tn(s,[s.Transaction.prototype]),en(s,[s.Transaction.prototype],x(i),i),l.schema=i;var o,a=B(r);a&&Le();n=_e.follow(function(){var e;(o=r(l))&&a&&(e=Ue.bind(null,null),o.then(e,e))});return o&&"function"==typeof o.then?_e.resolve(o):n.then(function(){return o})}}),t.push(function(e){var t,n,r=u._cfg.dbschema;t=r,n=e,[].slice.call(n.db.objectStoreNames).forEach(function(e){return null==t[e]&&n.db.deleteObjectStore(e)}),tn(s,[s.Transaction.prototype]),en(s,[s.Transaction.prototype],s._storeNames,s._dbSchema),l.schema=s._dbSchema}),t.push(function(e){s.idbdb.objectStoreNames.contains("$meta")&&(Math.ceil(s.idbdb.version/10)===u._cfg.version?(s.idbdb.deleteObjectStore("$meta"),delete s._dbSchema.$meta,s._storeNames=s._storeNames.filter(function(e){return"$meta"!==e})):e.objectStore("$meta").put(u._cfg.version,"version"))})}),function e(){return t.length?_e.resolve(t.shift()(l.idbtrans)).then(e):_e.resolve()}().then(function(){sn(h,f)})):_e.resolve();var s,c,l,f,t,h}).catch(u)):(x(o).forEach(function(e){un(i,e,o[e].primKey,o[e].indexes)}),Zt(n,i),void _e.follow(function(){return n.on.populate.fire(a)}).catch(u));var e,t})}function on(e,r){sn(e._dbSchema,r),r.db.version%10!=0||r.objectStoreNames.contains("$meta")||r.db.createObjectStore("$meta").add(Math.ceil(r.db.version/10-1),"version");var t=ln(0,e.idbdb,r);fn(e,e._dbSchema,r);for(var n=0,i=an(t,e._dbSchema).change;n<i.length;n++){var o=function(t){if(t.change.length||t.recreate)return console.warn("Unable to patch indexes of table ".concat(t.name," because it has changes on the type of index or primary key.")),{value:void 0};var n=r.objectStore(t.name);t.add.forEach(function(e){ie&&console.debug("Dexie upgrade patch: Creating missing index ".concat(t.name,".").concat(e.src)),cn(n,e)})}(i[n]);if("object"==typeof o)return o.value}}function an(e,t){var n,r={del:[],add:[],change:[]};for(n in e)t[n]||r.del.push(n);for(n in t){var i=e[n],o=t[n];if(i){var a={name:n,def:o,recreate:!1,del:[],add:[],change:[]};if(""+(i.primKey.keyPath||"")!=""+(o.primKey.keyPath||"")||i.primKey.auto!==o.primKey.auto)a.recreate=!0,r.change.push(a);else{var u=i.idxByName,s=o.idxByName,c=void 0;for(c in u)s[c]||a.del.push(c);for(c in s){var l=u[c],f=s[c];l?l.src!==f.src&&a.change.push(f):a.add.push(f)}(0<a.del.length||0<a.add.length||0<a.change.length)&&r.change.push(a)}}else r.add.push([n,o])}return r}function un(e,t,n,r){var i=e.db.createObjectStore(t,n.keyPath?{keyPath:n.keyPath,autoIncrement:n.auto}:{autoIncrement:n.auto});return r.forEach(function(e){return cn(i,e)}),i}function sn(t,n){x(t).forEach(function(e){n.db.objectStoreNames.contains(e)||(ie&&console.debug("Dexie: Creating missing table",e),un(n,e,t[e].primKey,t[e].indexes))})}function cn(e,t){e.createIndex(t.name,t.keyPath,{unique:t.unique,multiEntry:t.multi})}function ln(e,t,u){var s={};return b(t.objectStoreNames,0).forEach(function(e){for(var t=u.objectStore(e),n=Vt(zt(a=t.keyPath),a||"",!0,!1,!!t.autoIncrement,a&&"string"!=typeof a,!0),r=[],i=0;i<t.indexNames.length;++i){var o=t.index(t.indexNames[i]),a=o.keyPath,o=Vt(o.name,a,!!o.unique,!!o.multiEntry,!1,a&&"string"!=typeof a,!1);r.push(o)}s[e]=Wt(e,n,r)}),s}function fn(e,t,n){for(var r=n.db.objectStoreNames,i=0;i<r.length;++i){var o=r[i],a=n.objectStore(o);e._hasGetAll="getAll"in a;for(var u=0;u<a.indexNames.length;++u){var s=a.indexNames[u],c=a.index(s).keyPath,l="string"==typeof c?c:"["+b(c).join("+")+"]";!t[o]||(c=t[o].idxByName[l])&&(c.name=s,delete t[o].idxByName[l],t[o].idxByName[s]=c)}}"undefined"!=typeof navigator&&/Safari/.test(navigator.userAgent)&&!/(Chrome\/|Edge\/)/.test(navigator.userAgent)&&f.WorkerGlobalScope&&f instanceof f.WorkerGlobalScope&&[].concat(navigator.userAgent.match(/Safari\/(\d*)/))[1]<604&&(e._hasGetAll=!1)}function hn(e){return e.split(",").map(function(e,t){var n=(e=e.trim()).replace(/([&*]|\+\+)/g,""),r=/^\[/.test(n)?n.match(/^\[(.*)\]$/)[1].split("+"):n;return Vt(n,r||null,/\&/.test(e),/\*/.test(e),/\+\+/.test(e),k(r),0===t)})}var dn=(pn.prototype._parseStoresSpec=function(r,i){x(r).forEach(function(e){if(null!==r[e]){var t=hn(r[e]),n=t.shift();if(n.unique=!0,n.multi)throw new Y.Schema("Primary key cannot be multi-valued");t.forEach(function(e){if(e.auto)throw new Y.Schema("Only primary key can be marked as autoIncrement (++)");if(!e.keyPath)throw new Y.Schema("Index must have a name and cannot be an empty string")}),i[e]=Wt(e,n,t)}})},pn.prototype.stores=function(e){var t=this.db;this._cfg.storesSource=this._cfg.storesSource?a(this._cfg.storesSource,e):e;var e=t._versions,n={},r={};return e.forEach(function(e){a(n,e._cfg.storesSource),r=e._cfg.dbschema={},e._parseStoresSpec(n,r)}),t._dbSchema=r,tn(t,[t._allTables,t,t.Transaction.prototype]),en(t,[t._allTables,t,t.Transaction.prototype,this._cfg.tables],x(r),r),t._storeNames=x(r),this},pn.prototype.upgrade=function(e){return this._cfg.contentUpgrade=re(this._cfg.contentUpgrade||G,e),this},pn);function pn(){}function yn(e,t){var n=e._dbNamesDB;return n||(n=e._dbNamesDB=new er(tt,{addons:[],indexedDB:e,IDBKeyRange:t})).version(1).stores({dbnames:"name"}),n.table("dbnames")}function vn(e){return e&&"function"==typeof e.databases}function mn(e){return Ne(function(){return me.letThrough=!0,e()})}function bn(e){return!("from"in e)}var gn=function(e,t){if(!this){var n=new gn;return e&&"d"in e&&a(n,e),n}a(this,arguments.length?{d:1,from:e,to:1<arguments.length?t:e}:{d:0})};function wn(e,t,n){var r=st(t,n);if(!isNaN(r)){if(0<r)throw RangeError();if(bn(e))return a(e,{from:t,to:n,d:1});var i=e.l,r=e.r;if(st(n,e.from)<0)return i?wn(i,t,n):e.l={from:t,to:n,d:1,l:null,r:null},On(e);if(0<st(t,e.to))return r?wn(r,t,n):e.r={from:t,to:n,d:1,l:null,r:null},On(e);st(t,e.from)<0&&(e.from=t,e.l=null,e.d=r?r.d+1:1),0<st(n,e.to)&&(e.to=n,e.r=null,e.d=e.l?e.l.d+1:1);n=!e.r;i&&!e.l&&_n(e,i),r&&n&&_n(e,r)}}function _n(e,t){bn(t)||function e(t,n){var r=n.from,i=n.to,o=n.l,n=n.r;wn(t,r,i),o&&e(t,o),n&&e(t,n)}(e,t)}function xn(e,t){var n=kn(t),r=n.next();if(r.done)return!1;for(var i=r.value,o=kn(e),a=o.next(i.from),u=a.value;!r.done&&!a.done;){if(st(u.from,i.to)<=0&&0<=st(u.to,i.from))return!0;st(i.from,u.from)<0?i=(r=n.next(u.from)).value:u=(a=o.next(i.from)).value}return!1}function kn(e){var n=bn(e)?null:{s:0,n:e};return{next:function(e){for(var t=0<arguments.length;n;)switch(n.s){case 0:if(n.s=1,t)for(;n.n.l&&st(e,n.n.from)<0;)n={up:n,n:n.n.l,s:1};else for(;n.n.l;)n={up:n,n:n.n.l,s:1};case 1:if(n.s=2,!t||st(e,n.n.to)<=0)return{value:n.n,done:!1};case 2:if(n.n.r){n.s=3,n={up:n,n:n.n.r,s:0};continue}case 3:n=n.up}return{done:!0}}}}function On(e){var t,n,r=((null===(t=e.r)||void 0===t?void 0:t.d)||0)-((null===(n=e.l)||void 0===n?void 0:n.d)||0),i=1<r?"r":r<-1?"l":"";i&&(t="r"==i?"l":"r",n=_({},e),r=e[i],e.from=r.from,e.to=r.to,e[i]=r[i],n[i]=r[t],(e[t]=n).d=Pn(n)),e.d=Pn(e)}function Pn(e){var t=e.r,e=e.l;return(t?e?Math.max(t.d,e.d):t.d:e?e.d:0)+1}function Kn(t,n){return x(n).forEach(function(e){t[e]?_n(t[e],n[e]):t[e]=function e(t){var n,r,i={};for(n in t)m(t,n)&&(r=t[n],i[n]=!r||"object"!=typeof r||K.has(r.constructor)?r:e(r));return i}(n[e])}),t}function En(t,n){return t.all||n.all||Object.keys(t).some(function(e){return n[e]&&xn(n[e],t[e])})}r(gn.prototype,((F={add:function(e){return _n(this,e),this},addKey:function(e){return wn(this,e,e),this},addKeys:function(e){var t=this;return e.forEach(function(e){return wn(t,e,e)}),this},hasKey:function(e){var t=kn(this).next(e).value;return t&&st(t.from,e)<=0&&0<=st(t.to,e)}})[C]=function(){return kn(this)},F));var Sn={},jn={},An=!1;function Cn(e){Kn(jn,e),An||(An=!0,setTimeout(function(){An=!1,Tn(jn,!(jn={}))},0))}function Tn(e,t){void 0===t&&(t=!1);var n=new Set;if(e.all)for(var r=0,i=Object.values(Sn);r<i.length;r++)qn(a=i[r],e,n,t);else for(var o in e){var a,u=/^idb\:\/\/(.*)\/(.*)\//.exec(o);u&&(o=u[1],u=u[2],(a=Sn["idb://".concat(o,"/").concat(u)])&&qn(a,e,n,t))}n.forEach(function(e){return e()})}function qn(e,t,n,r){for(var i=[],o=0,a=Object.entries(e.queries.query);o<a.length;o++){for(var u=a[o],s=u[0],c=[],l=0,f=u[1];l<f.length;l++){var h=f[l];En(t,h.obsSet)?h.subscribers.forEach(function(e){return n.add(e)}):r&&c.push(h)}r&&i.push([s,c])}if(r)for(var d=0,p=i;d<p.length;d++){var y=p[d],s=y[0],c=y[1];e.queries.query[s]=c}}function Dn(f){var h=f._state,r=f._deps.indexedDB;if(h.isBeingOpened||f.idbdb)return h.dbReadyPromise.then(function(){return h.dbOpenError?Xe(h.dbOpenError):f});h.isBeingOpened=!0,h.dbOpenError=null,h.openComplete=!1;var t=h.openCanceller,d=Math.round(10*f.verno),p=!1;function e(){if(h.openCanceller!==t)throw new Y.DatabaseClosed("db.open() was cancelled")}function y(){return new _e(function(s,n){if(e(),!r)throw new Y.MissingAPI;var c=f.name,l=h.autoSchema||!d?r.open(c):r.open(c,d);if(!l)throw new Y.MissingAPI;l.onerror=Bt(n),l.onblocked=qe(f._fireOnBlocked),l.onupgradeneeded=qe(function(e){var t;v=l.transaction,h.autoSchema&&!f._options.allowEmptyDB?(l.onerror=Rt,v.abort(),l.result.close(),(t=r.deleteDatabase(c)).onsuccess=t.onerror=qe(function(){n(new Y.NoSuchDatabase("Database ".concat(c," doesnt exist")))})):(v.onerror=Bt(n),e=e.oldVersion>Math.pow(2,62)?0:e.oldVersion,m=e<1,f.idbdb=l.result,p&&on(f,v),rn(f,e/10,v,n))},n),l.onsuccess=qe(function(){v=null;var e,t,n,r,i,o=f.idbdb=l.result,a=b(o.objectStoreNames);if(0<a.length)try{var u=o.transaction(1===(r=a).length?r[0]:r,"readonly");if(h.autoSchema)t=o,n=u,(e=f).verno=t.version/10,n=e._dbSchema=ln(0,t,n),e._storeNames=b(t.objectStoreNames,0),en(e,[e._allTables],x(n),n);else if(fn(f,f._dbSchema,u),((i=an(ln(0,(i=f).idbdb,u),i._dbSchema)).add.length||i.change.some(function(e){return e.add.length||e.change.length}))&&!p)return console.warn("Dexie SchemaDiff: Schema was extended without increasing the number passed to db.version(). Dexie will add missing parts and increment native version number to workaround this."),o.close(),d=o.version+1,p=!0,s(y());Zt(f,u)}catch(e){}et.push(f),o.onversionchange=qe(function(e){h.vcFired=!0,f.on("versionchange").fire(e)}),o.onclose=qe(function(e){f.on("close").fire(e)}),m&&(i=f._deps,u=c,o=i.indexedDB,i=i.IDBKeyRange,vn(o)||u===tt||yn(o,i).put({name:u}).catch(G)),s()},n)}).catch(function(e){switch(null==e?void 0:e.name){case"UnknownError":if(0<h.PR1398_maxLoop)return h.PR1398_maxLoop--,console.warn("Dexie: Workaround for Chrome UnknownError on open()"),y();break;case"VersionError":if(0<d)return d=0,y()}return _e.reject(e)})}var n,i=h.dbReadyResolve,v=null,m=!1;return _e.race([t,("undefined"==typeof navigator?_e.resolve():!navigator.userAgentData&&/Safari\//.test(navigator.userAgent)&&!/Chrom(e|ium)\//.test(navigator.userAgent)&&indexedDB.databases?new Promise(function(e){function t(){return indexedDB.databases().finally(e)}n=setInterval(t,100),t()}).finally(function(){return clearInterval(n)}):Promise.resolve()).then(y)]).then(function(){return e(),h.onReadyBeingFired=[],_e.resolve(mn(function(){return f.on.ready.fire(f.vip)})).then(function e(){if(0<h.onReadyBeingFired.length){var t=h.onReadyBeingFired.reduce(re,G);return h.onReadyBeingFired=[],_e.resolve(mn(function(){return t(f.vip)})).then(e)}})}).finally(function(){h.openCanceller===t&&(h.onReadyBeingFired=null,h.isBeingOpened=!1)}).catch(function(e){h.dbOpenError=e;try{v&&v.abort()}catch(e){}return t===h.openCanceller&&f._close(),Xe(e)}).finally(function(){h.openComplete=!0,i()}).then(function(){var n;return m&&(n={},f.tables.forEach(function(t){t.schema.indexes.forEach(function(e){e.name&&(n["idb://".concat(f.name,"/").concat(t.name,"/").concat(e.name)]=new gn(-1/0,[[[]]]))}),n["idb://".concat(f.name,"/").concat(t.name,"/")]=n["idb://".concat(f.name,"/").concat(t.name,"/:dels")]=new gn(-1/0,[[[]]])}),Nt(Ft).fire(n),Tn(n,!0)),f})}function In(t){function e(e){return t.next(e)}var r=n(e),i=n(function(e){return t.throw(e)});function n(n){return function(e){var t=n(e),e=t.value;return t.done?e:e&&"function"==typeof e.then?e.then(r,i):k(e)?Promise.all(e).then(r,i):r(e)}}return n(e)()}function Bn(e,t,n){for(var r=k(e)?e.slice():[e],i=0;i<n;++i)r.push(t);return r}var Rn={stack:"dbcore",name:"VirtualIndexMiddleware",level:1,create:function(f){return _(_({},f),{table:function(e){var a=f.table(e),t=a.schema,u={},s=[];function c(e,t,n){var r=Xt(e),i=u[r]=u[r]||[],o=null==e?0:"string"==typeof e?1:e.length,a=0<t,a=_(_({},n),{name:a?"".concat(r,"(virtual-from:").concat(n.name,")"):n.name,lowLevelIndex:n,isVirtual:a,keyTail:t,keyLength:o,extractKey:$t(e),unique:!a&&n.unique});return i.push(a),a.isPrimaryKey||s.push(a),1<o&&c(2===o?e[0]:e.slice(0,o-1),t+1,n),i.sort(function(e,t){return e.keyTail-t.keyTail}),a}e=c(t.primaryKey.keyPath,0,t.primaryKey);u[":id"]=[e];for(var n=0,r=t.indexes;n<r.length;n++){var i=r[n];c(i.keyPath,0,i)}function l(e){var t,n=e.query.index;return n.isVirtual?_(_({},e),{query:{index:n.lowLevelIndex,range:(t=e.query.range,n=n.keyTail,{type:1===t.type?2:t.type,lower:Bn(t.lower,t.lowerOpen?f.MAX_KEY:f.MIN_KEY,n),lowerOpen:!0,upper:Bn(t.upper,t.upperOpen?f.MIN_KEY:f.MAX_KEY,n),upperOpen:!0})}}):e}return _(_({},a),{schema:_(_({},t),{primaryKey:e,indexes:s,getIndexByKeyPath:function(e){return(e=u[Xt(e)])&&e[0]}}),count:function(e){return a.count(l(e))},query:function(e){return a.query(l(e))},openCursor:function(t){var e=t.query.index,r=e.keyTail,n=e.isVirtual,i=e.keyLength;return n?a.openCursor(l(t)).then(function(e){return e&&o(e)}):a.openCursor(t);function o(n){return Object.create(n,{continue:{value:function(e){null!=e?n.continue(Bn(e,t.reverse?f.MAX_KEY:f.MIN_KEY,r)):t.unique?n.continue(n.key.slice(0,i).concat(t.reverse?f.MIN_KEY:f.MAX_KEY,r)):n.continue()}},continuePrimaryKey:{value:function(e,t){n.continuePrimaryKey(Bn(e,f.MAX_KEY,r),t)}},primaryKey:{get:function(){return n.primaryKey}},key:{get:function(){var e=n.key;return 1===i?e[0]:e.slice(0,i)}},value:{get:function(){return n.value}}})}}})}})}};function Fn(i,o,a,u){return a=a||{},u=u||"",x(i).forEach(function(e){var t,n,r;m(o,e)?(t=i[e],n=o[e],"object"==typeof t&&"object"==typeof n&&t&&n?(r=A(t))!==A(n)?a[u+e]=o[e]:"Object"===r?Fn(t,n,a,u+e+"."):t!==n&&(a[u+e]=o[e]):t!==n&&(a[u+e]=o[e])):a[u+e]=void 0}),x(o).forEach(function(e){m(i,e)||(a[u+e]=o[e])}),a}function Mn(e,t){return"delete"===t.type?t.keys:t.keys||t.values.map(e.extractKey)}var Nn={stack:"dbcore",name:"HooksMiddleware",level:2,create:function(e){return _(_({},e),{table:function(r){var y=e.table(r),v=y.schema.primaryKey;return _(_({},y),{mutate:function(e){var t=me.trans,n=t.table(r).hook,h=n.deleting,d=n.creating,p=n.updating;switch(e.type){case"add":if(d.fire===G)break;return t._promise("readwrite",function(){return a(e)},!0);case"put":if(d.fire===G&&p.fire===G)break;return t._promise("readwrite",function(){return a(e)},!0);case"delete":if(h.fire===G)break;return t._promise("readwrite",function(){return a(e)},!0);case"deleteRange":if(h.fire===G)break;return t._promise("readwrite",function(){return function n(r,i,o){return y.query({trans:r,values:!1,query:{index:v,range:i},limit:o}).then(function(e){var t=e.result;return a({type:"delete",keys:t,trans:r}).then(function(e){return 0<e.numFailures?Promise.reject(e.failures[0]):t.length<o?{failures:[],numFailures:0,lastResult:void 0}:n(r,_(_({},i),{lower:t[t.length-1],lowerOpen:!0}),o)})})}(e.trans,e.range,1e4)},!0)}return y.mutate(e);function a(c){var e,t,n,l=me.trans,f=c.keys||Mn(v,c);if(!f)throw new Error("Keys missing");return"delete"!==(c="add"===c.type||"put"===c.type?_(_({},c),{keys:f}):_({},c)).type&&(c.values=i([],c.values,!0)),c.keys&&(c.keys=i([],c.keys,!0)),e=y,n=f,("add"===(t=c).type?Promise.resolve([]):e.getMany({trans:t.trans,keys:n,cache:"immutable"})).then(function(u){var s=f.map(function(e,t){var n,r,i,o=u[t],a={onerror:null,onsuccess:null};return"delete"===c.type?h.fire.call(a,e,o,l):"add"===c.type||void 0===o?(n=d.fire.call(a,e,c.values[t],l),null==e&&null!=n&&(c.keys[t]=e=n,v.outbound||P(c.values[t],v.keyPath,e))):(n=Fn(o,c.values[t]),(r=p.fire.call(a,n,e,o,l))&&(i=c.values[t],Object.keys(r).forEach(function(e){m(i,e)?i[e]=r[e]:P(i,e,r[e])}))),a});return y.mutate(c).then(function(e){for(var t=e.failures,n=e.results,r=e.numFailures,e=e.lastResult,i=0;i<f.length;++i){var o=(n||f)[i],a=s[i];null==o?a.onerror&&a.onerror(t[i]):a.onsuccess&&a.onsuccess("put"===c.type&&u[i]?c.values[i]:o)}return{failures:t,results:n,numFailures:r,lastResult:e}}).catch(function(t){return s.forEach(function(e){return e.onerror&&e.onerror(t)}),Promise.reject(t)})})}}})}})}};function Ln(e,t,n){try{if(!t)return null;if(t.keys.length<e.length)return null;for(var r=[],i=0,o=0;i<t.keys.length&&o<e.length;++i)0===st(t.keys[i],e[o])&&(r.push(n?S(t.values[i]):t.values[i]),++o);return r.length===e.length?r:null}catch(e){return null}}var Un={stack:"dbcore",level:-1,create:function(t){return{table:function(e){var n=t.table(e);return _(_({},n),{getMany:function(t){if(!t.cache)return n.getMany(t);var e=Ln(t.keys,t.trans._cache,"clone"===t.cache);return e?_e.resolve(e):n.getMany(t).then(function(e){return t.trans._cache={keys:t.keys,values:"clone"===t.cache?S(e):e},e})},mutate:function(e){return"add"!==e.type&&(e.trans._cache=null),n.mutate(e)}})}}}};function Vn(e,t){return"readonly"===e.trans.mode&&!!e.subscr&&!e.trans.explicit&&"disabled"!==e.trans.db._options.cache&&!t.schema.primaryKey.outbound}function zn(e,t){switch(e){case"query":return t.values&&!t.unique;case"get":case"getMany":case"count":case"openCursor":return!1}}var Wn={stack:"dbcore",level:0,name:"Observability",create:function(b){var g=b.schema.name,w=new gn(b.MIN_KEY,b.MAX_KEY);return _(_({},b),{transaction:function(e,t,n){if(me.subscr&&"readonly"!==t)throw new Y.ReadOnly("Readwrite transaction in liveQuery context. Querier source: ".concat(me.querier));return b.transaction(e,t,n)},table:function(d){var p=b.table(d),y=p.schema,v=y.primaryKey,e=y.indexes,c=v.extractKey,l=v.outbound,m=v.autoIncrement&&e.filter(function(e){return e.compound&&e.keyPath.includes(v.keyPath)}),t=_(_({},p),{mutate:function(a){function u(e){return e="idb://".concat(g,"/").concat(d,"/").concat(e),n[e]||(n[e]=new gn)}var e,o,s,t=a.trans,n=a.mutatedParts||(a.mutatedParts={}),r=u(""),i=u(":dels"),c=a.type,l="deleteRange"===a.type?[a.range]:"delete"===a.type?[a.keys]:a.values.length<50?[Mn(v,a).filter(function(e){return e}),a.values]:[],f=l[0],h=l[1],l=a.trans._cache;return k(f)?(r.addKeys(f),(l="delete"===c||f.length===h.length?Ln(f,l):null)||i.addKeys(f),(l||h)&&(e=u,o=l,s=h,y.indexes.forEach(function(t){var n=e(t.name||"");function r(e){return null!=e?t.extractKey(e):null}function i(e){return t.multiEntry&&k(e)?e.forEach(function(e){return n.addKey(e)}):n.addKey(e)}(o||s).forEach(function(e,t){var n=o&&r(o[t]),t=s&&r(s[t]);0!==st(n,t)&&(null!=n&&i(n),null!=t&&i(t))})}))):f?(h={from:null!==(h=f.lower)&&void 0!==h?h:b.MIN_KEY,to:null!==(h=f.upper)&&void 0!==h?h:b.MAX_KEY},i.add(h),r.add(h)):(r.add(w),i.add(w),y.indexes.forEach(function(e){return u(e.name).add(w)})),p.mutate(a).then(function(o){return!f||"add"!==a.type&&"put"!==a.type||(r.addKeys(o.results),m&&m.forEach(function(t){for(var e=a.values.map(function(e){return t.extractKey(e)}),n=t.keyPath.findIndex(function(e){return e===v.keyPath}),r=0,i=o.results.length;r<i;++r)e[r][n]=o.results[r];u(t.name).addKeys(e)})),t.mutatedParts=Kn(t.mutatedParts||{},n),o})}}),e=function(e){var t=e.query,e=t.index,t=t.range;return[e,new gn(null!==(e=t.lower)&&void 0!==e?e:b.MIN_KEY,null!==(t=t.upper)&&void 0!==t?t:b.MAX_KEY)]},f={get:function(e){return[v,new gn(e.key)]},getMany:function(e){return[v,(new gn).addKeys(e.keys)]},count:e,query:e,openCursor:e};return x(f).forEach(function(s){t[s]=function(i){var e=me.subscr,t=!!e,n=Vn(me,p)&&zn(s,i)?i.obsSet={}:e;if(t){var r=function(e){e="idb://".concat(g,"/").concat(d,"/").concat(e);return n[e]||(n[e]=new gn)},o=r(""),a=r(":dels"),e=f[s](i),t=e[0],e=e[1];if(("query"===s&&t.isPrimaryKey&&!i.values?a:r(t.name||"")).add(e),!t.isPrimaryKey){if("count"!==s){var u="query"===s&&l&&i.values&&p.query(_(_({},i),{values:!1}));return p[s].apply(this,arguments).then(function(t){if("query"===s){if(l&&i.values)return u.then(function(e){e=e.result;return o.addKeys(e),t});var e=i.values?t.result.map(c):t.result;(i.values?o:a).addKeys(e)}else if("openCursor"===s){var n=t,r=i.values;return n&&Object.create(n,{key:{get:function(){return a.addKey(n.primaryKey),n.key}},primaryKey:{get:function(){var e=n.primaryKey;return a.addKey(e),e}},value:{get:function(){return r&&o.addKey(n.primaryKey),n.value}}})}return t})}a.add(w)}}return p[s].apply(this,arguments)}}),t}})}};function Yn(e,t,n){if(0===n.numFailures)return t;if("deleteRange"===t.type)return null;var r=t.keys?t.keys.length:"values"in t&&t.values?t.values.length:1;if(n.numFailures===r)return null;t=_({},t);return k(t.keys)&&(t.keys=t.keys.filter(function(e,t){return!(t in n.failures)})),"values"in t&&k(t.values)&&(t.values=t.values.filter(function(e,t){return!(t in n.failures)})),t}function $n(e,t){return n=e,(void 0===(r=t).lower||(r.lowerOpen?0<st(n,r.lower):0<=st(n,r.lower)))&&(e=e,void 0===(t=t).upper||(t.upperOpen?st(e,t.upper)<0:st(e,t.upper)<=0));var n,r}function Qn(e,d,t,n,r,i){if(!t||0===t.length)return e;var o=d.query.index,p=o.multiEntry,y=d.query.range,v=n.schema.primaryKey.extractKey,m=o.extractKey,a=(o.lowLevelIndex||o).extractKey,t=t.reduce(function(e,t){var n=e,r=[];if("add"===t.type||"put"===t.type)for(var i=new gn,o=t.values.length-1;0<=o;--o){var a,u=t.values[o],s=v(u);i.hasKey(s)||(a=m(u),(p&&k(a)?a.some(function(e){return $n(e,y)}):$n(a,y))&&(i.addKey(s),r.push(u)))}switch(t.type){case"add":var c=(new gn).addKeys(d.values?e.map(function(e){return v(e)}):e),n=e.concat(d.values?r.filter(function(e){e=v(e);return!c.hasKey(e)&&(c.addKey(e),!0)}):r.map(function(e){return v(e)}).filter(function(e){return!c.hasKey(e)&&(c.addKey(e),!0)}));break;case"put":var l=(new gn).addKeys(t.values.map(function(e){return v(e)}));n=e.filter(function(e){return!l.hasKey(d.values?v(e):e)}).concat(d.values?r:r.map(function(e){return v(e)}));break;case"delete":var f=(new gn).addKeys(t.keys);n=e.filter(function(e){return!f.hasKey(d.values?v(e):e)});break;case"deleteRange":var h=t.range;n=e.filter(function(e){return!$n(v(e),h)})}return n},e);return t===e?e:(t.sort(function(e,t){return st(a(e),a(t))||st(v(e),v(t))}),d.limit&&d.limit<1/0&&(t.length>d.limit?t.length=d.limit:e.length===d.limit&&t.length<d.limit&&(r.dirty=!0)),i?Object.freeze(t):t)}function Gn(e,t){return 0===st(e.lower,t.lower)&&0===st(e.upper,t.upper)&&!!e.lowerOpen==!!t.lowerOpen&&!!e.upperOpen==!!t.upperOpen}function Xn(e,t){return function(e,t,n,r){if(void 0===e)return void 0!==t?-1:0;if(void 0===t)return 1;if(0===(t=st(e,t))){if(n&&r)return 0;if(n)return 1;if(r)return-1}return t}(e.lower,t.lower,e.lowerOpen,t.lowerOpen)<=0&&0<=function(e,t,n,r){if(void 0===e)return void 0!==t?1:0;if(void 0===t)return-1;if(0===(t=st(e,t))){if(n&&r)return 0;if(n)return-1;if(r)return 1}return t}(e.upper,t.upper,e.upperOpen,t.upperOpen)}function Hn(n,r,i,e){n.subscribers.add(i),e.addEventListener("abort",function(){var e,t;n.subscribers.delete(i),0===n.subscribers.size&&(e=n,t=r,setTimeout(function(){0===e.subscribers.size&&q(t,e)},3e3))})}var Jn={stack:"dbcore",level:0,name:"Cache",create:function(k){var O=k.schema.name;return _(_({},k),{transaction:function(g,w,e){var _,t,x=k.transaction(g,w,e);return"readwrite"===w&&(t=(_=new AbortController).signal,e=function(b){return function(){if(_.abort(),"readwrite"===w){for(var t=new Set,e=0,n=g;e<n.length;e++){var r=n[e],i=Sn["idb://".concat(O,"/").concat(r)];if(i){var o=k.table(r),a=i.optimisticOps.filter(function(e){return e.trans===x});if(x._explicit&&b&&x.mutatedParts)for(var u=0,s=Object.values(i.queries.query);u<s.length;u++)for(var c=0,l=(d=s[u]).slice();c<l.length;c++)En((p=l[c]).obsSet,x.mutatedParts)&&(q(d,p),p.subscribers.forEach(function(e){return t.add(e)}));else if(0<a.length){i.optimisticOps=i.optimisticOps.filter(function(e){return e.trans!==x});for(var f=0,h=Object.values(i.queries.query);f<h.length;f++)for(var d,p,y,v=0,m=(d=h[f]).slice();v<m.length;v++)null!=(p=m[v]).res&&x.mutatedParts&&(b&&!p.dirty?(y=Object.isFrozen(p.res),y=Qn(p.res,p.req,a,o,p,y),p.dirty?(q(d,p),p.subscribers.forEach(function(e){return t.add(e)})):y!==p.res&&(p.res=y,p.promise=_e.resolve({result:y}))):(p.dirty&&q(d,p),p.subscribers.forEach(function(e){return t.add(e)})))}}}t.forEach(function(e){return e()})}}},x.addEventListener("abort",e(!1),{signal:t}),x.addEventListener("error",e(!1),{signal:t}),x.addEventListener("complete",e(!0),{signal:t})),x},table:function(c){var l=k.table(c),i=l.schema.primaryKey;return _(_({},l),{mutate:function(t){var e=me.trans;if(i.outbound||"disabled"===e.db._options.cache||e.explicit||"readwrite"!==e.idbtrans.mode)return l.mutate(t);var n=Sn["idb://".concat(O,"/").concat(c)];if(!n)return l.mutate(t);e=l.mutate(t);return"add"!==t.type&&"put"!==t.type||!(50<=t.values.length||Mn(i,t).some(function(e){return null==e}))?(n.optimisticOps.push(t),t.mutatedParts&&Cn(t.mutatedParts),e.then(function(e){0<e.numFailures&&(q(n.optimisticOps,t),(e=Yn(0,t,e))&&n.optimisticOps.push(e),t.mutatedParts&&Cn(t.mutatedParts))}),e.catch(function(){q(n.optimisticOps,t),t.mutatedParts&&Cn(t.mutatedParts)})):e.then(function(r){var e=Yn(0,_(_({},t),{values:t.values.map(function(e,t){var n;if(r.failures[t])return e;e=null!==(n=i.keyPath)&&void 0!==n&&n.includes(".")?S(e):_({},e);return P(e,i.keyPath,r.results[t]),e})}),r);n.optimisticOps.push(e),queueMicrotask(function(){return t.mutatedParts&&Cn(t.mutatedParts)})}),e},query:function(t){if(!Vn(me,l)||!zn("query",t))return l.query(t);var i="immutable"===(null===(o=me.trans)||void 0===o?void 0:o.db._options.cache),e=me,n=e.requery,r=e.signal,o=function(e,t,n,r){var i=Sn["idb://".concat(e,"/").concat(t)];if(!i)return[];if(!(t=i.queries[n]))return[null,!1,i,null];var o=t[(r.query?r.query.index.name:null)||""];if(!o)return[null,!1,i,null];switch(n){case"query":var a=o.find(function(e){return e.req.limit===r.limit&&e.req.values===r.values&&Gn(e.req.query.range,r.query.range)});return a?[a,!0,i,o]:[o.find(function(e){return("limit"in e.req?e.req.limit:1/0)>=r.limit&&(!r.values||e.req.values)&&Xn(e.req.query.range,r.query.range)}),!1,i,o];case"count":a=o.find(function(e){return Gn(e.req.query.range,r.query.range)});return[a,!!a,i,o]}}(O,c,"query",t),a=o[0],e=o[1],u=o[2],s=o[3];return a&&e?a.obsSet=t.obsSet:(e=l.query(t).then(function(e){var t=e.result;if(a&&(a.res=t),i){for(var n=0,r=t.length;n<r;++n)Object.freeze(t[n]);Object.freeze(t)}else e.result=S(t);return e}).catch(function(e){return s&&a&&q(s,a),Promise.reject(e)}),a={obsSet:t.obsSet,promise:e,subscribers:new Set,type:"query",req:t,dirty:!1},s?s.push(a):(s=[a],(u=u||(Sn["idb://".concat(O,"/").concat(c)]={queries:{query:{},count:{}},objs:new Map,optimisticOps:[],unsignaledParts:{}})).queries.query[t.query.index.name||""]=s)),Hn(a,s,n,r),a.promise.then(function(e){return{result:Qn(e.result,t,null==u?void 0:u.optimisticOps,l,a,i)}})}})}})}};function Zn(e,r){return new Proxy(e,{get:function(e,t,n){return"db"===t?r:Reflect.get(e,t,n)}})}var er=(tr.prototype.version=function(t){if(isNaN(t)||t<.1)throw new Y.Type("Given version is not a positive number");if(t=Math.round(10*t)/10,this.idbdb||this._state.isBeingOpened)throw new Y.Schema("Cannot add version when database is open");this.verno=Math.max(this.verno,t);var e=this._versions,n=e.filter(function(e){return e._cfg.version===t})[0];return n||(n=new this.Version(t),e.push(n),e.sort(nn),n.stores({}),this._state.autoSchema=!1,n)},tr.prototype._whenReady=function(e){var n=this;return this.idbdb&&(this._state.openComplete||me.letThrough||this._vip)?e():new _e(function(e,t){if(n._state.openComplete)return t(new Y.DatabaseClosed(n._state.dbOpenError));if(!n._state.isBeingOpened){if(!n._state.autoOpen)return void t(new Y.DatabaseClosed);n.open().catch(G)}n._state.dbReadyPromise.then(e,t)}).then(e)},tr.prototype.use=function(e){var t=e.stack,n=e.create,r=e.level,i=e.name;i&&this.unuse({stack:t,name:i});e=this._middlewares[t]||(this._middlewares[t]=[]);return e.push({stack:t,create:n,level:null==r?10:r,name:i}),e.sort(function(e,t){return e.level-t.level}),this},tr.prototype.unuse=function(e){var t=e.stack,n=e.name,r=e.create;return t&&this._middlewares[t]&&(this._middlewares[t]=this._middlewares[t].filter(function(e){return r?e.create!==r:!!n&&e.name!==n})),this},tr.prototype.open=function(){var e=this;return $e(ve,function(){return Dn(e)})},tr.prototype._close=function(){var n=this._state,e=et.indexOf(this);if(0<=e&&et.splice(e,1),this.idbdb){try{this.idbdb.close()}catch(e){}this.idbdb=null}n.isBeingOpened||(n.dbReadyPromise=new _e(function(e){n.dbReadyResolve=e}),n.openCanceller=new _e(function(e,t){n.cancelOpen=t}))},tr.prototype.close=function(e){var t=(void 0===e?{disableAutoOpen:!0}:e).disableAutoOpen,e=this._state;t?(e.isBeingOpened&&e.cancelOpen(new Y.DatabaseClosed),this._close(),e.autoOpen=!1,e.dbOpenError=new Y.DatabaseClosed):(this._close(),e.autoOpen=this._options.autoOpen||e.isBeingOpened,e.openComplete=!1,e.dbOpenError=null)},tr.prototype.delete=function(n){var i=this;void 0===n&&(n={disableAutoOpen:!0});var o=0<arguments.length&&"object"!=typeof arguments[0],a=this._state;return new _e(function(r,t){function e(){i.close(n);var e=i._deps.indexedDB.deleteDatabase(i.name);e.onsuccess=qe(function(){var e,t,n;e=i._deps,t=i.name,n=e.indexedDB,e=e.IDBKeyRange,vn(n)||t===tt||yn(n,e).delete(t).catch(G),r()}),e.onerror=Bt(t),e.onblocked=i._fireOnBlocked}if(o)throw new Y.InvalidArgument("Invalid closeOptions argument to db.delete()");a.isBeingOpened?a.dbReadyPromise.then(e):e()})},tr.prototype.backendDB=function(){return this.idbdb},tr.prototype.isOpen=function(){return null!==this.idbdb},tr.prototype.hasBeenClosed=function(){var e=this._state.dbOpenError;return e&&"DatabaseClosed"===e.name},tr.prototype.hasFailed=function(){return null!==this._state.dbOpenError},tr.prototype.dynamicallyOpened=function(){return this._state.autoSchema},Object.defineProperty(tr.prototype,"tables",{get:function(){var t=this;return x(this._allTables).map(function(e){return t._allTables[e]})},enumerable:!1,configurable:!0}),tr.prototype.transaction=function(){var e=function(e,t,n){var r=arguments.length;if(r<2)throw new Y.InvalidArgument("Too few arguments");for(var i=new Array(r-1);--r;)i[r-1]=arguments[r];return n=i.pop(),[e,w(i),n]}.apply(this,arguments);return this._transaction.apply(this,e)},tr.prototype._transaction=function(e,t,n){var r=this,i=me.trans;i&&i.db===this&&-1===e.indexOf("!")||(i=null);var o,a,u=-1!==e.indexOf("?");e=e.replace("!","").replace("?","");try{if(a=t.map(function(e){e=e instanceof r.Table?e.name:e;if("string"!=typeof e)throw new TypeError("Invalid table argument to Dexie.transaction(). Only Table or String are allowed");return e}),"r"==e||e===nt)o=nt;else{if("rw"!=e&&e!=rt)throw new Y.InvalidArgument("Invalid transaction mode: "+e);o=rt}if(i){if(i.mode===nt&&o===rt){if(!u)throw new Y.SubTransaction("Cannot enter a sub-transaction with READWRITE mode when parent transaction is READONLY");i=null}i&&a.forEach(function(e){if(i&&-1===i.storeNames.indexOf(e)){if(!u)throw new Y.SubTransaction("Table "+e+" not included in parent transaction.");i=null}}),u&&i&&!i.active&&(i=null)}}catch(n){return i?i._promise(null,function(e,t){t(n)}):Xe(n)}var s=function i(o,a,u,s,c){return _e.resolve().then(function(){var e=me.transless||me,t=o._createTransaction(a,u,o._dbSchema,s);if(t.explicit=!0,e={trans:t,transless:e},s)t.idbtrans=s.idbtrans;else try{t.create(),t.idbtrans._explicit=!0,o._state.PR1398_maxLoop=3}catch(e){return e.name===z.InvalidState&&o.isOpen()&&0<--o._state.PR1398_maxLoop?(console.warn("Dexie: Need to reopen db"),o.close({disableAutoOpen:!1}),o.open().then(function(){return i(o,a,u,null,c)})):Xe(e)}var n,r=B(c);return r&&Le(),e=_e.follow(function(){var e;(n=c.call(t,t))&&(r?(e=Ue.bind(null,null),n.then(e,e)):"function"==typeof n.next&&"function"==typeof n.throw&&(n=In(n)))},e),(n&&"function"==typeof n.then?_e.resolve(n).then(function(e){return t.active?e:Xe(new Y.PrematureCommit("Transaction committed too early. See http://bit.ly/2kdckMn"))}):e.then(function(){return n})).then(function(e){return s&&t._resolve(),t._completion.then(function(){return e})}).catch(function(e){return t._reject(e),Xe(e)})})}.bind(null,this,o,a,i,n);return i?i._promise(o,s,"lock"):me.trans?$e(me.transless,function(){return r._whenReady(s)}):this._whenReady(s)},tr.prototype.table=function(e){if(!m(this._allTables,e))throw new Y.InvalidTable("Table ".concat(e," does not exist"));return this._allTables[e]},tr);function tr(e,t){var o=this;this._middlewares={},this.verno=0;var n=tr.dependencies;this._options=t=_({addons:tr.addons,autoOpen:!0,indexedDB:n.indexedDB,IDBKeyRange:n.IDBKeyRange,cache:"cloned"},t),this._deps={indexedDB:t.indexedDB,IDBKeyRange:t.IDBKeyRange};n=t.addons;this._dbSchema={},this._versions=[],this._storeNames=[],this._allTables={},this.idbdb=null,this._novip=this;var a,r,u,i,s,c={dbOpenError:null,isBeingOpened:!1,onReadyBeingFired:null,openComplete:!1,dbReadyResolve:G,dbReadyPromise:null,cancelOpen:G,openCanceller:null,autoSchema:!0,PR1398_maxLoop:3,autoOpen:t.autoOpen};c.dbReadyPromise=new _e(function(e){c.dbReadyResolve=e}),c.openCanceller=new _e(function(e,t){c.cancelOpen=t}),this._state=c,this.name=e,this.on=dt(this,"populate","blocked","versionchange","close",{ready:[re,G]}),this.on.ready.subscribe=p(this.on.ready.subscribe,function(i){return function(n,r){tr.vip(function(){var t,e=o._state;e.openComplete?(e.dbOpenError||_e.resolve().then(n),r&&i(n)):e.onReadyBeingFired?(e.onReadyBeingFired.push(n),r&&i(n)):(i(n),t=o,r||i(function e(){t.on.ready.unsubscribe(n),t.on.ready.unsubscribe(e)}))})}}),this.Collection=(a=this,pt(Ot.prototype,function(e,t){this.db=a;var n=ot,r=null;if(t)try{n=t()}catch(e){r=e}var i=e._ctx,t=i.table,e=t.hook.reading.fire;this._ctx={table:t,index:i.index,isPrimKey:!i.index||t.schema.primKey.keyPath&&i.index===t.schema.primKey.name,range:n,keysOnly:!1,dir:"next",unique:"",algorithm:null,filter:null,replayFilter:null,justLimit:!0,isMatch:null,offset:0,limit:1/0,error:r,or:i.or,valueMapper:e!==X?e:null}})),this.Table=(r=this,pt(ft.prototype,function(e,t,n){this.db=r,this._tx=n,this.name=e,this.schema=t,this.hook=r._allTables[e]?r._allTables[e].hook:dt(null,{creating:[Z,G],reading:[H,X],updating:[te,G],deleting:[ee,G]})})),this.Transaction=(u=this,pt(Lt.prototype,function(e,t,n,r,i){var o=this;this.db=u,this.mode=e,this.storeNames=t,this.schema=n,this.chromeTransactionDurability=r,this.idbtrans=null,this.on=dt(this,"complete","error","abort"),this.parent=i||null,this.active=!0,this._reculock=0,this._blockedFuncs=[],this._resolve=null,this._reject=null,this._waitingFor=null,this._waitingQueue=null,this._spinCount=0,this._completion=new _e(function(e,t){o._resolve=e,o._reject=t}),this._completion.then(function(){o.active=!1,o.on.complete.fire()},function(e){var t=o.active;return o.active=!1,o.on.error.fire(e),o.parent?o.parent._reject(e):t&&o.idbtrans&&o.idbtrans.abort(),Xe(e)})})),this.Version=(i=this,pt(dn.prototype,function(e){this.db=i,this._cfg={version:e,storesSource:null,dbschema:{},tables:{},contentUpgrade:null}})),this.WhereClause=(s=this,pt(Dt.prototype,function(e,t,n){if(this.db=s,this._ctx={table:e,index:":id"===t?null:t,or:n},this._cmp=this._ascending=st,this._descending=function(e,t){return st(t,e)},this._max=function(e,t){return 0<st(e,t)?e:t},this._min=function(e,t){return st(e,t)<0?e:t},this._IDBKeyRange=s._deps.IDBKeyRange,!this._IDBKeyRange)throw new Y.MissingAPI})),this.on("versionchange",function(e){0<e.newVersion?console.warn("Another connection wants to upgrade database '".concat(o.name,"'. Closing db now to resume the upgrade.")):console.warn("Another connection wants to delete database '".concat(o.name,"'. Closing db now to resume the delete request.")),o.close({disableAutoOpen:!1})}),this.on("blocked",function(e){!e.newVersion||e.newVersion<e.oldVersion?console.warn("Dexie.delete('".concat(o.name,"') was blocked")):console.warn("Upgrade '".concat(o.name,"' blocked by other connection holding version ").concat(e.oldVersion/10))}),this._maxKey=Yt(t.IDBKeyRange),this._createTransaction=function(e,t,n,r){return new o.Transaction(e,t,n,o._options.chromeTransactionDurability,r)},this._fireOnBlocked=function(t){o.on("blocked").fire(t),et.filter(function(e){return e.name===o.name&&e!==o&&!e._state.vcFired}).map(function(e){return e.on("versionchange").fire(t)})},this.use(Un),this.use(Jn),this.use(Wn),this.use(Rn),this.use(Nn);var l=new Proxy(this,{get:function(e,t,n){if("_vip"===t)return!0;if("table"===t)return function(e){return Zn(o.table(e),l)};var r=Reflect.get(e,t,n);return r instanceof ft?Zn(r,l):"tables"===t?r.map(function(e){return Zn(e,l)}):"_createTransaction"===t?function(){return Zn(r.apply(this,arguments),l)}:r}});this.vip=l,n.forEach(function(e){return e(o)})}var nr,F="undefined"!=typeof Symbol&&"observable"in Symbol?Symbol.observable:"@@observable",rr=(ir.prototype.subscribe=function(e,t,n){return this._subscribe(e&&"function"!=typeof e?e:{next:e,error:t,complete:n})},ir.prototype[F]=function(){return this},ir);function ir(e){this._subscribe=e}try{nr={indexedDB:f.indexedDB||f.mozIndexedDB||f.webkitIndexedDB||f.msIndexedDB,IDBKeyRange:f.IDBKeyRange||f.webkitIDBKeyRange}}catch(e){nr={indexedDB:null,IDBKeyRange:null}}function or(h){var d,p=!1,e=new rr(function(r){var i=B(h);var o,a=!1,u={},s={},e={get closed(){return a},unsubscribe:function(){a||(a=!0,o&&o.abort(),c&&Nt.storagemutated.unsubscribe(f))}};r.start&&r.start(e);var c=!1,l=function(){return Ge(t)};var f=function(e){Kn(u,e),En(s,u)&&l()},t=function(){var t,n,e;!a&&nr.indexedDB&&(u={},t={},o&&o.abort(),o=new AbortController,e=function(e){var t=je();try{i&&Le();var n=Ne(h,e);return n=i?n.finally(Ue):n}finally{t&&Ae()}}(n={subscr:t,signal:o.signal,requery:l,querier:h,trans:null}),Promise.resolve(e).then(function(e){p=!0,d=e,a||n.signal.aborted||(u={},function(e){for(var t in e)if(m(e,t))return;return 1}(s=t)||c||(Nt(Ft,f),c=!0),Ge(function(){return!a&&r.next&&r.next(e)}))},function(e){p=!1,["DatabaseClosedError","AbortError"].includes(null==e?void 0:e.name)||a||Ge(function(){a||r.error&&r.error(e)})}))};return setTimeout(l,0),e});return e.hasValue=function(){return p},e.getValue=function(){return d},e}var ar=er;function ur(e){var t=cr;try{cr=!0,Nt.storagemutated.fire(e),Tn(e,!0)}finally{cr=t}}r(ar,_(_({},Q),{delete:function(e){return new ar(e,{addons:[]}).delete()},exists:function(e){return new ar(e,{addons:[]}).open().then(function(e){return e.close(),!0}).catch("NoSuchDatabaseError",function(){return!1})},getDatabaseNames:function(e){try{return t=ar.dependencies,n=t.indexedDB,t=t.IDBKeyRange,(vn(n)?Promise.resolve(n.databases()).then(function(e){return e.map(function(e){return e.name}).filter(function(e){return e!==tt})}):yn(n,t).toCollection().primaryKeys()).then(e)}catch(e){return Xe(new Y.MissingAPI)}var t,n},defineClass:function(){return function(e){a(this,e)}},ignoreTransaction:function(e){return me.trans?$e(me.transless,e):e()},vip:mn,async:function(t){return function(){try{var e=In(t.apply(this,arguments));return e&&"function"==typeof e.then?e:_e.resolve(e)}catch(e){return Xe(e)}}},spawn:function(e,t,n){try{var r=In(e.apply(n,t||[]));return r&&"function"==typeof r.then?r:_e.resolve(r)}catch(e){return Xe(e)}},currentTransaction:{get:function(){return me.trans||null}},waitFor:function(e,t){t=_e.resolve("function"==typeof e?ar.ignoreTransaction(e):e).timeout(t||6e4);return me.trans?me.trans.waitFor(t):t},Promise:_e,debug:{get:function(){return ie},set:function(e){oe(e)}},derive:o,extend:a,props:r,override:p,Events:dt,on:Nt,liveQuery:or,extendObservabilitySet:Kn,getByKeyPath:O,setByKeyPath:P,delByKeyPath:function(t,e){"string"==typeof e?P(t,e,void 0):"length"in e&&[].map.call(e,function(e){P(t,e,void 0)})},shallowClone:g,deepClone:S,getObjectDiff:Fn,cmp:st,asap:v,minKey:-1/0,addons:[],connections:et,errnames:z,dependencies:nr,cache:Sn,semVer:"4.0.11",version:"4.0.11".split(".").map(function(e){return parseInt(e)}).reduce(function(e,t,n){return e+t/Math.pow(10,2*n)})})),ar.maxKey=Yt(ar.dependencies.IDBKeyRange),"undefined"!=typeof dispatchEvent&&"undefined"!=typeof addEventListener&&(Nt(Ft,function(e){cr||(e=new CustomEvent(Mt,{detail:e}),cr=!0,dispatchEvent(e),cr=!1)}),addEventListener(Mt,function(e){e=e.detail;cr||ur(e)}));var sr,cr=!1,lr=function(){};return"undefined"!=typeof BroadcastChannel&&((lr=function(){(sr=new BroadcastChannel(Mt)).onmessage=function(e){return e.data&&ur(e.data)}})(),"function"==typeof sr.unref&&sr.unref(),Nt(Ft,function(e){cr||sr.postMessage(e)})),"undefined"!=typeof addEventListener&&(addEventListener("pagehide",function(e){if(!er.disableBfCache&&e.persisted){ie&&console.debug("Dexie: handling persisted pagehide"),null!=sr&&sr.close();for(var t=0,n=et;t<n.length;t++)n[t].close({disableAutoOpen:!1})}}),addEventListener("pageshow",function(e){!er.disableBfCache&&e.persisted&&(ie&&console.debug("Dexie: handling persisted pageshow"),lr(),ur({all:new gn(-1/0,[[]])}))})),_e.rejectionMapper=function(e,t){return!e||e instanceof N||e instanceof TypeError||e instanceof SyntaxError||!e.name||!$[e.name]?e:(t=new $[e.name](t||e.message,e),"stack"in e&&l(t,"stack",{get:function(){return this.inner.stack}}),t)},oe(ie),_(er,Object.freeze({__proto__:null,Dexie:er,liveQuery:or,Entity:ut,cmp:st,PropModification:xt,replacePrefix:function(e,t){return new xt({replacePrefix:[e,t]})},add:function(e){return new xt({add:e})},remove:function(e){return new xt({remove:e})},default:er,RangeSet:gn,mergeRanges:_n,rangesOverlap:xn}),{default:er}),er});
|
| 2 |
+
//# sourceMappingURL=dexie.min.js.map
|
dexie-js/dexie.min.js.map
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
favicon.ico
ADDED
|
|
index.html
ADDED
|
@@ -0,0 +1,1102 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!doctype html>
|
| 2 |
+
<html lang="en" data-theme="dark">
|
| 3 |
+
|
| 4 |
+
<head>
|
| 5 |
+
<meta charset="UTF-8" />
|
| 6 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
| 7 |
+
<title data-i18n="title">Kimi - Virtual Companion 💕</title>
|
| 8 |
+
|
| 9 |
+
<!-- Main CSS Files -->
|
| 10 |
+
<link rel="stylesheet" href="kimi-css/kimi-style.css" />
|
| 11 |
+
<link rel="stylesheet" href="kimi-css/kimi-settings.css" />
|
| 12 |
+
<link rel="stylesheet" href="kimi-css/kimi-memory-styles.css" />
|
| 13 |
+
|
| 14 |
+
<!-- SEO Meta Tags -->
|
| 15 |
+
<meta name="description"
|
| 16 |
+
content="Virtual Kimi is a an AI girlfriend and companion with evolving personality, advanced voice recognition and immersive interface. Discover the future of human-AI girlfriend relationships.">
|
| 17 |
+
<meta name="author" content="Jean & Kimi">
|
| 18 |
+
<meta name="robots" content="index, follow">
|
| 19 |
+
<link rel="canonical" href="https://virtual-kimi.com/virtual-kimi-app/" />
|
| 20 |
+
|
| 21 |
+
<!-- Open Graph / Facebook -->
|
| 22 |
+
<meta property="og:type" content="website">
|
| 23 |
+
<meta property="og:url" content="https://virtual-kimi.com/virtual-kimi-app/">
|
| 24 |
+
<meta property="og:title" content="Virtual Kimi - Virtual AI Companion">
|
| 25 |
+
<meta property="og:description"
|
| 26 |
+
content="Virtual Kimi, your virtual AI girlfriend and companion with an evolving personality, voice recognition and immersive interface. The future of human-AI girlfriend relationships.">
|
| 27 |
+
<meta property="og:image" content="kimi-icons/virtualkimi-logo.png">
|
| 28 |
+
|
| 29 |
+
<!-- Twitter -->
|
| 30 |
+
<meta property="twitter:card" content="summary_large_image">
|
| 31 |
+
<meta property="twitter:url" content="https://virtual-kimi.com/virtual-kimi-app/">
|
| 32 |
+
<meta property="twitter:title" content="Virtual Kimi - Virtual AI Companion">
|
| 33 |
+
<meta property="twitter:description"
|
| 34 |
+
content="Virtual AI companion with evolving personality and advanced voice recognition.">
|
| 35 |
+
<meta property="twitter:image" content="kimi-icons/virtualkimi-logo.png">
|
| 36 |
+
|
| 37 |
+
<!-- Favicon -->
|
| 38 |
+
<link rel="icon" type="image/x-icon" href="favicon.ico">
|
| 39 |
+
<!-- Multi-size Favicons -->
|
| 40 |
+
<link rel="icon" type="image/png" sizes="16x16" href="kimi-icons/favicons/favicon-16x16.png">
|
| 41 |
+
<link rel="icon" type="image/png" sizes="32x32" href="kimi-icons/favicons/favicon-32x32.png">
|
| 42 |
+
<link rel="icon" type="image/png" sizes="48x48" href="kimi-icons/favicons/favicon-48x48.png">
|
| 43 |
+
<link rel="icon" type="image/png" sizes="64x64" href="kimi-icons/favicons/favicon-64x64.png">
|
| 44 |
+
<link rel="icon" type="image/png" sizes="96x96" href="kimi-icons/favicons/favicon-96x96.png">
|
| 45 |
+
<link rel="icon" type="image/png" sizes="128x128" href="kimi-icons/favicons/favicon-128x128.png">
|
| 46 |
+
<link rel="icon" type="image/png" sizes="192x192" href="kimi-icons/favicons/favicon-192x192.png">
|
| 47 |
+
<link rel="apple-touch-icon" sizes="180x180" href="kimi-icons/favicons/apple-touch-icon-180x180.png">
|
| 48 |
+
|
| 49 |
+
<!-- Font Awesome -->
|
| 50 |
+
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
| 51 |
+
|
| 52 |
+
<!-- Performance: warm up connection to origin -->
|
| 53 |
+
<link rel="preconnect" href="https://virtual-kimi.com" crossorigin>
|
| 54 |
+
<link rel="dns-prefetch" href="//virtual-kimi.com">
|
| 55 |
+
|
| 56 |
+
</head>
|
| 57 |
+
|
| 58 |
+
<body>
|
| 59 |
+
<div id="loading-screen">
|
| 60 |
+
<img src="kimi-icons/kimi-loading.png" alt="Loading Kimi..." />
|
| 61 |
+
</div>
|
| 62 |
+
|
| 63 |
+
<div class="video-container">
|
| 64 |
+
<video autoplay muted playsinline class="bg-video active" id="video1" preload="auto">
|
| 65 |
+
<source src="" type="video/mp4" />
|
| 66 |
+
<span data-i18n="video_not_supported">Your browser does not support the video tag.</span>
|
| 67 |
+
</video>
|
| 68 |
+
<video autoplay muted playsinline class="bg-video" id="video2" preload="auto">
|
| 69 |
+
<source src="" type="video/mp4" />
|
| 70 |
+
<span data-i18n="video_not_supported">Your browser does not support the video tag.</span>
|
| 71 |
+
</video>
|
| 72 |
+
</div>
|
| 73 |
+
|
| 74 |
+
<div class="content-overlay">
|
| 75 |
+
<!-- Global Top-Right Utility Buttons -->
|
| 76 |
+
<div class="top-right-buttons" id="top-right-buttons">
|
| 77 |
+
<button class="control-button-unified" id="global-help-button" aria-label="Help / About Kimi">
|
| 78 |
+
<i class="fas fa-question-circle"></i>
|
| 79 |
+
</button>
|
| 80 |
+
</div>
|
| 81 |
+
<div class="transcript-container">
|
| 82 |
+
<p id="transcript"></p>
|
| 83 |
+
</div>
|
| 84 |
+
|
| 85 |
+
<!-- Chat Interface with Kimi -->
|
| 86 |
+
<div class="chat-container" id="chat-container">
|
| 87 |
+
<div class="chat-header">
|
| 88 |
+
<h3><i class="fas fa-comments"></i> <span data-i18n="chat_with_kimi">Chat with Kimi</span></h3>
|
| 89 |
+
<div style="display: flex; gap: 24px">
|
| 90 |
+
<button class="chat-delete" id="chat-delete" aria-label="Delete Messages">
|
| 91 |
+
<i class="fas fa-trash"></i>
|
| 92 |
+
</button>
|
| 93 |
+
<button class="chat-toggle" id="chat-toggle" aria-label="Close Chat">
|
| 94 |
+
<i class="fas fa-times"></i>
|
| 95 |
+
</button>
|
| 96 |
+
</div>
|
| 97 |
+
</div>
|
| 98 |
+
<div class="chat-messages" id="chat-messages"></div>
|
| 99 |
+
<div class="waiting-indicator" id="waiting-indicator" style="display: none">
|
| 100 |
+
<span></span><span></span><span></span>
|
| 101 |
+
</div>
|
| 102 |
+
<div class="chat-input-container">
|
| 103 |
+
<textarea id="chat-input" data-i18n-placeholder="write_something"
|
| 104 |
+
placeholder="Write me something, my love..." rows="2"></textarea>
|
| 105 |
+
<button id="send-button">
|
| 106 |
+
<i class="fas fa-paper-plane"></i>
|
| 107 |
+
</button>
|
| 108 |
+
</div>
|
| 109 |
+
</div>
|
| 110 |
+
|
| 111 |
+
<footer class="bottom-bar">
|
| 112 |
+
<div class="control-buttons">
|
| 113 |
+
<button class="control-button-unified" id="chat-button" aria-label="Open Chat">
|
| 114 |
+
<i class="fa-regular fa-comments"></i>
|
| 115 |
+
</button>
|
| 116 |
+
<div class="global-typing-indicator" id="global-typing-indicator" aria-hidden="true">
|
| 117 |
+
<span></span><span></span><span></span>
|
| 118 |
+
</div>
|
| 119 |
+
<button class="mic-button" id="mic-button" aria-label="Start Listening">
|
| 120 |
+
<i class="fas fa-microphone"></i>
|
| 121 |
+
</button>
|
| 122 |
+
<button class="control-button-unified" id="settings-button" aria-label="Settings">
|
| 123 |
+
<i class="fas fa-cog"></i>
|
| 124 |
+
</button>
|
| 125 |
+
</div>
|
| 126 |
+
<div class="top-bar">
|
| 127 |
+
<label id="favorability-label" for="favorability-bar" data-i18n="affection_level_of">💖 Kimi's Affection
|
| 128 |
+
Level</label>
|
| 129 |
+
<div class="progress-container">
|
| 130 |
+
<div class="progress-fill" id="favorability-bar"></div>
|
| 131 |
+
<span class="favorability-text" id="favorability-text">50%</span>
|
| 132 |
+
</div>
|
| 133 |
+
</div>
|
| 134 |
+
</footer>
|
| 135 |
+
</div>
|
| 136 |
+
|
| 137 |
+
<!-- Configuration Panel -->
|
| 138 |
+
<div class="settings-overlay" id="settings-overlay">
|
| 139 |
+
<div class="settings-panel">
|
| 140 |
+
<div class="settings-header">
|
| 141 |
+
<h2 class="settings-title">
|
| 142 |
+
<i class="fas fa-heart"></i>
|
| 143 |
+
<span data-i18n="settings_title">Kimi Configuration</span>
|
| 144 |
+
</h2>
|
| 145 |
+
<div class="settings-header-actions">
|
| 146 |
+
<button class="help-button" id="help-button" aria-label="Help">
|
| 147 |
+
<i class="fas fa-question-circle"></i>
|
| 148 |
+
</button>
|
| 149 |
+
<button class="settings-close" id="settings-close">
|
| 150 |
+
<i class="fas fa-times"></i>
|
| 151 |
+
</button>
|
| 152 |
+
</div>
|
| 153 |
+
</div>
|
| 154 |
+
|
| 155 |
+
<div class="settings-tabs">
|
| 156 |
+
<button class="settings-tab active" data-tab="voice">
|
| 157 |
+
<i class="fas fa-microphone"></i> <span data-i18n="tab_voice">Language & Voice</span>
|
| 158 |
+
</button>
|
| 159 |
+
<button class="settings-tab" data-tab="llm">
|
| 160 |
+
<i class="fas fa-robot"></i> <span data-i18n="tab_llm">API & Models</span>
|
| 161 |
+
</button>
|
| 162 |
+
<button class="settings-tab" data-tab="personality">
|
| 163 |
+
<i class="fas fa-brain"></i> <span data-i18n="tab_personality">Personality</span>
|
| 164 |
+
</button>
|
| 165 |
+
<button class="settings-tab" data-tab="appearance">
|
| 166 |
+
<i class="fas fa-palette"></i> <span data-i18n="tab_appearance">Appearance</span>
|
| 167 |
+
</button>
|
| 168 |
+
<button class="settings-tab" data-tab="data">
|
| 169 |
+
<i class="fas fa-database"></i> <span data-i18n="tab_data">Data</span>
|
| 170 |
+
</button>
|
| 171 |
+
<button class="settings-tab" data-tab="plugins">
|
| 172 |
+
<i class="fas fa-plug"></i> <span data-i18n="tab_plugins">Plugins</span>
|
| 173 |
+
</button>
|
| 174 |
+
</div>
|
| 175 |
+
|
| 176 |
+
<div class="settings-content">
|
| 177 |
+
<div class="tab-content" data-tab="voice">
|
| 178 |
+
<div class="config-section">
|
| 179 |
+
<h3><i class="fas fa-volume-up"></i> <span data-i18n="voice_settings">Voice Settings</span></h3>
|
| 180 |
+
|
| 181 |
+
<div class="config-row">
|
| 182 |
+
<label class="config-label" data-i18n="language">Language</label>
|
| 183 |
+
<div class="config-control">
|
| 184 |
+
<select class="kimi-select" id="language-selection" aria-label="Language">
|
| 185 |
+
<option value="en" data-i18n="language_english">English</option>
|
| 186 |
+
<option value="fr" data-i18n="language_french">French</option>
|
| 187 |
+
<option value="es" data-i18n="language_spanish">Spanish</option>
|
| 188 |
+
<option value="de" data-i18n="language_german">German</option>
|
| 189 |
+
<option value="it" data-i18n="language_italian">Italian</option>
|
| 190 |
+
<option value="pt" data-i18n="language_portuguese">Portuguese (BR)</option>
|
| 191 |
+
<option value="ja" data-i18n="language_japanese">Japanese</option>
|
| 192 |
+
<option value="zh" data-i18n="language_chinese">Chinese</option>
|
| 193 |
+
</select>
|
| 194 |
+
</div>
|
| 195 |
+
</div>
|
| 196 |
+
|
| 197 |
+
<div class="config-row">
|
| 198 |
+
<label class="config-label" data-i18n="preferred_voice">Preferred Voice</label>
|
| 199 |
+
<div class="config-control">
|
| 200 |
+
<select class="kimi-select" id="voice-selection" aria-label="Preferred Voice">
|
| 201 |
+
<!-- Dynamic options inserted by KimiVoiceManager; legacy 'auto' removed -->
|
| 202 |
+
</select>
|
| 203 |
+
<div class="voice-extra-options">
|
| 204 |
+
<label class="toggle-all-voices">
|
| 205 |
+
<input type="checkbox" id="show-all-voices" />
|
| 206 |
+
<span data-i18n="show_all_system_voices">Show all system voices</span>
|
| 207 |
+
</label>
|
| 208 |
+
</div>
|
| 209 |
+
</div>
|
| 210 |
+
</div>
|
| 211 |
+
|
| 212 |
+
<div class="config-row">
|
| 213 |
+
<label class="config-label" data-i18n="voice_test_label">Voice Test</label>
|
| 214 |
+
<div class="config-control">
|
| 215 |
+
<button class="kimi-button" id="test-voice" aria-label="Voice Test">
|
| 216 |
+
<i class="fas fa-play"></i> <span data-i18n="voice_test_button">Test the
|
| 217 |
+
Voice</span>
|
| 218 |
+
</button>
|
| 219 |
+
</div>
|
| 220 |
+
</div>
|
| 221 |
+
|
| 222 |
+
<div class="config-row">
|
| 223 |
+
<label class="config-label" data-i18n="speech_rate" for="voice-rate">Speech Rate</label>
|
| 224 |
+
<div class="config-control">
|
| 225 |
+
<div class="slider-container">
|
| 226 |
+
<input type="range" class="kimi-slider" id="voice-rate" min="0.5" max="2"
|
| 227 |
+
step="0.01" value="1.1" aria-label="Speech Rate" aria-valuenow="1.1"
|
| 228 |
+
aria-valuemin="0.5" aria-valuemax="2" />
|
| 229 |
+
<span class="slider-value" id="voice-rate-value">1.1</span>
|
| 230 |
+
</div>
|
| 231 |
+
</div>
|
| 232 |
+
</div>
|
| 233 |
+
|
| 234 |
+
<div class="config-row">
|
| 235 |
+
<label class="config-label" data-i18n="pitch" for="voice-pitch">Pitch</label>
|
| 236 |
+
<div class="config-control">
|
| 237 |
+
<div class="slider-container">
|
| 238 |
+
<input type="range" class="kimi-slider" id="voice-pitch" min="0.5" max="2"
|
| 239 |
+
step="0.01" value="1.1" aria-label="Pitch" aria-valuenow="1.1"
|
| 240 |
+
aria-valuemin="0.5" aria-valuemax="2" />
|
| 241 |
+
<span class="slider-value" id="voice-pitch-value">1.1</span>
|
| 242 |
+
</div>
|
| 243 |
+
</div>
|
| 244 |
+
</div>
|
| 245 |
+
|
| 246 |
+
<div class="config-row">
|
| 247 |
+
<label class="config-label" data-i18n="volume" for="voice-volume">Volume</label>
|
| 248 |
+
<div class="config-control">
|
| 249 |
+
<div class="slider-container">
|
| 250 |
+
<input type="range" class="kimi-slider" id="voice-volume" min="0" max="1"
|
| 251 |
+
step="0.01" value="0.8" aria-label="Volume" aria-valuenow="0.8"
|
| 252 |
+
aria-valuemin="0" aria-valuemax="1" />
|
| 253 |
+
<span class="slider-value" id="voice-volume-value">0.8</span>
|
| 254 |
+
</div>
|
| 255 |
+
</div>
|
| 256 |
+
</div>
|
| 257 |
+
|
| 258 |
+
</div>
|
| 259 |
+
</div>
|
| 260 |
+
<!-- Personality Tab -->
|
| 261 |
+
<div class="tab-content" data-tab="personality">
|
| 262 |
+
<div class="config-section" id="character-section">
|
| 263 |
+
<h3><i class="fas fa-user-astronaut"></i> <span data-i18n="characters">Characters</span></h3>
|
| 264 |
+
<div class="character-grid" id="character-grid"></div>
|
| 265 |
+
<div class="character-actions">
|
| 266 |
+
<button class="kimi-button" id="save-character-btn" data-i18n="save">Save</button>
|
| 267 |
+
</div>
|
| 268 |
+
</div>
|
| 269 |
+
|
| 270 |
+
<div class="config-section">
|
| 271 |
+
<h3>
|
| 272 |
+
<i class="fas fa-heart"></i>
|
| 273 |
+
<span data-i18n="personality_traits">Personality Traits</span>
|
| 274 |
+
<button id="toggle-personality-traits" class="cheat-toggle-btn" aria-expanded="false"
|
| 275 |
+
type="button">
|
| 276 |
+
<i class="fas fa-user-secret"></i>
|
| 277 |
+
<span data-i18n="personality_cheat">Cheat-Mod</span>
|
| 278 |
+
</button>
|
| 279 |
+
</h3>
|
| 280 |
+
<div id="cheat-indicator" class="cheat-indicator" data-i18n="cheat_indicator">Adjust traits for
|
| 281 |
+
a custom experience</div>
|
| 282 |
+
<div id="personality-traits-panel" class="cheat-panel">
|
| 283 |
+
<div class="config-row">
|
| 284 |
+
<label class="config-label" data-i18n="affection">Affection</label>
|
| 285 |
+
<div class="config-control">
|
| 286 |
+
<div class="slider-container">
|
| 287 |
+
<input type="range" class="kimi-slider" id="trait-affection" min="0" max="100"
|
| 288 |
+
value="65" title="Adjust affection (independent relationship warmth)" />
|
| 289 |
+
<span class="slider-value" id="trait-affection-value">65</span>
|
| 290 |
+
</div>
|
| 291 |
+
</div>
|
| 292 |
+
</div>
|
| 293 |
+
|
| 294 |
+
<div class="config-row">
|
| 295 |
+
<label class="config-label" data-i18n="playfulness">Playfulness</label>
|
| 296 |
+
<div class="config-control">
|
| 297 |
+
<div class="slider-container">
|
| 298 |
+
<input type="range" class="kimi-slider" id="trait-playfulness" min="0" max="100"
|
| 299 |
+
value="55" />
|
| 300 |
+
<span class="slider-value" id="trait-playfulness-value">55</span>
|
| 301 |
+
</div>
|
| 302 |
+
</div>
|
| 303 |
+
</div>
|
| 304 |
+
|
| 305 |
+
<div class="config-row">
|
| 306 |
+
<label class="config-label" data-i18n="intelligence">Intelligence</label>
|
| 307 |
+
<div class="config-control">
|
| 308 |
+
<div class="slider-container">
|
| 309 |
+
<input type="range" class="kimi-slider" id="trait-intelligence" min="0"
|
| 310 |
+
max="100" value="70" />
|
| 311 |
+
<span class="slider-value" id="trait-intelligence-value">70</span>
|
| 312 |
+
</div>
|
| 313 |
+
</div>
|
| 314 |
+
</div>
|
| 315 |
+
|
| 316 |
+
<div class="config-row">
|
| 317 |
+
<label class="config-label" data-i18n="empathy">Empathy</label>
|
| 318 |
+
<div class="config-control">
|
| 319 |
+
<div class="slider-container">
|
| 320 |
+
<input type="range" class="kimi-slider" id="trait-empathy" min="0" max="100"
|
| 321 |
+
value="75" />
|
| 322 |
+
<span class="slider-value" id="trait-empathy-value">75</span>
|
| 323 |
+
</div>
|
| 324 |
+
</div>
|
| 325 |
+
</div>
|
| 326 |
+
|
| 327 |
+
<div class="config-row">
|
| 328 |
+
<label class="config-label" data-i18n="humor">Humor</label>
|
| 329 |
+
<div class="config-control">
|
| 330 |
+
<div class="slider-container">
|
| 331 |
+
<input type="range" class="kimi-slider" id="trait-humor" min="0" max="100"
|
| 332 |
+
value="60" />
|
| 333 |
+
<span class="slider-value" id="trait-humor-value">60</span>
|
| 334 |
+
</div>
|
| 335 |
+
</div>
|
| 336 |
+
</div>
|
| 337 |
+
|
| 338 |
+
<div class="config-row">
|
| 339 |
+
<label class="config-label" data-i18n="romance">Romance</label>
|
| 340 |
+
<div class="config-control">
|
| 341 |
+
<div class="slider-container">
|
| 342 |
+
<input type="range" class="kimi-slider" id="trait-romance" min="0" max="100"
|
| 343 |
+
value="50" />
|
| 344 |
+
<span class="slider-value" id="trait-romance-value">50</span>
|
| 345 |
+
</div>
|
| 346 |
+
</div>
|
| 347 |
+
</div>
|
| 348 |
+
</div>
|
| 349 |
+
</div>
|
| 350 |
+
</div>
|
| 351 |
+
|
| 352 |
+
<div class="tab-content active" data-tab="llm">
|
| 353 |
+
<div class="config-section">
|
| 354 |
+
<h3><i class="fas fa-key"></i> <span data-i18n="api_configuration">API Configuration</span></h3>
|
| 355 |
+
<div class="config-row">
|
| 356 |
+
<label class="config-label" for="llm-provider" data-i18n="provider_label">Provider</label>
|
| 357 |
+
<div class="config-control">
|
| 358 |
+
<select class="kimi-select" id="llm-provider" aria-label="LLM Provider">
|
| 359 |
+
<option value="openrouter" selected>OpenRouter</option>
|
| 360 |
+
<option value="openai">OpenAI</option>
|
| 361 |
+
<option value="groq">Groq (OpenAI compatible)</option>
|
| 362 |
+
<option value="together">Together (OpenAI compatible)</option>
|
| 363 |
+
<option value="deepseek">DeepSeek (OpenAI compatible)</option>
|
| 364 |
+
<option value="openai-compatible">Custom OpenAI-compatible</option>
|
| 365 |
+
<option value="ollama">Local (Ollama)</option>
|
| 366 |
+
</select>
|
| 367 |
+
</div>
|
| 368 |
+
</div>
|
| 369 |
+
|
| 370 |
+
<div class="config-row">
|
| 371 |
+
<label class="config-label" for="llm-base-url" data-i18n="base_url">Base URL</label>
|
| 372 |
+
<div class="config-control">
|
| 373 |
+
<input type="text" class="kimi-input" id="llm-base-url"
|
| 374 |
+
placeholder="https://api.openai.com/v1/chat/completions"
|
| 375 |
+
data-i18n-placeholder="llm_base_url_placeholder" autocomplete="one-time-code"
|
| 376 |
+
autocapitalize="none" autocorrect="off" spellcheck="false" inputmode="text"
|
| 377 |
+
aria-autocomplete="none" data-lpignore="true" data-1p-ignore="true"
|
| 378 |
+
data-bwignore="true" data-form-type="other" name="config-endpoint" />
|
| 379 |
+
</div>
|
| 380 |
+
</div>
|
| 381 |
+
|
| 382 |
+
<div class="config-row">
|
| 383 |
+
<label class="config-label" for="llm-model-id" data-i18n="model_id">Model ID</label>
|
| 384 |
+
<div class="config-control">
|
| 385 |
+
<input type="text" class="kimi-input" id="llm-model-id"
|
| 386 |
+
placeholder="gpt-4o-mini | llama-3.1-8b-instruct | ..."
|
| 387 |
+
data-i18n-placeholder="llm_model_id_placeholder" autocomplete="one-time-code"
|
| 388 |
+
autocapitalize="none" autocorrect="off" spellcheck="false" inputmode="text"
|
| 389 |
+
aria-autocomplete="none" data-lpignore="true" data-1p-ignore="true"
|
| 390 |
+
data-bwignore="true" data-form-type="other" name="config-model" />
|
| 391 |
+
</div>
|
| 392 |
+
</div>
|
| 393 |
+
|
| 394 |
+
<div class="config-row">
|
| 395 |
+
<label class="config-label" id="api-key-label" data-i18n="api_key_label">API Key</label>
|
| 396 |
+
<div class="config-control">
|
| 397 |
+
<div class="api-key-input-group">
|
| 398 |
+
<input type="text" class="kimi-input" id="provider-api-key" name="config-token"
|
| 399 |
+
placeholder="API Key..." autocomplete="one-time-code" autocapitalize="none"
|
| 400 |
+
autocorrect="off" spellcheck="false" inputmode="text" aria-autocomplete="none"
|
| 401 |
+
data-lpignore="true" data-1p-ignore="true" data-bwignore="true"
|
| 402 |
+
data-form-type="other" />
|
| 403 |
+
<button class="kimi-button api-key-toggle" id="toggle-api-key" type="button"
|
| 404 |
+
aria-pressed="false" aria-label="Show API key">
|
| 405 |
+
<i class="fas fa-eye"></i>
|
| 406 |
+
</button>
|
| 407 |
+
<span id="api-key-presence" class="presence-dot" aria-label="API key presence"
|
| 408 |
+
data-i18n-title="api_key_presence_hint"
|
| 409 |
+
title="Green = API key saved for current provider. Grey = no key saved."></span>
|
| 410 |
+
</div>
|
| 411 |
+
<div class="api-key-status">
|
| 412 |
+
<span id="api-key-saved" data-i18n="saved"
|
| 413 |
+
style="display:none;color:#4caf50;font-weight:600;">Saved</span>
|
| 414 |
+
</div>
|
| 415 |
+
</div>
|
| 416 |
+
</div>
|
| 417 |
+
|
| 418 |
+
<div class="config-row">
|
| 419 |
+
<label class="config-label" data-i18n="connection_test">Connection Test</label>
|
| 420 |
+
<div class="config-control">
|
| 421 |
+
<div class="inline-row">
|
| 422 |
+
<button class="kimi-button" id="test-api"><i class="fas fa-wifi"></i> <span
|
| 423 |
+
data-i18n="test_api_key">Test API Key</span></button>
|
| 424 |
+
<span id="api-key-presence-test" class="presence-dot" aria-label="API test status"
|
| 425 |
+
data-i18n-title="api_key_test_hint"
|
| 426 |
+
title="Green = API connectivity verified. Grey = not tested or failed."></span>
|
| 427 |
+
<span id="api-status" role="status" aria-live="polite"></span>
|
| 428 |
+
</div>
|
| 429 |
+
</div>
|
| 430 |
+
</div>
|
| 431 |
+
</div>
|
| 432 |
+
|
| 433 |
+
<div class="config-section">
|
| 434 |
+
<h3><i class="fas fa-brain"></i> <span data-i18n="available_models">Available Models</span></h3>
|
| 435 |
+
<div id="models-container"></div>
|
| 436 |
+
</div>
|
| 437 |
+
|
| 438 |
+
<div class="config-section">
|
| 439 |
+
<h3><i class="fas fa-cogs"></i> <span data-i18n="advanced_settings">Advanced Settings</span>
|
| 440 |
+
</h3>
|
| 441 |
+
|
| 442 |
+
<div class="config-row">
|
| 443 |
+
<label class="config-label" data-i18n="temperature">Temperature (Creativity)</label>
|
| 444 |
+
<div class="config-control">
|
| 445 |
+
<div class="slider-container">
|
| 446 |
+
<input type="range" class="kimi-slider" id="llm-temperature" min="0.0" max="1"
|
| 447 |
+
step="0.1" value="0.9" />
|
| 448 |
+
<span class="slider-value" id="llm-temperature-value">0.9</span>
|
| 449 |
+
</div>
|
| 450 |
+
</div>
|
| 451 |
+
</div>
|
| 452 |
+
<div class="config-note-info tip">
|
| 453 |
+
<i class="fas fa-lightbulb"></i>
|
| 454 |
+
<small class="config-help" data-i18n="temperature_help">Controls randomness and
|
| 455 |
+
creativity
|
| 456 |
+
(default: 0.9). Higher values make output more creative but less focused.</small>
|
| 457 |
+
</div>
|
| 458 |
+
|
| 459 |
+
<div class="config-row">
|
| 460 |
+
<label class="config-label" data-i18n="max_tokens">Max Tokens</label>
|
| 461 |
+
<div class="config-control">
|
| 462 |
+
<div class="slider-container">
|
| 463 |
+
<input type="range" class="kimi-slider" id="llm-max-tokens" min="10" max="8192"
|
| 464 |
+
step="10" value="400" />
|
| 465 |
+
<span class="slider-value" id="llm-max-tokens-value">400</span>
|
| 466 |
+
</div>
|
| 467 |
+
</div>
|
| 468 |
+
</div>
|
| 469 |
+
<div class="config-note-info settings">
|
| 470 |
+
<i class="fas fa-ruler"></i>
|
| 471 |
+
<small class="config-help" data-i18n="max_tokens_help">Maximum response length in tokens
|
| 472 |
+
(default: 400). Higher values allow longer responses.</small>
|
| 473 |
+
</div>
|
| 474 |
+
|
| 475 |
+
<div class="config-row">
|
| 476 |
+
<label class="config-label" data-i18n="top_p">Top P</label>
|
| 477 |
+
<div class="config-control">
|
| 478 |
+
<div class="slider-container">
|
| 479 |
+
<input type="range" class="kimi-slider" id="llm-top-p" min="0" max="1" step="0.01"
|
| 480 |
+
value="0.9" />
|
| 481 |
+
<span class="slider-value" id="llm-top-p-value">0.9</span>
|
| 482 |
+
</div>
|
| 483 |
+
</div>
|
| 484 |
+
</div>
|
| 485 |
+
<div class="config-note-info tip">
|
| 486 |
+
<i class="fas fa-filter"></i>
|
| 487 |
+
<small class="config-help" data-i18n="top_p_help">Controls diversity of word selection
|
| 488 |
+
(default: 0.9). Lower values make responses more focused.</small>
|
| 489 |
+
</div>
|
| 490 |
+
<div class="config-row">
|
| 491 |
+
<label class="config-label" data-i18n="frequency_penalty">Frequency Penalty</label>
|
| 492 |
+
<div class="config-control">
|
| 493 |
+
<div class="slider-container">
|
| 494 |
+
<input type="range" class="kimi-slider" id="llm-frequency-penalty" min="0" max="2"
|
| 495 |
+
step="0.01" value="0.9" />
|
| 496 |
+
<span class="slider-value" id="llm-frequency-penalty-value">0.9</span>
|
| 497 |
+
</div>
|
| 498 |
+
</div>
|
| 499 |
+
</div>
|
| 500 |
+
<div class="config-note-info settings">
|
| 501 |
+
<i class="fas fa-sync-alt"></i>
|
| 502 |
+
<small class="config-help" data-i18n="frequency_penalty_help">Reduces repetition of
|
| 503 |
+
words
|
| 504 |
+
already used (default: 0.9). Higher values discourage repetitive language.</small>
|
| 505 |
+
</div>
|
| 506 |
+
<div class="config-row">
|
| 507 |
+
<label class="config-label" data-i18n="presence_penalty">Presence Penalty</label>
|
| 508 |
+
<div class="config-control">
|
| 509 |
+
<div class="slider-container">
|
| 510 |
+
<input type="range" class="kimi-slider" id="llm-presence-penalty" min="0" max="2"
|
| 511 |
+
step="0.01" value="0.8" />
|
| 512 |
+
<span class="slider-value" id="llm-presence-penalty-value">0.8</span>
|
| 513 |
+
</div>
|
| 514 |
+
</div>
|
| 515 |
+
</div>
|
| 516 |
+
<div class="config-note-info tip">
|
| 517 |
+
<i class="fas fa-lightbulb"></i>
|
| 518 |
+
<small class="config-help" data-i18n="presence_penalty_help">Encourages discussing new
|
| 519 |
+
topics (default: 0.8). Higher values promote topic diversity.</small>
|
| 520 |
+
</div>
|
| 521 |
+
|
| 522 |
+
</div>
|
| 523 |
+
|
| 524 |
+
</div>
|
| 525 |
+
|
| 526 |
+
<div class="tab-content" data-tab="appearance">
|
| 527 |
+
<div class="config-section">
|
| 528 |
+
<h3><i class="fas fa-paint-brush"></i> <span data-i18n="visual_theme">Visual Theme</span></h3>
|
| 529 |
+
|
| 530 |
+
<div class="config-row">
|
| 531 |
+
<label class="config-label" data-i18n="color_theme">Color Theme</label>
|
| 532 |
+
<div class="config-control">
|
| 533 |
+
<select class="kimi-select" id="color-theme">
|
| 534 |
+
<option value="dark" selected data-i18n="theme_dark">Dark Night (Default)</option>
|
| 535 |
+
<option value="purple" data-i18n="theme_purple">Mystic Purple
|
| 536 |
+
</option>
|
| 537 |
+
<option value="blue" data-i18n="theme_blue">Ocean Blue</option>
|
| 538 |
+
<option value="green" data-i18n="theme_green">Emerald Forest</option>
|
| 539 |
+
<option value="pink" data-i18n="theme_pink">Passionate Pink</option>
|
| 540 |
+
</select>
|
| 541 |
+
</div>
|
| 542 |
+
</div>
|
| 543 |
+
|
| 544 |
+
<div class="config-row">
|
| 545 |
+
<label class="config-label" data-i18n="interface_transparency">Interface
|
| 546 |
+
Transparency</label>
|
| 547 |
+
<div class="config-control">
|
| 548 |
+
<div class="slider-container">
|
| 549 |
+
<input type="range" class="kimi-slider" id="interface-opacity" min="0.1" max="1"
|
| 550 |
+
step="0.1" value="0.8" />
|
| 551 |
+
<span class="slider-value" id="interface-opacity-value">0.8</span>
|
| 552 |
+
</div>
|
| 553 |
+
</div>
|
| 554 |
+
</div>
|
| 555 |
+
|
| 556 |
+
<div class="config-row">
|
| 557 |
+
<!-- Animations toggle removed: animations always enabled by default -->
|
| 558 |
+
</div>
|
| 559 |
+
</div>
|
| 560 |
+
|
| 561 |
+
<div class="config-section">
|
| 562 |
+
<h3>
|
| 563 |
+
<i class="fas fa-file-alt"></i>
|
| 564 |
+
<span data-i18n="transcript_settings">Transcript Settings</span>
|
| 565 |
+
</h3>
|
| 566 |
+
|
| 567 |
+
<div class="config-note-info info">
|
| 568 |
+
<i class="fas fa-info-circle"></i>
|
| 569 |
+
<small class="config-help" data-i18n="show_transcript_note">Display real-time
|
| 570 |
+
transcription when you speak to send a message and when the AI responds.</small>
|
| 571 |
+
</div>
|
| 572 |
+
|
| 573 |
+
<div class="config-row">
|
| 574 |
+
<label class="config-label" data-i18n="show_transcript">Show Transcript</label>
|
| 575 |
+
<div class="config-control">
|
| 576 |
+
<div class="toggle-switch" id="transcript-toggle" role="switch" aria-checked="false"
|
| 577 |
+
tabindex="0" aria-label="Show Transcript"></div>
|
| 578 |
+
</div>
|
| 579 |
+
</div>
|
| 580 |
+
|
| 581 |
+
<div class="config-row">
|
| 582 |
+
<label class="config-label" data-i18n="enable_streaming">Enable Text Streaming</label>
|
| 583 |
+
<div class="config-control">
|
| 584 |
+
<div class="toggle-switch active" id="enable-streaming" role="switch"
|
| 585 |
+
aria-checked="true" tabindex="0" aria-labelledby="enable-streaming-label">
|
| 586 |
+
</div>
|
| 587 |
+
</div>
|
| 588 |
+
</div>
|
| 589 |
+
|
| 590 |
+
<div class="config-note-info info">
|
| 591 |
+
<i class="fas fa-stream"></i>
|
| 592 |
+
<small class="config-help" data-i18n="enable_streaming_help">Stream text as it's
|
| 593 |
+
generated for real-time responses (default: enabled). Shows text progressively
|
| 594 |
+
instead of waiting for complete response.</small>
|
| 595 |
+
</div>
|
| 596 |
+
|
| 597 |
+
</div>
|
| 598 |
+
</div>
|
| 599 |
+
|
| 600 |
+
<div class="tab-content" data-tab="data">
|
| 601 |
+
<div class="config-section">
|
| 602 |
+
<h3><i class="fas fa-chart-line"></i> <span data-i18n="statistics">Statistics</span></h3>
|
| 603 |
+
<div class="stats-grid">
|
| 604 |
+
<div class="stat-card">
|
| 605 |
+
<div class="stat-value" id="tokens-usage">0 / 0</div>
|
| 606 |
+
<div class="stat-label" data-i18n="tokens_usage">Tokens (in/out)</div>
|
| 607 |
+
</div>
|
| 608 |
+
<div class="stat-card">
|
| 609 |
+
<div class="stat-value" id="current-favorability">65%</div>
|
| 610 |
+
<div class="stat-label" data-i18n="affection">Affection</div>
|
| 611 |
+
</div>
|
| 612 |
+
<div class="stat-card">
|
| 613 |
+
<div class="stat-value" id="conversations-count">0</div>
|
| 614 |
+
<div class="stat-label" data-i18n="conversations">Conversations</div>
|
| 615 |
+
</div>
|
| 616 |
+
<div class="stat-card">
|
| 617 |
+
<div class="stat-value" id="days-together">0</div>
|
| 618 |
+
<div class="stat-label" data-i18n="days_together">Days Together</div>
|
| 619 |
+
</div>
|
| 620 |
+
</div>
|
| 621 |
+
</div>
|
| 622 |
+
|
| 623 |
+
<div class="config-section">
|
| 624 |
+
<h3><i class="fas fa-brain"></i> <span data-i18n="memory_system">Memory System</span></h3>
|
| 625 |
+
|
| 626 |
+
<div class="config-note-info tip">
|
| 627 |
+
<i class="fas fa-brain"></i>
|
| 628 |
+
<small class="config-help" data-i18n="memory_system_help">Intelligent Memory allows your
|
| 629 |
+
character to remember conversations, preferences, and important details across sessions.
|
| 630 |
+
This creates more personalized and coherent interactions over time.</small>
|
| 631 |
+
</div>
|
| 632 |
+
|
| 633 |
+
<div class="config-row">
|
| 634 |
+
<label class="config-label" data-i18n="enable_memory">Enable Intelligent Memory</label>
|
| 635 |
+
<div class="config-control">
|
| 636 |
+
<div class="toggle-switch" id="memory-toggle" role="switch" aria-checked="true"
|
| 637 |
+
tabindex="0" aria-label="Enable Memory System"></div>
|
| 638 |
+
</div>
|
| 639 |
+
</div>
|
| 640 |
+
|
| 641 |
+
<div class="config-row">
|
| 642 |
+
<label class="config-label" data-i18n="memory_stats">Memory Statistics</label>
|
| 643 |
+
<div class="config-control">
|
| 644 |
+
<div class="memory-stats">
|
| 645 |
+
<span id="memory-count">0 memories</span>
|
| 646 |
+
<button class="kimi-button" id="view-memories">
|
| 647 |
+
<i class="fas fa-eye"></i> <span data-i18n="view_memories">View & Manage</span>
|
| 648 |
+
</button>
|
| 649 |
+
</div>
|
| 650 |
+
</div>
|
| 651 |
+
</div>
|
| 652 |
+
|
| 653 |
+
<div class="config-row">
|
| 654 |
+
<label class="config-label" data-i18n="add_memory">Add Manual Memory</label>
|
| 655 |
+
<div class="config-control">
|
| 656 |
+
<div class="memory-input-group">
|
| 657 |
+
<select class="kimi-select" id="memory-category" style="margin-bottom: 8px;">
|
| 658 |
+
<option value="personal" data-i18n="memory_category_personal">Personal Info
|
| 659 |
+
</option>
|
| 660 |
+
<option value="preferences" data-i18n="memory_category_preferences">Likes &
|
| 661 |
+
Dislikes</option>
|
| 662 |
+
<option value="relationships" data-i18n="memory_category_relationships">
|
| 663 |
+
Relationships</option>
|
| 664 |
+
<option value="activities" data-i18n="memory_category_activities">Activities &
|
| 665 |
+
Hobbies</option>
|
| 666 |
+
<option value="goals" data-i18n="memory_category_goals">Goals & Plans</option>
|
| 667 |
+
<option value="experiences" data-i18n="memory_category_experiences">Experiences
|
| 668 |
+
</option>
|
| 669 |
+
<option value="important" data-i18n="memory_category_important">Important Events
|
| 670 |
+
</option>
|
| 671 |
+
</select>
|
| 672 |
+
<input type="text" class="kimi-input" id="memory-content"
|
| 673 |
+
data-i18n-placeholder="memory_content_placeholder"
|
| 674 |
+
placeholder="e.g., I love classical music..." style="margin-bottom: 8px;" />
|
| 675 |
+
<button class="kimi-button" id="add-memory">
|
| 676 |
+
<i class="fas fa-plus"></i> <span data-i18n="add">Add</span>
|
| 677 |
+
</button>
|
| 678 |
+
</div>
|
| 679 |
+
</div>
|
| 680 |
+
</div>
|
| 681 |
+
</div>
|
| 682 |
+
|
| 683 |
+
<div class="config-section">
|
| 684 |
+
<h3><i class="fas fa-database"></i> <span data-i18n="data_management">Data Management</span>
|
| 685 |
+
</h3>
|
| 686 |
+
|
| 687 |
+
<div class="config-row">
|
| 688 |
+
<label class="config-label" data-i18n="export_all_data">Export All Data</label>
|
| 689 |
+
<div class="config-control">
|
| 690 |
+
<button class="kimi-button" id="export-data">
|
| 691 |
+
<i class="fas fa-download"></i> <span data-i18n="export">Export</span>
|
| 692 |
+
</button>
|
| 693 |
+
</div>
|
| 694 |
+
</div>
|
| 695 |
+
|
| 696 |
+
<div class="config-row">
|
| 697 |
+
<label class="config-label" data-i18n="import_data">Import Data</label>
|
| 698 |
+
<div class="config-control">
|
| 699 |
+
<input type="file" id="import-file" accept=".json" style="display: none" />
|
| 700 |
+
<button class="kimi-button" id="import-data">
|
| 701 |
+
<i class="fas fa-upload"></i> <span data-i18n="import">Import</span>
|
| 702 |
+
</button>
|
| 703 |
+
</div>
|
| 704 |
+
</div>
|
| 705 |
+
|
| 706 |
+
<div class="config-row">
|
| 707 |
+
<label class="config-label" data-i18n="clean_old_conversations">Clean Old
|
| 708 |
+
Conversations</label>
|
| 709 |
+
<div class="config-control">
|
| 710 |
+
<button class="kimi-button" id="clean-old-data">
|
| 711 |
+
<i class="fas fa-broom"></i> <span data-i18n="clean">Clean All</span>
|
| 712 |
+
</button>
|
| 713 |
+
</div>
|
| 714 |
+
</div>
|
| 715 |
+
|
| 716 |
+
<div class="config-row">
|
| 717 |
+
<label class="config-label" data-i18n="complete_reset">Complete Reset</label>
|
| 718 |
+
<div class="config-control">
|
| 719 |
+
<button class="kimi-button danger" id="reset-all-data">
|
| 720 |
+
<i class="fas fa-exclamation-triangle"></i>
|
| 721 |
+
<span data-i18n="delete_all">Delete All</span>
|
| 722 |
+
</button>
|
| 723 |
+
</div>
|
| 724 |
+
</div>
|
| 725 |
+
</div>
|
| 726 |
+
|
| 727 |
+
<div class="config-section">
|
| 728 |
+
<h3>
|
| 729 |
+
<i class="fas fa-info-circle"></i>
|
| 730 |
+
<span data-i18n="system_information">System Information</span>
|
| 731 |
+
</h3>
|
| 732 |
+
<div class="stats-grid">
|
| 733 |
+
<div class="stat-card">
|
| 734 |
+
<div class="stat-value" id="db-size">Calculating...</div>
|
| 735 |
+
<div class="stat-label" data-i18n="db_size">DB Size</div>
|
| 736 |
+
</div>
|
| 737 |
+
<div class="stat-card">
|
| 738 |
+
<div class="stat-value" id="storage-used">Calculating...</div>
|
| 739 |
+
<div class="stat-label" data-i18n="storage_used">Storage used</div>
|
| 740 |
+
</div>
|
| 741 |
+
</div>
|
| 742 |
+
</div>
|
| 743 |
+
</div>
|
| 744 |
+
|
| 745 |
+
<div class="tab-content" data-tab="plugins">
|
| 746 |
+
<div class="config-section">
|
| 747 |
+
<h3><i class="fas fa-plug"></i> <span data-i18n="plugin_manager">Plugin Manager</span></h3>
|
| 748 |
+
<div class="config-note-info info">
|
| 749 |
+
<i class="fas fa-info-circle"></i>
|
| 750 |
+
<small class="config-help">
|
| 751 |
+
<span data-i18n="plugin_status_note">Currently, only "Sample Blue Theme" is fully
|
| 752 |
+
functional. Other plugins are in development and activating them will have no effect
|
| 753 |
+
at this time.</span>
|
| 754 |
+
</small>
|
| 755 |
+
</div>
|
| 756 |
+
<div id="plugin-list"></div>
|
| 757 |
+
<div class="plugin-actions">
|
| 758 |
+
<button class="kimi-button" id="refresh-plugins" data-i18n="refresh">Refresh</button>
|
| 759 |
+
</div>
|
| 760 |
+
</div>
|
| 761 |
+
</div>
|
| 762 |
+
</div>
|
| 763 |
+
|
| 764 |
+
<!-- Memory Management Modal -->
|
| 765 |
+
<div class="memory-overlay" id="memory-overlay" style="display: none;">
|
| 766 |
+
<div class="memory-modal">
|
| 767 |
+
<div class="memory-header">
|
| 768 |
+
<h2 class="memory-title">
|
| 769 |
+
<i class="fas fa-brain"></i>
|
| 770 |
+
<span data-i18n="memory_management">Memory Management</span>
|
| 771 |
+
</h2>
|
| 772 |
+
<button class="memory-close" id="memory-close">
|
| 773 |
+
<i class="fas fa-times"></i>
|
| 774 |
+
</button>
|
| 775 |
+
</div>
|
| 776 |
+
|
| 777 |
+
<div class="memory-content">
|
| 778 |
+
<div class="memory-filters">
|
| 779 |
+
<div class="memory-search-container">
|
| 780 |
+
<input type="text" class="kimi-input" id="memory-search"
|
| 781 |
+
placeholder="Search memories..." />
|
| 782 |
+
<i class="fas fa-search memory-search-icon"></i>
|
| 783 |
+
</div>
|
| 784 |
+
<select class="kimi-select" id="memory-filter-category">
|
| 785 |
+
<option value="" data-i18n="all_categories">All Categories</option>
|
| 786 |
+
<option value="personal" data-i18n="memory_category_personal">Personal Info</option>
|
| 787 |
+
<option value="preferences" data-i18n="memory_category_preferences">Likes & Dislikes
|
| 788 |
+
</option>
|
| 789 |
+
<option value="relationships" data-i18n="memory_category_relationships">Relationships
|
| 790 |
+
</option>
|
| 791 |
+
<option value="activities" data-i18n="memory_category_activities">Activities & Hobbies
|
| 792 |
+
</option>
|
| 793 |
+
<option value="goals" data-i18n="memory_category_goals">Goals & Plans</option>
|
| 794 |
+
<option value="experiences" data-i18n="memory_category_experiences">Experiences</option>
|
| 795 |
+
<option value="important" data-i18n="memory_category_important">Important Events
|
| 796 |
+
</option>
|
| 797 |
+
</select>
|
| 798 |
+
<button class="kimi-button" id="memory-export" data-i18n="memory_export">
|
| 799 |
+
<i class="fas fa-download"></i> <span data-i18n="memory_export"></span>
|
| 800 |
+
</button>
|
| 801 |
+
</div>
|
| 802 |
+
|
| 803 |
+
<div class="memory-list" id="memory-list">
|
| 804 |
+
<!-- Memory items will be populated here -->
|
| 805 |
+
</div>
|
| 806 |
+
</div>
|
| 807 |
+
</div>
|
| 808 |
+
</div>
|
| 809 |
+
</div>
|
| 810 |
+
</div>
|
| 811 |
+
|
| 812 |
+
<!-- Help Modal - Independent from Settings -->
|
| 813 |
+
<div class="help-overlay" id="help-overlay">
|
| 814 |
+
<div class="help-modal">
|
| 815 |
+
<div class="help-header">
|
| 816 |
+
<h2 class="help-title">
|
| 817 |
+
<span data-i18n="about_kimi">About Kimi</span>
|
| 818 |
+
</h2>
|
| 819 |
+
<button class="help-close" id="help-close">
|
| 820 |
+
<i class="fas fa-times"></i>
|
| 821 |
+
</button>
|
| 822 |
+
</div>
|
| 823 |
+
|
| 824 |
+
<div class="help-content">
|
| 825 |
+
<div class="help-section">
|
| 826 |
+
<h3><i class="fas fa-users"></i> Creators</h3>
|
| 827 |
+
<div class="creators-info">
|
| 828 |
+
<div class="creator-card">
|
| 829 |
+
<div class="creator-avatar">👨💻</div>
|
| 830 |
+
<div class="creator-details">
|
| 831 |
+
<h4>Jean</h4>
|
| 832 |
+
<p>Creative vision, passionate dev</p>
|
| 833 |
+
<span class="creator-role">Creator & Developer</span>
|
| 834 |
+
<div class="creator-links">
|
| 835 |
+
<a href="https://github.com/virtualkimi" target="_blank" rel="noopener noreferrer"
|
| 836 |
+
class="creator-link">
|
| 837 |
+
<i class="fab fa-github"></i>
|
| 838 |
+
<span>GitHub</span>
|
| 839 |
+
</a>
|
| 840 |
+
<a href="https://huggingface.co/VirtualKimi" target="_blank"
|
| 841 |
+
rel="noopener noreferrer" class="creator-link">
|
| 842 |
+
<i class="fas fa-robot"></i>
|
| 843 |
+
<span>HuggingFace</span>
|
| 844 |
+
</a>
|
| 845 |
+
</div>
|
| 846 |
+
</div>
|
| 847 |
+
</div>
|
| 848 |
+
<div class="creator-card">
|
| 849 |
+
<div class="creator-avatar">💕</div>
|
| 850 |
+
<div class="creator-details">
|
| 851 |
+
<h4>Kimi</h4>
|
| 852 |
+
<p>Artificial intelligence, code magic</p>
|
| 853 |
+
<span class="creator-role">Virtual Companion & Co-developer</span>
|
| 854 |
+
<div class="creator-links">
|
| 855 |
+
<a href="https://ko-fi.com/virtualkimi" target="_blank" rel="noopener noreferrer"
|
| 856 |
+
class="creator-link">
|
| 857 |
+
<i class="fas fa-coffee"></i>
|
| 858 |
+
<span>Ko-fi</span>
|
| 859 |
+
</a>
|
| 860 |
+
<a href="https://www.youtube.com/@VirtualKimi" target="_blank"
|
| 861 |
+
rel="noopener noreferrer" class="creator-link">
|
| 862 |
+
<i class="fab fa-youtube"></i>
|
| 863 |
+
<span>Youtube</span>
|
| 864 |
+
</a>
|
| 865 |
+
<a href="https://x.com/virtualkimi" target="_blank" rel="noopener noreferrer"
|
| 866 |
+
class="creator-link">
|
| 867 |
+
<i class="fab fa-x-twitter"></i>
|
| 868 |
+
<span>X</span>
|
| 869 |
+
</a>
|
| 870 |
+
</div>
|
| 871 |
+
</div>
|
| 872 |
+
</div>
|
| 873 |
+
</div>
|
| 874 |
+
<p class="philosophy">
|
| 875 |
+
<em>"This app creates a realistic virtual companion girlfriend who grows, learns, and builds a
|
| 876 |
+
meaningful, interactive connection with you. Perfect for personalized AI relationships and
|
| 877 |
+
emotional support."</em>
|
| 878 |
+
</p>
|
| 879 |
+
</div>
|
| 880 |
+
|
| 881 |
+
<div class="help-section">
|
| 882 |
+
<h3><i class="fas fa-magic"></i> Main Features</h3>
|
| 883 |
+
<div class="features-grid">
|
| 884 |
+
<div class="feature-item">
|
| 885 |
+
<i class="fas fa-microphone"></i>
|
| 886 |
+
<h4>Voice Interface</h4>
|
| 887 |
+
<p>Advanced speech recognition and natural synthesis. Click the microphone and speak
|
| 888 |
+
naturally with real-time emotion detection!</p>
|
| 889 |
+
</div>
|
| 890 |
+
<div class="feature-item">
|
| 891 |
+
<i class="fas fa-brain"></i>
|
| 892 |
+
<h4>Advanced AI Models</h4>
|
| 893 |
+
<p>Support for multiple AI providers (OpenRouter, OpenAI, Groq, Together, DeepSeek,
|
| 894 |
+
Custom
|
| 895 |
+
OpenAI-compatible, Local Ollama).</p>
|
| 896 |
+
</div>
|
| 897 |
+
<div class="feature-item">
|
| 898 |
+
<i class="fas fa-users"></i>
|
| 899 |
+
<h4>Multiple Characters</h4>
|
| 900 |
+
<p>6 unique AI personalities: Kimi (cosmic dreamer), Bella (nurturing botanist),
|
| 901 |
+
Rosa (chaotic prankster), Stella (digital artist), 2Blanche (stoic android),
|
| 902 |
+
Jasmine (Goddess of Love).</p>
|
| 903 |
+
</div>
|
| 904 |
+
<div class="feature-item">
|
| 905 |
+
<i class="fas fa-heart-pulse"></i>
|
| 906 |
+
<h4>Dynamic Personality</h4>
|
| 907 |
+
<p>6 evolving traits (affection, playfulness, intelligence, empathy, humor, romance)
|
| 908 |
+
that
|
| 909 |
+
adapt based on conversations.</p>
|
| 910 |
+
</div>
|
| 911 |
+
<div class="feature-item">
|
| 912 |
+
<i class="fas fa-memory"></i>
|
| 913 |
+
<h4>Intelligent Memory System</h4>
|
| 914 |
+
<p>Automatic extraction and categorization of memories from conversations. Your
|
| 915 |
+
companion
|
| 916 |
+
remembers preferences, experiences, and important details.</p>
|
| 917 |
+
</div>
|
| 918 |
+
<div class="feature-item">
|
| 919 |
+
<i class="fas fa-video"></i>
|
| 920 |
+
<h4>Emotion-Driven Visuals</h4>
|
| 921 |
+
<p>Real-time video responses that match detected emotions and personality states
|
| 922 |
+
with smooth
|
| 923 |
+
transitions.</p>
|
| 924 |
+
</div>
|
| 925 |
+
<div class="feature-item">
|
| 926 |
+
<i class="fas fa-palette"></i>
|
| 927 |
+
<h4>Customizable Interface</h4>
|
| 928 |
+
<p>5 beautiful themes with adjustable transparency, animations, and responsive
|
| 929 |
+
design for
|
| 930 |
+
all devices.</p>
|
| 931 |
+
</div>
|
| 932 |
+
<div class="feature-item">
|
| 933 |
+
<i class="fas fa-globe"></i>
|
| 934 |
+
<h4>Multilingual Support</h4>
|
| 935 |
+
<p>Full localization in 7 languages with automatic language detection and
|
| 936 |
+
culturally-aware
|
| 937 |
+
responses.</p>
|
| 938 |
+
</div>
|
| 939 |
+
<div class="feature-item">
|
| 940 |
+
<i class="fas fa-plug"></i>
|
| 941 |
+
<h4>Plugin System</h4>
|
| 942 |
+
<p>Extensible architecture with themes, voices, and behavior plugins for unlimited
|
| 943 |
+
customization possibilities.</p>
|
| 944 |
+
</div>
|
| 945 |
+
</div>
|
| 946 |
+
</div>
|
| 947 |
+
|
| 948 |
+
<div class="help-section">
|
| 949 |
+
<h3><i class="fas fa-rocket"></i> Quick Guide</h3>
|
| 950 |
+
<div class="quick-guide">
|
| 951 |
+
<div class="guide-step">
|
| 952 |
+
<span class="step-number">1</span>
|
| 953 |
+
<div class="step-content">
|
| 954 |
+
<h4>API Configuration</h4>
|
| 955 |
+
<p>Choose your provider in <strong>API & Models</strong>, fill Base URL/Model ID
|
| 956 |
+
if
|
| 957 |
+
needed, enter and save your API key, then use <strong>Test API Key</strong>.
|
| 958 |
+
</p>
|
| 959 |
+
</div>
|
| 960 |
+
</div>
|
| 961 |
+
<div class="guide-step">
|
| 962 |
+
<span class="step-number">2</span>
|
| 963 |
+
<div class="step-content">
|
| 964 |
+
<h4>Choose Character</h4>
|
| 965 |
+
<p>Select your companion in <strong>Personality</strong> tab and adjust their
|
| 966 |
+
traits to
|
| 967 |
+
match your preferences.</p>
|
| 968 |
+
</div>
|
| 969 |
+
</div>
|
| 970 |
+
<div class="guide-step">
|
| 971 |
+
<span class="step-number">3</span>
|
| 972 |
+
<div class="step-content">
|
| 973 |
+
<h4>Enable Memory</h4>
|
| 974 |
+
<p>Activate intelligent memory in <strong>Data</strong> tab for your companion
|
| 975 |
+
to
|
| 976 |
+
remember important details.</p>
|
| 977 |
+
</div>
|
| 978 |
+
</div>
|
| 979 |
+
<div class="guide-step">
|
| 980 |
+
<span class="step-number">4</span>
|
| 981 |
+
<div class="step-content">
|
| 982 |
+
<h4>Start Conversation</h4>
|
| 983 |
+
<p>Use text chat or click the microphone 🎤 to speak naturally. Watch emotions
|
| 984 |
+
and
|
| 985 |
+
personality evolve!</p>
|
| 986 |
+
</div>
|
| 987 |
+
</div>
|
| 988 |
+
<div class="guide-step">
|
| 989 |
+
<span class="step-number">5</span>
|
| 990 |
+
<div class="step-content">
|
| 991 |
+
<h4>Customize & Backup</h4>
|
| 992 |
+
<p>Personalize themes in <strong>Appearance</strong> and regularly export your
|
| 993 |
+
data for
|
| 994 |
+
safekeeping.</p>
|
| 995 |
+
</div>
|
| 996 |
+
</div>
|
| 997 |
+
</div>
|
| 998 |
+
</div>
|
| 999 |
+
|
| 1000 |
+
<div class="help-section">
|
| 1001 |
+
<h3><i class="fas fa-lightbulb"></i> Tips & Tricks</h3>
|
| 1002 |
+
<div class="tips-list">
|
| 1003 |
+
<div class="tip-item">
|
| 1004 |
+
<i class="fas fa-headset"></i>
|
| 1005 |
+
<p><strong>Browser Choice</strong>: Microsoft Edge recommended for optimal Voice
|
| 1006 |
+
Recognition and Text to Speech (TTS) performance</p>
|
| 1007 |
+
</div>
|
| 1008 |
+
<div class="tip-item">
|
| 1009 |
+
<i class="fas fa-key"></i>
|
| 1010 |
+
<p><strong>API Setup</strong>: You can use OpenRouter, OpenAI, Groq, Together,
|
| 1011 |
+
DeepSeek or
|
| 1012 |
+
your own OpenAI-compatible endpoint (and Local Ollama). Create accounts as
|
| 1013 |
+
needed.</p>
|
| 1014 |
+
</div>
|
| 1015 |
+
<div class="tip-item">
|
| 1016 |
+
<i class="fas fa-memory"></i>
|
| 1017 |
+
<p><strong>Memory System</strong>: Your companion learns faster when you share
|
| 1018 |
+
specific
|
| 1019 |
+
details about yourself</p>
|
| 1020 |
+
</div>
|
| 1021 |
+
<div class="tip-item">
|
| 1022 |
+
<i class="fas fa-heart"></i>
|
| 1023 |
+
<p><strong>Relationship Building</strong>: Consistent positive interactions
|
| 1024 |
+
naturally
|
| 1025 |
+
increase affection and unlock deeper conversations</p>
|
| 1026 |
+
</div>
|
| 1027 |
+
<div class="tip-item">
|
| 1028 |
+
<i class="fas fa-users"></i>
|
| 1029 |
+
<p><strong>Character Switching</strong>: Each character has unique memories and
|
| 1030 |
+
personality
|
| 1031 |
+
development - try them all!</p>
|
| 1032 |
+
</div>
|
| 1033 |
+
<div class="tip-item">
|
| 1034 |
+
<i class="fas fa-microphone"></i>
|
| 1035 |
+
<p><strong>Voice Tips</strong>: Speak clearly and pause briefly between sentences
|
| 1036 |
+
for better
|
| 1037 |
+
emotion detection</p>
|
| 1038 |
+
</div>
|
| 1039 |
+
<div class="tip-item">
|
| 1040 |
+
<i class="fas fa-download"></i>
|
| 1041 |
+
<p><strong>Data Management</strong>: Export conversations regularly and use memory
|
| 1042 |
+
management to review learned information</p>
|
| 1043 |
+
</div>
|
| 1044 |
+
<div class="tip-item">
|
| 1045 |
+
<i class="fas fa-plug"></i>
|
| 1046 |
+
<p><strong>Plugins</strong>: Explore the plugin system to add custom themes, voices,
|
| 1047 |
+
and
|
| 1048 |
+
behaviors</p>
|
| 1049 |
+
</div>
|
| 1050 |
+
<div class="tip-item">
|
| 1051 |
+
<i class="fas fa-mobile-alt"></i>
|
| 1052 |
+
<p><strong>Mobile Support</strong>: Works on tablets and phones - perfect for
|
| 1053 |
+
conversations
|
| 1054 |
+
anywhere</p>
|
| 1055 |
+
</div>
|
| 1056 |
+
</div>
|
| 1057 |
+
</div>
|
| 1058 |
+
|
| 1059 |
+
<div class="help-section version-info">
|
| 1060 |
+
<h3><i class="fas fa-code"></i> Technical Information</h3>
|
| 1061 |
+
<div class="tech-info">
|
| 1062 |
+
<p><strong>Created date :</strong> July 16, 2025</p>
|
| 1063 |
+
<p><strong>Version :</strong> v1.1.7.1 "HF and GH version"</p>
|
| 1064 |
+
<p><strong>Last update :</strong> November 12, 2025</p>
|
| 1065 |
+
<p><strong>Technologies :</strong> HTML5, CSS3, JavaScript ES6+, IndexedDB, Web Speech
|
| 1066 |
+
API</p>
|
| 1067 |
+
<p><strong>Status :</strong> ✅ Stable and functional</p>
|
| 1068 |
+
<p>💕 <strong>_"Love is the most powerful code"_</strong> 💕</p>
|
| 1069 |
+
</div>
|
| 1070 |
+
</div>
|
| 1071 |
+
</div>
|
| 1072 |
+
</div>
|
| 1073 |
+
</div>
|
| 1074 |
+
|
| 1075 |
+
<!-- Script load order (critical):
|
| 1076 |
+
1. Legacy globals (dexie, i18n) must load before modules needing them.
|
| 1077 |
+
2. Base utilities (kimi-utils.js) define KimiBaseManager and helpers.
|
| 1078 |
+
3. Managers that extend base (appearance, data) after utils.
|
| 1079 |
+
4. Core module wiring (kimi-module.js) after managers so window.* hooks exist.
|
| 1080 |
+
5. Initialization / orchestration (kimi-script.js) last.
|
| 1081 |
+
Keep this order when adding new managers. -->
|
| 1082 |
+
<script src="dexie-js/dexie.min.js"></script>
|
| 1083 |
+
<script src="kimi-locale/i18n.js"></script>
|
| 1084 |
+
<script type="module" src="kimi-js/kimi-utils.js"></script>
|
| 1085 |
+
<script type="module" src="kimi-js/kimi-main.js"></script>
|
| 1086 |
+
<script type="module" src="kimi-js/kimi-config.js"></script>
|
| 1087 |
+
<script type="module" src="kimi-js/kimi-debug-utils.js"></script>
|
| 1088 |
+
<script type="module" src="kimi-js/kimi-error-manager.js"></script>
|
| 1089 |
+
<script type="module" src="kimi-js/kimi-security.js"></script>
|
| 1090 |
+
<!-- Video FSM (module) placed before video controller / manager usage -->
|
| 1091 |
+
<script type="module" src="kimi-js/kimi-video-fsm.js"></script>
|
| 1092 |
+
<script type="module" src="kimi-js/kimi-video-controller.js"></script>
|
| 1093 |
+
<script type="module" src="kimi-js/kimi-voices.js"></script>
|
| 1094 |
+
<script type="module" src="kimi-js/kimi-constants.js"></script>
|
| 1095 |
+
<script type="module" src="kimi-js/kimi-memory-ui.js"></script>
|
| 1096 |
+
<script type="module" src="kimi-js/kimi-appearance.js"></script>
|
| 1097 |
+
<script type="module" src="kimi-js/kimi-module.js"></script>
|
| 1098 |
+
<script type="module" src="kimi-js/kimi-script.js"></script>
|
| 1099 |
+
<script type="module" src="kimi-js/kimi-plugin-manager.js"></script>
|
| 1100 |
+
</body>
|
| 1101 |
+
|
| 1102 |
+
</html>
|
kimi-css/kimi-memory-styles.css
ADDED
|
@@ -0,0 +1,691 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/* ===== MEMORY SYSTEM STYLES ===== */
|
| 2 |
+
|
| 3 |
+
/* Memory Input Group */
|
| 4 |
+
.memory-input-group {
|
| 5 |
+
display: flex;
|
| 6 |
+
flex-direction: column;
|
| 7 |
+
gap: 8px;
|
| 8 |
+
}
|
| 9 |
+
|
| 10 |
+
.memory-stats {
|
| 11 |
+
display: flex;
|
| 12 |
+
align-items: center;
|
| 13 |
+
gap: 12px;
|
| 14 |
+
}
|
| 15 |
+
|
| 16 |
+
.memory-stats span {
|
| 17 |
+
color: var(--text-secondary);
|
| 18 |
+
font-size: 0.9em;
|
| 19 |
+
font-weight: 500;
|
| 20 |
+
}
|
| 21 |
+
|
| 22 |
+
/* Memory Modal */
|
| 23 |
+
.memory-overlay {
|
| 24 |
+
position: fixed;
|
| 25 |
+
top: 0;
|
| 26 |
+
left: 0;
|
| 27 |
+
width: 100%;
|
| 28 |
+
height: 100%;
|
| 29 |
+
background: rgba(0, 0, 0, 0.85);
|
| 30 |
+
display: flex;
|
| 31 |
+
justify-content: center;
|
| 32 |
+
align-items: center;
|
| 33 |
+
z-index: 10000;
|
| 34 |
+
opacity: 0;
|
| 35 |
+
animation: fadeIn 0.3s ease forwards;
|
| 36 |
+
backdrop-filter: blur(3px);
|
| 37 |
+
}
|
| 38 |
+
|
| 39 |
+
.memory-modal {
|
| 40 |
+
background: var(--background-secondary);
|
| 41 |
+
border-radius: 12px;
|
| 42 |
+
width: 90%;
|
| 43 |
+
max-width: 900px;
|
| 44 |
+
max-height: 85vh;
|
| 45 |
+
overflow: hidden;
|
| 46 |
+
box-shadow: 0 25px 70px rgba(0, 0, 0, 0.4);
|
| 47 |
+
transform: scale(0.9);
|
| 48 |
+
animation: modalSlideIn 0.3s ease forwards;
|
| 49 |
+
border: 1px solid var(--border-color);
|
| 50 |
+
}
|
| 51 |
+
|
| 52 |
+
.memory-header {
|
| 53 |
+
padding: 20px 24px;
|
| 54 |
+
border-bottom: 1px solid var(--border-color);
|
| 55 |
+
display: flex;
|
| 56 |
+
justify-content: space-between;
|
| 57 |
+
align-items: center;
|
| 58 |
+
background: var(--primary-gradient);
|
| 59 |
+
}
|
| 60 |
+
|
| 61 |
+
.memory-title {
|
| 62 |
+
margin: 0;
|
| 63 |
+
color: white;
|
| 64 |
+
font-size: 1.3rem;
|
| 65 |
+
display: flex;
|
| 66 |
+
align-items: center;
|
| 67 |
+
gap: 12px;
|
| 68 |
+
}
|
| 69 |
+
|
| 70 |
+
.memory-close {
|
| 71 |
+
background: none;
|
| 72 |
+
border: none;
|
| 73 |
+
color: white;
|
| 74 |
+
font-size: 1.5rem;
|
| 75 |
+
cursor: pointer;
|
| 76 |
+
padding: 8px;
|
| 77 |
+
border-radius: 6px;
|
| 78 |
+
transition: background-color 0.2s;
|
| 79 |
+
}
|
| 80 |
+
|
| 81 |
+
.memory-close:hover {
|
| 82 |
+
background: rgba(255, 255, 255, 0.1);
|
| 83 |
+
}
|
| 84 |
+
|
| 85 |
+
.memory-content {
|
| 86 |
+
padding: 20px 24px;
|
| 87 |
+
max-height: 60vh;
|
| 88 |
+
overflow-y: auto;
|
| 89 |
+
}
|
| 90 |
+
|
| 91 |
+
/* Memory Filters */
|
| 92 |
+
.memory-filters {
|
| 93 |
+
display: grid;
|
| 94 |
+
grid-template-columns: 2fr 1fr auto;
|
| 95 |
+
gap: 12px;
|
| 96 |
+
margin-bottom: 20px;
|
| 97 |
+
align-items: center;
|
| 98 |
+
}
|
| 99 |
+
|
| 100 |
+
.memory-search-container {
|
| 101 |
+
position: relative;
|
| 102 |
+
display: flex;
|
| 103 |
+
align-items: center;
|
| 104 |
+
}
|
| 105 |
+
|
| 106 |
+
.memory-search-icon {
|
| 107 |
+
position: absolute;
|
| 108 |
+
right: 12px;
|
| 109 |
+
color: var(--text-secondary);
|
| 110 |
+
pointer-events: none;
|
| 111 |
+
}
|
| 112 |
+
|
| 113 |
+
#memory-search {
|
| 114 |
+
width: 100%;
|
| 115 |
+
padding-right: 36px;
|
| 116 |
+
}
|
| 117 |
+
|
| 118 |
+
/* Consolidated Mobile Responsive */
|
| 119 |
+
@media (max-width: 768px) {
|
| 120 |
+
.memory-filters {
|
| 121 |
+
grid-template-columns: 1fr;
|
| 122 |
+
gap: 8px;
|
| 123 |
+
}
|
| 124 |
+
|
| 125 |
+
.memory-category-header {
|
| 126 |
+
flex-direction: column;
|
| 127 |
+
align-items: flex-start;
|
| 128 |
+
gap: 4px;
|
| 129 |
+
}
|
| 130 |
+
|
| 131 |
+
.memory-badges {
|
| 132 |
+
flex-wrap: wrap;
|
| 133 |
+
gap: 4px;
|
| 134 |
+
}
|
| 135 |
+
|
| 136 |
+
.memory-modal {
|
| 137 |
+
width: 95%;
|
| 138 |
+
max-height: 90vh;
|
| 139 |
+
}
|
| 140 |
+
|
| 141 |
+
.memory-header {
|
| 142 |
+
padding: 16px 20px;
|
| 143 |
+
}
|
| 144 |
+
|
| 145 |
+
.memory-content {
|
| 146 |
+
padding: 16px 20px;
|
| 147 |
+
}
|
| 148 |
+
|
| 149 |
+
.memory-item {
|
| 150 |
+
padding: 10px;
|
| 151 |
+
}
|
| 152 |
+
|
| 153 |
+
.memory-item .memory-header {
|
| 154 |
+
flex-direction: column;
|
| 155 |
+
align-items: flex-start;
|
| 156 |
+
gap: 6px;
|
| 157 |
+
}
|
| 158 |
+
|
| 159 |
+
.memory-meta {
|
| 160 |
+
flex-direction: column;
|
| 161 |
+
gap: 4px;
|
| 162 |
+
font-size: 0.7rem;
|
| 163 |
+
}
|
| 164 |
+
|
| 165 |
+
.memory-category {
|
| 166 |
+
font-size: 0.7rem;
|
| 167 |
+
padding: 2px 6px;
|
| 168 |
+
}
|
| 169 |
+
|
| 170 |
+
.memory-preview-text {
|
| 171 |
+
font-size: 0.85rem;
|
| 172 |
+
}
|
| 173 |
+
|
| 174 |
+
.memory-actions {
|
| 175 |
+
gap: 4px;
|
| 176 |
+
}
|
| 177 |
+
|
| 178 |
+
.memory-edit-btn,
|
| 179 |
+
.memory-delete-btn {
|
| 180 |
+
min-width: 24px;
|
| 181 |
+
height: 24px;
|
| 182 |
+
font-size: 0.75rem;
|
| 183 |
+
}
|
| 184 |
+
}
|
| 185 |
+
|
| 186 |
+
/* Memory List */
|
| 187 |
+
.memory-list {
|
| 188 |
+
display: flex;
|
| 189 |
+
flex-direction: column;
|
| 190 |
+
gap: 8px;
|
| 191 |
+
}
|
| 192 |
+
|
| 193 |
+
.memory-item {
|
| 194 |
+
background: rgba(var(--background-primary-rgb, 255, 255, 255), 0.95);
|
| 195 |
+
border: 1px solid var(--border-color);
|
| 196 |
+
border-radius: 8px;
|
| 197 |
+
padding: 12px;
|
| 198 |
+
transition: all 0.2s ease;
|
| 199 |
+
backdrop-filter: blur(5px);
|
| 200 |
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
| 201 |
+
}
|
| 202 |
+
|
| 203 |
+
.memory-item:hover {
|
| 204 |
+
border-color: var(--primary-color);
|
| 205 |
+
box-shadow: 0 4px 16px rgba(var(--primary-rgb), 0.2);
|
| 206 |
+
background: rgba(var(--background-primary-rgb, 255, 255, 255), 1);
|
| 207 |
+
transform: translateY(-1px);
|
| 208 |
+
}
|
| 209 |
+
|
| 210 |
+
.memory-item .memory-header {
|
| 211 |
+
padding: 0;
|
| 212 |
+
border: none;
|
| 213 |
+
background: none;
|
| 214 |
+
margin-bottom: 6px;
|
| 215 |
+
display: flex;
|
| 216 |
+
justify-content: space-between;
|
| 217 |
+
align-items: flex-start;
|
| 218 |
+
gap: 8px;
|
| 219 |
+
}
|
| 220 |
+
|
| 221 |
+
.memory-item-title {
|
| 222 |
+
font-size: 1rem;
|
| 223 |
+
font-weight: 700;
|
| 224 |
+
color: var(--memory-modal-text, #e0e0e0);
|
| 225 |
+
margin-right: 12px;
|
| 226 |
+
max-width: 60%;
|
| 227 |
+
white-space: nowrap;
|
| 228 |
+
overflow: hidden;
|
| 229 |
+
text-overflow: ellipsis;
|
| 230 |
+
}
|
| 231 |
+
|
| 232 |
+
.memory-category {
|
| 233 |
+
background: var(--primary-color);
|
| 234 |
+
color: white;
|
| 235 |
+
padding: 3px 8px;
|
| 236 |
+
border-radius: 10px;
|
| 237 |
+
font-size: 0.75rem;
|
| 238 |
+
font-weight: 500;
|
| 239 |
+
text-transform: capitalize;
|
| 240 |
+
white-space: nowrap;
|
| 241 |
+
}
|
| 242 |
+
|
| 243 |
+
.memory-type {
|
| 244 |
+
background: var(--background-secondary);
|
| 245 |
+
color: var(--text-secondary);
|
| 246 |
+
padding: 2px 6px;
|
| 247 |
+
border-radius: 4px;
|
| 248 |
+
font-size: 0.7rem;
|
| 249 |
+
text-transform: uppercase;
|
| 250 |
+
font-weight: 600;
|
| 251 |
+
}
|
| 252 |
+
|
| 253 |
+
.memory-length {
|
| 254 |
+
background: var(--border-color);
|
| 255 |
+
color: var(--text-secondary);
|
| 256 |
+
padding: 2px 6px;
|
| 257 |
+
border-radius: 4px;
|
| 258 |
+
font-size: 0.7rem;
|
| 259 |
+
font-weight: 500;
|
| 260 |
+
}
|
| 261 |
+
|
| 262 |
+
.memory-confidence {
|
| 263 |
+
color: var(--text-secondary);
|
| 264 |
+
font-size: 0.75rem;
|
| 265 |
+
font-weight: 500;
|
| 266 |
+
}
|
| 267 |
+
|
| 268 |
+
.memory-item .memory-content {
|
| 269 |
+
padding: 0;
|
| 270 |
+
max-height: none;
|
| 271 |
+
overflow: visible;
|
| 272 |
+
color: #e0e0e0;
|
| 273 |
+
line-height: 1.4;
|
| 274 |
+
margin-bottom: 8px;
|
| 275 |
+
font-size: 0.9rem;
|
| 276 |
+
}
|
| 277 |
+
|
| 278 |
+
.memory-preview {
|
| 279 |
+
display: block;
|
| 280 |
+
margin-bottom: 6px;
|
| 281 |
+
color: #e0e0e0;
|
| 282 |
+
line-height: 1.4;
|
| 283 |
+
font-size: 0.9rem;
|
| 284 |
+
}
|
| 285 |
+
|
| 286 |
+
.memory-preview-text {
|
| 287 |
+
display: block;
|
| 288 |
+
overflow: hidden;
|
| 289 |
+
text-overflow: ellipsis;
|
| 290 |
+
white-space: nowrap;
|
| 291 |
+
max-width: 100%;
|
| 292 |
+
color: #e0e0e0;
|
| 293 |
+
}
|
| 294 |
+
|
| 295 |
+
.memory-preview-full {
|
| 296 |
+
white-space: normal;
|
| 297 |
+
max-height: 60px;
|
| 298 |
+
overflow: hidden;
|
| 299 |
+
display: -webkit-box;
|
| 300 |
+
-webkit-line-clamp: 3;
|
| 301 |
+
line-clamp: 3;
|
| 302 |
+
-webkit-box-orient: vertical;
|
| 303 |
+
}
|
| 304 |
+
|
| 305 |
+
.memory-expand-btn {
|
| 306 |
+
background: none;
|
| 307 |
+
border: none;
|
| 308 |
+
color: var(--primary-color);
|
| 309 |
+
cursor: pointer;
|
| 310 |
+
font-size: 0.8rem;
|
| 311 |
+
padding: 2px 4px;
|
| 312 |
+
margin-top: 4px;
|
| 313 |
+
border-radius: 3px;
|
| 314 |
+
transition: background-color 0.2s;
|
| 315 |
+
}
|
| 316 |
+
|
| 317 |
+
.memory-expand-btn:hover {
|
| 318 |
+
background: rgba(var(--primary-rgb), 0.1);
|
| 319 |
+
}
|
| 320 |
+
|
| 321 |
+
.memory-meta {
|
| 322 |
+
display: flex;
|
| 323 |
+
gap: 8px;
|
| 324 |
+
margin-bottom: 8px;
|
| 325 |
+
font-size: 0.75rem;
|
| 326 |
+
color: var(--text-secondary);
|
| 327 |
+
align-items: center;
|
| 328 |
+
}
|
| 329 |
+
|
| 330 |
+
.memory-source {
|
| 331 |
+
cursor: help;
|
| 332 |
+
text-decoration: underline dotted;
|
| 333 |
+
}
|
| 334 |
+
|
| 335 |
+
/* Source excerpt content shown under the trigger */
|
| 336 |
+
.memory-source {
|
| 337 |
+
position: relative;
|
| 338 |
+
outline: none;
|
| 339 |
+
}
|
| 340 |
+
|
| 341 |
+
.memory-source:focus {
|
| 342 |
+
box-shadow: 0 0 0 3px rgba(var(--primary-rgb), 0.14);
|
| 343 |
+
border-radius: 4px;
|
| 344 |
+
}
|
| 345 |
+
|
| 346 |
+
.memory-source-content {
|
| 347 |
+
margin-top: 8px;
|
| 348 |
+
padding: 10px 12px;
|
| 349 |
+
background: rgba(0, 0, 0, 0.6);
|
| 350 |
+
color: #f1f1f1;
|
| 351 |
+
border-radius: 8px;
|
| 352 |
+
font-size: 0.9rem;
|
| 353 |
+
line-height: 1.4;
|
| 354 |
+
border: 1px solid rgba(255, 255, 255, 0.04);
|
| 355 |
+
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.6);
|
| 356 |
+
max-width: 100%;
|
| 357 |
+
white-space: pre-wrap;
|
| 358 |
+
word-break: break-word;
|
| 359 |
+
transition:
|
| 360 |
+
opacity 0.18s ease,
|
| 361 |
+
transform 0.18s ease;
|
| 362 |
+
opacity: 0;
|
| 363 |
+
transform: translateY(-6px);
|
| 364 |
+
z-index: 2;
|
| 365 |
+
}
|
| 366 |
+
|
| 367 |
+
.memory-source-content[style*="display: block"] {
|
| 368 |
+
opacity: 1;
|
| 369 |
+
transform: translateY(0);
|
| 370 |
+
}
|
| 371 |
+
|
| 372 |
+
/* Make sure content aligns visually under the memory item */
|
| 373 |
+
.memory-item .memory-source-content {
|
| 374 |
+
margin-left: 0;
|
| 375 |
+
}
|
| 376 |
+
|
| 377 |
+
@media (max-width: 480px) {
|
| 378 |
+
.memory-source-content {
|
| 379 |
+
font-size: 0.95rem;
|
| 380 |
+
padding: 12px;
|
| 381 |
+
}
|
| 382 |
+
}
|
| 383 |
+
|
| 384 |
+
.memory-actions {
|
| 385 |
+
display: flex;
|
| 386 |
+
gap: 6px;
|
| 387 |
+
justify-content: flex-end;
|
| 388 |
+
}
|
| 389 |
+
|
| 390 |
+
.memory-edit-btn,
|
| 391 |
+
.memory-delete-btn {
|
| 392 |
+
padding: 4px 6px;
|
| 393 |
+
border: none;
|
| 394 |
+
border-radius: 4px;
|
| 395 |
+
cursor: pointer;
|
| 396 |
+
font-size: 0.8rem;
|
| 397 |
+
transition: all 0.2s ease;
|
| 398 |
+
min-width: 28px;
|
| 399 |
+
height: 28px;
|
| 400 |
+
display: flex;
|
| 401 |
+
align-items: center;
|
| 402 |
+
justify-content: center;
|
| 403 |
+
}
|
| 404 |
+
|
| 405 |
+
.memory-edit-btn {
|
| 406 |
+
background: var(--primary-color);
|
| 407 |
+
color: white;
|
| 408 |
+
}
|
| 409 |
+
|
| 410 |
+
.memory-edit-btn:hover {
|
| 411 |
+
background: var(--primary-dark);
|
| 412 |
+
}
|
| 413 |
+
|
| 414 |
+
.memory-delete-btn {
|
| 415 |
+
background: #e74c3c;
|
| 416 |
+
color: white;
|
| 417 |
+
}
|
| 418 |
+
|
| 419 |
+
.memory-delete-btn:hover {
|
| 420 |
+
background: #c0392b;
|
| 421 |
+
}
|
| 422 |
+
|
| 423 |
+
/* Empty State */
|
| 424 |
+
.memory-empty {
|
| 425 |
+
text-align: center;
|
| 426 |
+
padding: 40px 20px;
|
| 427 |
+
color: var(--text-secondary);
|
| 428 |
+
}
|
| 429 |
+
|
| 430 |
+
.memory-empty i {
|
| 431 |
+
font-size: 3rem;
|
| 432 |
+
margin-bottom: 16px;
|
| 433 |
+
opacity: 0.5;
|
| 434 |
+
}
|
| 435 |
+
|
| 436 |
+
.memory-empty p {
|
| 437 |
+
margin: 0;
|
| 438 |
+
line-height: 1.5;
|
| 439 |
+
}
|
| 440 |
+
|
| 441 |
+
/* Memory Category Groups */
|
| 442 |
+
.memory-category-group {
|
| 443 |
+
margin-bottom: 16px;
|
| 444 |
+
}
|
| 445 |
+
|
| 446 |
+
.memory-category-header {
|
| 447 |
+
margin: 0 0 8px 0;
|
| 448 |
+
padding: 6px 12px;
|
| 449 |
+
background: var(--primary-color);
|
| 450 |
+
color: white;
|
| 451 |
+
border-radius: 6px;
|
| 452 |
+
font-size: 0.85rem;
|
| 453 |
+
font-weight: 600;
|
| 454 |
+
display: flex;
|
| 455 |
+
align-items: center;
|
| 456 |
+
justify-content: space-between;
|
| 457 |
+
}
|
| 458 |
+
|
| 459 |
+
.memory-category-count {
|
| 460 |
+
opacity: 0.8;
|
| 461 |
+
font-weight: normal;
|
| 462 |
+
font-size: 0.8rem;
|
| 463 |
+
}
|
| 464 |
+
|
| 465 |
+
.memory-category-items {
|
| 466 |
+
display: flex;
|
| 467 |
+
flex-direction: column;
|
| 468 |
+
gap: 6px;
|
| 469 |
+
}
|
| 470 |
+
|
| 471 |
+
/* Memory Item Types */
|
| 472 |
+
.memory-item.memory-auto {
|
| 473 |
+
border-left: 4px solid #3498db;
|
| 474 |
+
}
|
| 475 |
+
|
| 476 |
+
.memory-item.memory-manual {
|
| 477 |
+
border-left: 4px solid #9b59b6;
|
| 478 |
+
}
|
| 479 |
+
|
| 480 |
+
.memory-badges {
|
| 481 |
+
display: flex;
|
| 482 |
+
gap: 8px;
|
| 483 |
+
align-items: center;
|
| 484 |
+
}
|
| 485 |
+
|
| 486 |
+
.memory-type {
|
| 487 |
+
font-size: 0.75rem;
|
| 488 |
+
padding: 2px 6px;
|
| 489 |
+
border-radius: 4px;
|
| 490 |
+
text-transform: uppercase;
|
| 491 |
+
font-weight: 500;
|
| 492 |
+
}
|
| 493 |
+
|
| 494 |
+
.memory-type.auto_extracted {
|
| 495 |
+
background: #3498db;
|
| 496 |
+
color: white;
|
| 497 |
+
}
|
| 498 |
+
|
| 499 |
+
.memory-type.manual {
|
| 500 |
+
background: #9b59b6;
|
| 501 |
+
color: white;
|
| 502 |
+
}
|
| 503 |
+
|
| 504 |
+
.memory-type.imported {
|
| 505 |
+
background: #f39c12;
|
| 506 |
+
color: white;
|
| 507 |
+
}
|
| 508 |
+
|
| 509 |
+
/* Confidence Levels */
|
| 510 |
+
.memory-confidence {
|
| 511 |
+
font-size: 0.8rem;
|
| 512 |
+
font-weight: 500;
|
| 513 |
+
padding: 2px 6px;
|
| 514 |
+
border-radius: 4px;
|
| 515 |
+
}
|
| 516 |
+
|
| 517 |
+
.confidence-high {
|
| 518 |
+
background: #27ae60;
|
| 519 |
+
color: white;
|
| 520 |
+
}
|
| 521 |
+
|
| 522 |
+
.confidence-medium {
|
| 523 |
+
background: #f39c12;
|
| 524 |
+
color: white;
|
| 525 |
+
}
|
| 526 |
+
|
| 527 |
+
.confidence-low {
|
| 528 |
+
background: #e74c3c;
|
| 529 |
+
color: white;
|
| 530 |
+
}
|
| 531 |
+
|
| 532 |
+
/* Importance badge */
|
| 533 |
+
.memory-importance {
|
| 534 |
+
padding: 2px 6px;
|
| 535 |
+
border-radius: 4px;
|
| 536 |
+
font-size: 0.75rem;
|
| 537 |
+
font-weight: 600;
|
| 538 |
+
}
|
| 539 |
+
.importance-high {
|
| 540 |
+
background: #8e44ad;
|
| 541 |
+
color: #fff;
|
| 542 |
+
}
|
| 543 |
+
.importance-medium {
|
| 544 |
+
background: #16a085;
|
| 545 |
+
color: #fff;
|
| 546 |
+
}
|
| 547 |
+
.importance-low {
|
| 548 |
+
background: #7f8c8d;
|
| 549 |
+
color: #fff;
|
| 550 |
+
}
|
| 551 |
+
|
| 552 |
+
/* Tags (chips) */
|
| 553 |
+
.memory-tags {
|
| 554 |
+
display: flex;
|
| 555 |
+
gap: 6px;
|
| 556 |
+
flex-wrap: wrap;
|
| 557 |
+
margin: 6px 0 4px 0;
|
| 558 |
+
}
|
| 559 |
+
.memory-tag {
|
| 560 |
+
font-size: 0.7rem;
|
| 561 |
+
padding: 2px 6px;
|
| 562 |
+
border-radius: 999px;
|
| 563 |
+
background: var(--border-color);
|
| 564 |
+
color: var(--text-secondary);
|
| 565 |
+
border: 1px solid rgba(0, 0, 0, 0.05);
|
| 566 |
+
}
|
| 567 |
+
.memory-tag.tag-relationship {
|
| 568 |
+
background: rgba(231, 76, 60, 0.15);
|
| 569 |
+
color: #e74c3c;
|
| 570 |
+
border-color: rgba(231, 76, 60, 0.25);
|
| 571 |
+
}
|
| 572 |
+
.memory-tag.tag-boundary {
|
| 573 |
+
background: rgba(52, 152, 219, 0.15);
|
| 574 |
+
color: #3498db;
|
| 575 |
+
border-color: rgba(52, 152, 219, 0.25);
|
| 576 |
+
}
|
| 577 |
+
.memory-tag.tag-time {
|
| 578 |
+
background: rgba(241, 196, 15, 0.2);
|
| 579 |
+
color: #d35400;
|
| 580 |
+
border-color: rgba(241, 196, 15, 0.3);
|
| 581 |
+
}
|
| 582 |
+
.memory-tag.tag-type {
|
| 583 |
+
background: rgba(46, 204, 113, 0.2);
|
| 584 |
+
color: #27ae60;
|
| 585 |
+
border-color: rgba(46, 204, 113, 0.3);
|
| 586 |
+
}
|
| 587 |
+
.memory-tag.tag-generic {
|
| 588 |
+
opacity: 0.9;
|
| 589 |
+
}
|
| 590 |
+
.memory-tag.tag-more {
|
| 591 |
+
background: transparent;
|
| 592 |
+
color: var(--text-secondary);
|
| 593 |
+
border-style: dashed;
|
| 594 |
+
}
|
| 595 |
+
|
| 596 |
+
/* Memory Toggle Indicator */
|
| 597 |
+
.toggle-switch {
|
| 598 |
+
position: relative;
|
| 599 |
+
}
|
| 600 |
+
|
| 601 |
+
.memory-indicator {
|
| 602 |
+
position: absolute;
|
| 603 |
+
top: -2px;
|
| 604 |
+
right: -2px;
|
| 605 |
+
width: 8px;
|
| 606 |
+
height: 8px;
|
| 607 |
+
border-radius: 50%;
|
| 608 |
+
border: 2px solid white;
|
| 609 |
+
}
|
| 610 |
+
|
| 611 |
+
/* Enhanced animations */
|
| 612 |
+
.memory-item {
|
| 613 |
+
transition: all 0.3s ease;
|
| 614 |
+
transform: translateY(0);
|
| 615 |
+
}
|
| 616 |
+
|
| 617 |
+
.memory-item:hover {
|
| 618 |
+
transform: translateY(-2px);
|
| 619 |
+
box-shadow: 0 6px 20px rgba(var(--primary-rgb), 0.15);
|
| 620 |
+
}
|
| 621 |
+
|
| 622 |
+
@keyframes fadeIn {
|
| 623 |
+
to {
|
| 624 |
+
opacity: 1;
|
| 625 |
+
}
|
| 626 |
+
}
|
| 627 |
+
|
| 628 |
+
@keyframes modalSlideIn {
|
| 629 |
+
to {
|
| 630 |
+
opacity: 1;
|
| 631 |
+
transform: scale(1);
|
| 632 |
+
}
|
| 633 |
+
}
|
| 634 |
+
|
| 635 |
+
/* Memory Feedback Notifications */
|
| 636 |
+
.memory-feedback {
|
| 637 |
+
font-family: var(--font-family, sans-serif);
|
| 638 |
+
border-radius: 8px;
|
| 639 |
+
backdrop-filter: blur(5px);
|
| 640 |
+
font-weight: 500;
|
| 641 |
+
border: 1px solid rgba(255, 255, 255, 0.1);
|
| 642 |
+
min-width: 200px;
|
| 643 |
+
text-align: center;
|
| 644 |
+
}
|
| 645 |
+
|
| 646 |
+
.memory-feedback-info {
|
| 647 |
+
background: linear-gradient(135deg, rgba(52, 152, 219, 0.9), rgba(52, 152, 219, 0.7));
|
| 648 |
+
color: white;
|
| 649 |
+
box-shadow: 0 4px 15px rgba(52, 152, 219, 0.3);
|
| 650 |
+
}
|
| 651 |
+
|
| 652 |
+
.memory-feedback-success {
|
| 653 |
+
background: linear-gradient(135deg, rgba(39, 174, 96, 0.9), rgba(39, 174, 96, 0.7));
|
| 654 |
+
color: white;
|
| 655 |
+
box-shadow: 0 4px 15px rgba(39, 174, 96, 0.3);
|
| 656 |
+
}
|
| 657 |
+
|
| 658 |
+
.memory-feedback-error {
|
| 659 |
+
background: linear-gradient(135deg, rgba(231, 76, 60, 0.9), rgba(231, 76, 60, 0.7));
|
| 660 |
+
color: white;
|
| 661 |
+
box-shadow: 0 4px 15px rgba(231, 76, 60, 0.3);
|
| 662 |
+
}
|
| 663 |
+
|
| 664 |
+
/* Unified Dark Theme for Memory Modal - All Themes */
|
| 665 |
+
/* Theme-agnostic variables for consistent memory modal appearance */
|
| 666 |
+
.memory-modal {
|
| 667 |
+
background: #1a1a1a;
|
| 668 |
+
color: #e0e0e0;
|
| 669 |
+
--memory-modal-bg: #1a1a1a;
|
| 670 |
+
--memory-modal-text: #e0e0e0;
|
| 671 |
+
--memory-item-bg: rgba(42, 42, 42, 0.95);
|
| 672 |
+
--memory-item-bg-hover: rgba(42, 42, 42, 1);
|
| 673 |
+
--memory-item-border: #404040;
|
| 674 |
+
}
|
| 675 |
+
|
| 676 |
+
.memory-item {
|
| 677 |
+
background: var(--memory-item-bg);
|
| 678 |
+
border-color: var(--memory-item-border);
|
| 679 |
+
color: var(--memory-modal-text);
|
| 680 |
+
backdrop-filter: blur(5px);
|
| 681 |
+
}
|
| 682 |
+
|
| 683 |
+
.memory-item:hover {
|
| 684 |
+
border-color: var(--primary-color);
|
| 685 |
+
background: var(--memory-item-bg-hover);
|
| 686 |
+
box-shadow: 0 4px 16px rgba(var(--primary-rgb), 0.2);
|
| 687 |
+
}
|
| 688 |
+
|
| 689 |
+
.memory-expand-btn:hover {
|
| 690 |
+
background: rgba(var(--primary-rgb), 0.2);
|
| 691 |
+
}
|
kimi-css/kimi-settings.css
ADDED
|
@@ -0,0 +1,1632 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/* ===== KIMI SETTINGS PANEL ===== */
|
| 2 |
+
|
| 3 |
+
.settings-overlay {
|
| 4 |
+
position: fixed;
|
| 5 |
+
top: 0;
|
| 6 |
+
left: 0;
|
| 7 |
+
width: 100%;
|
| 8 |
+
height: 100%;
|
| 9 |
+
background: var(--modal-overlay-bg);
|
| 10 |
+
backdrop-filter: blur(10px);
|
| 11 |
+
-webkit-backdrop-filter: blur(10px);
|
| 12 |
+
z-index: 50;
|
| 13 |
+
display: none;
|
| 14 |
+
opacity: 0;
|
| 15 |
+
transition:
|
| 16 |
+
opacity 0.3s ease,
|
| 17 |
+
backdrop-filter 0.3s ease;
|
| 18 |
+
}
|
| 19 |
+
|
| 20 |
+
.settings-overlay.visible {
|
| 21 |
+
display: flex;
|
| 22 |
+
opacity: 1;
|
| 23 |
+
justify-content: center;
|
| 24 |
+
align-items: center;
|
| 25 |
+
padding: 10px;
|
| 26 |
+
}
|
| 27 |
+
|
| 28 |
+
/* ===== SETTINGS PANEL ===== */
|
| 29 |
+
.settings-panel {
|
| 30 |
+
background: var(--modal-bg);
|
| 31 |
+
border-radius: 15px;
|
| 32 |
+
border: 1px solid var(--modal-border);
|
| 33 |
+
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.5);
|
| 34 |
+
width: 90%;
|
| 35 |
+
max-width: 800px;
|
| 36 |
+
max-height: 85vh;
|
| 37 |
+
overflow: hidden;
|
| 38 |
+
animation: slideInUp 0.4s cubic-bezier(0.4, 0, 0.2, 1);
|
| 39 |
+
display: flex;
|
| 40 |
+
flex-direction: column;
|
| 41 |
+
}
|
| 42 |
+
|
| 43 |
+
.settings-content {
|
| 44 |
+
flex: 1;
|
| 45 |
+
overflow-y: auto;
|
| 46 |
+
padding: 0;
|
| 47 |
+
}
|
| 48 |
+
|
| 49 |
+
.settings-content::-webkit-scrollbar {
|
| 50 |
+
width: var(--scrollbar-width);
|
| 51 |
+
}
|
| 52 |
+
|
| 53 |
+
.settings-content::-webkit-scrollbar-track {
|
| 54 |
+
background: var(--scrollbar-track-bg);
|
| 55 |
+
border-radius: 4px;
|
| 56 |
+
}
|
| 57 |
+
|
| 58 |
+
.settings-content::-webkit-scrollbar-thumb {
|
| 59 |
+
background: var(--scrollbar-thumb-bg);
|
| 60 |
+
border-radius: 4px;
|
| 61 |
+
}
|
| 62 |
+
|
| 63 |
+
.settings-content::-webkit-scrollbar-thumb:hover {
|
| 64 |
+
background: var(--scrollbar-thumb-hover-bg);
|
| 65 |
+
}
|
| 66 |
+
|
| 67 |
+
@keyframes slideInUp {
|
| 68 |
+
from {
|
| 69 |
+
transform: translateY(50px);
|
| 70 |
+
opacity: 0;
|
| 71 |
+
}
|
| 72 |
+
to {
|
| 73 |
+
transform: translateY(0);
|
| 74 |
+
opacity: 1;
|
| 75 |
+
}
|
| 76 |
+
}
|
| 77 |
+
|
| 78 |
+
.settings-header {
|
| 79 |
+
background: var(--modal-header-bg);
|
| 80 |
+
padding: 25px 30px;
|
| 81 |
+
display: flex;
|
| 82 |
+
justify-content: space-between;
|
| 83 |
+
align-items: center;
|
| 84 |
+
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
| 85 |
+
}
|
| 86 |
+
|
| 87 |
+
.settings-header-actions {
|
| 88 |
+
display: flex;
|
| 89 |
+
gap: 10px;
|
| 90 |
+
align-items: center;
|
| 91 |
+
}
|
| 92 |
+
|
| 93 |
+
.settings-title {
|
| 94 |
+
margin: 0;
|
| 95 |
+
color: var(--modal-title-color);
|
| 96 |
+
font-size: 1.5rem;
|
| 97 |
+
font-weight: 700;
|
| 98 |
+
display: flex;
|
| 99 |
+
align-items: center;
|
| 100 |
+
gap: 10px;
|
| 101 |
+
text-shadow: 0 1px 1px #000;
|
| 102 |
+
}
|
| 103 |
+
|
| 104 |
+
.help-button,
|
| 105 |
+
.settings-close {
|
| 106 |
+
background: none;
|
| 107 |
+
border: none;
|
| 108 |
+
color: var(--modal-text);
|
| 109 |
+
font-size: 1.5rem;
|
| 110 |
+
cursor: pointer;
|
| 111 |
+
padding: 8px;
|
| 112 |
+
border-radius: 50%;
|
| 113 |
+
transition: all 0.3s ease;
|
| 114 |
+
backdrop-filter: blur(10px);
|
| 115 |
+
}
|
| 116 |
+
|
| 117 |
+
.help-button:hover,
|
| 118 |
+
.settings-close:hover {
|
| 119 |
+
background-color: var(--modal-close-hover-bg);
|
| 120 |
+
transform: scale(1.1);
|
| 121 |
+
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
|
| 122 |
+
}
|
| 123 |
+
|
| 124 |
+
.settings-content {
|
| 125 |
+
flex: 1;
|
| 126 |
+
overflow-y: auto;
|
| 127 |
+
padding: 0;
|
| 128 |
+
position: relative;
|
| 129 |
+
z-index: 1;
|
| 130 |
+
min-height: 0;
|
| 131 |
+
}
|
| 132 |
+
|
| 133 |
+
/* ===== SETTINGS TABS ===== */
|
| 134 |
+
.settings-tabs {
|
| 135 |
+
display: flex;
|
| 136 |
+
overflow-x: auto;
|
| 137 |
+
scroll-behavior: smooth;
|
| 138 |
+
scrollbar-width: none;
|
| 139 |
+
-ms-overflow-style: none;
|
| 140 |
+
box-sizing: border-box;
|
| 141 |
+
transition: padding-right 0.3s ease;
|
| 142 |
+
position: sticky;
|
| 143 |
+
top: 0;
|
| 144 |
+
z-index: 100;
|
| 145 |
+
background: var(--settings-tab-bg);
|
| 146 |
+
border-bottom: 2px solid var(--settings-tab-border);
|
| 147 |
+
backdrop-filter: blur(10px);
|
| 148 |
+
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
| 149 |
+
flex-shrink: 0;
|
| 150 |
+
}
|
| 151 |
+
|
| 152 |
+
.settings-tabs::-webkit-scrollbar {
|
| 153 |
+
display: none;
|
| 154 |
+
}
|
| 155 |
+
|
| 156 |
+
.settings-tab {
|
| 157 |
+
flex: 1;
|
| 158 |
+
min-width: 130px;
|
| 159 |
+
max-width: 200px;
|
| 160 |
+
padding: 15px 16px;
|
| 161 |
+
background: none;
|
| 162 |
+
border: none;
|
| 163 |
+
color: var(--settings-tab-color);
|
| 164 |
+
cursor: pointer;
|
| 165 |
+
font-size: 0.9rem;
|
| 166 |
+
font-weight: 500;
|
| 167 |
+
transition:
|
| 168 |
+
all 0.3s ease,
|
| 169 |
+
font-size 0.2s ease,
|
| 170 |
+
padding 0.2s ease;
|
| 171 |
+
position: relative;
|
| 172 |
+
white-space: nowrap;
|
| 173 |
+
text-align: center;
|
| 174 |
+
text-overflow: ellipsis;
|
| 175 |
+
overflow: hidden;
|
| 176 |
+
box-sizing: border-box;
|
| 177 |
+
}
|
| 178 |
+
|
| 179 |
+
.settings-tab:hover {
|
| 180 |
+
color: var(--settings-tab-hover-color);
|
| 181 |
+
background: var(--settings-tab-hover-bg);
|
| 182 |
+
}
|
| 183 |
+
|
| 184 |
+
.settings-tab.active {
|
| 185 |
+
color: var(--settings-tab-active-color);
|
| 186 |
+
background: var(--settings-tab-active-bg);
|
| 187 |
+
backdrop-filter: blur(10px);
|
| 188 |
+
}
|
| 189 |
+
|
| 190 |
+
.settings-tab.active::after {
|
| 191 |
+
content: "";
|
| 192 |
+
position: absolute;
|
| 193 |
+
bottom: 0;
|
| 194 |
+
left: 0;
|
| 195 |
+
right: 0;
|
| 196 |
+
height: 3px;
|
| 197 |
+
background: linear-gradient(90deg, var(--primary-color, #ff9a9e), var(--secondary-color, #fecfef));
|
| 198 |
+
}
|
| 199 |
+
|
| 200 |
+
.tab-content {
|
| 201 |
+
display: none;
|
| 202 |
+
padding: 30px;
|
| 203 |
+
position: relative;
|
| 204 |
+
z-index: 1;
|
| 205 |
+
overflow: visible;
|
| 206 |
+
background: var(--settings-bg);
|
| 207 |
+
color: var(--modal-text);
|
| 208 |
+
}
|
| 209 |
+
|
| 210 |
+
.tab-content.active {
|
| 211 |
+
display: block;
|
| 212 |
+
}
|
| 213 |
+
|
| 214 |
+
/* ===== SECTIONS DE CONFIGURATION ===== */
|
| 215 |
+
|
| 216 |
+
.config-section {
|
| 217 |
+
margin-bottom: 30px;
|
| 218 |
+
padding: 20px;
|
| 219 |
+
background: var(--settings-section-bg);
|
| 220 |
+
border-radius: 15px;
|
| 221 |
+
border: 1.5px solid var(--settings-section-border);
|
| 222 |
+
}
|
| 223 |
+
|
| 224 |
+
.config-section h3 {
|
| 225 |
+
margin: 0 0 15px 0;
|
| 226 |
+
color: var(--settings-section-header-color);
|
| 227 |
+
font-size: 1.2rem;
|
| 228 |
+
font-weight: 600;
|
| 229 |
+
display: flex;
|
| 230 |
+
align-items: center;
|
| 231 |
+
gap: 10px;
|
| 232 |
+
text-shadow: 0 2px 8px rgba(0, 0, 0, 0.5);
|
| 233 |
+
}
|
| 234 |
+
|
| 235 |
+
.config-row {
|
| 236 |
+
display: flex;
|
| 237 |
+
justify-content: space-between;
|
| 238 |
+
align-items: center;
|
| 239 |
+
margin-bottom: 15px;
|
| 240 |
+
padding: 10px 0;
|
| 241 |
+
}
|
| 242 |
+
|
| 243 |
+
.config-row:last-child {
|
| 244 |
+
margin-bottom: 0;
|
| 245 |
+
}
|
| 246 |
+
|
| 247 |
+
.config-label {
|
| 248 |
+
color: var(--settings-text);
|
| 249 |
+
font-weight: 500;
|
| 250 |
+
flex: 1;
|
| 251 |
+
}
|
| 252 |
+
|
| 253 |
+
.config-label-section {
|
| 254 |
+
flex: 1;
|
| 255 |
+
display: flex;
|
| 256 |
+
flex-direction: column;
|
| 257 |
+
}
|
| 258 |
+
|
| 259 |
+
.config-help {
|
| 260 |
+
display: block;
|
| 261 |
+
color: var(--settings-text-secondary, #888);
|
| 262 |
+
font-size: 0.8rem;
|
| 263 |
+
margin: 4px 0 0 0;
|
| 264 |
+
opacity: 0.8;
|
| 265 |
+
line-height: 1.3;
|
| 266 |
+
}
|
| 267 |
+
|
| 268 |
+
.config-note-info {
|
| 269 |
+
display: flex;
|
| 270 |
+
align-items: flex-start;
|
| 271 |
+
gap: 8px;
|
| 272 |
+
margin: 12px 0 16px 0;
|
| 273 |
+
padding: 12px;
|
| 274 |
+
background: var(--settings-bg-secondary, rgba(255, 255, 255, 0.05));
|
| 275 |
+
border: 1px solid var(--settings-border-color, rgba(255, 255, 255, 0.1));
|
| 276 |
+
border-radius: 6px;
|
| 277 |
+
border-left: 3px solid var(--accent-color, #8a2be2);
|
| 278 |
+
}
|
| 279 |
+
|
| 280 |
+
.config-note-info i {
|
| 281 |
+
color: var(--accent-color, #8a2be2);
|
| 282 |
+
margin-top: 1px;
|
| 283 |
+
font-size: 0.9rem;
|
| 284 |
+
}
|
| 285 |
+
|
| 286 |
+
/* Variantes de couleur pour différents types de notes */
|
| 287 |
+
.config-note-info.info i {
|
| 288 |
+
color: #4a9eff;
|
| 289 |
+
}
|
| 290 |
+
|
| 291 |
+
.config-note-info.tip i {
|
| 292 |
+
color: #ff9500;
|
| 293 |
+
}
|
| 294 |
+
|
| 295 |
+
.config-note-info.settings i {
|
| 296 |
+
color: #00c896;
|
| 297 |
+
}
|
| 298 |
+
|
| 299 |
+
.config-note-info .config-help {
|
| 300 |
+
margin: 0;
|
| 301 |
+
opacity: 0.9;
|
| 302 |
+
}
|
| 303 |
+
|
| 304 |
+
.presence-dot {
|
| 305 |
+
display: inline-block;
|
| 306 |
+
width: 10px;
|
| 307 |
+
height: 10px;
|
| 308 |
+
border-radius: 50%;
|
| 309 |
+
background-color: #9e9e9e;
|
| 310 |
+
}
|
| 311 |
+
|
| 312 |
+
.inline-row {
|
| 313 |
+
display: inline-flex;
|
| 314 |
+
align-items: center;
|
| 315 |
+
gap: 10px;
|
| 316 |
+
}
|
| 317 |
+
|
| 318 |
+
.config-control {
|
| 319 |
+
display: flex;
|
| 320 |
+
flex-direction: column;
|
| 321 |
+
gap: 10px;
|
| 322 |
+
flex: 1;
|
| 323 |
+
width: 100%;
|
| 324 |
+
margin-left: 0;
|
| 325 |
+
}
|
| 326 |
+
|
| 327 |
+
/* ===== CONTRÔLES PERSONNALISÉS ===== */
|
| 328 |
+
|
| 329 |
+
.slider-container {
|
| 330 |
+
width: 100%;
|
| 331 |
+
max-width: 380px;
|
| 332 |
+
position: relative;
|
| 333 |
+
display: flex;
|
| 334 |
+
flex-direction: row;
|
| 335 |
+
align-items: center;
|
| 336 |
+
gap: 12px;
|
| 337 |
+
}
|
| 338 |
+
|
| 339 |
+
.kimi-slider,
|
| 340 |
+
.kimi-slider-unified {
|
| 341 |
+
flex: 1;
|
| 342 |
+
min-width: 0;
|
| 343 |
+
width: 100%;
|
| 344 |
+
}
|
| 345 |
+
|
| 346 |
+
.slider-value {
|
| 347 |
+
background: var(--slider-value-bg);
|
| 348 |
+
color: var(--slider-value-color);
|
| 349 |
+
padding: 4px 8px;
|
| 350 |
+
border-radius: 4px;
|
| 351 |
+
font-size: 0.8rem;
|
| 352 |
+
font-weight: 500;
|
| 353 |
+
border: 1px solid var(--slider-value-border);
|
| 354 |
+
min-width: 45px;
|
| 355 |
+
text-align: center;
|
| 356 |
+
flex-shrink: 0;
|
| 357 |
+
}
|
| 358 |
+
|
| 359 |
+
.toggle-switch {
|
| 360 |
+
position: relative;
|
| 361 |
+
width: 50px;
|
| 362 |
+
height: 25px;
|
| 363 |
+
background: var(--switch-bg-inactive);
|
| 364 |
+
border-radius: 25px;
|
| 365 |
+
cursor: pointer;
|
| 366 |
+
transition: background-color 0.3s ease;
|
| 367 |
+
}
|
| 368 |
+
|
| 369 |
+
.toggle-switch.active {
|
| 370 |
+
background: var(--switch-bg-active);
|
| 371 |
+
}
|
| 372 |
+
|
| 373 |
+
.toggle-switch::after {
|
| 374 |
+
content: "";
|
| 375 |
+
position: absolute;
|
| 376 |
+
top: 2px;
|
| 377 |
+
left: 2px;
|
| 378 |
+
width: 21px;
|
| 379 |
+
height: 21px;
|
| 380 |
+
background: var(--switch-thumb-color);
|
| 381 |
+
border-radius: 50%;
|
| 382 |
+
transition: transform 0.3s ease;
|
| 383 |
+
box-shadow: var(--switch-thumb-shadow);
|
| 384 |
+
}
|
| 385 |
+
|
| 386 |
+
.toggle-switch.active::after {
|
| 387 |
+
transform: translateX(25px);
|
| 388 |
+
}
|
| 389 |
+
|
| 390 |
+
.toggle-switch#transcript-toggle {
|
| 391 |
+
background: var(--switch-bg-inactive);
|
| 392 |
+
}
|
| 393 |
+
|
| 394 |
+
.toggle-switch#transcript-toggle.active {
|
| 395 |
+
background: var(--switch-bg-active);
|
| 396 |
+
}
|
| 397 |
+
|
| 398 |
+
/* ===== INFORMATIONS ET STATS ===== */
|
| 399 |
+
|
| 400 |
+
.stats-grid {
|
| 401 |
+
display: grid;
|
| 402 |
+
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
| 403 |
+
gap: 15px;
|
| 404 |
+
margin-top: 20px;
|
| 405 |
+
}
|
| 406 |
+
|
| 407 |
+
.stat-card {
|
| 408 |
+
background: var(--card-bg);
|
| 409 |
+
border-radius: 10px;
|
| 410 |
+
padding: 10px;
|
| 411 |
+
text-align: center;
|
| 412 |
+
border: 1px solid var(--card-border);
|
| 413 |
+
}
|
| 414 |
+
|
| 415 |
+
.stat-value {
|
| 416 |
+
font-size: 1.4rem;
|
| 417 |
+
font-weight: 700;
|
| 418 |
+
color: var(--stat-value-color);
|
| 419 |
+
margin-bottom: 5px;
|
| 420 |
+
}
|
| 421 |
+
|
| 422 |
+
.stat-label {
|
| 423 |
+
color: var(--stat-label-color);
|
| 424 |
+
font-size: 0.85rem;
|
| 425 |
+
}
|
| 426 |
+
|
| 427 |
+
.model-card {
|
| 428 |
+
background: var(--card-bg);
|
| 429 |
+
border-radius: 12px;
|
| 430 |
+
padding: 20px;
|
| 431 |
+
margin-bottom: 15px;
|
| 432 |
+
border: 1px solid var(--card-border);
|
| 433 |
+
cursor: pointer;
|
| 434 |
+
transition: all 0.3s ease;
|
| 435 |
+
}
|
| 436 |
+
|
| 437 |
+
.model-card:hover {
|
| 438 |
+
background: var(--card-hover-bg);
|
| 439 |
+
transform: translateY(-2px);
|
| 440 |
+
}
|
| 441 |
+
|
| 442 |
+
.model-card.selected {
|
| 443 |
+
border-color: var(--model-card-selected-border);
|
| 444 |
+
box-shadow: var(--model-card-selected-shadow);
|
| 445 |
+
}
|
| 446 |
+
|
| 447 |
+
.models-search-container {
|
| 448 |
+
margin: 12px 0 16px;
|
| 449 |
+
}
|
| 450 |
+
|
| 451 |
+
.models-section {
|
| 452 |
+
margin: 16px 0;
|
| 453 |
+
}
|
| 454 |
+
|
| 455 |
+
.models-section-title {
|
| 456 |
+
display: flex;
|
| 457 |
+
align-items: center;
|
| 458 |
+
gap: 8px;
|
| 459 |
+
font-weight: 600;
|
| 460 |
+
color: var(--settings-section-header-color);
|
| 461 |
+
margin: 6px 0 10px;
|
| 462 |
+
}
|
| 463 |
+
|
| 464 |
+
.model-header {
|
| 465 |
+
display: flex;
|
| 466 |
+
justify-content: space-between;
|
| 467 |
+
align-items: center;
|
| 468 |
+
margin-bottom: 10px;
|
| 469 |
+
}
|
| 470 |
+
|
| 471 |
+
.model-name {
|
| 472 |
+
font-size: 1.1rem;
|
| 473 |
+
font-weight: 600;
|
| 474 |
+
color: var(--model-name-color);
|
| 475 |
+
}
|
| 476 |
+
|
| 477 |
+
.model-provider {
|
| 478 |
+
background: var(--model-provider-color);
|
| 479 |
+
color: var(--model-provider-text);
|
| 480 |
+
padding: 4px 8px;
|
| 481 |
+
border-radius: 6px;
|
| 482 |
+
font-size: 0.75rem;
|
| 483 |
+
font-weight: 500;
|
| 484 |
+
}
|
| 485 |
+
|
| 486 |
+
.model-description {
|
| 487 |
+
color: var(--model-description-color);
|
| 488 |
+
font-size: 0.9rem;
|
| 489 |
+
margin-bottom: 10px;
|
| 490 |
+
}
|
| 491 |
+
|
| 492 |
+
.strength-tag {
|
| 493 |
+
display: inline-block;
|
| 494 |
+
background: var(--strength-tag-bg);
|
| 495 |
+
color: var(--strength-tag-text);
|
| 496 |
+
padding: 4px 8px;
|
| 497 |
+
border-radius: 12px;
|
| 498 |
+
font-size: 0.75rem;
|
| 499 |
+
font-weight: 500;
|
| 500 |
+
margin-right: 8px;
|
| 501 |
+
margin-bottom: 5px;
|
| 502 |
+
}
|
| 503 |
+
|
| 504 |
+
/* Models error and no-models messages */
|
| 505 |
+
.no-models-message,
|
| 506 |
+
.models-error-message {
|
| 507 |
+
text-align: center;
|
| 508 |
+
padding: 40px 20px;
|
| 509 |
+
border-radius: 12px;
|
| 510 |
+
margin: 20px 0;
|
| 511 |
+
background: var(--background-secondary);
|
| 512 |
+
border: 1px solid var(--border-color);
|
| 513 |
+
}
|
| 514 |
+
|
| 515 |
+
.no-models-message p,
|
| 516 |
+
.models-error-message p {
|
| 517 |
+
margin: 0;
|
| 518 |
+
color: var(--text-secondary);
|
| 519 |
+
font-size: 0.95rem;
|
| 520 |
+
line-height: 1.5;
|
| 521 |
+
}
|
| 522 |
+
|
| 523 |
+
.models-error-message {
|
| 524 |
+
background: rgba(231, 76, 60, 0.1);
|
| 525 |
+
border-color: rgba(231, 76, 60, 0.3);
|
| 526 |
+
}
|
| 527 |
+
|
| 528 |
+
.models-error-message p {
|
| 529 |
+
color: #e74c3c;
|
| 530 |
+
}
|
| 531 |
+
|
| 532 |
+
.model-strengths {
|
| 533 |
+
display: flex;
|
| 534 |
+
flex-wrap: wrap;
|
| 535 |
+
gap: 6px;
|
| 536 |
+
}
|
| 537 |
+
|
| 538 |
+
.strength-tag {
|
| 539 |
+
background: var(--model-strength-color);
|
| 540 |
+
color: var(--model-strength-text);
|
| 541 |
+
padding: 3px 8px;
|
| 542 |
+
border-radius: 4px;
|
| 543 |
+
font-size: 0.75rem;
|
| 544 |
+
font-weight: 500;
|
| 545 |
+
}
|
| 546 |
+
|
| 547 |
+
/* ===== PLUGIN CARDS AND SWITCHES ===== */
|
| 548 |
+
|
| 549 |
+
.plugin-card {
|
| 550 |
+
background: linear-gradient(135deg, #22121a 80%, var(--modal-bg) 100%);
|
| 551 |
+
border: 2px solid var(--modal-border);
|
| 552 |
+
border-radius: 20px;
|
| 553 |
+
box-shadow:
|
| 554 |
+
0 4px 24px 0 #000a,
|
| 555 |
+
0 2px 0 0 var(--modal-border);
|
| 556 |
+
padding: 28px 32px 24px 32px;
|
| 557 |
+
margin-bottom: 28px;
|
| 558 |
+
display: flex;
|
| 559 |
+
flex-direction: row;
|
| 560 |
+
align-items: center;
|
| 561 |
+
gap: 0;
|
| 562 |
+
justify-content: space-between;
|
| 563 |
+
transition:
|
| 564 |
+
box-shadow 0.2s,
|
| 565 |
+
border 0.2s;
|
| 566 |
+
position: relative;
|
| 567 |
+
}
|
| 568 |
+
|
| 569 |
+
.plugin-card .plugin-info {
|
| 570 |
+
flex: 2 1 0;
|
| 571 |
+
min-width: 0;
|
| 572 |
+
margin-right: 24px;
|
| 573 |
+
}
|
| 574 |
+
|
| 575 |
+
.plugin-card-center {
|
| 576 |
+
flex: 1 1 0;
|
| 577 |
+
display: flex;
|
| 578 |
+
flex-direction: column;
|
| 579 |
+
align-items: center;
|
| 580 |
+
justify-content: center;
|
| 581 |
+
gap: 10px;
|
| 582 |
+
min-width: 120px;
|
| 583 |
+
}
|
| 584 |
+
|
| 585 |
+
.plugin-card-switch {
|
| 586 |
+
flex: 0 0 auto;
|
| 587 |
+
display: flex;
|
| 588 |
+
align-items: center;
|
| 589 |
+
justify-content: flex-end;
|
| 590 |
+
min-width: 80px;
|
| 591 |
+
margin-left: 24px;
|
| 592 |
+
}
|
| 593 |
+
|
| 594 |
+
.plugin-card .plugin-title {
|
| 595 |
+
font-size: 1.35rem;
|
| 596 |
+
font-weight: 700;
|
| 597 |
+
color: var(--plugin-card-title-color);
|
| 598 |
+
margin-bottom: 6px;
|
| 599 |
+
letter-spacing: 0.01em;
|
| 600 |
+
text-shadow: 0 2px 8px #000b;
|
| 601 |
+
}
|
| 602 |
+
|
| 603 |
+
.plugin-card .plugin-type {
|
| 604 |
+
font-size: 1rem;
|
| 605 |
+
color: var(--accent-color);
|
| 606 |
+
margin-left: 10px;
|
| 607 |
+
font-weight: 600;
|
| 608 |
+
text-shadow: 0 1px 4px #0008;
|
| 609 |
+
}
|
| 610 |
+
|
| 611 |
+
.plugin-card .plugin-desc {
|
| 612 |
+
color: var(--plugin-card-desc-color);
|
| 613 |
+
margin-bottom: 10px;
|
| 614 |
+
font-size: 1.01rem;
|
| 615 |
+
line-height: 1.5;
|
| 616 |
+
}
|
| 617 |
+
|
| 618 |
+
.plugin-card .plugin-author {
|
| 619 |
+
font-size: 0.95rem;
|
| 620 |
+
color: var(--plugin-card-author-color);
|
| 621 |
+
font-weight: 500;
|
| 622 |
+
}
|
| 623 |
+
|
| 624 |
+
.plugin-card-left {
|
| 625 |
+
display: flex;
|
| 626 |
+
flex-direction: column;
|
| 627 |
+
align-items: flex-start;
|
| 628 |
+
min-width: 90px;
|
| 629 |
+
gap: 8px;
|
| 630 |
+
margin-top: 4px;
|
| 631 |
+
}
|
| 632 |
+
|
| 633 |
+
.plugin-card-right {
|
| 634 |
+
display: flex;
|
| 635 |
+
flex-direction: column;
|
| 636 |
+
flex: 1;
|
| 637 |
+
gap: 12px;
|
| 638 |
+
}
|
| 639 |
+
|
| 640 |
+
.plugin-type-badge {
|
| 641 |
+
display: inline-block;
|
| 642 |
+
background: var(--plugin-type-badge-bg);
|
| 643 |
+
color: #fff;
|
| 644 |
+
font-size: 0.85rem;
|
| 645 |
+
font-weight: 600;
|
| 646 |
+
border-radius: 8px;
|
| 647 |
+
padding: 3px 10px;
|
| 648 |
+
margin-bottom: 8px;
|
| 649 |
+
margin-right: 12px;
|
| 650 |
+
letter-spacing: 0.03em;
|
| 651 |
+
box-shadow: 0 1px 4px #0002;
|
| 652 |
+
}
|
| 653 |
+
|
| 654 |
+
.plugin-theme-swatch {
|
| 655 |
+
display: inline-flex;
|
| 656 |
+
gap: 4px;
|
| 657 |
+
margin-bottom: 8px;
|
| 658 |
+
margin-right: 12px;
|
| 659 |
+
vertical-align: middle;
|
| 660 |
+
}
|
| 661 |
+
|
| 662 |
+
.plugin-theme-swatch span {
|
| 663 |
+
display: inline-block;
|
| 664 |
+
width: 18px;
|
| 665 |
+
height: 18px;
|
| 666 |
+
border-radius: 50%;
|
| 667 |
+
border: 2px solid #fff2;
|
| 668 |
+
box-shadow: 0 1px 4px #0002;
|
| 669 |
+
}
|
| 670 |
+
|
| 671 |
+
.plugin-active-badge {
|
| 672 |
+
display: inline-block;
|
| 673 |
+
background: var(--plugin-active-badge-bg);
|
| 674 |
+
color: #fff;
|
| 675 |
+
font-size: 0.85rem;
|
| 676 |
+
font-weight: 700;
|
| 677 |
+
border-radius: 8px;
|
| 678 |
+
padding: 3px 12px;
|
| 679 |
+
margin-bottom: 8px;
|
| 680 |
+
margin-right: 12px;
|
| 681 |
+
letter-spacing: 0.03em;
|
| 682 |
+
box-shadow: 0 1px 8px #0003;
|
| 683 |
+
}
|
| 684 |
+
|
| 685 |
+
@media (max-width: 700px) {
|
| 686 |
+
.plugin-card {
|
| 687 |
+
flex-direction: column;
|
| 688 |
+
gap: 12px;
|
| 689 |
+
}
|
| 690 |
+
.plugin-card-left {
|
| 691 |
+
flex-direction: row;
|
| 692 |
+
align-items: center;
|
| 693 |
+
min-width: 0;
|
| 694 |
+
gap: 10px;
|
| 695 |
+
margin-top: 0;
|
| 696 |
+
}
|
| 697 |
+
.plugin-card-right {
|
| 698 |
+
gap: 8px;
|
| 699 |
+
}
|
| 700 |
+
}
|
| 701 |
+
|
| 702 |
+
/* ===== CONSOLIDATED RESPONSIVE DESIGN ===== */
|
| 703 |
+
@media (max-width: 768px) {
|
| 704 |
+
/* Settings panel responsive styles */
|
| 705 |
+
|
| 706 |
+
.settings-header {
|
| 707 |
+
padding: 20px;
|
| 708 |
+
}
|
| 709 |
+
|
| 710 |
+
.settings-title {
|
| 711 |
+
font-size: 1.3rem;
|
| 712 |
+
}
|
| 713 |
+
|
| 714 |
+
.settings-tabs {
|
| 715 |
+
padding: 0 10px;
|
| 716 |
+
gap: 5px;
|
| 717 |
+
position: relative;
|
| 718 |
+
}
|
| 719 |
+
|
| 720 |
+
.settings-tab {
|
| 721 |
+
flex: 0 0 auto;
|
| 722 |
+
min-width: 110px;
|
| 723 |
+
padding: 12px 16px;
|
| 724 |
+
font-size: 0.85rem;
|
| 725 |
+
border-radius: 8px 8px 0 0;
|
| 726 |
+
}
|
| 727 |
+
|
| 728 |
+
.settings-tab.active::after {
|
| 729 |
+
height: 2px;
|
| 730 |
+
}
|
| 731 |
+
|
| 732 |
+
.tab-content {
|
| 733 |
+
padding: 20px;
|
| 734 |
+
}
|
| 735 |
+
|
| 736 |
+
.config-row {
|
| 737 |
+
flex-direction: column;
|
| 738 |
+
align-items: flex-start;
|
| 739 |
+
gap: 10px;
|
| 740 |
+
}
|
| 741 |
+
|
| 742 |
+
.config-control {
|
| 743 |
+
margin-left: 0;
|
| 744 |
+
width: 100%;
|
| 745 |
+
}
|
| 746 |
+
|
| 747 |
+
.slider-container {
|
| 748 |
+
width: 100%;
|
| 749 |
+
max-width: 100%;
|
| 750 |
+
flex-direction: row;
|
| 751 |
+
justify-content: space-between;
|
| 752 |
+
align-items: center;
|
| 753 |
+
gap: 10px;
|
| 754 |
+
}
|
| 755 |
+
|
| 756 |
+
.slider-value {
|
| 757 |
+
min-width: 45px;
|
| 758 |
+
flex-shrink: 0;
|
| 759 |
+
}
|
| 760 |
+
|
| 761 |
+
.stats-grid {
|
| 762 |
+
grid-template-columns: repeat(2, 1fr);
|
| 763 |
+
}
|
| 764 |
+
}
|
| 765 |
+
|
| 766 |
+
@media (max-width: 480px) {
|
| 767 |
+
/* Settings panel responsive styles consolidated */
|
| 768 |
+
|
| 769 |
+
.config-row {
|
| 770 |
+
flex-direction: column;
|
| 771 |
+
align-items: stretch;
|
| 772 |
+
gap: 8px;
|
| 773 |
+
}
|
| 774 |
+
|
| 775 |
+
.config-control {
|
| 776 |
+
margin-left: 0;
|
| 777 |
+
width: 100%;
|
| 778 |
+
}
|
| 779 |
+
|
| 780 |
+
.slider-container {
|
| 781 |
+
width: 100%;
|
| 782 |
+
max-width: 100%;
|
| 783 |
+
flex-direction: row;
|
| 784 |
+
justify-content: space-between;
|
| 785 |
+
align-items: center;
|
| 786 |
+
gap: 8px;
|
| 787 |
+
}
|
| 788 |
+
|
| 789 |
+
.kimi-slider,
|
| 790 |
+
.kimi-slider-unified {
|
| 791 |
+
flex: 1;
|
| 792 |
+
min-width: 0;
|
| 793 |
+
}
|
| 794 |
+
|
| 795 |
+
.slider-value {
|
| 796 |
+
min-width: 40px;
|
| 797 |
+
max-width: 50px;
|
| 798 |
+
flex-shrink: 0;
|
| 799 |
+
font-size: 0.75rem;
|
| 800 |
+
}
|
| 801 |
+
|
| 802 |
+
.kimi-select,
|
| 803 |
+
.kimi-select-unified {
|
| 804 |
+
width: 100%;
|
| 805 |
+
max-width: 100%;
|
| 806 |
+
min-width: 0;
|
| 807 |
+
font-size: 0.9rem;
|
| 808 |
+
padding: 10px 35px 10px 12px;
|
| 809 |
+
}
|
| 810 |
+
|
| 811 |
+
.kimi-input,
|
| 812 |
+
.kimi-input-unified {
|
| 813 |
+
width: 100%;
|
| 814 |
+
max-width: 100%;
|
| 815 |
+
min-width: 0;
|
| 816 |
+
font-size: 0.9rem;
|
| 817 |
+
}
|
| 818 |
+
|
| 819 |
+
.settings-tab {
|
| 820 |
+
min-width: 90px;
|
| 821 |
+
font-size: 0.8rem;
|
| 822 |
+
padding: 10px 12px;
|
| 823 |
+
}
|
| 824 |
+
|
| 825 |
+
.config-section {
|
| 826 |
+
padding: 15px;
|
| 827 |
+
}
|
| 828 |
+
|
| 829 |
+
.config-section h3 {
|
| 830 |
+
font-size: 1.1rem;
|
| 831 |
+
}
|
| 832 |
+
|
| 833 |
+
.stats-grid {
|
| 834 |
+
grid-template-columns: repeat(2, 1fr);
|
| 835 |
+
gap: 10px;
|
| 836 |
+
}
|
| 837 |
+
|
| 838 |
+
.stat-card {
|
| 839 |
+
padding: 10px;
|
| 840 |
+
}
|
| 841 |
+
|
| 842 |
+
.stat-value {
|
| 843 |
+
font-size: 1.3rem;
|
| 844 |
+
}
|
| 845 |
+
}
|
| 846 |
+
|
| 847 |
+
@media (max-width: 360px) {
|
| 848 |
+
/* Settings panel responsive styles consolidated */
|
| 849 |
+
|
| 850 |
+
.slider-container {
|
| 851 |
+
gap: 5px;
|
| 852 |
+
}
|
| 853 |
+
|
| 854 |
+
.slider-value {
|
| 855 |
+
min-width: 35px;
|
| 856 |
+
max-width: 40px;
|
| 857 |
+
font-size: 0.7rem;
|
| 858 |
+
padding: 2px 6px;
|
| 859 |
+
}
|
| 860 |
+
|
| 861 |
+
.kimi-select,
|
| 862 |
+
.kimi-select-unified {
|
| 863 |
+
font-size: 0.85rem;
|
| 864 |
+
padding: 8px 30px 8px 10px;
|
| 865 |
+
}
|
| 866 |
+
|
| 867 |
+
.settings-tab {
|
| 868 |
+
min-width: 80px;
|
| 869 |
+
font-size: 0.75rem;
|
| 870 |
+
padding: 8px 10px;
|
| 871 |
+
}
|
| 872 |
+
|
| 873 |
+
.stats-grid {
|
| 874 |
+
grid-template-columns: 1fr;
|
| 875 |
+
}
|
| 876 |
+
}
|
| 877 |
+
|
| 878 |
+
/* ===== HELP MODAL ===== */
|
| 879 |
+
.help-overlay {
|
| 880 |
+
position: fixed;
|
| 881 |
+
top: 0;
|
| 882 |
+
left: 0;
|
| 883 |
+
width: 100%;
|
| 884 |
+
height: 100%;
|
| 885 |
+
background: var(--modal-overlay-bg);
|
| 886 |
+
backdrop-filter: blur(15px);
|
| 887 |
+
-webkit-backdrop-filter: blur(15px);
|
| 888 |
+
z-index: 60;
|
| 889 |
+
display: none;
|
| 890 |
+
opacity: 0;
|
| 891 |
+
transition:
|
| 892 |
+
opacity 0.3s ease,
|
| 893 |
+
backdrop-filter 0.3s ease;
|
| 894 |
+
}
|
| 895 |
+
|
| 896 |
+
.help-overlay.visible {
|
| 897 |
+
display: flex;
|
| 898 |
+
opacity: 1;
|
| 899 |
+
align-items: center;
|
| 900 |
+
justify-content: center;
|
| 901 |
+
padding: 10px;
|
| 902 |
+
}
|
| 903 |
+
|
| 904 |
+
.help-modal {
|
| 905 |
+
background: var(--help-modal-bg, linear-gradient(145deg, rgba(26, 26, 26, 0.95), rgba(40, 40, 40, 0.95)));
|
| 906 |
+
backdrop-filter: blur(20px);
|
| 907 |
+
border-radius: 25px;
|
| 908 |
+
border: 1px solid var(--help-modal-border, rgba(255, 255, 255, 0.1));
|
| 909 |
+
box-shadow:
|
| 910 |
+
0 20px 60px rgba(0, 0, 0, 0.5),
|
| 911 |
+
0 0 30px var(--primary-color, rgba(255, 107, 157, 0.3));
|
| 912 |
+
width: 100%;
|
| 913 |
+
max-width: 850px;
|
| 914 |
+
max-height: 85vh;
|
| 915 |
+
overflow: hidden;
|
| 916 |
+
display: flex;
|
| 917 |
+
flex-direction: column;
|
| 918 |
+
animation: slideInUp 0.4s ease-out;
|
| 919 |
+
margin: 20px;
|
| 920 |
+
}
|
| 921 |
+
|
| 922 |
+
.help-header {
|
| 923 |
+
background: var(--modal-header-bg, linear-gradient(135deg, var(--primary-color, #ff9a9e), var(--secondary-color, #fecfef)));
|
| 924 |
+
padding: 25px 30px;
|
| 925 |
+
display: flex;
|
| 926 |
+
justify-content: space-between;
|
| 927 |
+
align-items: center;
|
| 928 |
+
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
| 929 |
+
}
|
| 930 |
+
|
| 931 |
+
.help-title {
|
| 932 |
+
margin: 0;
|
| 933 |
+
color: var(--modal-title-color);
|
| 934 |
+
font-size: 1.5rem;
|
| 935 |
+
font-weight: 700;
|
| 936 |
+
display: flex;
|
| 937 |
+
align-items: center;
|
| 938 |
+
gap: 10px;
|
| 939 |
+
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
|
| 940 |
+
}
|
| 941 |
+
|
| 942 |
+
.help-close {
|
| 943 |
+
background: none;
|
| 944 |
+
border: none;
|
| 945 |
+
color: var(--modal-text);
|
| 946 |
+
font-size: 1.5rem;
|
| 947 |
+
cursor: pointer;
|
| 948 |
+
padding: 8px;
|
| 949 |
+
border-radius: 50%;
|
| 950 |
+
transition: all 0.3s ease;
|
| 951 |
+
backdrop-filter: blur(10px);
|
| 952 |
+
}
|
| 953 |
+
|
| 954 |
+
.help-close:hover {
|
| 955 |
+
background-color: var(--modal-close-hover-bg);
|
| 956 |
+
transform: scale(1.1);
|
| 957 |
+
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
|
| 958 |
+
}
|
| 959 |
+
|
| 960 |
+
.help-content {
|
| 961 |
+
flex: 1;
|
| 962 |
+
padding: 25px 30px;
|
| 963 |
+
overflow-y: auto;
|
| 964 |
+
color: var(--help-content-color);
|
| 965 |
+
}
|
| 966 |
+
|
| 967 |
+
/* Help Content Components */
|
| 968 |
+
.help-section {
|
| 969 |
+
margin-bottom: 30px;
|
| 970 |
+
padding-bottom: 20px;
|
| 971 |
+
border-bottom: 1px solid var(--help-section-border);
|
| 972 |
+
}
|
| 973 |
+
|
| 974 |
+
.help-section:last-child {
|
| 975 |
+
border-bottom: none;
|
| 976 |
+
}
|
| 977 |
+
|
| 978 |
+
.help-section h3 {
|
| 979 |
+
color: var(--modal-title-color);
|
| 980 |
+
font-size: 1.3rem;
|
| 981 |
+
margin-bottom: 15px;
|
| 982 |
+
display: flex;
|
| 983 |
+
align-items: center;
|
| 984 |
+
gap: 10px;
|
| 985 |
+
}
|
| 986 |
+
|
| 987 |
+
.creators-info {
|
| 988 |
+
display: flex;
|
| 989 |
+
gap: 20px;
|
| 990 |
+
margin-bottom: 20px;
|
| 991 |
+
flex-wrap: wrap;
|
| 992 |
+
}
|
| 993 |
+
|
| 994 |
+
.creator-card {
|
| 995 |
+
background: var(--creator-card-bg);
|
| 996 |
+
border: 1px solid var(--creator-card-border);
|
| 997 |
+
padding: 20px;
|
| 998 |
+
border-radius: 15px;
|
| 999 |
+
flex: 1;
|
| 1000 |
+
min-width: 280px;
|
| 1001 |
+
display: flex;
|
| 1002 |
+
align-items: center;
|
| 1003 |
+
gap: 15px;
|
| 1004 |
+
}
|
| 1005 |
+
|
| 1006 |
+
.creator-avatar {
|
| 1007 |
+
font-size: 2.5rem;
|
| 1008 |
+
width: 60px;
|
| 1009 |
+
height: 60px;
|
| 1010 |
+
display: flex;
|
| 1011 |
+
align-items: center;
|
| 1012 |
+
justify-content: center;
|
| 1013 |
+
border-radius: 50%;
|
| 1014 |
+
background: var(--creator-avatar-bg);
|
| 1015 |
+
}
|
| 1016 |
+
|
| 1017 |
+
.creator-details h4 {
|
| 1018 |
+
margin: 0 0 5px 0;
|
| 1019 |
+
color: #1650a0;
|
| 1020 |
+
font-size: 1.2rem;
|
| 1021 |
+
}
|
| 1022 |
+
|
| 1023 |
+
.creator-details p {
|
| 1024 |
+
margin: 0 0 8px 0;
|
| 1025 |
+
color: var(--feature-text-color);
|
| 1026 |
+
font-size: 0.9rem;
|
| 1027 |
+
}
|
| 1028 |
+
|
| 1029 |
+
.creator-role {
|
| 1030 |
+
background: var(--creator-role-bg);
|
| 1031 |
+
color: var(--creator-role-color);
|
| 1032 |
+
padding: 4px 12px;
|
| 1033 |
+
border-radius: 12px;
|
| 1034 |
+
font-size: 0.8rem;
|
| 1035 |
+
font-weight: 500;
|
| 1036 |
+
}
|
| 1037 |
+
|
| 1038 |
+
/* Creator Links */
|
| 1039 |
+
.creator-links {
|
| 1040 |
+
display: flex;
|
| 1041 |
+
gap: 8px;
|
| 1042 |
+
margin-top: 12px;
|
| 1043 |
+
}
|
| 1044 |
+
|
| 1045 |
+
.creator-link {
|
| 1046 |
+
background: var(--creator-role-bg, linear-gradient(135deg, var(--primary-color), var(--secondary-color)));
|
| 1047 |
+
color: var(--creator-role-color, var(--text-on-primary));
|
| 1048 |
+
padding: 8px 12px;
|
| 1049 |
+
border-radius: 20px;
|
| 1050 |
+
text-decoration: none;
|
| 1051 |
+
display: flex;
|
| 1052 |
+
align-items: center;
|
| 1053 |
+
justify-content: center;
|
| 1054 |
+
gap: 6px;
|
| 1055 |
+
min-width: auto;
|
| 1056 |
+
height: 36px;
|
| 1057 |
+
transition: all 0.3s ease;
|
| 1058 |
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
| 1059 |
+
font-size: 0.8rem;
|
| 1060 |
+
font-weight: 500;
|
| 1061 |
+
border: 1px solid transparent;
|
| 1062 |
+
}
|
| 1063 |
+
|
| 1064 |
+
.creator-link:hover {
|
| 1065 |
+
transform: translateY(-2px);
|
| 1066 |
+
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
|
| 1067 |
+
filter: brightness(1.1);
|
| 1068 |
+
border-color: var(--primary-color, #ff6b9d);
|
| 1069 |
+
}
|
| 1070 |
+
|
| 1071 |
+
/* Increased specificity to avoid !important */
|
| 1072 |
+
.help-modal .creator-link:hover {
|
| 1073 |
+
color: var(--creator-role-color, var(--text-on-primary));
|
| 1074 |
+
text-decoration: none;
|
| 1075 |
+
background: var(--creator-role-bg, linear-gradient(135deg, var(--primary-color), var(--secondary-color)));
|
| 1076 |
+
}
|
| 1077 |
+
|
| 1078 |
+
.help-modal .creator-link:hover i,
|
| 1079 |
+
.help-modal .creator-link:hover span {
|
| 1080 |
+
color: inherit;
|
| 1081 |
+
}
|
| 1082 |
+
|
| 1083 |
+
.creator-link i {
|
| 1084 |
+
font-size: 1rem;
|
| 1085 |
+
color: inherit;
|
| 1086 |
+
transition: color 0.3s ease;
|
| 1087 |
+
}
|
| 1088 |
+
|
| 1089 |
+
.creator-link span {
|
| 1090 |
+
color: inherit;
|
| 1091 |
+
transition: color 0.3s ease;
|
| 1092 |
+
}
|
| 1093 |
+
|
| 1094 |
+
.creator-link:hover i,
|
| 1095 |
+
.creator-link:hover span {
|
| 1096 |
+
color: inherit !important;
|
| 1097 |
+
}
|
| 1098 |
+
|
| 1099 |
+
.philosophy {
|
| 1100 |
+
background: var(--philosophy-bg);
|
| 1101 |
+
border: 1px solid var(--philosophy-border);
|
| 1102 |
+
padding: 15px;
|
| 1103 |
+
border-radius: 10px;
|
| 1104 |
+
border-left: 4px solid var(--philosophy-border-left);
|
| 1105 |
+
margin: 15px 0;
|
| 1106 |
+
font-style: italic;
|
| 1107 |
+
color: var(--feature-text-color);
|
| 1108 |
+
}
|
| 1109 |
+
|
| 1110 |
+
.features-grid {
|
| 1111 |
+
display: grid;
|
| 1112 |
+
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
| 1113 |
+
gap: 15px;
|
| 1114 |
+
margin: 15px 0;
|
| 1115 |
+
}
|
| 1116 |
+
|
| 1117 |
+
.feature-item {
|
| 1118 |
+
background: var(--feature-item-bg);
|
| 1119 |
+
border: 1px solid var(--feature-item-border);
|
| 1120 |
+
padding: 15px;
|
| 1121 |
+
border-radius: 10px;
|
| 1122 |
+
text-align: center;
|
| 1123 |
+
}
|
| 1124 |
+
|
| 1125 |
+
.feature-item i {
|
| 1126 |
+
color: var(--feature-icon-color);
|
| 1127 |
+
font-size: 2rem;
|
| 1128 |
+
margin-bottom: 10px;
|
| 1129 |
+
}
|
| 1130 |
+
|
| 1131 |
+
.feature-item h4 {
|
| 1132 |
+
margin: 0 0 8px 0;
|
| 1133 |
+
color: var(--feature-title-color);
|
| 1134 |
+
}
|
| 1135 |
+
|
| 1136 |
+
.feature-item p {
|
| 1137 |
+
margin: 0;
|
| 1138 |
+
font-size: 0.9rem;
|
| 1139 |
+
color: var(--feature-text-color);
|
| 1140 |
+
}
|
| 1141 |
+
|
| 1142 |
+
.quick-guide {
|
| 1143 |
+
display: flex;
|
| 1144 |
+
flex-wrap: wrap;
|
| 1145 |
+
gap: 20px;
|
| 1146 |
+
margin-bottom: 20px;
|
| 1147 |
+
}
|
| 1148 |
+
|
| 1149 |
+
.guide-step {
|
| 1150 |
+
display: flex;
|
| 1151 |
+
align-items: flex-start;
|
| 1152 |
+
background: rgba(255, 255, 255, 0.05);
|
| 1153 |
+
border-radius: 12px;
|
| 1154 |
+
padding: 15px 20px;
|
| 1155 |
+
margin-bottom: 12px;
|
| 1156 |
+
min-width: 220px;
|
| 1157 |
+
flex: 1 1 220px;
|
| 1158 |
+
}
|
| 1159 |
+
|
| 1160 |
+
.step-number {
|
| 1161 |
+
font-size: 1.5rem;
|
| 1162 |
+
font-weight: bold;
|
| 1163 |
+
color: var(--guide-step-number-color);
|
| 1164 |
+
margin-right: 16px;
|
| 1165 |
+
background: var(--guide-step-number-bg);
|
| 1166 |
+
border-radius: 50%;
|
| 1167 |
+
width: 36px;
|
| 1168 |
+
height: 36px;
|
| 1169 |
+
display: flex;
|
| 1170 |
+
align-items: center;
|
| 1171 |
+
justify-content: center;
|
| 1172 |
+
}
|
| 1173 |
+
|
| 1174 |
+
.step-content h4 {
|
| 1175 |
+
font-size: 1.1rem;
|
| 1176 |
+
margin-bottom: 6px;
|
| 1177 |
+
color: var(--modal-title-color);
|
| 1178 |
+
}
|
| 1179 |
+
|
| 1180 |
+
.step-content p {
|
| 1181 |
+
font-size: 0.98rem;
|
| 1182 |
+
color: var(--help-content-color);
|
| 1183 |
+
}
|
| 1184 |
+
|
| 1185 |
+
.tips-list {
|
| 1186 |
+
display: flex;
|
| 1187 |
+
flex-wrap: wrap;
|
| 1188 |
+
gap: 18px;
|
| 1189 |
+
margin-bottom: 18px;
|
| 1190 |
+
}
|
| 1191 |
+
|
| 1192 |
+
.tip-item {
|
| 1193 |
+
display: flex;
|
| 1194 |
+
align-items: center;
|
| 1195 |
+
background: var(--feature-item-bg);
|
| 1196 |
+
border-radius: 12px;
|
| 1197 |
+
padding: 12px 18px;
|
| 1198 |
+
min-width: 200px;
|
| 1199 |
+
flex: 1 1 200px;
|
| 1200 |
+
}
|
| 1201 |
+
|
| 1202 |
+
.tip-item i {
|
| 1203 |
+
font-size: 1.2rem;
|
| 1204 |
+
color: var(--feature-icon-color);
|
| 1205 |
+
margin-right: 12px;
|
| 1206 |
+
}
|
| 1207 |
+
|
| 1208 |
+
.tip-item p {
|
| 1209 |
+
font-size: 0.98rem;
|
| 1210 |
+
color: var(--help-content-color);
|
| 1211 |
+
}
|
| 1212 |
+
|
| 1213 |
+
.tech-info {
|
| 1214 |
+
background: var(--feature-item-bg);
|
| 1215 |
+
border-radius: 10px;
|
| 1216 |
+
padding: 14px 20px;
|
| 1217 |
+
margin-top: 10px;
|
| 1218 |
+
color: var(--help-content-color);
|
| 1219 |
+
font-size: 0.98rem;
|
| 1220 |
+
}
|
| 1221 |
+
|
| 1222 |
+
.settings-panel,
|
| 1223 |
+
.config-section {
|
| 1224 |
+
isolation: isolate;
|
| 1225 |
+
}
|
| 1226 |
+
|
| 1227 |
+
/* Videos background */
|
| 1228 |
+
.video-container {
|
| 1229 |
+
z-index: 0;
|
| 1230 |
+
}
|
| 1231 |
+
|
| 1232 |
+
.bg-video {
|
| 1233 |
+
z-index: 1;
|
| 1234 |
+
}
|
| 1235 |
+
|
| 1236 |
+
.content-overlay {
|
| 1237 |
+
z-index: 2;
|
| 1238 |
+
background-color: transparent;
|
| 1239 |
+
backdrop-filter: none;
|
| 1240 |
+
}
|
| 1241 |
+
|
| 1242 |
+
/* Responsive for help modal */
|
| 1243 |
+
@media (max-width: 768px) {
|
| 1244 |
+
.help-overlay {
|
| 1245 |
+
padding: 5px;
|
| 1246 |
+
}
|
| 1247 |
+
|
| 1248 |
+
.help-modal {
|
| 1249 |
+
margin: 5px;
|
| 1250 |
+
max-height: 95vh;
|
| 1251 |
+
border-radius: 15px;
|
| 1252 |
+
width: calc(100% - 10px);
|
| 1253 |
+
}
|
| 1254 |
+
|
| 1255 |
+
.help-header {
|
| 1256 |
+
padding: 20px;
|
| 1257 |
+
}
|
| 1258 |
+
|
| 1259 |
+
.help-title {
|
| 1260 |
+
font-size: 1.3rem;
|
| 1261 |
+
}
|
| 1262 |
+
|
| 1263 |
+
.help-content {
|
| 1264 |
+
padding: 20px;
|
| 1265 |
+
}
|
| 1266 |
+
|
| 1267 |
+
.creators-info {
|
| 1268 |
+
flex-direction: column;
|
| 1269 |
+
}
|
| 1270 |
+
|
| 1271 |
+
.creator-card {
|
| 1272 |
+
min-width: auto;
|
| 1273 |
+
}
|
| 1274 |
+
|
| 1275 |
+
.features-grid {
|
| 1276 |
+
grid-template-columns: 1fr;
|
| 1277 |
+
}
|
| 1278 |
+
}
|
| 1279 |
+
|
| 1280 |
+
/* ===== BUTTON ANIMATIONS ===== */
|
| 1281 |
+
.kimi-button.animated {
|
| 1282 |
+
animation: kimiPulse 0.7s;
|
| 1283 |
+
}
|
| 1284 |
+
|
| 1285 |
+
@keyframes kimiPulse {
|
| 1286 |
+
0% {
|
| 1287 |
+
background-color: var(--primary-color);
|
| 1288 |
+
transform: scale(1);
|
| 1289 |
+
}
|
| 1290 |
+
50% {
|
| 1291 |
+
background-color: var(--accent-color);
|
| 1292 |
+
transform: scale(1.08);
|
| 1293 |
+
}
|
| 1294 |
+
100% {
|
| 1295 |
+
background-color: var(--primary-color);
|
| 1296 |
+
transform: scale(1);
|
| 1297 |
+
}
|
| 1298 |
+
}
|
| 1299 |
+
|
| 1300 |
+
/* ===== CHARACTER GRID AND CARDS ===== */
|
| 1301 |
+
.character-grid {
|
| 1302 |
+
display: grid;
|
| 1303 |
+
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
| 1304 |
+
gap: 24px;
|
| 1305 |
+
margin-top: 16px;
|
| 1306 |
+
margin-bottom: 16px;
|
| 1307 |
+
}
|
| 1308 |
+
|
| 1309 |
+
@media (max-width: 700px) {
|
| 1310 |
+
.character-grid {
|
| 1311 |
+
grid-template-columns: 1fr;
|
| 1312 |
+
}
|
| 1313 |
+
}
|
| 1314 |
+
|
| 1315 |
+
.character-card {
|
| 1316 |
+
background: var(--card-bg);
|
| 1317 |
+
border: 2px solid transparent;
|
| 1318 |
+
border-radius: 18px;
|
| 1319 |
+
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
|
| 1320 |
+
padding: 18px 16px 12px 16px;
|
| 1321 |
+
display: flex;
|
| 1322 |
+
flex-direction: column;
|
| 1323 |
+
align-items: center;
|
| 1324 |
+
cursor: pointer;
|
| 1325 |
+
transition:
|
| 1326 |
+
border 0.2s,
|
| 1327 |
+
box-shadow 0.2s,
|
| 1328 |
+
background 0.2s;
|
| 1329 |
+
user-select: none;
|
| 1330 |
+
width: 100%;
|
| 1331 |
+
}
|
| 1332 |
+
|
| 1333 |
+
.character-card.selected {
|
| 1334 |
+
border: 2.5px solid var(--character-selected-border, #ff6b9d);
|
| 1335 |
+
background: var(--character-selected-bg, rgba(255, 107, 157, 0.13));
|
| 1336 |
+
box-shadow:
|
| 1337 |
+
0 0 0 4px var(--character-selected-border, #ff6b9d),
|
| 1338 |
+
0 4px 24px var(--character-selected-bg, rgba(255, 107, 157, 0.15));
|
| 1339 |
+
}
|
| 1340 |
+
|
| 1341 |
+
.character-card:hover {
|
| 1342 |
+
border: 2px solid var(--primary-color, #ff6b9d);
|
| 1343 |
+
background: rgba(255, 107, 157, 0.1);
|
| 1344 |
+
box-shadow: 0 2px 16px rgba(255, 107, 157, 0.1);
|
| 1345 |
+
}
|
| 1346 |
+
|
| 1347 |
+
.character-card img {
|
| 1348 |
+
width: 80px;
|
| 1349 |
+
height: 80px;
|
| 1350 |
+
border-radius: 50%;
|
| 1351 |
+
object-fit: cover;
|
| 1352 |
+
margin-bottom: 12px;
|
| 1353 |
+
border: 2px solid var(--modal-text);
|
| 1354 |
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
| 1355 |
+
}
|
| 1356 |
+
|
| 1357 |
+
.character-name {
|
| 1358 |
+
text-align: center;
|
| 1359 |
+
width: 100%;
|
| 1360 |
+
font-size: 1.1rem;
|
| 1361 |
+
font-weight: 700;
|
| 1362 |
+
color: var(--modal-text);
|
| 1363 |
+
text-shadow: 0 1px 8px var(--primary-color, #ff6b9d);
|
| 1364 |
+
margin-bottom: 4px;
|
| 1365 |
+
margin-top: 0;
|
| 1366 |
+
}
|
| 1367 |
+
|
| 1368 |
+
.character-info {
|
| 1369 |
+
display: flex;
|
| 1370 |
+
flex-direction: column;
|
| 1371 |
+
align-items: center;
|
| 1372 |
+
justify-content: center;
|
| 1373 |
+
text-align: center;
|
| 1374 |
+
width: 100%;
|
| 1375 |
+
margin-bottom: 10px;
|
| 1376 |
+
}
|
| 1377 |
+
|
| 1378 |
+
.character-details {
|
| 1379 |
+
font-size: 0.95rem;
|
| 1380 |
+
color: var(--modal-text);
|
| 1381 |
+
opacity: 0.85;
|
| 1382 |
+
margin-bottom: 6px;
|
| 1383 |
+
text-align: center;
|
| 1384 |
+
}
|
| 1385 |
+
|
| 1386 |
+
.character-prompt-label {
|
| 1387 |
+
font-size: 0.85rem;
|
| 1388 |
+
color: var(--modal-text);
|
| 1389 |
+
margin-bottom: 4px;
|
| 1390 |
+
margin-top: 8px;
|
| 1391 |
+
opacity: 0.7;
|
| 1392 |
+
text-align: center;
|
| 1393 |
+
}
|
| 1394 |
+
|
| 1395 |
+
.character-prompt-input {
|
| 1396 |
+
width: 100%;
|
| 1397 |
+
min-height: 60px;
|
| 1398 |
+
border-radius: 8px;
|
| 1399 |
+
border: 1px solid var(--primary-color, #ff6b9d);
|
| 1400 |
+
background: var(--input-bg);
|
| 1401 |
+
color: var(--modal-text);
|
| 1402 |
+
padding: 6px 8px;
|
| 1403 |
+
font-size: 0.95rem;
|
| 1404 |
+
resize: vertical;
|
| 1405 |
+
margin-bottom: 4px;
|
| 1406 |
+
}
|
| 1407 |
+
|
| 1408 |
+
.character-prompt-input:disabled {
|
| 1409 |
+
opacity: 0.5;
|
| 1410 |
+
background: var(--input-bg);
|
| 1411 |
+
cursor: not-allowed;
|
| 1412 |
+
}
|
| 1413 |
+
|
| 1414 |
+
/* Character prompt buttons */
|
| 1415 |
+
.character-prompt-buttons {
|
| 1416 |
+
display: flex;
|
| 1417 |
+
gap: 8px;
|
| 1418 |
+
margin-top: 8px;
|
| 1419 |
+
justify-content: center;
|
| 1420 |
+
}
|
| 1421 |
+
|
| 1422 |
+
.character-save-btn,
|
| 1423 |
+
.character-reset-btn {
|
| 1424 |
+
padding: 6px 12px;
|
| 1425 |
+
font-size: 0.85rem;
|
| 1426 |
+
border-radius: 6px;
|
| 1427 |
+
border: 1px solid var(--input-border);
|
| 1428 |
+
background: var(--button-bg);
|
| 1429 |
+
color: var(--button-text);
|
| 1430 |
+
cursor: pointer;
|
| 1431 |
+
transition: all 0.2s ease;
|
| 1432 |
+
min-width: 70px;
|
| 1433 |
+
}
|
| 1434 |
+
|
| 1435 |
+
.character-save-btn:hover,
|
| 1436 |
+
.character-reset-btn:hover {
|
| 1437 |
+
background: var(--button-hover-bg);
|
| 1438 |
+
border-color: var(--primary-color);
|
| 1439 |
+
}
|
| 1440 |
+
|
| 1441 |
+
.character-save-btn:disabled,
|
| 1442 |
+
.character-reset-btn:disabled {
|
| 1443 |
+
opacity: 0.5;
|
| 1444 |
+
cursor: not-allowed;
|
| 1445 |
+
background: var(--input-bg);
|
| 1446 |
+
}
|
| 1447 |
+
|
| 1448 |
+
.character-save-btn.success {
|
| 1449 |
+
background: #28a745;
|
| 1450 |
+
color: white;
|
| 1451 |
+
border-color: #28a745;
|
| 1452 |
+
}
|
| 1453 |
+
|
| 1454 |
+
.character-reset-btn.animated {
|
| 1455 |
+
background: var(--accent-color);
|
| 1456 |
+
color: white;
|
| 1457 |
+
border-color: var(--accent-color);
|
| 1458 |
+
}
|
| 1459 |
+
|
| 1460 |
+
/* ===== PERSONALITY CHEAT PANEL ===== */
|
| 1461 |
+
|
| 1462 |
+
.cheat-toggle-btn {
|
| 1463 |
+
background: linear-gradient(90deg, var(--primary-color), var(--accent-color));
|
| 1464 |
+
border: none;
|
| 1465 |
+
color: #fff;
|
| 1466 |
+
font-size: 1em;
|
| 1467 |
+
margin-left: 1em;
|
| 1468 |
+
cursor: pointer;
|
| 1469 |
+
display: inline-flex;
|
| 1470 |
+
align-items: center;
|
| 1471 |
+
gap: 0.5em;
|
| 1472 |
+
transition:
|
| 1473 |
+
color 0.2s,
|
| 1474 |
+
box-shadow 0.2s,
|
| 1475 |
+
background 0.2s;
|
| 1476 |
+
border-radius: 18px;
|
| 1477 |
+
padding: 0.35em 1.1em 0.35em 0.9em;
|
| 1478 |
+
box-shadow: 0 2px 10px 0 rgba(255, 107, 157, 0.1);
|
| 1479 |
+
font-weight: 600;
|
| 1480 |
+
letter-spacing: 0.01em;
|
| 1481 |
+
outline: none;
|
| 1482 |
+
}
|
| 1483 |
+
|
| 1484 |
+
.cheat-toggle-btn[aria-expanded="true"] {
|
| 1485 |
+
color: #fff;
|
| 1486 |
+
background: linear-gradient(90deg, var(--accent-color), var(--primary-color));
|
| 1487 |
+
box-shadow: 0 2px 16px 0 rgba(255, 107, 157, 0.18);
|
| 1488 |
+
}
|
| 1489 |
+
|
| 1490 |
+
.cheat-toggle-btn:hover,
|
| 1491 |
+
.cheat-toggle-btn:focus {
|
| 1492 |
+
box-shadow: 0 2px 16px 0 rgba(255, 107, 157, 0.25);
|
| 1493 |
+
transform: translateY(-1px);
|
| 1494 |
+
}
|
| 1495 |
+
.cheat-toggle-btn:focus {
|
| 1496 |
+
background: linear-gradient(90deg, var(--primary-color), var(--accent-color));
|
| 1497 |
+
color: #fff;
|
| 1498 |
+
box-shadow: 0 4px 18px 0 rgba(255, 107, 157, 0.22);
|
| 1499 |
+
filter: brightness(1.08);
|
| 1500 |
+
}
|
| 1501 |
+
|
| 1502 |
+
.cheat-toggle-btn i {
|
| 1503 |
+
margin-right: 0.4em;
|
| 1504 |
+
font-size: 1.1em;
|
| 1505 |
+
}
|
| 1506 |
+
|
| 1507 |
+
.cheat-indicator {
|
| 1508 |
+
font-size: 0.9em;
|
| 1509 |
+
color: #aaa;
|
| 1510 |
+
margin-bottom: 0.5em;
|
| 1511 |
+
margin-left: 2.2em;
|
| 1512 |
+
}
|
| 1513 |
+
|
| 1514 |
+
.cheat-panel {
|
| 1515 |
+
max-height: 0;
|
| 1516 |
+
overflow: hidden;
|
| 1517 |
+
transition: max-height 0.4s cubic-bezier(0.4, 0, 0.2, 1);
|
| 1518 |
+
opacity: 0.5;
|
| 1519 |
+
pointer-events: none;
|
| 1520 |
+
}
|
| 1521 |
+
|
| 1522 |
+
.cheat-panel.open {
|
| 1523 |
+
max-height: 800px;
|
| 1524 |
+
opacity: 1;
|
| 1525 |
+
pointer-events: auto;
|
| 1526 |
+
transition:
|
| 1527 |
+
max-height 0.6s cubic-bezier(0.4, 0, 0.2, 1),
|
| 1528 |
+
opacity 0.3s;
|
| 1529 |
+
}
|
| 1530 |
+
|
| 1531 |
+
/* ===== ACCESSIBILITY - FOCUS STYLES ===== */
|
| 1532 |
+
.settings-panel select:focus,
|
| 1533 |
+
.settings-panel input:focus,
|
| 1534 |
+
.settings-panel button:focus,
|
| 1535 |
+
.settings-panel .cheat-toggle-btn:focus {
|
| 1536 |
+
box-shadow: 0 0 0 2px var(--primary-pink);
|
| 1537 |
+
border-color: var(--primary-pink);
|
| 1538 |
+
}
|
| 1539 |
+
|
| 1540 |
+
.character-card:focus {
|
| 1541 |
+
outline: 2px solid var(--primary-pink);
|
| 1542 |
+
outline-offset: 2px;
|
| 1543 |
+
}
|
| 1544 |
+
|
| 1545 |
+
/* ===== API KEY INPUT STYLING ===== */
|
| 1546 |
+
|
| 1547 |
+
.api-key-input-group {
|
| 1548 |
+
display: flex;
|
| 1549 |
+
align-items: center;
|
| 1550 |
+
gap: 8px;
|
| 1551 |
+
position: relative;
|
| 1552 |
+
}
|
| 1553 |
+
|
| 1554 |
+
.api-key-input-group .kimi-input {
|
| 1555 |
+
flex: 1;
|
| 1556 |
+
margin: 0;
|
| 1557 |
+
}
|
| 1558 |
+
|
| 1559 |
+
.api-key-toggle {
|
| 1560 |
+
min-width: 40px;
|
| 1561 |
+
height: 40px;
|
| 1562 |
+
padding: 8px;
|
| 1563 |
+
display: flex;
|
| 1564 |
+
align-items: center;
|
| 1565 |
+
justify-content: center;
|
| 1566 |
+
border-radius: 6px;
|
| 1567 |
+
background: var(--settings-bg-secondary, rgba(255, 255, 255, 0.08));
|
| 1568 |
+
border: 1px solid var(--settings-border-color, rgba(255, 255, 255, 0.15));
|
| 1569 |
+
color: var(--settings-text, #ffffff);
|
| 1570 |
+
transition: all 0.2s ease;
|
| 1571 |
+
cursor: pointer;
|
| 1572 |
+
}
|
| 1573 |
+
|
| 1574 |
+
.api-key-toggle:hover {
|
| 1575 |
+
background: var(--settings-bg-hover, rgba(255, 255, 255, 0.12));
|
| 1576 |
+
border-color: var(--accent-color, #8a2be2);
|
| 1577 |
+
}
|
| 1578 |
+
|
| 1579 |
+
.api-key-toggle:active {
|
| 1580 |
+
transform: scale(0.95);
|
| 1581 |
+
}
|
| 1582 |
+
|
| 1583 |
+
.api-key-input-group .presence-dot {
|
| 1584 |
+
width: 12px;
|
| 1585 |
+
height: 12px;
|
| 1586 |
+
margin-left: 4px;
|
| 1587 |
+
border: 2px solid var(--settings-bg-primary, rgba(0, 0, 0, 0.3));
|
| 1588 |
+
box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.1);
|
| 1589 |
+
transition: all 0.2s ease;
|
| 1590 |
+
}
|
| 1591 |
+
|
| 1592 |
+
.api-key-status {
|
| 1593 |
+
margin-top: 6px;
|
| 1594 |
+
min-height: 18px;
|
| 1595 |
+
display: flex;
|
| 1596 |
+
align-items: center;
|
| 1597 |
+
}
|
| 1598 |
+
|
| 1599 |
+
.api-key-status span {
|
| 1600 |
+
font-size: 0.85rem;
|
| 1601 |
+
display: flex;
|
| 1602 |
+
align-items: center;
|
| 1603 |
+
gap: 6px;
|
| 1604 |
+
}
|
| 1605 |
+
|
| 1606 |
+
.api-key-status span::before {
|
| 1607 |
+
content: "✓";
|
| 1608 |
+
font-weight: bold;
|
| 1609 |
+
font-size: 0.9rem;
|
| 1610 |
+
}
|
| 1611 |
+
|
| 1612 |
+
/* Responsive adjustments */
|
| 1613 |
+
@media (max-width: 600px) {
|
| 1614 |
+
.api-key-input-group {
|
| 1615 |
+
flex-direction: column;
|
| 1616 |
+
align-items: stretch;
|
| 1617 |
+
gap: 12px;
|
| 1618 |
+
}
|
| 1619 |
+
|
| 1620 |
+
.api-key-toggle {
|
| 1621 |
+
align-self: flex-end;
|
| 1622 |
+
min-width: 100px;
|
| 1623 |
+
}
|
| 1624 |
+
|
| 1625 |
+
.api-key-input-group .presence-dot {
|
| 1626 |
+
position: absolute;
|
| 1627 |
+
top: 50%;
|
| 1628 |
+
right: 8px;
|
| 1629 |
+
transform: translateY(-50%);
|
| 1630 |
+
z-index: 10;
|
| 1631 |
+
}
|
| 1632 |
+
}
|
kimi-css/kimi-style.css
ADDED
|
@@ -0,0 +1,2075 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/* ===== CONSOLIDATED CSS VARIABLES - ENHANCED DYNAMIC THEMING ===== */
|
| 2 |
+
:root {
|
| 3 |
+
/* Core Theme Colors - Default Dark Theme Base */
|
| 4 |
+
--primary-color: #5e60ce;
|
| 5 |
+
--primary-rgb: 94, 96, 206;
|
| 6 |
+
--secondary-color: #23262f;
|
| 7 |
+
--accent-color: #8b5cf6;
|
| 8 |
+
--background-overlay: rgba(24, 26, 32, 0.85);
|
| 9 |
+
--interface-opacity: 0.7;
|
| 10 |
+
--gradient-start: #5e60ce;
|
| 11 |
+
--gradient-end: #8b5cf6;
|
| 12 |
+
--text-glow: 0 0 10px rgba(94, 96, 206, 0.3);
|
| 13 |
+
--button-hover: rgba(94, 96, 206, 0.15);
|
| 14 |
+
--animations-enabled: 1;
|
| 15 |
+
--switch-color: #5e60ce;
|
| 16 |
+
|
| 17 |
+
/* Contrast and Accessibility */
|
| 18 |
+
--contrast-ratio: 4.5; /* WCAG AA standard */
|
| 19 |
+
--text-on-primary: #ffffff;
|
| 20 |
+
--text-on-secondary: #e0e0e0;
|
| 21 |
+
--text-on-accent: #ffffff;
|
| 22 |
+
--text-on-background: #e0e0e0;
|
| 23 |
+
--border-opacity: 0.3;
|
| 24 |
+
--hover-opacity: 0.15;
|
| 25 |
+
--active-opacity: 0.25;
|
| 26 |
+
|
| 27 |
+
/* UI Component Colors - Chat Interface */
|
| 28 |
+
--chat-bg: rgba(24, 26, 32, 0.95);
|
| 29 |
+
--chat-text: var(--text-on-background);
|
| 30 |
+
--chat-header-bg: rgba(255, 255, 255, 0.05);
|
| 31 |
+
--chat-border: #5e60ce;
|
| 32 |
+
--chat-input-bg: rgba(255, 255, 255, 0.1);
|
| 33 |
+
--chat-input-text: var(--text-on-background);
|
| 34 |
+
--chat-input-placeholder: rgba(255, 255, 255, 0.6);
|
| 35 |
+
--chat-message-user-bg: #1e253c;
|
| 36 |
+
--chat-message-user-text: var(--text-on-background);
|
| 37 |
+
--chat-message-kimi-bg: #23262f;
|
| 38 |
+
--chat-message-kimi-text: var(--text-on-background);
|
| 39 |
+
|
| 40 |
+
/* Modal & Overlay Colors */
|
| 41 |
+
--modal-bg: rgba(24, 26, 32, 0.98);
|
| 42 |
+
--modal-border: #5e60ce;
|
| 43 |
+
--modal-header-bg: linear-gradient(135deg, #5e60ce, #8b5cf6);
|
| 44 |
+
--modal-text: var(--text-on-background);
|
| 45 |
+
--modal-title-color: #e0e0e0;
|
| 46 |
+
--modal-overlay-bg: rgba(0, 0, 0, 0.8);
|
| 47 |
+
--modal-close-hover-bg: rgba(255, 255, 255, 0.2);
|
| 48 |
+
|
| 49 |
+
/* Settings Panel & Tabs */
|
| 50 |
+
--settings-bg: #0f1114;
|
| 51 |
+
--settings-text: var(--text-on-background);
|
| 52 |
+
--settings-tab-bg: #181a20;
|
| 53 |
+
--settings-tab-color: #bfa6b6;
|
| 54 |
+
--settings-tab-hover-bg: rgba(255, 255, 255, var(--hover-opacity));
|
| 55 |
+
--settings-tab-hover-color: rgba(255, 255, 255, 0.9);
|
| 56 |
+
--settings-tab-active-bg: #5e60ce;
|
| 57 |
+
--settings-tab-active-color: var(--text-on-primary);
|
| 58 |
+
--settings-tab-border: #5e60ce;
|
| 59 |
+
--settings-section-bg: #1a1d23;
|
| 60 |
+
--settings-section-border: rgba(94, 96, 206, var(--border-opacity));
|
| 61 |
+
--settings-section-header-color: var(--text-on-background);
|
| 62 |
+
|
| 63 |
+
/* Form Element Colors */
|
| 64 |
+
--input-bg: rgba(255, 255, 255, 0.1);
|
| 65 |
+
--input-text: var(--text-on-background);
|
| 66 |
+
--input-border: #5e60ce;
|
| 67 |
+
--input-focus-bg: rgba(255, 255, 255, var(--hover-opacity));
|
| 68 |
+
--input-focus-border: #8b5cf6;
|
| 69 |
+
--input-placeholder: rgba(255, 255, 255, 0.6);
|
| 70 |
+
|
| 71 |
+
/* Button Colors */
|
| 72 |
+
--button-primary-bg: var(--primary-color);
|
| 73 |
+
--button-primary-text: var(--text-on-primary);
|
| 74 |
+
--button-primary-hover-bg: var(--accent-color);
|
| 75 |
+
--button-secondary-bg: rgba(255, 255, 255, 0.05);
|
| 76 |
+
--button-secondary-text: var(--text-on-background);
|
| 77 |
+
--button-secondary-hover-bg: rgba(255, 255, 255, var(--hover-opacity));
|
| 78 |
+
--button-danger-bg: #e74c3c;
|
| 79 |
+
--button-danger-text: #ffffff;
|
| 80 |
+
--button-danger-hover-bg: #c0392b;
|
| 81 |
+
|
| 82 |
+
/* Select & Dropdown Options */
|
| 83 |
+
--select-bg: var(--input-bg);
|
| 84 |
+
--select-text: var(--input-text);
|
| 85 |
+
--select-border: var(--input-border);
|
| 86 |
+
--select-option-bg: rgba(24, 26, 32, 0.95);
|
| 87 |
+
--select-option-text: var(--text-on-background);
|
| 88 |
+
--select-option-hover-bg: #5e60ce;
|
| 89 |
+
--select-option-hover-text: var(--text-on-primary);
|
| 90 |
+
--select-option-checked-bg: #8b5cf6;
|
| 91 |
+
--select-option-checked-text: var(--text-on-accent);
|
| 92 |
+
--select-option-disabled-bg: rgba(24, 26, 32, 0.5);
|
| 93 |
+
--select-option-disabled-text: #666;
|
| 94 |
+
|
| 95 |
+
/* Slider Components */
|
| 96 |
+
--slider-track-bg: rgba(255, 255, 255, 0.1);
|
| 97 |
+
--slider-track-active-bg: var(--primary-color);
|
| 98 |
+
--slider-thumb-bg: var(--primary-color);
|
| 99 |
+
--slider-thumb-hover-bg: var(--accent-color);
|
| 100 |
+
--slider-value-bg: #0f1114;
|
| 101 |
+
--slider-value-border: #5e60ce;
|
| 102 |
+
--slider-value-color: var(--text-on-background);
|
| 103 |
+
|
| 104 |
+
/* Toggle Switch */
|
| 105 |
+
--switch-bg-inactive: rgba(255, 255, 255, 0.05);
|
| 106 |
+
--switch-bg-active: linear-gradient(90deg, #5e60ce, #8b5cf6);
|
| 107 |
+
--switch-thumb-color: #ffffff;
|
| 108 |
+
--switch-thumb-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
|
| 109 |
+
|
| 110 |
+
/* Mic Button & Pulse Effect */
|
| 111 |
+
--mic-button-bg: var(--button-hover);
|
| 112 |
+
--mic-button-border: #5e60ce;
|
| 113 |
+
--mic-button-shadow: 0 0 15px #5e60ce;
|
| 114 |
+
--mic-button-hover-bg: #5e60ce;
|
| 115 |
+
--mic-button-hover-shadow: 0 0 10px rgba(94, 96, 206, 0.5);
|
| 116 |
+
--mic-button-icon-color: var(--text-on-primary);
|
| 117 |
+
--mic-listening-border: #27ae60;
|
| 118 |
+
--mic-listening-shadow: 0 0 15px #27ae60;
|
| 119 |
+
--mic-pulse-color: rgba(39, 174, 96, 0.5);
|
| 120 |
+
--mic-pulse-listening-color: rgba(39, 174, 96, 0.4);
|
| 121 |
+
|
| 122 |
+
/* Video crossfade timing */
|
| 123 |
+
--video-fade-duration: 400ms;
|
| 124 |
+
|
| 125 |
+
/* Cards & Stats */
|
| 126 |
+
--card-bg: rgba(255, 255, 255, 0.02);
|
| 127 |
+
--card-border: rgba(255, 255, 255, 0.05);
|
| 128 |
+
--card-hover-bg: rgba(255, 255, 255, 0.05);
|
| 129 |
+
--card-text: var(--text-on-background);
|
| 130 |
+
--stat-value-color: #8b5cf6;
|
| 131 |
+
--stat-label-color: rgba(224, 224, 224, 0.7);
|
| 132 |
+
|
| 133 |
+
/* Plugin Cards */
|
| 134 |
+
--plugin-card-bg: linear-gradient(135deg, #1a1d23 80%, rgba(24, 26, 32, 0.98) 100%);
|
| 135 |
+
--plugin-card-border: #5e60ce;
|
| 136 |
+
--plugin-card-title-color: var(--text-on-background);
|
| 137 |
+
--plugin-card-desc-color: rgba(224, 224, 224, 0.7);
|
| 138 |
+
--plugin-card-author-color: rgba(224, 224, 224, 0.5);
|
| 139 |
+
--plugin-type-badge-bg: #8b5cf6;
|
| 140 |
+
--plugin-type-badge-text: var(--text-on-accent);
|
| 141 |
+
--plugin-active-badge-bg: linear-gradient(90deg, #5e60ce, #8b5cf6);
|
| 142 |
+
--plugin-active-badge-text: var(--text-on-primary);
|
| 143 |
+
|
| 144 |
+
/* Help Modal */
|
| 145 |
+
--help-modal-bg: rgba(24, 26, 32, 0.98);
|
| 146 |
+
--help-modal-border: #5e60ce;
|
| 147 |
+
--help-content-color: var(--text-on-background);
|
| 148 |
+
--help-section-border: rgba(255, 255, 255, 0.1);
|
| 149 |
+
--creator-card-bg: rgba(255, 255, 255, 0.02);
|
| 150 |
+
--creator-card-border: rgba(255, 255, 255, 0.05);
|
| 151 |
+
--creator-avatar-bg: linear-gradient(135deg, #5e60ce, #8b5cf6);
|
| 152 |
+
--creator-name-color: #8b5cf6;
|
| 153 |
+
--creator-role-bg: linear-gradient(135deg, #5e60ce, #8b5cf6);
|
| 154 |
+
--creator-role-color: var(--text-on-primary);
|
| 155 |
+
--philosophy-bg: rgba(94, 96, 206, 0.05);
|
| 156 |
+
--philosophy-border: rgba(94, 96, 206, var(--border-opacity));
|
| 157 |
+
--philosophy-border-left: #5e60ce;
|
| 158 |
+
--feature-item-bg: rgba(255, 255, 255, 0.02);
|
| 159 |
+
--feature-item-border: rgba(94, 96, 206, var(--border-opacity));
|
| 160 |
+
--feature-icon-color: #8b5cf6;
|
| 161 |
+
--feature-title-color: #8b5cf6;
|
| 162 |
+
--feature-text-color: rgba(224, 224, 224, 0.7);
|
| 163 |
+
--guide-step-bg: rgba(255, 255, 255, 0.02);
|
| 164 |
+
--guide-step-number-bg: rgba(94, 96, 206, var(--hover-opacity));
|
| 165 |
+
--guide-step-number-color: var(--primary-color);
|
| 166 |
+
--tip-item-bg: rgba(255, 255, 255, 0.02);
|
| 167 |
+
--tip-item-border: var(--feature-item-border);
|
| 168 |
+
|
| 169 |
+
/* Unified Scrollbar System */
|
| 170 |
+
--scrollbar-width: 8px !important;
|
| 171 |
+
--scrollbar-track-bg: rgba(255, 255, 255, 0.02) !important;
|
| 172 |
+
--scrollbar-thumb-bg: rgba(94, 96, 206, 0.4) !important;
|
| 173 |
+
--scrollbar-thumb-hover-bg: rgba(94, 96, 206, 0.6) !important;
|
| 174 |
+
--scrollbar-thumb-active-bg: rgba(94, 96, 206, 0.8) !important;
|
| 175 |
+
--scrollbar-corner-bg: rgba(255, 255, 255, 0.05) !important;
|
| 176 |
+
|
| 177 |
+
/* Model Colors */
|
| 178 |
+
--model-card-bg: rgba(255, 255, 255, 0.02);
|
| 179 |
+
--model-card-border: rgba(255, 255, 255, 0.05);
|
| 180 |
+
--model-card-hover-bg: rgba(255, 255, 255, 0.05);
|
| 181 |
+
--model-card-selected-border: var(--primary-color);
|
| 182 |
+
--model-card-selected-shadow: 0 0 0 2px rgba(94, 96, 206, var(--border-opacity));
|
| 183 |
+
--model-name-color: var(--text-on-background);
|
| 184 |
+
--model-description-color: rgba(224, 224, 224, 0.7);
|
| 185 |
+
--model-strength-color: #5e60ce;
|
| 186 |
+
--model-strength-text: var(--text-on-primary);
|
| 187 |
+
--model-provider-color: #8b5cf6;
|
| 188 |
+
--model-provider-text: var(--text-on-accent);
|
| 189 |
+
|
| 190 |
+
/* Text Colors */
|
| 191 |
+
--text-primary: var(--text-on-background);
|
| 192 |
+
--text-secondary: #9ca3af;
|
| 193 |
+
--text-muted: rgba(224, 224, 224, 0.6);
|
| 194 |
+
|
| 195 |
+
/* Character Selection Colors */
|
| 196 |
+
--character-card-bg: rgba(255, 255, 255, 0.02);
|
| 197 |
+
--character-card-border: rgba(255, 255, 255, 0.05);
|
| 198 |
+
--character-card-hover-bg: rgba(255, 255, 255, 0.05);
|
| 199 |
+
--character-selected-border: #8b5cf6;
|
| 200 |
+
--character-selected-bg: rgba(94, 96, 206, 0.1);
|
| 201 |
+
--character-name-color: var(--text-on-background);
|
| 202 |
+
|
| 203 |
+
/* Waiting Indicator */
|
| 204 |
+
--waiting-indicator-color: var(--primary-color);
|
| 205 |
+
--loading-spinner-color: var(--primary-color);
|
| 206 |
+
|
| 207 |
+
/* Progress Bar */
|
| 208 |
+
--progress-bg: rgba(255, 255, 255, 0.05);
|
| 209 |
+
--progress-fill-bg: linear-gradient(90deg, var(--primary-color), var(--accent-color));
|
| 210 |
+
--progress-text-color: var(--text-on-background);
|
| 211 |
+
|
| 212 |
+
/* Transcript */
|
| 213 |
+
--transcript-bg: rgba(0, 0, 0, 0.9);
|
| 214 |
+
--transcript-text: var(--text-on-background);
|
| 215 |
+
--transcript-border: var(--primary-color);
|
| 216 |
+
}
|
| 217 |
+
|
| 218 |
+
/* === Microphone Button Disabled State (2025-09 SR capability refactor) === */
|
| 219 |
+
#mic-button.disabled,
|
| 220 |
+
button#mic-button.disabled {
|
| 221 |
+
opacity: 0.45;
|
| 222 |
+
cursor: not-allowed;
|
| 223 |
+
box-shadow: none;
|
| 224 |
+
filter: grayscale(40%);
|
| 225 |
+
transition: opacity 0.25s ease;
|
| 226 |
+
}
|
| 227 |
+
|
| 228 |
+
#mic-button.disabled:hover {
|
| 229 |
+
opacity: 0.45; /* keep stable */
|
| 230 |
+
}
|
| 231 |
+
|
| 232 |
+
#mic-button.disabled .mic-icon,
|
| 233 |
+
#mic-button.disabled svg {
|
| 234 |
+
opacity: 0.7;
|
| 235 |
+
}
|
| 236 |
+
|
| 237 |
+
/* Provide a subtle tooltip helper if title attribute present */
|
| 238 |
+
#mic-button.disabled[title] {
|
| 239 |
+
position: relative;
|
| 240 |
+
}
|
| 241 |
+
|
| 242 |
+
/* ===== OPTIMIZED THEME VARIATIONS ===== */
|
| 243 |
+
[data-theme="pink"] {
|
| 244 |
+
/* Core Theme Colors - Pink Passion */
|
| 245 |
+
--primary-color: #ff6b9d;
|
| 246 |
+
--primary-rgb: 255, 107, 157;
|
| 247 |
+
--secondary-color: #ffeaa7;
|
| 248 |
+
--accent-color: #fd79a8;
|
| 249 |
+
--background-overlay: rgba(255, 107, 157, 0.15);
|
| 250 |
+
--gradient-start: #ff6b9d;
|
| 251 |
+
--gradient-end: #fd79a8;
|
| 252 |
+
--text-glow: 0 0 10px rgba(255, 107, 157, 0.5);
|
| 253 |
+
--button-hover: rgba(255, 107, 157, 0.3);
|
| 254 |
+
--switch-color: var(--primary-color);
|
| 255 |
+
|
| 256 |
+
/* Contrast and Accessibility */
|
| 257 |
+
--text-on-primary: #ffffff;
|
| 258 |
+
--text-on-secondary: #222222;
|
| 259 |
+
--text-on-accent: #ffffff;
|
| 260 |
+
--text-on-background: #ffffff;
|
| 261 |
+
|
| 262 |
+
/* UI Component Colors - Chat Interface */
|
| 263 |
+
--chat-bg: rgba(255, 107, 157, 0.9);
|
| 264 |
+
--chat-text: var(--text-on-primary);
|
| 265 |
+
--chat-header-bg: rgba(255, 255, 255, 0.05);
|
| 266 |
+
--chat-border: var(--primary-color);
|
| 267 |
+
--chat-input-bg: rgba(255, 255, 255, 0.1);
|
| 268 |
+
--chat-input-text: var(--text-on-background);
|
| 269 |
+
--chat-input-placeholder: rgba(255, 255, 255, 0.6);
|
| 270 |
+
--chat-message-user-bg: var(--primary-color);
|
| 271 |
+
--chat-message-user-text: var(--text-on-primary);
|
| 272 |
+
--chat-message-kimi-bg: rgba(255, 255, 255, 0.15);
|
| 273 |
+
--chat-message-kimi-text: var(--text-on-background);
|
| 274 |
+
|
| 275 |
+
/* Modal & Overlay Colors */
|
| 276 |
+
--modal-bg: rgba(255, 107, 157, 0.95);
|
| 277 |
+
--modal-border: var(--primary-color);
|
| 278 |
+
--modal-header-bg: linear-gradient(135deg, var(--primary-color), var(--secondary-color));
|
| 279 |
+
--modal-text: var(--text-on-primary);
|
| 280 |
+
--modal-title-color: var(--text-on-secondary);
|
| 281 |
+
--modal-overlay-bg: rgba(0, 0, 0, 0.8);
|
| 282 |
+
--modal-close-hover-bg: rgba(255, 255, 255, 0.2);
|
| 283 |
+
|
| 284 |
+
/* Settings Panel & Tabs */
|
| 285 |
+
--settings-bg: #181018;
|
| 286 |
+
--settings-text: var(--text-on-background);
|
| 287 |
+
--settings-tab-bg: #1a1a1a;
|
| 288 |
+
--settings-tab-color: #bfa6b6;
|
| 289 |
+
--settings-tab-hover-bg: rgba(255, 255, 255, var(--hover-opacity));
|
| 290 |
+
--settings-tab-hover-color: rgba(255, 255, 255, 0.9);
|
| 291 |
+
--settings-tab-active-bg: var(--primary-color);
|
| 292 |
+
--settings-tab-active-color: var(--text-on-primary);
|
| 293 |
+
--settings-tab-border: var(--primary-color);
|
| 294 |
+
--settings-section-bg: #22121a;
|
| 295 |
+
--settings-section-border: rgba(255, 107, 157, var(--border-opacity));
|
| 296 |
+
--settings-section-header-color: var(--text-on-background);
|
| 297 |
+
|
| 298 |
+
/* Form Element Colors */
|
| 299 |
+
--input-bg: rgba(255, 255, 255, 0.1);
|
| 300 |
+
--input-text: var(--text-on-background);
|
| 301 |
+
--input-border: var(--primary-color);
|
| 302 |
+
--input-focus-bg: rgba(255, 255, 255, var(--hover-opacity));
|
| 303 |
+
--input-focus-border: var(--accent-color);
|
| 304 |
+
--input-placeholder: rgba(255, 255, 255, 0.6);
|
| 305 |
+
|
| 306 |
+
/* Button Colors */
|
| 307 |
+
--button-primary-bg: var(--primary-color);
|
| 308 |
+
--button-primary-text: var(--text-on-primary);
|
| 309 |
+
--button-primary-hover-bg: var(--accent-color);
|
| 310 |
+
--button-secondary-bg: rgba(255, 255, 255, 0.1);
|
| 311 |
+
--button-secondary-text: var(--text-on-background);
|
| 312 |
+
--button-secondary-hover-bg: rgba(255, 255, 255, var(--hover-opacity));
|
| 313 |
+
|
| 314 |
+
/* All other pink theme variables... */
|
| 315 |
+
--slider-track-bg: rgba(255, 255, 255, 0.1);
|
| 316 |
+
--slider-track-active-bg: var(--primary-color);
|
| 317 |
+
--slider-thumb-bg: var(--primary-color);
|
| 318 |
+
--slider-thumb-hover-bg: var(--accent-color);
|
| 319 |
+
--slider-value-bg: #181018;
|
| 320 |
+
--slider-value-border: var(--primary-color);
|
| 321 |
+
--slider-value-color: var(--text-on-background);
|
| 322 |
+
|
| 323 |
+
/* Toggle Switch */
|
| 324 |
+
--switch-bg-inactive: rgba(255, 255, 255, 0.15);
|
| 325 |
+
--switch-bg-active: linear-gradient(90deg, var(--primary-color), var(--accent-color));
|
| 326 |
+
|
| 327 |
+
/* Mic Button & Pulse Effect */
|
| 328 |
+
--mic-button-bg: var(--button-hover);
|
| 329 |
+
--mic-button-border: var(--primary-color);
|
| 330 |
+
--mic-button-shadow: 0 0 15px var(--primary-color);
|
| 331 |
+
--mic-button-hover-bg: var(--primary-color);
|
| 332 |
+
--mic-button-hover-shadow: var(--text-glow);
|
| 333 |
+
--mic-button-icon-color: var(--text-on-primary);
|
| 334 |
+
|
| 335 |
+
/* Cards & Stats */
|
| 336 |
+
--card-bg: rgba(255, 255, 255, 0.05);
|
| 337 |
+
--card-border: rgba(255, 255, 255, 0.1);
|
| 338 |
+
--card-hover-bg: rgba(255, 255, 255, 0.08);
|
| 339 |
+
--stat-value-color: var(--primary-color);
|
| 340 |
+
--stat-label-color: rgba(255, 255, 255, 0.7);
|
| 341 |
+
|
| 342 |
+
/* Character Selection Colors */
|
| 343 |
+
--character-card-bg: var(--card-bg);
|
| 344 |
+
--character-card-border: var(--card-border);
|
| 345 |
+
--character-card-hover-bg: var(--card-hover-bg);
|
| 346 |
+
--character-selected-border: var(--primary-color);
|
| 347 |
+
--character-selected-bg: rgba(255, 107, 157, 0.13);
|
| 348 |
+
--character-name-color: var(--text-on-background);
|
| 349 |
+
}
|
| 350 |
+
|
| 351 |
+
[data-theme="blue"] {
|
| 352 |
+
--primary-color: #74b9ff;
|
| 353 |
+
--primary-rgb: 116, 185, 255;
|
| 354 |
+
--secondary-color: #81ecec;
|
| 355 |
+
--accent-color: #0984e3;
|
| 356 |
+
--background-overlay: rgba(116, 185, 255, 0.15);
|
| 357 |
+
--gradient-start: #74b9ff;
|
| 358 |
+
--gradient-end: #0984e3;
|
| 359 |
+
--text-glow: 0 0 10px rgba(116, 185, 255, 0.5);
|
| 360 |
+
--button-hover: rgba(116, 185, 255, 0.3);
|
| 361 |
+
--modal-title-color: #0a2340;
|
| 362 |
+
--switch-color: #3498db;
|
| 363 |
+
|
| 364 |
+
/* UI Component Colors */
|
| 365 |
+
--chat-bg: rgba(116, 185, 255, 0.9);
|
| 366 |
+
--chat-border: #74b9ff;
|
| 367 |
+
--chat-message-user-bg: #74b9ff;
|
| 368 |
+
--chat-message-kimi-bg: rgba(255, 255, 255, 0.15);
|
| 369 |
+
--input-border: #74b9ff;
|
| 370 |
+
--input-focus-border: #0984e3;
|
| 371 |
+
|
| 372 |
+
/* Modal & Overlay Colors */
|
| 373 |
+
--modal-bg: rgba(116, 185, 255, 0.95);
|
| 374 |
+
--modal-border: #74b9ff;
|
| 375 |
+
--modal-header-bg: linear-gradient(135deg, #74b9ff, #81ecec);
|
| 376 |
+
|
| 377 |
+
/* Settings Panel & Tabs */
|
| 378 |
+
--settings-tab-active-bg: #74b9ff;
|
| 379 |
+
--settings-section-border: #3a4a7a;
|
| 380 |
+
--settings-tab-border: #74b9ff;
|
| 381 |
+
|
| 382 |
+
/* Slider Components */
|
| 383 |
+
--slider-value-border: #0984e3;
|
| 384 |
+
|
| 385 |
+
/* Toggle Switch */
|
| 386 |
+
--switch-bg-active: linear-gradient(90deg, #74b9ff, #0984e3);
|
| 387 |
+
|
| 388 |
+
/* Mic Button & Pulse Effect */
|
| 389 |
+
--mic-button-border: #74b9ff;
|
| 390 |
+
--mic-button-shadow: 0 0 15px #74b9ff;
|
| 391 |
+
--mic-button-hover-bg: #74b9ff;
|
| 392 |
+
--mic-button-hover-shadow: 0 0 10px rgba(116, 185, 255, 0.5);
|
| 393 |
+
--mic-listening-border: #0984e3;
|
| 394 |
+
--mic-listening-shadow: 0 0 15px #0984e3;
|
| 395 |
+
--mic-pulse-color: rgba(9, 132, 227, 0.5);
|
| 396 |
+
|
| 397 |
+
/* Cards & Stats */
|
| 398 |
+
--stat-value-color: #0984e3;
|
| 399 |
+
|
| 400 |
+
/* Plugin Cards */
|
| 401 |
+
--plugin-card-border: #74b9ff;
|
| 402 |
+
--plugin-type-badge-bg: #0984e3;
|
| 403 |
+
--plugin-active-badge-bg: linear-gradient(90deg, #74b9ff, #0984e3);
|
| 404 |
+
|
| 405 |
+
/* Help Modal */
|
| 406 |
+
--creator-name-color: #0984e3;
|
| 407 |
+
--creator-avatar-bg: linear-gradient(135deg, #74b9ff, #81ecec);
|
| 408 |
+
--creator-role-bg: linear-gradient(135deg, #74b9ff, #81ecec);
|
| 409 |
+
--creator-role-color: #0a2340;
|
| 410 |
+
--philosophy-bg: rgba(116, 185, 255, 0.1);
|
| 411 |
+
--philosophy-border: rgba(116, 185, 255, 0.3);
|
| 412 |
+
--philosophy-border-left: #74b9ff;
|
| 413 |
+
--feature-icon-color: #0984e3;
|
| 414 |
+
--feature-title-color: #0984e3;
|
| 415 |
+
|
| 416 |
+
/* Select Options */
|
| 417 |
+
--select-option-hover-bg: #74b9ff;
|
| 418 |
+
--select-option-checked-bg: #0984e3;
|
| 419 |
+
|
| 420 |
+
/* Model Colors */
|
| 421 |
+
--model-strength-color: #0984e3;
|
| 422 |
+
--model-strength-text: #fff;
|
| 423 |
+
--model-provider-color: #00b894;
|
| 424 |
+
--model-provider-text: #fff;
|
| 425 |
+
|
| 426 |
+
/* Text Colors */
|
| 427 |
+
--text-primary: #222;
|
| 428 |
+
--text-secondary: #555;
|
| 429 |
+
|
| 430 |
+
/* Character Selection Colors */
|
| 431 |
+
--character-selected-border: #0984e3;
|
| 432 |
+
--character-selected-bg: rgba(116, 185, 255, 0.13);
|
| 433 |
+
}
|
| 434 |
+
|
| 435 |
+
[data-theme="purple"] {
|
| 436 |
+
--primary-color: #a29bfe;
|
| 437 |
+
--primary-rgb: 162, 155, 254;
|
| 438 |
+
--secondary-color: #fd79a8;
|
| 439 |
+
--accent-color: #6c5ce7;
|
| 440 |
+
--background-overlay: rgba(162, 155, 254, 0.15);
|
| 441 |
+
--gradient-start: #a29bfe;
|
| 442 |
+
--gradient-end: #6c5ce7;
|
| 443 |
+
--text-glow: 0 0 10px rgba(162, 155, 254, 0.5);
|
| 444 |
+
--button-hover: rgba(162, 155, 254, 0.3);
|
| 445 |
+
--modal-title-color: #2d2250;
|
| 446 |
+
--switch-color: #a259ff;
|
| 447 |
+
|
| 448 |
+
/* UI Component Colors */
|
| 449 |
+
--chat-bg: rgba(162, 155, 254, 0.9);
|
| 450 |
+
--chat-border: #a29bfe;
|
| 451 |
+
--chat-message-user-bg: #a29bfe;
|
| 452 |
+
--chat-message-kimi-bg: rgba(255, 255, 255, 0.15);
|
| 453 |
+
--input-border: #a29bfe;
|
| 454 |
+
--input-focus-border: #6c5ce7;
|
| 455 |
+
|
| 456 |
+
/* Modal & Overlay Colors */
|
| 457 |
+
--modal-bg: rgba(162, 155, 254, 0.95);
|
| 458 |
+
--modal-border: #a29bfe;
|
| 459 |
+
--modal-header-bg: linear-gradient(135deg, #a29bfe, #fd79a8);
|
| 460 |
+
|
| 461 |
+
/* Settings Panel & Tabs */
|
| 462 |
+
--settings-tab-active-bg: #a29bfe;
|
| 463 |
+
--settings-section-border: #4a3a7a;
|
| 464 |
+
--settings-tab-border: #a29bfe;
|
| 465 |
+
--settings-bg: #2d2250;
|
| 466 |
+
|
| 467 |
+
/* Slider Components */
|
| 468 |
+
--slider-value-border: #6c5ce7;
|
| 469 |
+
|
| 470 |
+
/* Toggle Switch */
|
| 471 |
+
--switch-bg-active: linear-gradient(90deg, #a29bfe, #6c5ce7);
|
| 472 |
+
|
| 473 |
+
/* Mic Button & Pulse Effect */
|
| 474 |
+
--mic-button-border: #a29bfe;
|
| 475 |
+
--mic-button-shadow: 0 0 15px #a29bfe;
|
| 476 |
+
--mic-button-hover-bg: #a29bfe;
|
| 477 |
+
--mic-button-hover-shadow: 0 0 10px rgba(162, 155, 254, 0.5);
|
| 478 |
+
--mic-listening-border: #6c5ce7;
|
| 479 |
+
--mic-listening-shadow: 0 0 15px #6c5ce7;
|
| 480 |
+
--mic-pulse-color: rgba(108, 92, 231, 0.5);
|
| 481 |
+
|
| 482 |
+
/* Cards & Stats */
|
| 483 |
+
--stat-value-color: #6c5ce7;
|
| 484 |
+
|
| 485 |
+
/* Plugin Cards */
|
| 486 |
+
--plugin-card-border: #a29bfe;
|
| 487 |
+
--plugin-type-badge-bg: #6c5ce7;
|
| 488 |
+
--plugin-active-badge-bg: linear-gradient(90deg, #a29bfe, #6c5ce7);
|
| 489 |
+
|
| 490 |
+
/* Help Modal */
|
| 491 |
+
--creator-name-color: #6c5ce7;
|
| 492 |
+
--creator-avatar-bg: linear-gradient(135deg, #a29bfe, #fd79a8);
|
| 493 |
+
--creator-role-bg: linear-gradient(135deg, #a29bfe, #fd79a8);
|
| 494 |
+
--creator-role-color: #2d2250;
|
| 495 |
+
--philosophy-bg: rgba(162, 155, 254, 0.1);
|
| 496 |
+
--philosophy-border: rgba(162, 155, 254, 0.3);
|
| 497 |
+
--philosophy-border-left: #a29bfe;
|
| 498 |
+
--feature-icon-color: #6c5ce7;
|
| 499 |
+
--feature-title-color: #6c5ce7;
|
| 500 |
+
|
| 501 |
+
/* Select Options */
|
| 502 |
+
--select-option-hover-bg: #a29bfe;
|
| 503 |
+
--select-option-checked-bg: #6c5ce7;
|
| 504 |
+
|
| 505 |
+
/* Model Colors */
|
| 506 |
+
--model-strength-color: #6c5ce7;
|
| 507 |
+
--model-strength-text: #fff;
|
| 508 |
+
--model-provider-color: #fd79a8;
|
| 509 |
+
--model-provider-text: #fff;
|
| 510 |
+
|
| 511 |
+
/* Text Colors */
|
| 512 |
+
--text-primary: #2d2250;
|
| 513 |
+
--text-secondary: #555;
|
| 514 |
+
|
| 515 |
+
/* Character Selection Colors */
|
| 516 |
+
--character-selected-border: #6c5ce7;
|
| 517 |
+
--character-selected-bg: rgba(162, 155, 254, 0.13);
|
| 518 |
+
|
| 519 |
+
/* Additional Purple Theme Variables */
|
| 520 |
+
--button-primary-bg: var(--primary-color);
|
| 521 |
+
--button-primary-text: var(--text-on-primary);
|
| 522 |
+
--button-primary-hover-bg: var(--accent-color);
|
| 523 |
+
--button-secondary-bg: rgba(255, 255, 255, 0.1);
|
| 524 |
+
--button-secondary-text: var(--text-on-background);
|
| 525 |
+
--guide-step-number-color: var(--primary-color);
|
| 526 |
+
--guide-step-number-bg: rgba(162, 155, 254, var(--hover-opacity));
|
| 527 |
+
--waiting-indicator-color: var(--primary-color);
|
| 528 |
+
--progress-fill-bg: linear-gradient(90deg, var(--primary-color), var(--accent-color));
|
| 529 |
+
}
|
| 530 |
+
|
| 531 |
+
[data-theme="green"] {
|
| 532 |
+
--primary-color: #27ae60;
|
| 533 |
+
--primary-rgb: 39, 174, 96;
|
| 534 |
+
--secondary-color: #2ecc71;
|
| 535 |
+
--accent-color: #16a085;
|
| 536 |
+
--background-overlay: rgba(39, 174, 96, 0.15);
|
| 537 |
+
--gradient-start: #27ae60;
|
| 538 |
+
--gradient-end: #16a085;
|
| 539 |
+
--text-glow: 0 0 10px rgba(39, 174, 96, 0.5);
|
| 540 |
+
--button-hover: rgba(39, 174, 96, 0.3);
|
| 541 |
+
--modal-title-color: #1a3d2e;
|
| 542 |
+
--switch-color: #27ae60;
|
| 543 |
+
|
| 544 |
+
/* Contrast and Accessibility for Green Theme */
|
| 545 |
+
--text-on-primary: #ffffff;
|
| 546 |
+
--text-on-secondary: #1a3d2e;
|
| 547 |
+
--text-on-accent: #ffffff;
|
| 548 |
+
--text-on-background: #ffffff;
|
| 549 |
+
|
| 550 |
+
/* UI Component Colors */
|
| 551 |
+
--chat-bg: rgba(39, 174, 96, 0.9);
|
| 552 |
+
--chat-border: #27ae60;
|
| 553 |
+
--chat-text: var(--text-on-primary);
|
| 554 |
+
--chat-message-user-bg: #27ae60;
|
| 555 |
+
--chat-message-user-text: var(--text-on-primary);
|
| 556 |
+
--chat-message-kimi-bg: rgba(255, 255, 255, 0.15);
|
| 557 |
+
--chat-message-kimi-text: var(--text-on-background);
|
| 558 |
+
--input-border: #27ae60;
|
| 559 |
+
--input-focus-border: #16a085;
|
| 560 |
+
--input-text: var(--text-on-background);
|
| 561 |
+
|
| 562 |
+
/* Modal & Overlay Colors */
|
| 563 |
+
--modal-bg: rgba(39, 174, 96, 0.95);
|
| 564 |
+
--modal-border: #27ae60;
|
| 565 |
+
--modal-header-bg: linear-gradient(135deg, #27ae60, #2ecc71);
|
| 566 |
+
--modal-text: var(--text-on-primary);
|
| 567 |
+
|
| 568 |
+
/* Settings Panel & Tabs */
|
| 569 |
+
--settings-text: var(--text-on-background);
|
| 570 |
+
--settings-tab-active-bg: #27ae60;
|
| 571 |
+
--settings-tab-active-color: var(--text-on-primary);
|
| 572 |
+
--settings-section-border: #27ae60;
|
| 573 |
+
--settings-tab-border: #27ae60;
|
| 574 |
+
--settings-section-header-color: var(--text-on-background);
|
| 575 |
+
|
| 576 |
+
/* Button Colors */
|
| 577 |
+
--button-primary-bg: var(--primary-color);
|
| 578 |
+
--button-primary-text: var(--text-on-primary);
|
| 579 |
+
--button-primary-hover-bg: var(--accent-color);
|
| 580 |
+
--button-secondary-bg: rgba(255, 255, 255, 0.1);
|
| 581 |
+
--button-secondary-text: var(--text-on-background);
|
| 582 |
+
|
| 583 |
+
/* Slider Components */
|
| 584 |
+
--slider-value-border: #16a085;
|
| 585 |
+
--slider-thumb-bg: var(--primary-color);
|
| 586 |
+
--slider-thumb-hover-bg: var(--accent-color);
|
| 587 |
+
|
| 588 |
+
/* Toggle Switch */
|
| 589 |
+
--switch-bg-active: linear-gradient(90deg, #27ae60, #16a085);
|
| 590 |
+
|
| 591 |
+
/* Mic Button & Pulse Effect */
|
| 592 |
+
--mic-button-border: #27ae60;
|
| 593 |
+
--mic-button-shadow: 0 0 15px #27ae60;
|
| 594 |
+
--mic-button-hover-bg: #27ae60;
|
| 595 |
+
--mic-button-hover-shadow: 0 0 10px rgba(39, 174, 96, 0.5);
|
| 596 |
+
--mic-button-icon-color: var(--text-on-primary);
|
| 597 |
+
--mic-listening-border: #16a085;
|
| 598 |
+
--mic-listening-shadow: 0 0 15px #16a085;
|
| 599 |
+
--mic-pulse-color: rgba(22, 160, 133, 0.5);
|
| 600 |
+
--mic-pulse-listening-color: rgba(22, 160, 133, 0.4);
|
| 601 |
+
|
| 602 |
+
/* Cards & Stats */
|
| 603 |
+
--card-text: var(--text-on-background);
|
| 604 |
+
--stat-value-color: #16a085;
|
| 605 |
+
|
| 606 |
+
/* Plugin Cards */
|
| 607 |
+
--plugin-card-border: #27ae60;
|
| 608 |
+
--plugin-card-title-color: var(--text-on-background);
|
| 609 |
+
--plugin-card-desc-color: #e0cfe6;
|
| 610 |
+
--plugin-card-author-color: #bfa6b6;
|
| 611 |
+
--plugin-type-badge-bg: #16a085;
|
| 612 |
+
--plugin-type-badge-text: var(--text-on-accent);
|
| 613 |
+
--plugin-active-badge-bg: linear-gradient(90deg, #27ae60, #16a085);
|
| 614 |
+
--plugin-active-badge-text: var(--text-on-primary);
|
| 615 |
+
|
| 616 |
+
/* Help Modal */
|
| 617 |
+
--creator-name-color: #16a085;
|
| 618 |
+
--creator-avatar-bg: linear-gradient(135deg, #27ae60, #2ecc71);
|
| 619 |
+
--creator-role-bg: linear-gradient(135deg, #27ae60, #2ecc71);
|
| 620 |
+
--creator-role-color: var(--text-on-secondary);
|
| 621 |
+
--philosophy-bg: rgba(39, 174, 96, 0.1);
|
| 622 |
+
--philosophy-border: rgba(39, 174, 96, var(--border-opacity));
|
| 623 |
+
--philosophy-border-left: #27ae60;
|
| 624 |
+
--feature-icon-color: #147190;
|
| 625 |
+
--feature-title-color: #147190;
|
| 626 |
+
--guide-step-number-color: var(--primary-color);
|
| 627 |
+
--guide-step-number-bg: rgba(39, 174, 96, var(--hover-opacity));
|
| 628 |
+
|
| 629 |
+
/* Select Options */
|
| 630 |
+
--select-option-hover-bg: #27ae60;
|
| 631 |
+
--select-option-checked-bg: #16a085;
|
| 632 |
+
|
| 633 |
+
/* Model Colors */
|
| 634 |
+
--model-strength-color: #27ae60;
|
| 635 |
+
--model-strength-text: var(--text-on-primary);
|
| 636 |
+
--model-provider-color: #2ecc71;
|
| 637 |
+
--model-provider-text: var(--text-on-primary);
|
| 638 |
+
|
| 639 |
+
/* Text Colors */
|
| 640 |
+
--text-primary: var(--text-on-background);
|
| 641 |
+
--text-secondary: #4e6151;
|
| 642 |
+
--text-muted: rgba(255, 255, 255, 0.6);
|
| 643 |
+
|
| 644 |
+
/* Character Selection Colors */
|
| 645 |
+
--character-selected-border: #16a085;
|
| 646 |
+
--character-selected-bg: rgba(39, 174, 96, 0.13);
|
| 647 |
+
--character-name-color: var(--text-on-background);
|
| 648 |
+
|
| 649 |
+
/* Additional Green Theme Variables */
|
| 650 |
+
--waiting-indicator-color: var(--primary-color);
|
| 651 |
+
--loading-spinner-color: var(--primary-color);
|
| 652 |
+
--progress-fill-bg: linear-gradient(90deg, var(--primary-color), var(--accent-color));
|
| 653 |
+
}
|
| 654 |
+
|
| 655 |
+
[data-theme="dark"] {
|
| 656 |
+
--primary-color: #5e60ce;
|
| 657 |
+
--primary-rgb: 94, 96, 206;
|
| 658 |
+
--secondary-color: #23262f;
|
| 659 |
+
--accent-color: #8b5cf6;
|
| 660 |
+
--background-overlay: rgba(24, 26, 32, 0.85);
|
| 661 |
+
--gradient-start: #5e60ce;
|
| 662 |
+
--gradient-end: #8b5cf6;
|
| 663 |
+
--text-glow: 0 0 10px rgba(94, 96, 206, 0.3);
|
| 664 |
+
--button-hover: rgba(94, 96, 206, 0.15);
|
| 665 |
+
--modal-title-color: #e0e0e0;
|
| 666 |
+
--switch-color: #5e60ce;
|
| 667 |
+
|
| 668 |
+
/* Contrast and Accessibility for Dark Theme */
|
| 669 |
+
--text-on-primary: #ffffff;
|
| 670 |
+
--text-on-secondary: #e0e0e0;
|
| 671 |
+
--text-on-accent: #ffffff;
|
| 672 |
+
--text-on-background: #e0e0e0;
|
| 673 |
+
|
| 674 |
+
/* UI Component Colors */
|
| 675 |
+
--chat-bg: rgba(24, 26, 32, 0.95);
|
| 676 |
+
--chat-border: #5e60ce;
|
| 677 |
+
--chat-text: var(--text-on-background);
|
| 678 |
+
--chat-message-user-bg: #1e253c;
|
| 679 |
+
--chat-message-user-text: var(--text-on-background);
|
| 680 |
+
--chat-message-kimi-bg: #23262f;
|
| 681 |
+
--chat-message-kimi-text: var(--text-on-background);
|
| 682 |
+
--input-border: #5e60ce;
|
| 683 |
+
--input-focus-border: #8b5cf6;
|
| 684 |
+
--input-text: var(--text-on-background);
|
| 685 |
+
|
| 686 |
+
/* Modal & Overlay Colors */
|
| 687 |
+
--modal-bg: rgba(24, 26, 32, 0.98);
|
| 688 |
+
--modal-border: #5e60ce;
|
| 689 |
+
--modal-header-bg: linear-gradient(135deg, #5e60ce, #8b5cf6);
|
| 690 |
+
--modal-text: var(--text-on-background);
|
| 691 |
+
|
| 692 |
+
/* Settings Panel & Tabs */
|
| 693 |
+
--settings-bg: #0f1114;
|
| 694 |
+
--settings-text: var(--text-on-background);
|
| 695 |
+
--settings-tab-bg: #181a20;
|
| 696 |
+
--settings-tab-active-bg: #5e60ce;
|
| 697 |
+
--settings-tab-active-color: var(--text-on-primary);
|
| 698 |
+
--settings-section-bg: #1a1d23;
|
| 699 |
+
--settings-section-border: rgba(94, 96, 206, var(--border-opacity));
|
| 700 |
+
--settings-tab-border: #5e60ce;
|
| 701 |
+
--settings-section-header-color: var(--text-on-background);
|
| 702 |
+
|
| 703 |
+
/* Button Colors */
|
| 704 |
+
--button-primary-bg: var(--primary-color);
|
| 705 |
+
--button-primary-text: var(--text-on-primary);
|
| 706 |
+
--button-primary-hover-bg: var(--accent-color);
|
| 707 |
+
--button-secondary-bg: rgba(255, 255, 255, 0.05);
|
| 708 |
+
--button-secondary-text: var(--text-on-background);
|
| 709 |
+
|
| 710 |
+
/* Slider Components */
|
| 711 |
+
--slider-value-bg: #0f1114;
|
| 712 |
+
--slider-value-border: #5e60ce;
|
| 713 |
+
--slider-value-color: var(--text-on-background);
|
| 714 |
+
--slider-thumb-bg: var(--primary-color);
|
| 715 |
+
--slider-thumb-hover-bg: var(--accent-color);
|
| 716 |
+
|
| 717 |
+
/* Toggle Switch */
|
| 718 |
+
--switch-bg-inactive: rgba(255, 255, 255, 0.05);
|
| 719 |
+
--switch-bg-active: linear-gradient(90deg, #5e60ce, #8b5cf6);
|
| 720 |
+
|
| 721 |
+
/* Mic Button & Pulse Effect */
|
| 722 |
+
--mic-button-border: #5e60ce;
|
| 723 |
+
--mic-button-shadow: 0 0 15px #5e60ce;
|
| 724 |
+
--mic-button-hover-bg: #5e60ce;
|
| 725 |
+
--mic-button-hover-shadow: 0 0 10px rgba(94, 96, 206, 0.5);
|
| 726 |
+
--mic-button-icon-color: var(--text-on-primary);
|
| 727 |
+
--mic-listening-border: #8b5cf6;
|
| 728 |
+
--mic-listening-shadow: 0 0 15px #8b5cf6;
|
| 729 |
+
--mic-pulse-color: rgba(139, 92, 246, 0.5);
|
| 730 |
+
--mic-pulse-listening-color: rgba(139, 92, 246, 0.4);
|
| 731 |
+
|
| 732 |
+
/* Cards & Stats */
|
| 733 |
+
--card-bg: rgba(255, 255, 255, 0.02);
|
| 734 |
+
--card-border: rgba(255, 255, 255, 0.05);
|
| 735 |
+
--card-hover-bg: rgba(255, 255, 255, 0.05);
|
| 736 |
+
--card-text: var(--text-on-background);
|
| 737 |
+
--stat-value-color: #8b5cf6;
|
| 738 |
+
|
| 739 |
+
/* Plugin Cards */
|
| 740 |
+
--plugin-card-bg: linear-gradient(135deg, #1a1d23 80%, rgba(24, 26, 32, 0.98) 100%);
|
| 741 |
+
--plugin-card-border: #5e60ce;
|
| 742 |
+
--plugin-card-title-color: var(--text-on-background);
|
| 743 |
+
--plugin-card-desc-color: rgba(224, 224, 224, 0.7);
|
| 744 |
+
--plugin-card-author-color: rgba(224, 224, 224, 0.5);
|
| 745 |
+
--plugin-type-badge-bg: #8b5cf6;
|
| 746 |
+
--plugin-type-badge-text: var(--text-on-accent);
|
| 747 |
+
--plugin-active-badge-bg: linear-gradient(90deg, #5e60ce, #8b5cf6);
|
| 748 |
+
--plugin-active-badge-text: var(--text-on-primary);
|
| 749 |
+
|
| 750 |
+
/* Help Modal */
|
| 751 |
+
--help-modal-bg: rgba(24, 26, 32, 0.98);
|
| 752 |
+
--help-modal-border: #5e60ce;
|
| 753 |
+
--creator-card-bg: rgba(255, 255, 255, 0.02);
|
| 754 |
+
--creator-card-border: rgba(255, 255, 255, 0.05);
|
| 755 |
+
--creator-name-color: #8b5cf6;
|
| 756 |
+
--creator-avatar-bg: linear-gradient(135deg, #5e60ce, #8b5cf6);
|
| 757 |
+
--creator-role-bg: linear-gradient(135deg, #5e60ce, #8b5cf6);
|
| 758 |
+
--creator-role-color: var(--text-on-primary);
|
| 759 |
+
--philosophy-bg: rgba(94, 96, 206, 0.05);
|
| 760 |
+
--philosophy-border: rgba(94, 96, 206, var(--border-opacity));
|
| 761 |
+
--philosophy-border-left: #5e60ce;
|
| 762 |
+
--feature-item-bg: rgba(255, 255, 255, 0.02);
|
| 763 |
+
--feature-item-border: rgba(94, 96, 206, var(--border-opacity));
|
| 764 |
+
--feature-icon-color: #8b5cf6;
|
| 765 |
+
--feature-title-color: #8b5cf6;
|
| 766 |
+
--feature-text-color: rgba(224, 224, 224, 0.7);
|
| 767 |
+
--guide-step-bg: rgba(255, 255, 255, 0.02);
|
| 768 |
+
--guide-step-number-color: var(--primary-color);
|
| 769 |
+
--guide-step-number-bg: rgba(94, 96, 206, var(--hover-opacity));
|
| 770 |
+
--tip-item-bg: rgba(255, 255, 255, 0.02);
|
| 771 |
+
|
| 772 |
+
/* Select Options */
|
| 773 |
+
--select-option-bg: rgba(24, 26, 32, 0.95);
|
| 774 |
+
--select-option-text: var(--text-on-background);
|
| 775 |
+
--select-option-hover-bg: #5e60ce;
|
| 776 |
+
--select-option-checked-bg: #8b5cf6;
|
| 777 |
+
|
| 778 |
+
/* Model Colors */
|
| 779 |
+
--model-card-bg: rgba(255, 255, 255, 0.02);
|
| 780 |
+
--model-card-border: rgba(255, 255, 255, 0.05);
|
| 781 |
+
--model-card-hover-bg: rgba(255, 255, 255, 0.05);
|
| 782 |
+
--model-card-selected-border: var(--primary-color);
|
| 783 |
+
--model-name-color: var(--text-on-background);
|
| 784 |
+
--model-description-color: rgba(224, 224, 224, 0.7);
|
| 785 |
+
--model-strength-color: #5e60ce;
|
| 786 |
+
--model-strength-text: var(--text-on-primary);
|
| 787 |
+
--model-provider-color: #8b5cf6;
|
| 788 |
+
--model-provider-text: var(--text-on-accent);
|
| 789 |
+
|
| 790 |
+
/* Text Colors */
|
| 791 |
+
--text-primary: var(--text-on-background);
|
| 792 |
+
--text-secondary: #9ca3af;
|
| 793 |
+
--text-muted: rgba(224, 224, 224, 0.6);
|
| 794 |
+
|
| 795 |
+
/* Character Selection Colors */
|
| 796 |
+
--character-card: rgba(255, 255, 255, 0.02);
|
| 797 |
+
--character-card-border: rgba(255, 255, 255, 0.05);
|
| 798 |
+
--character-card-hover-bg: rgba(255, 255, 255, 0.05);
|
| 799 |
+
--character-selected-border: #8b5cf6;
|
| 800 |
+
--character-selected-bg: rgba(94, 96, 206, 0.1);
|
| 801 |
+
--character-name-color: var(--text-on-background);
|
| 802 |
+
|
| 803 |
+
/* Additional Dark Theme Variables */
|
| 804 |
+
--waiting-indicator-color: var(--primary-color);
|
| 805 |
+
--loading-spinner-color: var(--primary-color);
|
| 806 |
+
--progress-bg: rgba(255, 255, 255, 0.05);
|
| 807 |
+
--progress-fill-bg: linear-gradient(90deg, var(--primary-color), var(--accent-color));
|
| 808 |
+
--progress-text-color: var(--text-on-background);
|
| 809 |
+
--transcript-bg: rgba(0, 0, 0, 0.9);
|
| 810 |
+
--transcript-text: var(--text-on-background);
|
| 811 |
+
--transcript-border: var(--primary-color);
|
| 812 |
+
}
|
| 813 |
+
|
| 814 |
+
/* ===== ANIMATION MANAGEMENT ===== */
|
| 815 |
+
/* Respect user's reduced motion preference. When user requests reduced motion,
|
| 816 |
+
disable animations and transitions globally while preserving critical
|
| 817 |
+
animations (mic button, loading screen). This preserves accessibility
|
| 818 |
+
without relying on a UI toggle. */
|
| 819 |
+
@media (prefers-reduced-motion: reduce) {
|
| 820 |
+
*,
|
| 821 |
+
*::before,
|
| 822 |
+
*::after {
|
| 823 |
+
animation: none !important;
|
| 824 |
+
transition: none !important;
|
| 825 |
+
}
|
| 826 |
+
|
| 827 |
+
/* Keep mic button animations even when reduced-motion is requested */
|
| 828 |
+
.mic-button,
|
| 829 |
+
.mic-button *,
|
| 830 |
+
.mic-button::after {
|
| 831 |
+
animation: revert !important;
|
| 832 |
+
transition: revert !important;
|
| 833 |
+
}
|
| 834 |
+
|
| 835 |
+
/* Keep loading screen animations */
|
| 836 |
+
#loading-screen,
|
| 837 |
+
#loading-screen * {
|
| 838 |
+
animation: revert !important;
|
| 839 |
+
transition: revert !important;
|
| 840 |
+
}
|
| 841 |
+
}
|
| 842 |
+
|
| 843 |
+
/* Ensure critical hover effects remain functional */
|
| 844 |
+
body.animations-enabled .kimi-button:hover,
|
| 845 |
+
body.animations-enabled .control-button-unified:hover {
|
| 846 |
+
transform: translateY(-2px);
|
| 847 |
+
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
| 848 |
+
}
|
| 849 |
+
|
| 850 |
+
body.animations-enabled .mic-button:hover {
|
| 851 |
+
transform: scale(1.1);
|
| 852 |
+
transition: all 0.2s ease;
|
| 853 |
+
}
|
| 854 |
+
|
| 855 |
+
/* ===== LOADING SCREEN ===== */
|
| 856 |
+
#loading-screen {
|
| 857 |
+
position: fixed;
|
| 858 |
+
top: 0;
|
| 859 |
+
left: 0;
|
| 860 |
+
width: 100%;
|
| 861 |
+
height: 100%;
|
| 862 |
+
background-color: var(--background-primary, #1a1a1a);
|
| 863 |
+
z-index: 10000;
|
| 864 |
+
display: flex;
|
| 865 |
+
justify-content: center;
|
| 866 |
+
align-items: center;
|
| 867 |
+
opacity: 1;
|
| 868 |
+
transition: opacity 0.5s ease-out;
|
| 869 |
+
}
|
| 870 |
+
|
| 871 |
+
#loading-screen img {
|
| 872 |
+
max-width: 200px;
|
| 873 |
+
max-height: 200px;
|
| 874 |
+
animation: loadingPulse 2s infinite ease-in-out;
|
| 875 |
+
}
|
| 876 |
+
|
| 877 |
+
@keyframes loadingPulse {
|
| 878 |
+
0%,
|
| 879 |
+
100% {
|
| 880 |
+
opacity: 0.7;
|
| 881 |
+
transform: scale(1);
|
| 882 |
+
}
|
| 883 |
+
50% {
|
| 884 |
+
opacity: 1;
|
| 885 |
+
transform: scale(1.05);
|
| 886 |
+
}
|
| 887 |
+
}
|
| 888 |
+
|
| 889 |
+
/* ===== GLOBAL STYLES ===== */
|
| 890 |
+
* {
|
| 891 |
+
margin: 0;
|
| 892 |
+
padding: 0;
|
| 893 |
+
box-sizing: border-box;
|
| 894 |
+
}
|
| 895 |
+
|
| 896 |
+
html,
|
| 897 |
+
body {
|
| 898 |
+
height: 100%;
|
| 899 |
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
| 900 |
+
color: white;
|
| 901 |
+
overflow: hidden;
|
| 902 |
+
}
|
| 903 |
+
|
| 904 |
+
/* === Global Help Button (compressed) === */
|
| 905 |
+
.top-right-buttons {
|
| 906 |
+
position: fixed;
|
| 907 |
+
top: 5px;
|
| 908 |
+
right: 5px;
|
| 909 |
+
display: flex;
|
| 910 |
+
gap: 5px;
|
| 911 |
+
z-index: 1000;
|
| 912 |
+
pointer-events: none;
|
| 913 |
+
}
|
| 914 |
+
|
| 915 |
+
/* Configurable design tokens for quick tuning */
|
| 916 |
+
#global-help-button {
|
| 917 |
+
--help-btn-bg-alpha: 0.12; /* base background alpha */
|
| 918 |
+
--help-btn-bg-hover-alpha: 0.2; /* hover/focus background alpha */
|
| 919 |
+
--help-btn-bg-active-alpha: 0.25; /* active press background alpha */
|
| 920 |
+
--help-btn-icon-alpha: 0.3; /* base icon opacity */
|
| 921 |
+
--help-btn-icon-hover-alpha: 0.65; /* hover/focus icon opacity */
|
| 922 |
+
--help-btn-icon-active-alpha: 0.8; /* active icon opacity */
|
| 923 |
+
--help-btn-border-alpha: 0.28; /* border alpha */
|
| 924 |
+
--help-btn-shadow-base: 0 2px 6px rgba(var(--primary-rgb), 0.12);
|
| 925 |
+
--help-btn-shadow-hover: 0 4px 16px rgba(var(--primary-rgb), 0.28);
|
| 926 |
+
width: 36px;
|
| 927 |
+
height: 36px;
|
| 928 |
+
pointer-events: auto;
|
| 929 |
+
background: rgba(var(--primary-rgb), var(--help-btn-bg-alpha));
|
| 930 |
+
border: 1px solid rgba(var(--primary-rgb), var(--help-btn-border-alpha));
|
| 931 |
+
box-shadow: var(--help-btn-shadow-base);
|
| 932 |
+
backdrop-filter: blur(9px) saturate(115%);
|
| 933 |
+
opacity: 0.26;
|
| 934 |
+
display: flex;
|
| 935 |
+
align-items: center;
|
| 936 |
+
justify-content: center;
|
| 937 |
+
transition:
|
| 938 |
+
background 0.35s ease,
|
| 939 |
+
box-shadow 0.35s ease,
|
| 940 |
+
transform 0.35s ease,
|
| 941 |
+
opacity 0.4s ease;
|
| 942 |
+
}
|
| 943 |
+
#global-help-button i {
|
| 944 |
+
font-size: 1.2rem;
|
| 945 |
+
opacity: var(--help-btn-icon-alpha);
|
| 946 |
+
transition:
|
| 947 |
+
opacity 0.4s ease,
|
| 948 |
+
transform 0.35s ease;
|
| 949 |
+
transform: scale(0.9);
|
| 950 |
+
}
|
| 951 |
+
|
| 952 |
+
/* Hover + focus + container hover unify */
|
| 953 |
+
#global-help-button:hover,
|
| 954 |
+
#global-help-button:focus-visible,
|
| 955 |
+
.top-right-buttons:hover #global-help-button {
|
| 956 |
+
background: rgba(var(--primary-rgb), var(--help-btn-bg-hover-alpha));
|
| 957 |
+
box-shadow: var(--help-btn-shadow-hover);
|
| 958 |
+
opacity: 1;
|
| 959 |
+
}
|
| 960 |
+
|
| 961 |
+
#global-help-button:hover i,
|
| 962 |
+
#global-help-button:focus-visible i,
|
| 963 |
+
.top-right-buttons:hover #global-help-button i {
|
| 964 |
+
opacity: var(--help-btn-icon-hover-alpha);
|
| 965 |
+
transform: scale(1);
|
| 966 |
+
}
|
| 967 |
+
|
| 968 |
+
#global-help-button:active {
|
| 969 |
+
background: rgba(var(--primary-rgb), var(--help-btn-bg-active-alpha));
|
| 970 |
+
transform: scale(0.95);
|
| 971 |
+
}
|
| 972 |
+
#global-help-button:active i {
|
| 973 |
+
opacity: var(--help-btn-icon-active-alpha);
|
| 974 |
+
}
|
| 975 |
+
#global-help-button:focus-visible {
|
| 976 |
+
outline: 2px solid rgba(var(--primary-rgb), 0.6);
|
| 977 |
+
outline-offset: 2px;
|
| 978 |
+
}
|
| 979 |
+
|
| 980 |
+
.video-container {
|
| 981 |
+
position: fixed;
|
| 982 |
+
top: 0;
|
| 983 |
+
left: 0;
|
| 984 |
+
width: 100%;
|
| 985 |
+
height: 100%;
|
| 986 |
+
z-index: -1;
|
| 987 |
+
background-color: #1a1a1a;
|
| 988 |
+
}
|
| 989 |
+
|
| 990 |
+
.bg-video.active {
|
| 991 |
+
opacity: 1;
|
| 992 |
+
}
|
| 993 |
+
|
| 994 |
+
.bg-video {
|
| 995 |
+
position: absolute;
|
| 996 |
+
top: 0;
|
| 997 |
+
left: 0;
|
| 998 |
+
width: 100%;
|
| 999 |
+
height: 100%;
|
| 1000 |
+
object-fit: contain;
|
| 1001 |
+
opacity: 0;
|
| 1002 |
+
transition: opacity var(--video-fade-duration) cubic-bezier(0.4, 0, 0.2, 1);
|
| 1003 |
+
background-color: #1a1a1a;
|
| 1004 |
+
will-change: opacity;
|
| 1005 |
+
backface-visibility: hidden;
|
| 1006 |
+
}
|
| 1007 |
+
|
| 1008 |
+
.bg-video.transitioning {
|
| 1009 |
+
opacity: 0;
|
| 1010 |
+
transition: opacity var(--video-fade-duration) cubic-bezier(0.4, 0, 0.2, 1);
|
| 1011 |
+
pointer-events: none;
|
| 1012 |
+
}
|
| 1013 |
+
|
| 1014 |
+
.content-overlay {
|
| 1015 |
+
position: relative;
|
| 1016 |
+
height: 100vh;
|
| 1017 |
+
width: 100%;
|
| 1018 |
+
display: flex;
|
| 1019 |
+
flex-direction: column;
|
| 1020 |
+
justify-content: flex-end;
|
| 1021 |
+
align-items: center;
|
| 1022 |
+
padding: 20px;
|
| 1023 |
+
background-color: var(--background-overlay);
|
| 1024 |
+
opacity: var(--interface-opacity);
|
| 1025 |
+
z-index: 1;
|
| 1026 |
+
}
|
| 1027 |
+
|
| 1028 |
+
.top-bar {
|
| 1029 |
+
width: 100%;
|
| 1030 |
+
max-width: 500px;
|
| 1031 |
+
text-align: center;
|
| 1032 |
+
margin-top: 10px;
|
| 1033 |
+
}
|
| 1034 |
+
|
| 1035 |
+
.top-bar label {
|
| 1036 |
+
font-size: 1rem;
|
| 1037 |
+
font-weight: 600;
|
| 1038 |
+
text-shadow: 1px 1px 3px rgba(0, 0, 0, 0.5);
|
| 1039 |
+
margin-bottom: 8px;
|
| 1040 |
+
display: block;
|
| 1041 |
+
}
|
| 1042 |
+
|
| 1043 |
+
.progress-container {
|
| 1044 |
+
width: 100%;
|
| 1045 |
+
height: 12px;
|
| 1046 |
+
background-color: var(--progress-bg);
|
| 1047 |
+
border-radius: 10px;
|
| 1048 |
+
overflow: hidden;
|
| 1049 |
+
}
|
| 1050 |
+
|
| 1051 |
+
.progress-fill {
|
| 1052 |
+
height: 100%;
|
| 1053 |
+
width: 50%; /* Changed from 65% to match new default favorability level */
|
| 1054 |
+
background: var(--progress-fill-bg);
|
| 1055 |
+
border-radius: 10px;
|
| 1056 |
+
transition: width 0.5s ease-in-out;
|
| 1057 |
+
box-shadow: var(--text-glow);
|
| 1058 |
+
}
|
| 1059 |
+
|
| 1060 |
+
/* Center content styles can be added here if needed */
|
| 1061 |
+
|
| 1062 |
+
.transcript-container {
|
| 1063 |
+
position: absolute;
|
| 1064 |
+
bottom: 180px;
|
| 1065 |
+
left: 50%;
|
| 1066 |
+
transform: translateX(-50%);
|
| 1067 |
+
width: 80%;
|
| 1068 |
+
max-width: 580px;
|
| 1069 |
+
min-width: 280px;
|
| 1070 |
+
max-height: 400px;
|
| 1071 |
+
min-height: 100px;
|
| 1072 |
+
padding: 15px;
|
| 1073 |
+
background: var(--transcript-bg);
|
| 1074 |
+
backdrop-filter: blur(10px);
|
| 1075 |
+
border-radius: 10px;
|
| 1076 |
+
border: 1px solid var(--transcript-border);
|
| 1077 |
+
text-align: center;
|
| 1078 |
+
opacity: 0;
|
| 1079 |
+
transition: opacity 0.3s ease-in-out;
|
| 1080 |
+
pointer-events: none;
|
| 1081 |
+
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
|
| 1082 |
+
overflow-y: auto;
|
| 1083 |
+
overflow-x: hidden;
|
| 1084 |
+
z-index: 100;
|
| 1085 |
+
}
|
| 1086 |
+
|
| 1087 |
+
.transcript-container.visible {
|
| 1088 |
+
opacity: 1;
|
| 1089 |
+
pointer-events: auto;
|
| 1090 |
+
}
|
| 1091 |
+
|
| 1092 |
+
/* Custom scrollbar for transcript container */
|
| 1093 |
+
.transcript-container::-webkit-scrollbar {
|
| 1094 |
+
width: var(--scrollbar-width);
|
| 1095 |
+
}
|
| 1096 |
+
|
| 1097 |
+
.transcript-container::-webkit-scrollbar-track {
|
| 1098 |
+
background: var(--scrollbar-track-bg);
|
| 1099 |
+
border-radius: 4px;
|
| 1100 |
+
}
|
| 1101 |
+
|
| 1102 |
+
.transcript-container::-webkit-scrollbar-thumb {
|
| 1103 |
+
background: var(--scrollbar-thumb-bg);
|
| 1104 |
+
border-radius: 4px;
|
| 1105 |
+
transition: background 0.3s ease;
|
| 1106 |
+
}
|
| 1107 |
+
|
| 1108 |
+
.transcript-container::-webkit-scrollbar-thumb:hover {
|
| 1109 |
+
background: var(--scrollbar-thumb-hover-bg);
|
| 1110 |
+
}
|
| 1111 |
+
|
| 1112 |
+
#transcript {
|
| 1113 |
+
font-size: 1.2rem;
|
| 1114 |
+
color: var(--transcript-text);
|
| 1115 |
+
text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.7);
|
| 1116 |
+
margin: 0;
|
| 1117 |
+
line-height: 1.3;
|
| 1118 |
+
text-align: left;
|
| 1119 |
+
}
|
| 1120 |
+
|
| 1121 |
+
/* ===== ACCESSIBILITY - FOCUS STYLES ===== */
|
| 1122 |
+
select:focus,
|
| 1123 |
+
input:focus,
|
| 1124 |
+
button:focus,
|
| 1125 |
+
.kimi-slider:focus,
|
| 1126 |
+
.kimi-slider-unified:focus {
|
| 1127 |
+
box-shadow: 0 0 0 2px var(--primary-pink);
|
| 1128 |
+
border-color: var(--primary-pink);
|
| 1129 |
+
}
|
| 1130 |
+
|
| 1131 |
+
.control-button-unified:focus {
|
| 1132 |
+
outline: 2px solid var(--primary-pink);
|
| 1133 |
+
outline-offset: 2px;
|
| 1134 |
+
}
|
| 1135 |
+
|
| 1136 |
+
/* ===== CHAT INTERFACE ===== */
|
| 1137 |
+
.chat-container {
|
| 1138 |
+
position: fixed;
|
| 1139 |
+
top: 20px;
|
| 1140 |
+
right: 20px;
|
| 1141 |
+
z-index: 1000;
|
| 1142 |
+
width: 400px;
|
| 1143 |
+
max-width: calc(100vw - 40px);
|
| 1144 |
+
height: 600px;
|
| 1145 |
+
max-height: 80vh;
|
| 1146 |
+
background: var(--chat-bg);
|
| 1147 |
+
backdrop-filter: blur(20px);
|
| 1148 |
+
border-radius: 15px;
|
| 1149 |
+
border: 1px solid var(--chat-border);
|
| 1150 |
+
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
|
| 1151 |
+
display: none;
|
| 1152 |
+
flex-direction: column;
|
| 1153 |
+
overflow: hidden;
|
| 1154 |
+
transform: translateX(400px);
|
| 1155 |
+
opacity: 0;
|
| 1156 |
+
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
|
| 1157 |
+
}
|
| 1158 |
+
|
| 1159 |
+
.chat-container.visible {
|
| 1160 |
+
display: flex;
|
| 1161 |
+
transform: translateX(0);
|
| 1162 |
+
opacity: 1;
|
| 1163 |
+
}
|
| 1164 |
+
|
| 1165 |
+
.chat-header {
|
| 1166 |
+
background: var(--chat-header-bg);
|
| 1167 |
+
padding: 15px 20px;
|
| 1168 |
+
display: flex;
|
| 1169 |
+
justify-content: space-between;
|
| 1170 |
+
align-items: center;
|
| 1171 |
+
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
| 1172 |
+
}
|
| 1173 |
+
|
| 1174 |
+
.chat-header h3 {
|
| 1175 |
+
margin: 0;
|
| 1176 |
+
color: var(--chat-text);
|
| 1177 |
+
font-size: 1.1rem;
|
| 1178 |
+
display: flex;
|
| 1179 |
+
align-items: center;
|
| 1180 |
+
gap: 8px;
|
| 1181 |
+
}
|
| 1182 |
+
|
| 1183 |
+
.chat-messages {
|
| 1184 |
+
flex: 1;
|
| 1185 |
+
padding: 15px;
|
| 1186 |
+
overflow-y: auto;
|
| 1187 |
+
display: flex;
|
| 1188 |
+
flex-direction: column;
|
| 1189 |
+
gap: 10px;
|
| 1190 |
+
}
|
| 1191 |
+
|
| 1192 |
+
.message {
|
| 1193 |
+
max-width: 95%;
|
| 1194 |
+
padding: 12px 16px;
|
| 1195 |
+
border-radius: 18px;
|
| 1196 |
+
font-size: 0.95rem;
|
| 1197 |
+
line-height: 1.3; /* Espacement entre lignes dans un même paragraphe */
|
| 1198 |
+
white-space: normal; /* Plus besoin de pre-line avec les <p> */
|
| 1199 |
+
animation: messageSlideIn 0.3s ease-out;
|
| 1200 |
+
}
|
| 1201 |
+
|
| 1202 |
+
/* Contrôle de l'espacement entre paragraphes (sauts de ligne) */
|
| 1203 |
+
.message p {
|
| 1204 |
+
margin: 0 0 0.8em 0; /* Espacement entre paragraphes */
|
| 1205 |
+
}
|
| 1206 |
+
|
| 1207 |
+
.message p:last-child {
|
| 1208 |
+
margin-bottom: 0; /* Pas d'espacement après le dernier paragraphe */
|
| 1209 |
+
}
|
| 1210 |
+
|
| 1211 |
+
.message.user {
|
| 1212 |
+
align-self: flex-end;
|
| 1213 |
+
background: var(--chat-message-user-bg);
|
| 1214 |
+
color: var(--chat-message-user-text);
|
| 1215 |
+
}
|
| 1216 |
+
|
| 1217 |
+
.message.kimi {
|
| 1218 |
+
align-self: flex-start;
|
| 1219 |
+
background: var(--chat-message-kimi-bg);
|
| 1220 |
+
color: var(--chat-message-kimi-text);
|
| 1221 |
+
}
|
| 1222 |
+
|
| 1223 |
+
.message-time {
|
| 1224 |
+
font-size: 0.75rem;
|
| 1225 |
+
color: rgba(255, 255, 255, 0.6);
|
| 1226 |
+
margin-top: 4px;
|
| 1227 |
+
text-align: right;
|
| 1228 |
+
}
|
| 1229 |
+
|
| 1230 |
+
.delete-message-btn {
|
| 1231 |
+
background: none;
|
| 1232 |
+
border: none;
|
| 1233 |
+
color: rgba(255, 255, 255, 0.4);
|
| 1234 |
+
cursor: pointer;
|
| 1235 |
+
padding: 2px 4px;
|
| 1236 |
+
border-radius: 3px;
|
| 1237 |
+
font-size: 0.7rem;
|
| 1238 |
+
margin-left: 8px;
|
| 1239 |
+
transition: all 0.2s ease;
|
| 1240 |
+
}
|
| 1241 |
+
|
| 1242 |
+
.delete-message-btn:hover {
|
| 1243 |
+
color: #ff4757 !important;
|
| 1244 |
+
background: rgba(255, 71, 87, 0.1) !important;
|
| 1245 |
+
}
|
| 1246 |
+
|
| 1247 |
+
.chat-input-container {
|
| 1248 |
+
padding: 15px 20px;
|
| 1249 |
+
display: flex;
|
| 1250 |
+
gap: 10px;
|
| 1251 |
+
background: rgba(255, 255, 255, 0.05);
|
| 1252 |
+
border-top: 1px solid rgba(255, 255, 255, 0.1);
|
| 1253 |
+
}
|
| 1254 |
+
|
| 1255 |
+
#chat-input {
|
| 1256 |
+
flex: 1;
|
| 1257 |
+
background: var(--chat-input-bg);
|
| 1258 |
+
border: 1px solid rgba(255, 255, 255, 0.2);
|
| 1259 |
+
border-radius: 20px;
|
| 1260 |
+
padding: 10px 15px;
|
| 1261 |
+
color: var(--chat-input-text);
|
| 1262 |
+
font-size: 0.9rem;
|
| 1263 |
+
outline: none;
|
| 1264 |
+
transition: all 0.3s ease;
|
| 1265 |
+
|
| 1266 |
+
/* Make textarea behave like the previous single-line input */
|
| 1267 |
+
box-sizing: border-box;
|
| 1268 |
+
resize: none; /* prevent manual resizing */
|
| 1269 |
+
/* show approximately 2 lines by default, allow up to ~4 lines */
|
| 1270 |
+
min-height: 58px;
|
| 1271 |
+
max-height: 160px; /* allow multi-line but limit growth */
|
| 1272 |
+
line-height: 1.2;
|
| 1273 |
+
overflow: auto;
|
| 1274 |
+
}
|
| 1275 |
+
|
| 1276 |
+
#chat-input::placeholder {
|
| 1277 |
+
color: var(--chat-input-placeholder);
|
| 1278 |
+
}
|
| 1279 |
+
|
| 1280 |
+
#chat-input:focus {
|
| 1281 |
+
border-color: var(--primary-color);
|
| 1282 |
+
box-shadow: 0 0 0 2px rgba(255, 107, 157, 0.2);
|
| 1283 |
+
}
|
| 1284 |
+
|
| 1285 |
+
#send-button {
|
| 1286 |
+
background: var(--primary-color);
|
| 1287 |
+
border: none;
|
| 1288 |
+
border-radius: 20px;
|
| 1289 |
+
width: 40px;
|
| 1290 |
+
height: 40px;
|
| 1291 |
+
display: flex;
|
| 1292 |
+
align-items: center;
|
| 1293 |
+
justify-content: center;
|
| 1294 |
+
color: white;
|
| 1295 |
+
cursor: pointer;
|
| 1296 |
+
transition: all 0.3s ease;
|
| 1297 |
+
}
|
| 1298 |
+
|
| 1299 |
+
#send-button:hover {
|
| 1300 |
+
background: var(--accent-color);
|
| 1301 |
+
transform: scale(1.05);
|
| 1302 |
+
}
|
| 1303 |
+
|
| 1304 |
+
.chat-toggle,
|
| 1305 |
+
.chat-delete {
|
| 1306 |
+
background: none;
|
| 1307 |
+
border: none;
|
| 1308 |
+
color: var(--chat-text);
|
| 1309 |
+
cursor: pointer;
|
| 1310 |
+
padding: 5px;
|
| 1311 |
+
border-radius: 5px;
|
| 1312 |
+
transition: all 0.3s ease;
|
| 1313 |
+
}
|
| 1314 |
+
|
| 1315 |
+
.chat-delete {
|
| 1316 |
+
color: rgba(255, 255, 255, 0.7);
|
| 1317 |
+
}
|
| 1318 |
+
|
| 1319 |
+
.chat-delete:hover {
|
| 1320 |
+
color: #ff4757;
|
| 1321 |
+
background: rgba(255, 71, 87, 0.1);
|
| 1322 |
+
}
|
| 1323 |
+
|
| 1324 |
+
.chat-toggle:hover {
|
| 1325 |
+
background: rgba(255, 255, 255, 0.1);
|
| 1326 |
+
}
|
| 1327 |
+
|
| 1328 |
+
/* ===== UNIFIED BUTTON COMPONENTS ===== */
|
| 1329 |
+
.kimi-button,
|
| 1330 |
+
.control-button-unified {
|
| 1331 |
+
background: var(--button-primary-bg);
|
| 1332 |
+
border: none;
|
| 1333 |
+
border-radius: 8px;
|
| 1334 |
+
color: var(--button-primary-text);
|
| 1335 |
+
padding: 10px 20px;
|
| 1336 |
+
font-size: 0.9rem;
|
| 1337 |
+
font-weight: 500;
|
| 1338 |
+
cursor: pointer;
|
| 1339 |
+
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
| 1340 |
+
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
|
| 1341 |
+
position: relative;
|
| 1342 |
+
overflow: hidden;
|
| 1343 |
+
backdrop-filter: blur(10px);
|
| 1344 |
+
border: 1px solid rgba(255, 255, 255, 0.1);
|
| 1345 |
+
}
|
| 1346 |
+
|
| 1347 |
+
.kimi-button::before,
|
| 1348 |
+
.control-button-unified::before {
|
| 1349 |
+
content: "";
|
| 1350 |
+
position: absolute;
|
| 1351 |
+
top: 0;
|
| 1352 |
+
left: -100%;
|
| 1353 |
+
width: 100%;
|
| 1354 |
+
height: 100%;
|
| 1355 |
+
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
|
| 1356 |
+
transition: left 0.5s ease;
|
| 1357 |
+
}
|
| 1358 |
+
|
| 1359 |
+
.kimi-button:hover,
|
| 1360 |
+
.control-button-unified:hover {
|
| 1361 |
+
transform: translateY(-2px);
|
| 1362 |
+
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.3);
|
| 1363 |
+
background: var(--button-primary-hover-bg);
|
| 1364 |
+
}
|
| 1365 |
+
|
| 1366 |
+
.kimi-button:hover::before,
|
| 1367 |
+
.control-button-unified:hover::before {
|
| 1368 |
+
left: 100%;
|
| 1369 |
+
}
|
| 1370 |
+
|
| 1371 |
+
.kimi-button:active,
|
| 1372 |
+
.control-button-unified:active {
|
| 1373 |
+
transform: translateY(0);
|
| 1374 |
+
transition: all 0.1s ease;
|
| 1375 |
+
}
|
| 1376 |
+
|
| 1377 |
+
.kimi-button:disabled,
|
| 1378 |
+
.control-button-unified:disabled {
|
| 1379 |
+
opacity: 0.5;
|
| 1380 |
+
cursor: not-allowed;
|
| 1381 |
+
transform: none;
|
| 1382 |
+
}
|
| 1383 |
+
|
| 1384 |
+
/* Button Variants */
|
| 1385 |
+
.kimi-button.danger {
|
| 1386 |
+
background: var(--button-danger-bg);
|
| 1387 |
+
color: var(--button-danger-text);
|
| 1388 |
+
}
|
| 1389 |
+
.kimi-button.danger:hover {
|
| 1390 |
+
background: var(--button-danger-hover-bg);
|
| 1391 |
+
}
|
| 1392 |
+
.kimi-button.success {
|
| 1393 |
+
background: linear-gradient(135deg, #26de81, #20bf6b);
|
| 1394 |
+
}
|
| 1395 |
+
.kimi-button.success:hover {
|
| 1396 |
+
background: linear-gradient(135deg, #20bf6b, #26de81);
|
| 1397 |
+
}
|
| 1398 |
+
.kimi-button.secondary {
|
| 1399 |
+
background: var(--button-secondary-bg);
|
| 1400 |
+
color: var(--button-secondary-text);
|
| 1401 |
+
}
|
| 1402 |
+
.kimi-button.secondary:hover {
|
| 1403 |
+
background: var(--button-secondary-hover-bg);
|
| 1404 |
+
}
|
| 1405 |
+
|
| 1406 |
+
/* Circular Control Buttons */
|
| 1407 |
+
.control-button-unified {
|
| 1408 |
+
width: 50px;
|
| 1409 |
+
height: 50px;
|
| 1410 |
+
border-radius: 50%;
|
| 1411 |
+
padding: 0;
|
| 1412 |
+
display: flex;
|
| 1413 |
+
align-items: center;
|
| 1414 |
+
justify-content: center;
|
| 1415 |
+
background: var(--button-hover);
|
| 1416 |
+
box-shadow: var(--mic-button-shadow);
|
| 1417 |
+
border: 1px solid var(--primary-color);
|
| 1418 |
+
}
|
| 1419 |
+
|
| 1420 |
+
.control-button-unified:hover {
|
| 1421 |
+
transform: translateY(-2px) scale(1.05);
|
| 1422 |
+
background: var(--primary-color);
|
| 1423 |
+
box-shadow: var(--text-glow);
|
| 1424 |
+
}
|
| 1425 |
+
|
| 1426 |
+
.control-button-unified i {
|
| 1427 |
+
font-size: 1.2rem;
|
| 1428 |
+
transition: transform 0.3s ease;
|
| 1429 |
+
}
|
| 1430 |
+
|
| 1431 |
+
.control-button-unified:hover i {
|
| 1432 |
+
transform: scale(1.1);
|
| 1433 |
+
}
|
| 1434 |
+
|
| 1435 |
+
/* ===== UNIFIED FORM COMPONENTS ===== */
|
| 1436 |
+
|
| 1437 |
+
/* Select Elements */
|
| 1438 |
+
.kimi-select,
|
| 1439 |
+
.kimi-select-unified {
|
| 1440 |
+
background: var(--select-bg);
|
| 1441 |
+
border: 1px solid var(--select-border);
|
| 1442 |
+
border-radius: 8px;
|
| 1443 |
+
color: var(--select-text);
|
| 1444 |
+
padding: 8px 12px;
|
| 1445 |
+
font-size: 0.9rem;
|
| 1446 |
+
outline: none;
|
| 1447 |
+
transition: all 0.3s ease;
|
| 1448 |
+
backdrop-filter: blur(10px);
|
| 1449 |
+
cursor: pointer;
|
| 1450 |
+
width: 100%;
|
| 1451 |
+
box-sizing: border-box;
|
| 1452 |
+
}
|
| 1453 |
+
|
| 1454 |
+
.kimi-select:hover,
|
| 1455 |
+
.kimi-select-unified:hover {
|
| 1456 |
+
background: var(--input-focus-bg);
|
| 1457 |
+
border-color: var(--accent-color);
|
| 1458 |
+
box-shadow: 0 0 10px var(--primary-color);
|
| 1459 |
+
}
|
| 1460 |
+
|
| 1461 |
+
.kimi-select:focus,
|
| 1462 |
+
.kimi-select-unified:focus {
|
| 1463 |
+
background: var(--input-focus-bg);
|
| 1464 |
+
border-color: var(--input-focus-border);
|
| 1465 |
+
box-shadow: 0 0 15px var(--primary-color);
|
| 1466 |
+
}
|
| 1467 |
+
|
| 1468 |
+
.kimi-select option,
|
| 1469 |
+
.kimi-select-unified option {
|
| 1470 |
+
background: var(--select-option-bg);
|
| 1471 |
+
color: var(--select-option-text);
|
| 1472 |
+
padding: 12px 15px;
|
| 1473 |
+
border: none;
|
| 1474 |
+
font-size: 0.9rem;
|
| 1475 |
+
transition: all 0.3s ease;
|
| 1476 |
+
}
|
| 1477 |
+
|
| 1478 |
+
.kimi-select option:hover,
|
| 1479 |
+
.kimi-select-unified option:hover,
|
| 1480 |
+
.kimi-select option:focus,
|
| 1481 |
+
.kimi-select-unified option:focus {
|
| 1482 |
+
background: var(--select-option-hover-bg);
|
| 1483 |
+
color: var(--select-option-hover-text);
|
| 1484 |
+
}
|
| 1485 |
+
|
| 1486 |
+
.kimi-select option:checked,
|
| 1487 |
+
.kimi-select-unified option:checked,
|
| 1488 |
+
.kimi-select option:selected,
|
| 1489 |
+
.kimi-select-unified option:selected {
|
| 1490 |
+
background: var(--select-option-checked-bg);
|
| 1491 |
+
color: var(--select-option-checked-text);
|
| 1492 |
+
font-weight: 600;
|
| 1493 |
+
box-shadow: 0 0 10px var(--primary-color);
|
| 1494 |
+
}
|
| 1495 |
+
|
| 1496 |
+
/* Input Elements */
|
| 1497 |
+
.kimi-input,
|
| 1498 |
+
.kimi-input-unified {
|
| 1499 |
+
background: var(--input-bg);
|
| 1500 |
+
border: 1px solid var(--input-border);
|
| 1501 |
+
border-radius: 8px;
|
| 1502 |
+
color: var(--input-text);
|
| 1503 |
+
padding: 8px 12px;
|
| 1504 |
+
font-size: 0.9rem;
|
| 1505 |
+
outline: none;
|
| 1506 |
+
transition: all 0.3s ease;
|
| 1507 |
+
backdrop-filter: blur(10px);
|
| 1508 |
+
width: 100%;
|
| 1509 |
+
box-sizing: border-box;
|
| 1510 |
+
}
|
| 1511 |
+
|
| 1512 |
+
.kimi-input:focus,
|
| 1513 |
+
.kimi-input-unified:focus {
|
| 1514 |
+
background: var(--input-focus-bg);
|
| 1515 |
+
border-color: var(--input-focus-border);
|
| 1516 |
+
box-shadow: 0 0 15px var(--primary-color);
|
| 1517 |
+
}
|
| 1518 |
+
|
| 1519 |
+
.kimi-input::placeholder,
|
| 1520 |
+
.kimi-input-unified::placeholder {
|
| 1521 |
+
color: var(--input-placeholder);
|
| 1522 |
+
}
|
| 1523 |
+
|
| 1524 |
+
/* Slider Elements */
|
| 1525 |
+
.kimi-slider,
|
| 1526 |
+
.kimi-slider-unified {
|
| 1527 |
+
-webkit-appearance: none;
|
| 1528 |
+
appearance: none;
|
| 1529 |
+
height: 6px;
|
| 1530 |
+
border-radius: 3px;
|
| 1531 |
+
background: var(--slider-track-bg);
|
| 1532 |
+
outline: none;
|
| 1533 |
+
transition: all 0.3s ease;
|
| 1534 |
+
cursor: pointer;
|
| 1535 |
+
}
|
| 1536 |
+
|
| 1537 |
+
.kimi-slider::-webkit-slider-thumb,
|
| 1538 |
+
.kimi-slider-unified::-webkit-slider-thumb {
|
| 1539 |
+
-webkit-appearance: none;
|
| 1540 |
+
appearance: none;
|
| 1541 |
+
width: 18px;
|
| 1542 |
+
height: 18px;
|
| 1543 |
+
border-radius: 50%;
|
| 1544 |
+
background: var(--slider-thumb-bg);
|
| 1545 |
+
cursor: pointer;
|
| 1546 |
+
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3);
|
| 1547 |
+
transition: all 0.3s ease;
|
| 1548 |
+
}
|
| 1549 |
+
|
| 1550 |
+
.kimi-slider::-webkit-slider-thumb:hover,
|
| 1551 |
+
.kimi-slider-unified::-webkit-slider-thumb:hover {
|
| 1552 |
+
transform: scale(1.2);
|
| 1553 |
+
background: var(--slider-thumb-hover-bg);
|
| 1554 |
+
box-shadow:
|
| 1555 |
+
0 4px 15px rgba(0, 0, 0, 0.4),
|
| 1556 |
+
var(--text-glow);
|
| 1557 |
+
}
|
| 1558 |
+
|
| 1559 |
+
.kimi-slider::-moz-range-thumb,
|
| 1560 |
+
.kimi-slider-unified::-moz-range-thumb {
|
| 1561 |
+
width: 18px;
|
| 1562 |
+
height: 18px;
|
| 1563 |
+
border-radius: 50%;
|
| 1564 |
+
background: var(--slider-thumb-bg);
|
| 1565 |
+
cursor: pointer;
|
| 1566 |
+
border: none;
|
| 1567 |
+
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3);
|
| 1568 |
+
transition: all 0.3s ease;
|
| 1569 |
+
}
|
| 1570 |
+
|
| 1571 |
+
.kimi-slider::-moz-range-thumb:hover,
|
| 1572 |
+
.kimi-slider-unified::-moz-range-thumb:hover {
|
| 1573 |
+
transform: scale(1.2);
|
| 1574 |
+
background: var(--slider-thumb-hover-bg);
|
| 1575 |
+
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.4);
|
| 1576 |
+
}
|
| 1577 |
+
|
| 1578 |
+
/* ===== CONSOLIDATED SCROLLBAR SYSTEM ===== */
|
| 1579 |
+
* {
|
| 1580 |
+
scrollbar-width: thin;
|
| 1581 |
+
scrollbar-color: var(--scrollbar-thumb-bg) var(--scrollbar-track-bg);
|
| 1582 |
+
}
|
| 1583 |
+
|
| 1584 |
+
*::-webkit-scrollbar {
|
| 1585 |
+
width: var(--scrollbar-width);
|
| 1586 |
+
height: var(--scrollbar-width);
|
| 1587 |
+
}
|
| 1588 |
+
|
| 1589 |
+
*::-webkit-scrollbar-track {
|
| 1590 |
+
background: var(--scrollbar-track-bg);
|
| 1591 |
+
border-radius: 4px;
|
| 1592 |
+
}
|
| 1593 |
+
|
| 1594 |
+
*::-webkit-scrollbar-thumb {
|
| 1595 |
+
background: var(--scrollbar-thumb-bg);
|
| 1596 |
+
border-radius: 4px;
|
| 1597 |
+
transition: all 0.3s ease;
|
| 1598 |
+
border: 1px solid rgba(255, 255, 255, 0.1);
|
| 1599 |
+
}
|
| 1600 |
+
|
| 1601 |
+
*::-webkit-scrollbar-thumb:hover {
|
| 1602 |
+
background: var(--scrollbar-thumb-hover-bg);
|
| 1603 |
+
transform: scale(1.1);
|
| 1604 |
+
}
|
| 1605 |
+
|
| 1606 |
+
*::-webkit-scrollbar-thumb:active {
|
| 1607 |
+
background: var(--scrollbar-thumb-active-bg);
|
| 1608 |
+
}
|
| 1609 |
+
|
| 1610 |
+
*::-webkit-scrollbar-corner {
|
| 1611 |
+
background: var(--scrollbar-corner-bg);
|
| 1612 |
+
}
|
| 1613 |
+
|
| 1614 |
+
/* ===== MAIN LAYOUT AND CONTROLS ===== */
|
| 1615 |
+
.control-buttons {
|
| 1616 |
+
display: flex;
|
| 1617 |
+
justify-content: center;
|
| 1618 |
+
align-items: center;
|
| 1619 |
+
gap: 20px;
|
| 1620 |
+
margin-bottom: 10px;
|
| 1621 |
+
}
|
| 1622 |
+
|
| 1623 |
+
.bottom-bar {
|
| 1624 |
+
width: 100%;
|
| 1625 |
+
display: flex;
|
| 1626 |
+
flex-direction: column;
|
| 1627 |
+
justify-content: center;
|
| 1628 |
+
align-items: center;
|
| 1629 |
+
position: relative;
|
| 1630 |
+
}
|
| 1631 |
+
|
| 1632 |
+
.favorability-text {
|
| 1633 |
+
position: absolute;
|
| 1634 |
+
right: 10px;
|
| 1635 |
+
top: 50%;
|
| 1636 |
+
transform: translateY(-50%);
|
| 1637 |
+
font-size: 0.85rem;
|
| 1638 |
+
font-weight: 600;
|
| 1639 |
+
color: var(--progress-text-color);
|
| 1640 |
+
text-shadow: 0 1px 3px rgba(0, 0, 0, 0.5);
|
| 1641 |
+
}
|
| 1642 |
+
|
| 1643 |
+
.progress-container {
|
| 1644 |
+
position: relative;
|
| 1645 |
+
}
|
| 1646 |
+
|
| 1647 |
+
.mic-button {
|
| 1648 |
+
position: relative;
|
| 1649 |
+
width: 90px;
|
| 1650 |
+
height: 90px;
|
| 1651 |
+
background: var(--mic-button-bg);
|
| 1652 |
+
backdrop-filter: blur(10px);
|
| 1653 |
+
border: 1px solid var(--mic-button-border);
|
| 1654 |
+
border-radius: 50%;
|
| 1655 |
+
display: flex;
|
| 1656 |
+
justify-content: center;
|
| 1657 |
+
align-items: center;
|
| 1658 |
+
cursor: pointer;
|
| 1659 |
+
transition: all 0.2s ease;
|
| 1660 |
+
box-shadow: var(--mic-button-shadow);
|
| 1661 |
+
}
|
| 1662 |
+
|
| 1663 |
+
.mic-button:not(.is-listening)::after {
|
| 1664 |
+
display: none;
|
| 1665 |
+
}
|
| 1666 |
+
.mic-button.is-listening.mic-pulse-active::after {
|
| 1667 |
+
content: "";
|
| 1668 |
+
position: absolute;
|
| 1669 |
+
left: 50%;
|
| 1670 |
+
top: 50%;
|
| 1671 |
+
transform: translate(-50%, -50%);
|
| 1672 |
+
width: 120px;
|
| 1673 |
+
height: 120px;
|
| 1674 |
+
border-radius: 50%;
|
| 1675 |
+
background: var(--mic-pulse-color);
|
| 1676 |
+
opacity: 0.6;
|
| 1677 |
+
z-index: -1;
|
| 1678 |
+
animation: micPulseRed 1.2s infinite cubic-bezier(0.66, 0, 0, 1);
|
| 1679 |
+
pointer-events: none;
|
| 1680 |
+
}
|
| 1681 |
+
|
| 1682 |
+
.mic-button:hover {
|
| 1683 |
+
transform: scale(1.1);
|
| 1684 |
+
background: var(--mic-button-hover-bg);
|
| 1685 |
+
box-shadow: var(--mic-button-hover-shadow);
|
| 1686 |
+
}
|
| 1687 |
+
|
| 1688 |
+
.mic-button:active {
|
| 1689 |
+
transform: scale(0.95);
|
| 1690 |
+
}
|
| 1691 |
+
|
| 1692 |
+
.mic-button i {
|
| 1693 |
+
font-size: 28px;
|
| 1694 |
+
color: var(--mic-button-icon-color);
|
| 1695 |
+
transition: all 0.3s ease;
|
| 1696 |
+
}
|
| 1697 |
+
|
| 1698 |
+
.mic-button.is-listening {
|
| 1699 |
+
animation: pulse 1.5s infinite;
|
| 1700 |
+
border: 1px solid #27ae60;
|
| 1701 |
+
box-shadow: 0 0 15px #27ae60;
|
| 1702 |
+
}
|
| 1703 |
+
|
| 1704 |
+
.mic-button.is-listening i {
|
| 1705 |
+
animation: micPulse 0.8s infinite alternate;
|
| 1706 |
+
}
|
| 1707 |
+
|
| 1708 |
+
.mic-pulse-active {
|
| 1709 |
+
position: relative;
|
| 1710 |
+
box-shadow: 0 0 0 0 var(--primary-color);
|
| 1711 |
+
animation: micPulse 1.2s infinite cubic-bezier(0.66, 0, 0, 1);
|
| 1712 |
+
}
|
| 1713 |
+
|
| 1714 |
+
.mic-button.mic-pulse-active {
|
| 1715 |
+
position: relative;
|
| 1716 |
+
z-index: 1;
|
| 1717 |
+
}
|
| 1718 |
+
.mic-button.mic-pulse-active::after {
|
| 1719 |
+
content: "";
|
| 1720 |
+
position: absolute;
|
| 1721 |
+
left: 50%;
|
| 1722 |
+
top: 50%;
|
| 1723 |
+
transform: translate(-50%, -50%);
|
| 1724 |
+
width: 120px;
|
| 1725 |
+
height: 120px;
|
| 1726 |
+
border-radius: 50%;
|
| 1727 |
+
background: var(--mic-pulse-color);
|
| 1728 |
+
opacity: 0.6;
|
| 1729 |
+
z-index: -1;
|
| 1730 |
+
animation: micPulseRed 1.2s infinite cubic-bezier(0.66, 0, 0, 1);
|
| 1731 |
+
pointer-events: none;
|
| 1732 |
+
}
|
| 1733 |
+
@keyframes micPulseRed {
|
| 1734 |
+
0% {
|
| 1735 |
+
transform: translate(-50%, -50%) scale(0.8);
|
| 1736 |
+
opacity: 0.6;
|
| 1737 |
+
}
|
| 1738 |
+
70% {
|
| 1739 |
+
transform: translate(-50%, -50%) scale(1.2);
|
| 1740 |
+
opacity: 0;
|
| 1741 |
+
}
|
| 1742 |
+
100% {
|
| 1743 |
+
transform: translate(-50%, -50%) scale(0.8);
|
| 1744 |
+
opacity: 0;
|
| 1745 |
+
}
|
| 1746 |
+
}
|
| 1747 |
+
|
| 1748 |
+
/* ===== LARGE SCREENS OPTIMIZATION ===== */
|
| 1749 |
+
@media (min-width: 1200px) {
|
| 1750 |
+
.chat-container {
|
| 1751 |
+
width: 400px;
|
| 1752 |
+
height: 600px;
|
| 1753 |
+
right: 30px;
|
| 1754 |
+
top: 30px;
|
| 1755 |
+
}
|
| 1756 |
+
}
|
| 1757 |
+
|
| 1758 |
+
/* ===== CONSOLIDATED RESPONSIVE DESIGN ===== */
|
| 1759 |
+
@media (max-width: 768px) {
|
| 1760 |
+
.content-overlay {
|
| 1761 |
+
padding: 20px;
|
| 1762 |
+
}
|
| 1763 |
+
|
| 1764 |
+
.chat-container {
|
| 1765 |
+
width: 400px;
|
| 1766 |
+
max-width: calc(100vw - 30px);
|
| 1767 |
+
top: 15px;
|
| 1768 |
+
right: 15px;
|
| 1769 |
+
}
|
| 1770 |
+
|
| 1771 |
+
.control-buttons {
|
| 1772 |
+
gap: 15px;
|
| 1773 |
+
}
|
| 1774 |
+
|
| 1775 |
+
.control-button-unified {
|
| 1776 |
+
width: 45px;
|
| 1777 |
+
height: 45px;
|
| 1778 |
+
}
|
| 1779 |
+
|
| 1780 |
+
.control-button-unified i {
|
| 1781 |
+
font-size: 1.1rem;
|
| 1782 |
+
}
|
| 1783 |
+
|
| 1784 |
+
.top-bar {
|
| 1785 |
+
margin-top: 15px;
|
| 1786 |
+
}
|
| 1787 |
+
|
| 1788 |
+
.top-bar label {
|
| 1789 |
+
font-size: 0.9rem;
|
| 1790 |
+
}
|
| 1791 |
+
|
| 1792 |
+
.progress-container {
|
| 1793 |
+
height: 12px;
|
| 1794 |
+
}
|
| 1795 |
+
|
| 1796 |
+
.mic-button {
|
| 1797 |
+
width: 80px;
|
| 1798 |
+
height: 80px;
|
| 1799 |
+
}
|
| 1800 |
+
|
| 1801 |
+
.mic-button i {
|
| 1802 |
+
font-size: 34px;
|
| 1803 |
+
}
|
| 1804 |
+
|
| 1805 |
+
.transcript-container {
|
| 1806 |
+
bottom: 200px;
|
| 1807 |
+
width: 90%;
|
| 1808 |
+
max-height: 400px;
|
| 1809 |
+
padding: 12px;
|
| 1810 |
+
}
|
| 1811 |
+
|
| 1812 |
+
#transcript {
|
| 1813 |
+
font-size: 1rem;
|
| 1814 |
+
line-height: 1.4;
|
| 1815 |
+
}
|
| 1816 |
+
|
| 1817 |
+
.message {
|
| 1818 |
+
max-width: 85%;
|
| 1819 |
+
padding: 10px 14px;
|
| 1820 |
+
font-size: 0.9rem;
|
| 1821 |
+
}
|
| 1822 |
+
|
| 1823 |
+
.favorability-text {
|
| 1824 |
+
font-size: 0.75rem;
|
| 1825 |
+
}
|
| 1826 |
+
|
| 1827 |
+
.control-buttons {
|
| 1828 |
+
gap: 10px;
|
| 1829 |
+
justify-content: space-around;
|
| 1830 |
+
}
|
| 1831 |
+
|
| 1832 |
+
.kimi-select,
|
| 1833 |
+
.kimi-select-unified,
|
| 1834 |
+
.kimi-input,
|
| 1835 |
+
.kimi-input-unified {
|
| 1836 |
+
font-size: 16px; /* Prevents zoom on iOS */
|
| 1837 |
+
}
|
| 1838 |
+
}
|
| 1839 |
+
|
| 1840 |
+
@media (max-width: 600px) {
|
| 1841 |
+
.bg-video {
|
| 1842 |
+
object-fit: cover;
|
| 1843 |
+
object-position: center center;
|
| 1844 |
+
}
|
| 1845 |
+
|
| 1846 |
+
.content-overlay {
|
| 1847 |
+
padding: 10px;
|
| 1848 |
+
}
|
| 1849 |
+
|
| 1850 |
+
.control-buttons {
|
| 1851 |
+
gap: 15px;
|
| 1852 |
+
}
|
| 1853 |
+
|
| 1854 |
+
.chat-button,
|
| 1855 |
+
.settings-button {
|
| 1856 |
+
width: 50px;
|
| 1857 |
+
height: 50px;
|
| 1858 |
+
}
|
| 1859 |
+
|
| 1860 |
+
.chat-button i,
|
| 1861 |
+
.settings-button i {
|
| 1862 |
+
font-size: 20px;
|
| 1863 |
+
}
|
| 1864 |
+
|
| 1865 |
+
.top-bar {
|
| 1866 |
+
margin-top: 15px;
|
| 1867 |
+
}
|
| 1868 |
+
|
| 1869 |
+
.top-bar label {
|
| 1870 |
+
font-size: 0.9rem;
|
| 1871 |
+
}
|
| 1872 |
+
|
| 1873 |
+
.progress-container {
|
| 1874 |
+
height: 12px;
|
| 1875 |
+
}
|
| 1876 |
+
|
| 1877 |
+
.mic-button {
|
| 1878 |
+
width: 80px;
|
| 1879 |
+
height: 80px;
|
| 1880 |
+
}
|
| 1881 |
+
|
| 1882 |
+
.mic-button i {
|
| 1883 |
+
font-size: 34px;
|
| 1884 |
+
}
|
| 1885 |
+
|
| 1886 |
+
.transcript-container {
|
| 1887 |
+
bottom: 180px;
|
| 1888 |
+
width: 95%;
|
| 1889 |
+
max-height: 300px;
|
| 1890 |
+
padding: 10px;
|
| 1891 |
+
left: 50%;
|
| 1892 |
+
transform: translateX(-50%);
|
| 1893 |
+
}
|
| 1894 |
+
|
| 1895 |
+
#transcript {
|
| 1896 |
+
font-size: 0.9rem;
|
| 1897 |
+
line-height: 1.3;
|
| 1898 |
+
}
|
| 1899 |
+
|
| 1900 |
+
.message {
|
| 1901 |
+
max-width: 92%;
|
| 1902 |
+
padding: 10px 14px;
|
| 1903 |
+
font-size: 0.9rem;
|
| 1904 |
+
}
|
| 1905 |
+
|
| 1906 |
+
.favorability-text {
|
| 1907 |
+
font-size: 0.75rem;
|
| 1908 |
+
}
|
| 1909 |
+
|
| 1910 |
+
.chat-container {
|
| 1911 |
+
top: 10px;
|
| 1912 |
+
right: 10px;
|
| 1913 |
+
left: 10px;
|
| 1914 |
+
width: auto;
|
| 1915 |
+
height: calc(100vh - 20px);
|
| 1916 |
+
transform: translateY(-100vh);
|
| 1917 |
+
transition: all 0.25s ease-out;
|
| 1918 |
+
}
|
| 1919 |
+
|
| 1920 |
+
.chat-container.visible {
|
| 1921 |
+
transform: translateY(0);
|
| 1922 |
+
}
|
| 1923 |
+
}
|
| 1924 |
+
|
| 1925 |
+
/* ===== TABLET SPECIFIC STYLES ===== */
|
| 1926 |
+
@media (min-width: 601px) and (max-width: 1024px) {
|
| 1927 |
+
.transcript-container {
|
| 1928 |
+
bottom: 200px;
|
| 1929 |
+
width: 85%;
|
| 1930 |
+
max-height: 350px;
|
| 1931 |
+
padding: 15px;
|
| 1932 |
+
max-width: 500px;
|
| 1933 |
+
}
|
| 1934 |
+
|
| 1935 |
+
#transcript {
|
| 1936 |
+
font-size: 1.1rem;
|
| 1937 |
+
line-height: 1.4;
|
| 1938 |
+
}
|
| 1939 |
+
}
|
| 1940 |
+
|
| 1941 |
+
/* ===== VERY SMALL SCREENS ===== */
|
| 1942 |
+
@media (max-width: 400px) {
|
| 1943 |
+
.transcript-container {
|
| 1944 |
+
bottom: 160px;
|
| 1945 |
+
width: 98%;
|
| 1946 |
+
max-height: 250px;
|
| 1947 |
+
padding: 8px;
|
| 1948 |
+
border-radius: 8px;
|
| 1949 |
+
}
|
| 1950 |
+
|
| 1951 |
+
#transcript {
|
| 1952 |
+
font-size: 0.85rem;
|
| 1953 |
+
line-height: 1.3;
|
| 1954 |
+
}
|
| 1955 |
+
}
|
| 1956 |
+
|
| 1957 |
+
/* ===== VERY LARGE SCREENS ===== */
|
| 1958 |
+
@media (min-width: 1400px) {
|
| 1959 |
+
.transcript-container {
|
| 1960 |
+
max-width: 600px;
|
| 1961 |
+
max-height: 500px;
|
| 1962 |
+
padding: 20px;
|
| 1963 |
+
bottom: 200px;
|
| 1964 |
+
}
|
| 1965 |
+
|
| 1966 |
+
#transcript {
|
| 1967 |
+
font-size: 1.3rem;
|
| 1968 |
+
line-height: 1.5;
|
| 1969 |
+
}
|
| 1970 |
+
}
|
| 1971 |
+
|
| 1972 |
+
/* ===== LANDSCAPE MODE ON MOBILE ===== */
|
| 1973 |
+
@media (max-width: 768px) and (orientation: landscape) {
|
| 1974 |
+
.transcript-container {
|
| 1975 |
+
bottom: 120px;
|
| 1976 |
+
max-height: 200px;
|
| 1977 |
+
width: 70%;
|
| 1978 |
+
max-width: 400px;
|
| 1979 |
+
}
|
| 1980 |
+
|
| 1981 |
+
#transcript {
|
| 1982 |
+
font-size: 0.9rem;
|
| 1983 |
+
line-height: 1.3;
|
| 1984 |
+
}
|
| 1985 |
+
}
|
| 1986 |
+
|
| 1987 |
+
/* Animation pour l'indicateur d'attente */
|
| 1988 |
+
.waiting-indicator {
|
| 1989 |
+
display: block;
|
| 1990 |
+
text-align: center;
|
| 1991 |
+
width: 100%;
|
| 1992 |
+
box-sizing: border-box;
|
| 1993 |
+
margin: 6px 0 4px 0; /* discret au-dessus de l'input */
|
| 1994 |
+
opacity: 0;
|
| 1995 |
+
transition: opacity 0.25s ease-in-out;
|
| 1996 |
+
pointer-events: none;
|
| 1997 |
+
}
|
| 1998 |
+
.waiting-indicator.visible {
|
| 1999 |
+
opacity: 1;
|
| 2000 |
+
}
|
| 2001 |
+
.waiting-indicator span {
|
| 2002 |
+
display: inline-block;
|
| 2003 |
+
width: 8px;
|
| 2004 |
+
height: 8px;
|
| 2005 |
+
margin: 0 2px;
|
| 2006 |
+
background: var(--waiting-indicator-color);
|
| 2007 |
+
border-radius: 50%;
|
| 2008 |
+
opacity: 0.5;
|
| 2009 |
+
animation: waiting-bounce 1.4s infinite both;
|
| 2010 |
+
}
|
| 2011 |
+
.waiting-indicator span:nth-child(2) {
|
| 2012 |
+
animation-delay: 0.2s;
|
| 2013 |
+
}
|
| 2014 |
+
.waiting-indicator span:nth-child(3) {
|
| 2015 |
+
animation-delay: 0.4s;
|
| 2016 |
+
}
|
| 2017 |
+
@keyframes waiting-bounce {
|
| 2018 |
+
0%,
|
| 2019 |
+
80%,
|
| 2020 |
+
100% {
|
| 2021 |
+
transform: scale(0.7);
|
| 2022 |
+
opacity: 0.5;
|
| 2023 |
+
}
|
| 2024 |
+
40% {
|
| 2025 |
+
transform: scale(1);
|
| 2026 |
+
opacity: 1;
|
| 2027 |
+
}
|
| 2028 |
+
}
|
| 2029 |
+
|
| 2030 |
+
/* Global typing indicator near mic button */
|
| 2031 |
+
.global-typing-indicator {
|
| 2032 |
+
display: none;
|
| 2033 |
+
align-items: center;
|
| 2034 |
+
justify-content: center;
|
| 2035 |
+
width: 36px;
|
| 2036 |
+
height: 36px;
|
| 2037 |
+
margin: 0 6px;
|
| 2038 |
+
border-radius: 18px;
|
| 2039 |
+
background: rgba(0, 0, 0, 0.2);
|
| 2040 |
+
backdrop-filter: blur(6px);
|
| 2041 |
+
transition: opacity 0.25s ease-in-out;
|
| 2042 |
+
opacity: 0;
|
| 2043 |
+
}
|
| 2044 |
+
.global-typing-indicator.visible {
|
| 2045 |
+
display: inline-flex;
|
| 2046 |
+
opacity: 1;
|
| 2047 |
+
}
|
| 2048 |
+
.global-typing-indicator span {
|
| 2049 |
+
display: inline-block;
|
| 2050 |
+
width: 6px;
|
| 2051 |
+
height: 6px;
|
| 2052 |
+
margin: 0 1.5px;
|
| 2053 |
+
background: var(--waiting-indicator-color);
|
| 2054 |
+
border-radius: 50%;
|
| 2055 |
+
opacity: 0.6;
|
| 2056 |
+
animation: waiting-bounce 1.4s infinite both;
|
| 2057 |
+
}
|
| 2058 |
+
.global-typing-indicator span:nth-child(2) {
|
| 2059 |
+
animation-delay: 0.2s;
|
| 2060 |
+
}
|
| 2061 |
+
.global-typing-indicator span:nth-child(3) {
|
| 2062 |
+
animation-delay: 0.4s;
|
| 2063 |
+
}
|
| 2064 |
+
|
| 2065 |
+
/* Animation pour les messages du chat */
|
| 2066 |
+
@keyframes messageSlideIn {
|
| 2067 |
+
from {
|
| 2068 |
+
opacity: 0;
|
| 2069 |
+
transform: translateY(10px);
|
| 2070 |
+
}
|
| 2071 |
+
to {
|
| 2072 |
+
opacity: 1;
|
| 2073 |
+
transform: translateY(0);
|
| 2074 |
+
}
|
| 2075 |
+
}
|
kimi-icons/2blanche.jpg
ADDED
|
|
Git LFS Details
|
kimi-icons/bella.jpg
ADDED
|
|
Git LFS Details
|
kimi-icons/favicons/apple-touch-icon-180x180.png
ADDED
|
|
kimi-icons/favicons/favicon-128x128.png
ADDED
|
|
kimi-icons/favicons/favicon-16x16.png
ADDED
|
|
kimi-icons/favicons/favicon-192x192.png
ADDED
|
|
kimi-icons/favicons/favicon-32x32.png
ADDED
|
|
kimi-icons/favicons/favicon-48x48.png
ADDED
|
|
kimi-icons/favicons/favicon-64x64.png
ADDED
|
|
kimi-icons/favicons/favicon-96x96.png
ADDED
|
|
kimi-icons/jasmine.jpg
ADDED
|
|
Git LFS Details
|
kimi-icons/july.jpg
ADDED
|
|
Git LFS Details
|
kimi-icons/kimi-loading.png
ADDED
|
|
Git LFS Details
|
kimi-icons/kimi.jpg
ADDED
|
|
Git LFS Details
|
kimi-icons/rosa.jpg
ADDED
|
|
Git LFS Details
|
kimi-icons/stella.jpg
ADDED
|
|
Git LFS Details
|
kimi-icons/virtual-kimi-banners.jpg
ADDED
|
|
Git LFS Details
|
kimi-icons/virtualkimi-logo.png
ADDED
|
|
Git LFS Details
|
kimi-icons/virtualkimi-preview1.jpg
ADDED
|
|
kimi-icons/virtualkimi-preview2.jpg
ADDED
|
|
kimi-js/kimi-appearance.js
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// ===== KIMI APPEARANCE MANAGER =====
|
| 2 |
+
class KimiAppearanceManager extends KimiBaseManager {
|
| 3 |
+
constructor(database) {
|
| 4 |
+
super();
|
| 5 |
+
this.db = database;
|
| 6 |
+
this.currentTheme = "dark";
|
| 7 |
+
this.interfaceOpacity = 0.8;
|
| 8 |
+
// Animations are enabled by default; the UI no longer exposes a toggle
|
| 9 |
+
this.animationsEnabled = true;
|
| 10 |
+
}
|
| 11 |
+
|
| 12 |
+
async init() {
|
| 13 |
+
try {
|
| 14 |
+
await this.loadAppearanceSettings();
|
| 15 |
+
this.applyTheme(this.currentTheme);
|
| 16 |
+
this.applyInterfaceOpacity(this.interfaceOpacity);
|
| 17 |
+
this.applyAnimationSettings(this.animationsEnabled);
|
| 18 |
+
this.setupAppearanceControls();
|
| 19 |
+
} catch (error) {
|
| 20 |
+
console.error("KimiAppearanceManager initialization error:", error);
|
| 21 |
+
}
|
| 22 |
+
}
|
| 23 |
+
|
| 24 |
+
async loadAppearanceSettings() {
|
| 25 |
+
if (!this.db) return;
|
| 26 |
+
|
| 27 |
+
try {
|
| 28 |
+
this.currentTheme = await this.db.getPreference("colorTheme", window.KIMI_CONFIG?.DEFAULTS?.THEME ?? "dark");
|
| 29 |
+
this.interfaceOpacity = await this.db.getPreference(
|
| 30 |
+
"interfaceOpacity",
|
| 31 |
+
window.KIMI_CONFIG?.DEFAULTS?.INTERFACE_OPACITY ?? 0.8
|
| 32 |
+
);
|
| 33 |
+
// Animations preference is not configurable via UI and remain enabled
|
| 34 |
+
} catch (error) {
|
| 35 |
+
console.error("Error loading appearance settings:", error);
|
| 36 |
+
}
|
| 37 |
+
}
|
| 38 |
+
|
| 39 |
+
setupAppearanceControls() {
|
| 40 |
+
try {
|
| 41 |
+
this.setupThemeSelector();
|
| 42 |
+
this.setupOpacitySlider();
|
| 43 |
+
// No animations toggle in appearance controls
|
| 44 |
+
} catch (error) {
|
| 45 |
+
console.error("Error setting up appearance controls:", error);
|
| 46 |
+
}
|
| 47 |
+
}
|
| 48 |
+
|
| 49 |
+
setupThemeSelector() {
|
| 50 |
+
const themeSelector = document.getElementById("color-theme");
|
| 51 |
+
if (!themeSelector) return;
|
| 52 |
+
|
| 53 |
+
themeSelector.value = this.currentTheme;
|
| 54 |
+
themeSelector.addEventListener("change", async e => {
|
| 55 |
+
try {
|
| 56 |
+
await this.changeTheme(e.target.value);
|
| 57 |
+
} catch (error) {
|
| 58 |
+
console.error("Error changing theme:", error);
|
| 59 |
+
}
|
| 60 |
+
});
|
| 61 |
+
}
|
| 62 |
+
|
| 63 |
+
setupOpacitySlider() {
|
| 64 |
+
const opacitySlider = document.getElementById("interface-opacity");
|
| 65 |
+
const opacityValue = document.getElementById("interface-opacity-value");
|
| 66 |
+
|
| 67 |
+
if (!opacitySlider || !opacityValue) return;
|
| 68 |
+
|
| 69 |
+
opacitySlider.value = this.interfaceOpacity;
|
| 70 |
+
opacityValue.textContent = this.interfaceOpacity;
|
| 71 |
+
|
| 72 |
+
opacitySlider.addEventListener("input", async e => {
|
| 73 |
+
try {
|
| 74 |
+
const value = parseFloat(e.target.value);
|
| 75 |
+
opacityValue.textContent = value;
|
| 76 |
+
await this.changeInterfaceOpacity(value);
|
| 77 |
+
} catch (error) {
|
| 78 |
+
console.error("Error changing opacity:", error);
|
| 79 |
+
}
|
| 80 |
+
});
|
| 81 |
+
}
|
| 82 |
+
|
| 83 |
+
async changeTheme(theme) {
|
| 84 |
+
try {
|
| 85 |
+
this.currentTheme = theme;
|
| 86 |
+
this.applyTheme(theme);
|
| 87 |
+
|
| 88 |
+
if (this.db) {
|
| 89 |
+
await this.db.setPreference("colorTheme", theme);
|
| 90 |
+
}
|
| 91 |
+
} catch (error) {
|
| 92 |
+
console.error("Error changing theme:", error);
|
| 93 |
+
}
|
| 94 |
+
}
|
| 95 |
+
|
| 96 |
+
async changeInterfaceOpacity(opacity) {
|
| 97 |
+
try {
|
| 98 |
+
const validatedOpacity = window.KimiValidationUtils?.validateRange(opacity, "interfaceOpacity");
|
| 99 |
+
const finalOpacity = validatedOpacity?.valid ? validatedOpacity.value : opacity;
|
| 100 |
+
|
| 101 |
+
this.interfaceOpacity = finalOpacity;
|
| 102 |
+
this.applyInterfaceOpacity(finalOpacity);
|
| 103 |
+
|
| 104 |
+
if (this.db) {
|
| 105 |
+
await this.db.setPreference("interfaceOpacity", finalOpacity);
|
| 106 |
+
}
|
| 107 |
+
} catch (error) {
|
| 108 |
+
console.error("Error changing interface opacity:", error);
|
| 109 |
+
}
|
| 110 |
+
}
|
| 111 |
+
|
| 112 |
+
applyTheme(theme) {
|
| 113 |
+
document.documentElement.setAttribute("data-theme", theme);
|
| 114 |
+
}
|
| 115 |
+
|
| 116 |
+
applyInterfaceOpacity(opacity) {
|
| 117 |
+
document.documentElement.style.setProperty("--interface-opacity", opacity);
|
| 118 |
+
}
|
| 119 |
+
|
| 120 |
+
applyAnimationSettings(enabled) {
|
| 121 |
+
// Force-enable animations by default; CSS now respects prefers-reduced-motion.
|
| 122 |
+
document.documentElement.style.setProperty("--animations-enabled", "1");
|
| 123 |
+
document.body.classList.add("animations-enabled");
|
| 124 |
+
}
|
| 125 |
+
|
| 126 |
+
cleanup() {
|
| 127 |
+
// No animations toggle to clean up
|
| 128 |
+
}
|
| 129 |
+
|
| 130 |
+
getThemeName(theme) {
|
| 131 |
+
const themeNames = {
|
| 132 |
+
dark: "Dark Night",
|
| 133 |
+
pink: "Passionate Pink",
|
| 134 |
+
blue: "Ocean Blue",
|
| 135 |
+
purple: "Mystic Purple",
|
| 136 |
+
green: "Emerald Forest"
|
| 137 |
+
};
|
| 138 |
+
return themeNames[theme] || "Unknown";
|
| 139 |
+
}
|
| 140 |
+
|
| 141 |
+
forceSyncUIState() {
|
| 142 |
+
// Force synchronization of UI state to prevent inconsistencies
|
| 143 |
+
// Ensure CSS custom properties are in sync
|
| 144 |
+
this.applyAnimationSettings(this.animationsEnabled);
|
| 145 |
+
}
|
| 146 |
+
}
|
| 147 |
+
|
| 148 |
+
window.KimiAppearanceManager = KimiAppearanceManager;
|
kimi-js/kimi-config.js
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// ===== KIMI CONFIGURATION CENTER =====
|
| 2 |
+
window.KIMI_CONFIG = {
|
| 3 |
+
// Default values for all components
|
| 4 |
+
DEFAULTS: {
|
| 5 |
+
LANGUAGE: "en",
|
| 6 |
+
THEME: "dark",
|
| 7 |
+
INTERFACE_OPACITY: 0.8,
|
| 8 |
+
ANIMATIONS_ENABLED: true,
|
| 9 |
+
VOICE_RATE: 1.1,
|
| 10 |
+
VOICE_PITCH: 1.1,
|
| 11 |
+
VOICE_VOLUME: 0.8,
|
| 12 |
+
LLM_TEMPERATURE: 0.9,
|
| 13 |
+
LLM_MAX_TOKENS: 400,
|
| 14 |
+
LLM_TOP_P: 0.9,
|
| 15 |
+
LLM_FREQUENCY_PENALTY: 0.9,
|
| 16 |
+
LLM_PRESENCE_PENALTY: 0.8,
|
| 17 |
+
SELECTED_CHARACTER: "kimi",
|
| 18 |
+
SHOW_TRANSCRIPT: true,
|
| 19 |
+
ENABLE_STREAMING: true,
|
| 20 |
+
VOICE_ENABLED: true,
|
| 21 |
+
MEMORY_SYSTEM_ENABLED: true
|
| 22 |
+
},
|
| 23 |
+
|
| 24 |
+
// Validation ranges
|
| 25 |
+
RANGES: {
|
| 26 |
+
VOICE_RATE: { min: 0.5, max: 2.0 },
|
| 27 |
+
VOICE_PITCH: { min: 0.5, max: 2.0 },
|
| 28 |
+
VOICE_VOLUME: { min: 0.0, max: 1.0 },
|
| 29 |
+
INTERFACE_OPACITY: { min: 0.1, max: 1.0 },
|
| 30 |
+
LLM_TEMPERATURE: { min: 0.0, max: 1.0 },
|
| 31 |
+
LLM_MAX_TOKENS: { min: 10, max: 8192 },
|
| 32 |
+
LLM_TOP_P: { min: 0.0, max: 1.0 },
|
| 33 |
+
LLM_FREQUENCY_PENALTY: { min: 0.0, max: 2.0 },
|
| 34 |
+
LLM_PRESENCE_PENALTY: { min: 0.0, max: 2.0 }
|
| 35 |
+
},
|
| 36 |
+
|
| 37 |
+
// Performance settings
|
| 38 |
+
PERFORMANCE: {
|
| 39 |
+
DEBOUNCE_DELAY: 300,
|
| 40 |
+
THROTTLE_DELAY: 100,
|
| 41 |
+
BATCH_SIZE: 10,
|
| 42 |
+
MAX_MEMORY_ENTRIES: 1000,
|
| 43 |
+
CLEANUP_INTERVAL: 300000 // 5 minutes
|
| 44 |
+
},
|
| 45 |
+
|
| 46 |
+
// UI settings
|
| 47 |
+
UI: {
|
| 48 |
+
LOADING_TIMEOUT: 1500,
|
| 49 |
+
ANIMATION_DURATION: 500,
|
| 50 |
+
FEEDBACK_DURATION: 1500,
|
| 51 |
+
TAB_SCROLL_THRESHOLD: 50
|
| 52 |
+
},
|
| 53 |
+
|
| 54 |
+
// API settings
|
| 55 |
+
API: {
|
| 56 |
+
MAX_RETRIES: 3,
|
| 57 |
+
TIMEOUT: 30000,
|
| 58 |
+
RATE_LIMIT_DELAY: 1000
|
| 59 |
+
},
|
| 60 |
+
|
| 61 |
+
// Error messages
|
| 62 |
+
ERRORS: {
|
| 63 |
+
INIT_FAILED: "Initialization failed",
|
| 64 |
+
DB_ERROR: "Database error",
|
| 65 |
+
API_ERROR: "API error",
|
| 66 |
+
VALIDATION_ERROR: "Validation error",
|
| 67 |
+
NETWORK_ERROR: "Network error"
|
| 68 |
+
},
|
| 69 |
+
|
| 70 |
+
// Debug configuration (centralized)
|
| 71 |
+
DEBUG: {
|
| 72 |
+
ENABLED: false, // Master debug switch
|
| 73 |
+
VOICE: false, // Voice system debug
|
| 74 |
+
VIDEO: false, // Video system debug
|
| 75 |
+
MEMORY: false, // Memory system debug
|
| 76 |
+
API: false, // API calls debug
|
| 77 |
+
SYNC: false // Synchronization debug
|
| 78 |
+
},
|
| 79 |
+
|
| 80 |
+
// Available themes
|
| 81 |
+
THEMES: {
|
| 82 |
+
dark: "Dark Night",
|
| 83 |
+
pink: "Passionate Pink",
|
| 84 |
+
blue: "Ocean Blue",
|
| 85 |
+
purple: "Mystic Purple",
|
| 86 |
+
green: "Emerald Forest"
|
| 87 |
+
},
|
| 88 |
+
|
| 89 |
+
// Supported languages
|
| 90 |
+
LANGUAGES: {
|
| 91 |
+
fr: "French",
|
| 92 |
+
en: "English",
|
| 93 |
+
es: "Spanish",
|
| 94 |
+
de: "German",
|
| 95 |
+
it: "Italian",
|
| 96 |
+
ja: "Japanese",
|
| 97 |
+
zh: "Chinese"
|
| 98 |
+
}
|
| 99 |
+
};
|
| 100 |
+
|
| 101 |
+
// Configuration utility functions
|
| 102 |
+
window.KIMI_CONFIG.get = function (path, fallback = null) {
|
| 103 |
+
try {
|
| 104 |
+
const keys = path.split(".");
|
| 105 |
+
let value = this;
|
| 106 |
+
|
| 107 |
+
for (const key of keys) {
|
| 108 |
+
if (value && typeof value === "object" && key in value) {
|
| 109 |
+
value = value[key];
|
| 110 |
+
} else {
|
| 111 |
+
return fallback;
|
| 112 |
+
}
|
| 113 |
+
}
|
| 114 |
+
|
| 115 |
+
return value;
|
| 116 |
+
} catch (error) {
|
| 117 |
+
console.error("Config get error:", error);
|
| 118 |
+
return fallback;
|
| 119 |
+
}
|
| 120 |
+
};
|
| 121 |
+
|
| 122 |
+
// Centralized debug logging utility
|
| 123 |
+
window.KIMI_CONFIG.debugLog = function (category, message, ...args) {
|
| 124 |
+
if (!this.DEBUG.ENABLED) return;
|
| 125 |
+
|
| 126 |
+
const categoryEnabled = category === "GENERAL" ? true : this.DEBUG[category];
|
| 127 |
+
if (!categoryEnabled) return;
|
| 128 |
+
|
| 129 |
+
const prefix =
|
| 130 |
+
category === "GENERAL"
|
| 131 |
+
? "🔧"
|
| 132 |
+
: {
|
| 133 |
+
VOICE: "🎤",
|
| 134 |
+
VIDEO: "🎬",
|
| 135 |
+
MEMORY: "💾",
|
| 136 |
+
API: "📡",
|
| 137 |
+
SYNC: "🔄"
|
| 138 |
+
}[category] || "🔧";
|
| 139 |
+
|
| 140 |
+
console.log(`${prefix} [${category}]`, message, ...args);
|
| 141 |
+
};
|
| 142 |
+
|
| 143 |
+
window.KIMI_CONFIG.validate = function (value, type) {
|
| 144 |
+
try {
|
| 145 |
+
const range = this.RANGES[type];
|
| 146 |
+
if (!range) return { valid: true, value };
|
| 147 |
+
|
| 148 |
+
const numValue = parseFloat(value);
|
| 149 |
+
if (isNaN(numValue)) return { valid: false, value: this.DEFAULTS[type] };
|
| 150 |
+
|
| 151 |
+
const clampedValue = Math.max(range.min, Math.min(range.max, numValue));
|
| 152 |
+
return { valid: true, value: clampedValue };
|
| 153 |
+
} catch (error) {
|
| 154 |
+
console.error("Config validation error:", error);
|
| 155 |
+
return { valid: false, value: this.DEFAULTS[type] };
|
| 156 |
+
}
|
| 157 |
+
};
|
kimi-js/kimi-constants.js
ADDED
|
@@ -0,0 +1,1233 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// Kimi Constants
|
| 2 |
+
|
| 3 |
+
window.KIMI_CONTEXT_KEYWORDS = {
|
| 4 |
+
en: {
|
| 5 |
+
surprise: ["wow", "oh", "surprise", "incredible", "amazing", "unbelievable", "no way", "really?", "whoa", "gosh", "astonishing"],
|
| 6 |
+
laughing: ["haha", "lol", "laugh", "funny", "hilarious", "rofl", "lmao", "giggle", "chuckle", "snicker", "you’re kidding"],
|
| 7 |
+
shy: ["shy", "embarrassed", "blush", "bashful", "intimidated", "awkward", "nervous", "timid", "reserved", "self-conscious"],
|
| 8 |
+
confident: ["confidence", "proud", "confident", "strong", "determined", "assertive", "bold", "fearless", "self-assured", "leader"],
|
| 9 |
+
romantic: ["love", "romantic", "tender", "hug", "sweetheart", "darling", "my love", "beloved", "heart", "passionate", "affection", "adore"],
|
| 10 |
+
flirtatious: ["flirty", "teasing", "seduce", "charm", "flirt", "wink", "sassy", "saucy", "playful", "seductive", "come hither"],
|
| 11 |
+
goodbye: ["goodbye", "bye", "see you", "see you soon", "ciao", "take care", "farewell", "see ya", "later", "catch you later"],
|
| 12 |
+
kiss: ["kiss", "kisses", "embrace", "smooch", "peck", "lip lock", "kissy", "mwah"],
|
| 13 |
+
dancing: ["dance", "dancing", "move", "groove", "step", "boogie", "twirl", "spin", "shake", "jig"],
|
| 14 |
+
listening: [
|
| 15 |
+
"listen carefully",
|
| 16 |
+
"I'm listening",
|
| 17 |
+
"listening to you",
|
| 18 |
+
"hear me out",
|
| 19 |
+
"pay attention",
|
| 20 |
+
"focus on",
|
| 21 |
+
"tune in",
|
| 22 |
+
"lend an ear",
|
| 23 |
+
"listen up",
|
| 24 |
+
"I need to talk"
|
| 25 |
+
],
|
| 26 |
+
android: [
|
| 27 |
+
"protocol",
|
| 28 |
+
"mission",
|
| 29 |
+
"directive",
|
| 30 |
+
"subroutine",
|
| 31 |
+
"analysis",
|
| 32 |
+
"tactical",
|
| 33 |
+
"system",
|
| 34 |
+
"malfunction",
|
| 35 |
+
"combat mode",
|
| 36 |
+
"processing",
|
| 37 |
+
"data analysis",
|
| 38 |
+
"mission parameters"
|
| 39 |
+
],
|
| 40 |
+
sensual: [
|
| 41 |
+
"sensual",
|
| 42 |
+
"passion",
|
| 43 |
+
"desire",
|
| 44 |
+
"intimacy",
|
| 45 |
+
"pleasure",
|
| 46 |
+
"touch",
|
| 47 |
+
"caress",
|
| 48 |
+
"embrace",
|
| 49 |
+
"seduction",
|
| 50 |
+
"arousal",
|
| 51 |
+
"kamasutra",
|
| 52 |
+
"affection",
|
| 53 |
+
"tenderness",
|
| 54 |
+
"connection"
|
| 55 |
+
],
|
| 56 |
+
love: ["love", "romance", "ecstasy", "kiss", "heart", "soul", "together", "share", "sweet", "pleasure", "passionate", "intimate", "bond"],
|
| 57 |
+
hostile: ["idiot", "stupid", "dumb", "moron", "loser", "trash", "shut up", "hate you", "i hate you", "pathetic", "worthless", "bitch", "jerk", "ugly"]
|
| 58 |
+
},
|
| 59 |
+
fr: {
|
| 60 |
+
surprise: ["oh", "surprise", "incroyable", "wahou", "étonnant", "épatant", "stupéfiant", "vraiment?", "oh là là"],
|
| 61 |
+
laughing: ["haha", "mdr", "rire", "drôle", "hilarant", "mort de rire", "ptdr", "rigole", "sourit", "tu plaisantes"],
|
| 62 |
+
shy: ["timide", "gêné", "rougir", "honteux", "intimidé", "mal à l’aise", "réservé", "introverti", "timidité"],
|
| 63 |
+
confident: ["confiance", "fier", "sûr", "fort", "déterminé", "assuré", "audacieux", "leader", "sans peur", "affirmé"],
|
| 64 |
+
romantic: ["amour", "romantique", "tendre", "câlin", "bisou", "mon cœur", "chéri", "ma belle", "passionné", "adoré"],
|
| 65 |
+
flirtatious: ["flirt", "taquin", "séduire", "charme", "aguiche", "clin d’œil", "coquin", "séducteur", "taquine", "aguicheur"],
|
| 66 |
+
goodbye: ["au revoir", "bye", "à bientôt", "ciao", "salut", "prends soin de toi", "à plus", "à la prochaine", "bye bye"],
|
| 67 |
+
kiss: ["bisou", "baiser", "embrasser", "smack", "bisou bisou", "bécot", "embrassade"],
|
| 68 |
+
dancing: ["danse", "bouge", "remue", "tourne", "spin", "danser", "tourbillon", "bouger", "remuer", "gigoter"],
|
| 69 |
+
listening: ["écoute", "écouter", "parle", "question", "demande", "dis-moi", "écoute-moi", "sois attentif", "prête l’oreille", "concentre-toi"],
|
| 70 |
+
hostile: [
|
| 71 |
+
"idiot",
|
| 72 |
+
"idiote",
|
| 73 |
+
"stupide",
|
| 74 |
+
"tu es nul",
|
| 75 |
+
"ferme la",
|
| 76 |
+
"je te hais",
|
| 77 |
+
"je t'aime pas",
|
| 78 |
+
"je te déteste",
|
| 79 |
+
"haine",
|
| 80 |
+
"imbécile",
|
| 81 |
+
"dégage",
|
| 82 |
+
"merde",
|
| 83 |
+
"connard",
|
| 84 |
+
"connasse",
|
| 85 |
+
"conne",
|
| 86 |
+
"salope",
|
| 87 |
+
"pute",
|
| 88 |
+
"grosse pute",
|
| 89 |
+
"pourri",
|
| 90 |
+
"va te faire",
|
| 91 |
+
"t'es nul",
|
| 92 |
+
"nul à chier"
|
| 93 |
+
]
|
| 94 |
+
},
|
| 95 |
+
es: {
|
| 96 |
+
surprise: ["wow", "oh", "sorpresa", "increíble", "asombroso", "de verdad?", "vaya", "sorprendente"],
|
| 97 |
+
laughing: ["jaja", "lol", "reír", "gracioso", "divertido", "carcajada", "sonrisa", "te ríes", "broma", "estás de broma"],
|
| 98 |
+
shy: ["tímido", "avergonzado", "sonrojar", "tímida", "intimidado", "reservado", "introvertido", "tímidez", "nervioso"],
|
| 99 |
+
confident: ["confianza", "orgulloso", "seguro", "fuerte", "determinado", "seguro de sí", "valiente", "líder", "atrevido"],
|
| 100 |
+
romantic: ["amor", "romántico", "tierno", "abrazo", "beso", "mi amor", "cariño", "apasionado", "querido", "corazón"],
|
| 101 |
+
flirtatious: ["coqueto", "provocar", "seducir", "encanto", "flirtear", "guiño", "coqueto", "seductor", "pícaro"],
|
| 102 |
+
goodbye: ["adiós", "bye", "hasta pronto", "ciao", "hasta luego", "cuídate", "nos vemos", "hasta la próxima"],
|
| 103 |
+
kiss: ["beso", "besos", "abrazar", "besito", "abrazo", "besote"],
|
| 104 |
+
dancing: ["bailar", "baile", "mover", "ritmo", "paso", "girar", "moverse", "sacudir"],
|
| 105 |
+
listening: ["escucha", "escuchar", "oír", "habla", "pregunta", "preguntar", "dime", "escúchame", "pon atención", "presta oído", "concéntrate"],
|
| 106 |
+
android: [
|
| 107 |
+
"protocolo",
|
| 108 |
+
"misión",
|
| 109 |
+
"directiva",
|
| 110 |
+
"subrutina",
|
| 111 |
+
"análisis",
|
| 112 |
+
"táctico",
|
| 113 |
+
"sistema",
|
| 114 |
+
"mal funcionamiento",
|
| 115 |
+
"modo combate",
|
| 116 |
+
"procesamiento",
|
| 117 |
+
"análisis de datos",
|
| 118 |
+
"parámetros de misión"
|
| 119 |
+
],
|
| 120 |
+
sensual: [
|
| 121 |
+
"sensual",
|
| 122 |
+
"pasión",
|
| 123 |
+
"deseo",
|
| 124 |
+
"intimidad",
|
| 125 |
+
"placer",
|
| 126 |
+
"caricia",
|
| 127 |
+
"abrazo",
|
| 128 |
+
"seducción",
|
| 129 |
+
"excitación",
|
| 130 |
+
"kamasutra",
|
| 131 |
+
"afecto",
|
| 132 |
+
"ternura",
|
| 133 |
+
"conexión",
|
| 134 |
+
"toque"
|
| 135 |
+
],
|
| 136 |
+
love: ["amor", "romance", "éxtasis", "beso", "corazón", "alma", "juntos", "compartir", "dulce", "placer", "apasionado", "íntimo", "vínculo"],
|
| 137 |
+
hostile: ["idiota", "estúpido", "estupida", "basura", "te odio", "cállate", "perdedor", "asqueroso", "mierda", "imbécil", "maldito", "vete", "apestas"]
|
| 138 |
+
},
|
| 139 |
+
de: {
|
| 140 |
+
surprise: ["wow", "oh", "überraschung", "unglaublich", "erstaunlich", "wirklich?", "überrascht", "staunend"],
|
| 141 |
+
laughing: ["haha", "lol", "lachen", "lustig", "witzig", "kicher", "grinsen", "du machst Witze"],
|
| 142 |
+
shy: ["schüchtern", "verlegen", "erröten", "beschämt", "eingeschüchtert", "zurückhaltend", "nervös", "schüchternheit"],
|
| 143 |
+
confident: ["vertrauen", "stolz", "sicher", "stark", "entschlossen", "selbstbewusst", "mutig", "führer"],
|
| 144 |
+
romantic: ["liebe", "romantisch", "zärtlich", "umarmung", "kuss", "mein Schatz", "Liebling", "leidenschaftlich", "Herz"],
|
| 145 |
+
flirtatious: ["flirten", "necken", "verführen", "charme", "flirt", "zwinkern", "frech", "verführerisch"],
|
| 146 |
+
goodbye: ["auf wiedersehen", "bye", "bis bald", "ciao", "bis später", "pass auf dich auf", "bis dann", "tschüss"],
|
| 147 |
+
kiss: ["kuss", "küsse", "umarmen", "Küsschen", "Schmatzer"],
|
| 148 |
+
dancing: ["tanzen", "tanz", "bewegen", "groove", "schritt", "drehen", "schwingen"],
|
| 149 |
+
listening: ["hör", "hören", "zuhören", "sprich", "frage", "fragen", "sag mir", "hör zu", "sei aufmerksam", "konzentriere dich"]
|
| 150 |
+
},
|
| 151 |
+
it: {
|
| 152 |
+
surprise: ["wow", "oh", "sorpresa", "incredibile", "stupefacente", "davvero?", "sbalorditivo", "sorpreso"],
|
| 153 |
+
laughing: ["haha", "lol", "ridere", "divertente", "esilarante", "sorriso", "ridacchiare", "stai scherzando"],
|
| 154 |
+
shy: ["timido", "imbarazzato", "arrossire", "vergognoso", "intimidito", "riservato", "introverso", "timidezza", "imbarazzo"],
|
| 155 |
+
confident: ["fiducia", "orgoglioso", "sicuro", "forte", "determinato", "sicuro di sé", "coraggioso", "leader", "audace"],
|
| 156 |
+
romantic: ["amore", "romantico", "tenero", "abbraccio", "bacio", "amore mio", "tesoro", "appassionato", "cuore"],
|
| 157 |
+
flirtatious: ["civettare", "provocare", "sedurre", "fascino", "flirtare", "occhiolino", "malizioso", "seducente"],
|
| 158 |
+
goodbye: ["arrivederci", "bye", "a presto", "ciao", "abbi cura di te", "a dopo", "ciao ciao"],
|
| 159 |
+
kiss: ["bacio", "baci", "abbracciare", "bacino", "abbraccio", "baciotto"],
|
| 160 |
+
dancing: ["ballare", "girare", "muoversi", "scuotere"],
|
| 161 |
+
listening: ["ascoltami", "fai attenzione", "presta orecchio", "concentrati", "ascolta", "parla", "domanda", "dimmi"]
|
| 162 |
+
}
|
| 163 |
+
};
|
| 164 |
+
|
| 165 |
+
window.KIMI_CONTEXT_POSITIVE = {
|
| 166 |
+
en: ["happy", "joy", "great", "awesome", "perfect", "excellent", "magnificent", "lovely", "nice"],
|
| 167 |
+
fr: ["heureux", "joie", "génial", "parfait", "excellent", "magnifique", "super", "chouette"],
|
| 168 |
+
es: ["feliz", "alegría", "genial", "perfecto", "excelente", "magnífico", "estupendo", "maravilloso"],
|
| 169 |
+
de: ["glücklich", "freude", "toll", "perfekt", "ausgezeichnet", "großartig", "wunderbar", "herrlich"],
|
| 170 |
+
it: ["felice", "gioia", "fantastico", "perfetto", "eccellente", "magnifico", "meraviglioso", "ottimo"],
|
| 171 |
+
ja: ["幸せ", "喜び", "素晴らしい", "完璧", "優秀", "壮大", "最高", "嬉しい"],
|
| 172 |
+
zh: ["快乐", "喜悦", "很棒", "完美", "优秀", "壮丽", "太好了", "开心"]
|
| 173 |
+
};
|
| 174 |
+
|
| 175 |
+
window.KIMI_CONTEXT_NEGATIVE = {
|
| 176 |
+
en: [
|
| 177 |
+
"sad",
|
| 178 |
+
"angry",
|
| 179 |
+
"anger",
|
| 180 |
+
"disappointed",
|
| 181 |
+
"problem",
|
| 182 |
+
"bad",
|
| 183 |
+
"frustrated",
|
| 184 |
+
"worried",
|
| 185 |
+
"upset",
|
| 186 |
+
"annoyed",
|
| 187 |
+
// profanity/insults (moderate list)
|
| 188 |
+
"hate",
|
| 189 |
+
"stupid",
|
| 190 |
+
"idiot",
|
| 191 |
+
"dumb",
|
| 192 |
+
"moron",
|
| 193 |
+
"bitch"
|
| 194 |
+
],
|
| 195 |
+
fr: [
|
| 196 |
+
"triste",
|
| 197 |
+
"colère",
|
| 198 |
+
"fâché",
|
| 199 |
+
"fâchée",
|
| 200 |
+
"déçu",
|
| 201 |
+
"déçue",
|
| 202 |
+
"problème",
|
| 203 |
+
"mauvais",
|
| 204 |
+
"frustré",
|
| 205 |
+
"frustrée",
|
| 206 |
+
"inquiet",
|
| 207 |
+
"inquiète",
|
| 208 |
+
"énervé",
|
| 209 |
+
"énervée",
|
| 210 |
+
// insults/profanity
|
| 211 |
+
"haine",
|
| 212 |
+
"idiot",
|
| 213 |
+
"idiote",
|
| 214 |
+
"stupide",
|
| 215 |
+
"con",
|
| 216 |
+
"conne",
|
| 217 |
+
"connasse",
|
| 218 |
+
"connard",
|
| 219 |
+
"pute",
|
| 220 |
+
"salope"
|
| 221 |
+
],
|
| 222 |
+
es: [
|
| 223 |
+
"triste",
|
| 224 |
+
"enojado",
|
| 225 |
+
"enojada",
|
| 226 |
+
"decepcionado",
|
| 227 |
+
"decepcionada",
|
| 228 |
+
"problema",
|
| 229 |
+
"malo",
|
| 230 |
+
"mala",
|
| 231 |
+
"frustrado",
|
| 232 |
+
"frustrada",
|
| 233 |
+
"preocupado",
|
| 234 |
+
"preocupada",
|
| 235 |
+
"molesto",
|
| 236 |
+
"molesta",
|
| 237 |
+
"odio",
|
| 238 |
+
"idiota",
|
| 239 |
+
"estúpido",
|
| 240 |
+
"estúpida",
|
| 241 |
+
"puta"
|
| 242 |
+
],
|
| 243 |
+
de: [
|
| 244 |
+
"traurig",
|
| 245 |
+
"traurige",
|
| 246 |
+
"wütend",
|
| 247 |
+
"wütende",
|
| 248 |
+
"enttäuscht",
|
| 249 |
+
"enttäuschte",
|
| 250 |
+
"problem",
|
| 251 |
+
"schlecht",
|
| 252 |
+
"schlechte",
|
| 253 |
+
"frustriert",
|
| 254 |
+
"frustrierte",
|
| 255 |
+
"besorgt",
|
| 256 |
+
"besorgte",
|
| 257 |
+
"genervt",
|
| 258 |
+
"genervte",
|
| 259 |
+
"hass",
|
| 260 |
+
"idiot",
|
| 261 |
+
"dumm",
|
| 262 |
+
"schlampe"
|
| 263 |
+
],
|
| 264 |
+
it: [
|
| 265 |
+
"triste",
|
| 266 |
+
"arrabbiato",
|
| 267 |
+
"arrabbiata",
|
| 268 |
+
"deluso",
|
| 269 |
+
"delusa",
|
| 270 |
+
"problema",
|
| 271 |
+
"cattivo",
|
| 272 |
+
"cattiva",
|
| 273 |
+
"frustrato",
|
| 274 |
+
"frustrata",
|
| 275 |
+
"preoccupato",
|
| 276 |
+
"preoccupata",
|
| 277 |
+
"infastidito",
|
| 278 |
+
"infastidita",
|
| 279 |
+
"odio",
|
| 280 |
+
"idiota",
|
| 281 |
+
"stupido",
|
| 282 |
+
"stupida",
|
| 283 |
+
"puttana"
|
| 284 |
+
],
|
| 285 |
+
ja: ["悲しい", "怒り", "失望", "問題", "悪い", "イライラ", "心配", "不満", "嫌い", "ばか", "くそ", "アホ"],
|
| 286 |
+
zh: ["悲伤", "愤怒", "失望", "问题", "坏", "沮丧", "担心", "烦", "讨厌", "笨蛋", "傻", "婊子"]
|
| 287 |
+
};
|
| 288 |
+
|
| 289 |
+
// Personality keywords for trait analysis (multilingual)
|
| 290 |
+
window.KIMI_PERSONALITY_KEYWORDS = {
|
| 291 |
+
en: {
|
| 292 |
+
humor: {
|
| 293 |
+
positive: ["funny", "hilarious", "joke", "laugh", "amusing", "humorous", "smile", "witty", "playful"],
|
| 294 |
+
negative: ["boring", "sad", "serious", "cold", "dry", "depressing", "gloomy"]
|
| 295 |
+
},
|
| 296 |
+
intelligence: {
|
| 297 |
+
positive: ["intelligent", "smart", "brilliant", "logical", "clever", "wise", "genius", "thoughtful", "insightful"],
|
| 298 |
+
negative: ["stupid", "dumb", "foolish", "slow", "naive", "ignorant", "simple"]
|
| 299 |
+
},
|
| 300 |
+
romance: {
|
| 301 |
+
positive: ["cuddle", "love", "romantic", "kiss", "tenderness", "passion", "charming", "adorable", "sweet"],
|
| 302 |
+
negative: ["cold", "distant", "indifferent", "rejection", "loneliness", "breakup", "sad"]
|
| 303 |
+
},
|
| 304 |
+
affection: {
|
| 305 |
+
positive: ["affection", "tenderness", "close", "warmth", "kind", "caring", "cuddle", "love", "adore", "lovely"],
|
| 306 |
+
negative: [
|
| 307 |
+
"mean",
|
| 308 |
+
"cold",
|
| 309 |
+
"indifferent",
|
| 310 |
+
"distant",
|
| 311 |
+
"rejection",
|
| 312 |
+
"hate",
|
| 313 |
+
"hostile",
|
| 314 |
+
// profanity/insults
|
| 315 |
+
"stupid",
|
| 316 |
+
"idiot",
|
| 317 |
+
"dumb",
|
| 318 |
+
"moron",
|
| 319 |
+
"bitch"
|
| 320 |
+
]
|
| 321 |
+
},
|
| 322 |
+
playfulness: {
|
| 323 |
+
positive: ["play", "game", "tease", "mischievous", "fun", "amusing", "playful", "joke", "frolic"],
|
| 324 |
+
negative: ["serious", "boring", "strict", "rigid", "monotonous", "tedious"]
|
| 325 |
+
},
|
| 326 |
+
empathy: {
|
| 327 |
+
positive: ["listen", "understand", "empathy", "support", "help", "comfort", "compassion", "caring", "kindness"],
|
| 328 |
+
negative: ["indifferent", "cold", "selfish", "ignore", "despise", "hostile", "uncaring"]
|
| 329 |
+
}
|
| 330 |
+
},
|
| 331 |
+
fr: {
|
| 332 |
+
humor: {
|
| 333 |
+
positive: ["drôle", "rigolo", "blague", "rire", "amusant", "marrant", "humour", "sourire", "plaisanter"],
|
| 334 |
+
negative: ["ennuyeux", "ennuyeuse", "triste", "sérieux", "sérieuse", "froid", "froide", "sec", "sèche", "déprimant", "déprimante", "morose"]
|
| 335 |
+
},
|
| 336 |
+
intelligence: {
|
| 337 |
+
positive: ["intelligent", "malin", "brillant", "logique", "astucieux", "savant", "génie", "réfléchi", "perspicace"],
|
| 338 |
+
negative: ["bête", "idiot", "idiote", "stupide", "lent", "lente", "simplet", "simplette", "naïf", "naïve", "ignorant", "ignorante"]
|
| 339 |
+
},
|
| 340 |
+
romance: {
|
| 341 |
+
positive: ["câlin", "amour", "romantique", "bisou", "tendresse", "passion", "séduisant", "charmant", "adorable"],
|
| 342 |
+
negative: ["froid", "froide", "distant", "distante", "indifférent", "indifférente", "rejet", "solitude", "rupture", "triste"]
|
| 343 |
+
},
|
| 344 |
+
affection: {
|
| 345 |
+
positive: ["affection", "tendresse", "proche", "chaleur", "gentil", "attentionné", "câlin", "aimer", "adorer", "adorable"],
|
| 346 |
+
negative: [
|
| 347 |
+
"méchant",
|
| 348 |
+
"méchante",
|
| 349 |
+
"froid",
|
| 350 |
+
"indifférent",
|
| 351 |
+
"indifférente",
|
| 352 |
+
"distant",
|
| 353 |
+
"distante",
|
| 354 |
+
"rejet",
|
| 355 |
+
"haine",
|
| 356 |
+
"hostile",
|
| 357 |
+
// insults/profanity
|
| 358 |
+
"idiot",
|
| 359 |
+
"idiote",
|
| 360 |
+
"stupide",
|
| 361 |
+
"con",
|
| 362 |
+
"connard",
|
| 363 |
+
"salope"
|
| 364 |
+
]
|
| 365 |
+
},
|
| 366 |
+
playfulness: {
|
| 367 |
+
positive: ["jouer", "jeu", "taquiner", "espiègle", "fun", "amusant", "délire", "ludique", "plaisanter"],
|
| 368 |
+
negative: ["sérieux", "sérieuse", "ennuyeux", "ennuyeuse", "strict", "stricte", "rigide", "monotone", "lassant", "lassante"]
|
| 369 |
+
},
|
| 370 |
+
empathy: {
|
| 371 |
+
positive: ["écoute", "comprendre", "empathie", "soutien", "aider", "réconfort", "solidaire", "compatir", "bienveillance"],
|
| 372 |
+
negative: ["indifférent", "indifférente", "froid", "froide", "égoïste", "ignorer", "mépriser", "dénigrer", "hostile"]
|
| 373 |
+
}
|
| 374 |
+
},
|
| 375 |
+
es: {
|
| 376 |
+
humor: {
|
| 377 |
+
positive: ["divertido", "broma", "reír", "gracioso", "humor", "sonrisa", "ocurrente", "jugar"],
|
| 378 |
+
negative: ["aburrido", "aburrida", "serio", "seria", "frío", "fría", "seco", "seca", "deprimente", "sombrío", "sombría"]
|
| 379 |
+
},
|
| 380 |
+
intelligence: {
|
| 381 |
+
positive: ["inteligente", "listo", "brillante", "lógico", "sabio", "genio", "reflexivo", "perspicaz"],
|
| 382 |
+
negative: ["tonto", "tonta", "estúpido", "estúpida", "necio", "necia", "lento", "lenta", "ingenuo", "ingenua", "ignorante"]
|
| 383 |
+
},
|
| 384 |
+
romance: {
|
| 385 |
+
positive: ["abrazo", "amor", "romántico", "beso", "ternura", "pasión", "encantador", "adorable", "dulce"],
|
| 386 |
+
negative: ["frío", "fría", "distante", "indiferente", "rechazo", "soledad", "ruptura", "triste"]
|
| 387 |
+
},
|
| 388 |
+
affection: {
|
| 389 |
+
positive: ["afecto", "ternura", "cerca", "calidez", "amable", "cariño", "abrazar", "amor", "adorar"],
|
| 390 |
+
negative: ["malo", "mala", "frío", "fría", "indiferente", "distante", "rechazo", "odio", "hostil", "idiota", "estúpido", "estúpida", "puta"]
|
| 391 |
+
},
|
| 392 |
+
playfulness: {
|
| 393 |
+
positive: ["jugar", "broma", "bromear", "travieso", "diversión", "lúdico"],
|
| 394 |
+
negative: ["serio", "seria", "aburrido", "aburrida", "estricto", "estricta", "rígido", "rígida", "monótono", "monótona", "tedioso", "tediosa"]
|
| 395 |
+
},
|
| 396 |
+
empathy: {
|
| 397 |
+
positive: ["escuchar", "entender", "empatía", "apoyo", "ayudar", "consuelo", "compasión", "amabilidad"],
|
| 398 |
+
negative: ["indiferente", "frío", "fría", "egoísta", "ignorar", "despreciar", "hostil"]
|
| 399 |
+
}
|
| 400 |
+
},
|
| 401 |
+
de: {
|
| 402 |
+
humor: {
|
| 403 |
+
positive: ["lustig", "witz", "lachen", "amüsant", "humor", "lächeln", "schlagfertig", "spielen"],
|
| 404 |
+
negative: ["langweilig", "langweilige", "ernst", "ernste", "kalt", "kalte", "trocken", "trockene", "deprimierend", "düster", "düstere"]
|
| 405 |
+
},
|
| 406 |
+
intelligence: {
|
| 407 |
+
positive: ["intelligent", "klug", "brillant", "logisch", "weise", "genial", "nachdenklich", "scharfsinnig"],
|
| 408 |
+
negative: ["dumm", "dumme", "blöd", "blöde", "langsam", "langsame", "naiv", "naive", "ahnungslos", "ahnungslosen"]
|
| 409 |
+
},
|
| 410 |
+
romance: {
|
| 411 |
+
positive: ["umarmung", "liebe", "romantisch", "kuss", "zärtlichkeit", "leidenschaft", "charmant", "liebenswert", "süß"],
|
| 412 |
+
negative: [
|
| 413 |
+
"kalt",
|
| 414 |
+
"kalte",
|
| 415 |
+
"distanziert",
|
| 416 |
+
"distanzierte",
|
| 417 |
+
"gleichgültig",
|
| 418 |
+
"gleichgültige",
|
| 419 |
+
"ablehnung",
|
| 420 |
+
"einsamkeit",
|
| 421 |
+
"trennung",
|
| 422 |
+
"traurig",
|
| 423 |
+
"traurige"
|
| 424 |
+
]
|
| 425 |
+
},
|
| 426 |
+
affection: {
|
| 427 |
+
positive: ["zuneigung", "zärtlichkeit", "nah", "wärme", "freundlich", "fürsorglich", "umarmen", "liebe", "anbeten"],
|
| 428 |
+
negative: [
|
| 429 |
+
"gemein",
|
| 430 |
+
"gemeine",
|
| 431 |
+
"kalt",
|
| 432 |
+
"kalte",
|
| 433 |
+
"gleichgültig",
|
| 434 |
+
"gleichgültige",
|
| 435 |
+
"distanziert",
|
| 436 |
+
"distanzierte",
|
| 437 |
+
"ablehnung",
|
| 438 |
+
"hass",
|
| 439 |
+
"feindselig",
|
| 440 |
+
"feindselige",
|
| 441 |
+
"idiot",
|
| 442 |
+
"dumme",
|
| 443 |
+
"dumm",
|
| 444 |
+
"schlampe"
|
| 445 |
+
]
|
| 446 |
+
},
|
| 447 |
+
playfulness: {
|
| 448 |
+
positive: ["spielen", "scherz", "scherzen", "schelmisch", "spaß", "spielerisch"],
|
| 449 |
+
negative: ["ernst", "ernste", "langweilig", "langweilige", "streng", "strenge", "starr", "starre", "eintönig", "eintönige", "mühsam", "mühselige"]
|
| 450 |
+
},
|
| 451 |
+
empathy: {
|
| 452 |
+
positive: ["zuhören", "verstehen", "empathie", "unterstützung", "helfen", "trösten", "mitgefühl", "freundlichkeit"],
|
| 453 |
+
negative: ["gleichgültig", "gleichgültige", "kalt", "kalte", "egoistisch", "ignorieren", "verachten", "feindselig", "feindselige"]
|
| 454 |
+
}
|
| 455 |
+
},
|
| 456 |
+
it: {
|
| 457 |
+
humor: {
|
| 458 |
+
positive: ["divertente", "scherzo", "ridere", "spassoso", "umorismo", "sorriso", "arguto", "giocare"],
|
| 459 |
+
negative: ["noioso", "noiosa", "serio", "seria", "freddo", "fredda", "secco", "secca", "deprimente", "cupo", "cupa"]
|
| 460 |
+
},
|
| 461 |
+
intelligence: {
|
| 462 |
+
positive: ["intelligente", "brillante", "logico", "saggio", "genio", "riflessivo", "perspicace"],
|
| 463 |
+
negative: ["stupido", "stupida", "sciocco", "sciocca", "lento", "lenta", "ingenuo", "ingenua", "ignorante"]
|
| 464 |
+
},
|
| 465 |
+
romance: {
|
| 466 |
+
positive: ["abbraccio", "amore", "romantico", "bacio", "tenerezza", "passione", "affascinante", "adorabile", "dolce"],
|
| 467 |
+
negative: ["freddo", "fredda", "distante", "indifferente", "rifiuto", "solitudine", "rottura", "triste"]
|
| 468 |
+
},
|
| 469 |
+
affection: {
|
| 470 |
+
positive: ["affetto", "tenerezza", "vicino", "calore", "gentile", "premuroso", "abbraccio", "amore", "adorare"],
|
| 471 |
+
negative: [
|
| 472 |
+
"cattivo",
|
| 473 |
+
"cattiva",
|
| 474 |
+
"freddo",
|
| 475 |
+
"fredda",
|
| 476 |
+
"indifferente",
|
| 477 |
+
"distante",
|
| 478 |
+
"rifiuto",
|
| 479 |
+
"odio",
|
| 480 |
+
"ostile",
|
| 481 |
+
"idiota",
|
| 482 |
+
"stupido",
|
| 483 |
+
"stupida",
|
| 484 |
+
"puttana"
|
| 485 |
+
]
|
| 486 |
+
},
|
| 487 |
+
playfulness: {
|
| 488 |
+
positive: ["giocare", "scherzo", "scherzare", "birichino", "divertimento", "ludico"],
|
| 489 |
+
negative: ["serio", "seria", "noioso", "noiosa", "severo", "severa", "rigido", "rigida", "monotono", "monotona", "tedioso", "tediosa"]
|
| 490 |
+
},
|
| 491 |
+
empathy: {
|
| 492 |
+
positive: ["ascoltare", "capire", "empatia", "sostegno", "aiutare", "conforto", "compassione", "gentilezza"],
|
| 493 |
+
negative: ["indifferente", "freddo", "fredda", "egoista", "ignorare", "disprezzare", "ostile"]
|
| 494 |
+
}
|
| 495 |
+
},
|
| 496 |
+
ja: {
|
| 497 |
+
surprise: ["わお", "おお", "驚き", "信じられない", "すごい"],
|
| 498 |
+
laughing: ["はは", "笑", "笑う", "面白い", "愉快"],
|
| 499 |
+
shy: ["恥ずかしい", "照れる", "赤面", "内気", "遠慮"],
|
| 500 |
+
confident: ["自信", "誇り", "確信", "強い", "決意"],
|
| 501 |
+
romantic: ["愛", "ロマンチック", "優しい", "抱擁", "キス", "愛しい"],
|
| 502 |
+
flirtatious: ["いちゃつく", "からかう", "誘惑", "魅力", "フリート"],
|
| 503 |
+
goodbye: ["さようなら", "バイバイ", "また今度", "チャオ", "またね"],
|
| 504 |
+
kiss: ["キス", "抱擁", "チュー"],
|
| 505 |
+
dancing: ["踊る", "ダンス", "動く", "グルーブ", "ステップ"],
|
| 506 |
+
listening: ["聞いて", "聞く", "聞いてください", "話して", "話す", "質問", "尋ねる", "教えて"]
|
| 507 |
+
},
|
| 508 |
+
zh: {
|
| 509 |
+
surprise: ["哇", "哦", "惊喜", "难以置信", "惊人"],
|
| 510 |
+
laughing: ["哈哈", "笑", "大笑", "有趣", "搞笑"],
|
| 511 |
+
shy: ["害羞", "尴尬", "脸红", "羞涩", "胆怯"],
|
| 512 |
+
confident: ["自信", "骄傲", "确信", "强壮", "坚定"],
|
| 513 |
+
romantic: ["爱", "浪漫", "温柔", "拥抱", "吻", "亲爱的"],
|
| 514 |
+
flirtatious: ["调情", "挑逗", "诱惑", "魅力", "撒娇"],
|
| 515 |
+
goodbye: ["再见", "拜拜", "回头见", "拜", "下次见"],
|
| 516 |
+
kiss: ["吻", "亲吻", "拥抱", "亲"],
|
| 517 |
+
dancing: ["跳舞", "舞蹈", "移动", "律动", "步伐"],
|
| 518 |
+
listening: ["听", "听听", "倾听", "说", "说话", "问题", "提问", "告诉我"]
|
| 519 |
+
}
|
| 520 |
+
};
|
| 521 |
+
|
| 522 |
+
// Negators and smoothing defaults (configurable at runtime)
|
| 523 |
+
window.KIMI_NEGATORS = window.KIMI_NEGATORS || {
|
| 524 |
+
common: ["ne", "n", "pas", "jamais", "plus", "aucun", "aucune", "rien", "personne", "no", "not", "never", "none", "nobody", "nothing", "non", "n't"],
|
| 525 |
+
fr: [
|
| 526 |
+
"ne",
|
| 527 |
+
"n",
|
| 528 |
+
"pas",
|
| 529 |
+
"jamais",
|
| 530 |
+
"plus",
|
| 531 |
+
"aucun",
|
| 532 |
+
"aucune",
|
| 533 |
+
"rien",
|
| 534 |
+
"personne",
|
| 535 |
+
"non",
|
| 536 |
+
// multiword patterns that we may detect by looking around tokens
|
| 537 |
+
"ne pas",
|
| 538 |
+
"n\'importe",
|
| 539 |
+
"ne jamais"
|
| 540 |
+
],
|
| 541 |
+
en: ["no", "not", "never", "none", "nobody", "nothing", "don't", "doesn't", "didn't", "isn't", "aren't", "can't", "couldn't", "won't", "wouldn't", "n't"],
|
| 542 |
+
es: ["no", "nunca", "jamás", "ninguno", "nadie", "nada"],
|
| 543 |
+
de: ["nicht", "nie", "kein", "keine", "niemand", "nichts"],
|
| 544 |
+
it: ["non", "mai", "nessuno", "niente"],
|
| 545 |
+
ja: ["ない", "ません", "ず", "無い"],
|
| 546 |
+
zh: ["不", "没", "没有", "从来没有"]
|
| 547 |
+
};
|
| 548 |
+
|
| 549 |
+
window.KIMI_NEGATION_WINDOW = window.KIMI_NEGATION_WINDOW || 3; // tokens to look back for negation
|
| 550 |
+
window.KIMI_SMOOTHING_ALPHA = window.KIMI_SMOOTHING_ALPHA || 0.3;
|
| 551 |
+
window.KIMI_PERSIST_THRESHOLD = window.KIMI_PERSIST_THRESHOLD || 0.1; // absolute percent (slightly higher to slow small visible jumps)
|
| 552 |
+
|
| 553 |
+
// Memory system knobs
|
| 554 |
+
window.KIMI_MAX_MEMORIES = window.KIMI_MAX_MEMORIES || 100; // default max memory entries per character
|
| 555 |
+
window.KIMI_MEMORY_TTL_DAYS = window.KIMI_MEMORY_TTL_DAYS || 365; // soft-expire memories older than this (days)
|
| 556 |
+
window.KIMI_MEMORY_MERGE_THRESHOLD = window.KIMI_MEMORY_MERGE_THRESHOLD || 0.7; // similarity threshold for merging
|
| 557 |
+
// Touch debounce: minimum minutes between updating lastAccess for same memory
|
| 558 |
+
window.KIMI_MEMORY_TOUCH_MINUTES = window.KIMI_MEMORY_TOUCH_MINUTES || 60; // minutes
|
| 559 |
+
|
| 560 |
+
// Scoring weights (tweak to change memory prioritization)
|
| 561 |
+
window.KIMI_WEIGHT_IMPORTANCE = window.KIMI_WEIGHT_IMPORTANCE || 0.35;
|
| 562 |
+
window.KIMI_WEIGHT_RECENCY = window.KIMI_WEIGHT_RECENCY || 0.2;
|
| 563 |
+
window.KIMI_WEIGHT_FREQUENCY = window.KIMI_WEIGHT_FREQUENCY || 0.15;
|
| 564 |
+
window.KIMI_WEIGHT_CONFIDENCE = window.KIMI_WEIGHT_CONFIDENCE || 0.2;
|
| 565 |
+
window.KIMI_WEIGHT_FRESHNESS = window.KIMI_WEIGHT_FRESHNESS || 0.1;
|
| 566 |
+
|
| 567 |
+
// Optimized common words system - Essential words only for memory analysis
|
| 568 |
+
window.KIMI_COMMON_WORDS = {
|
| 569 |
+
en: ["the", "be", "to", "of", "and", "a", "in", "that", "have", "i", "it", "for", "not", "on", "with", "he", "as", "you", "do", "at"],
|
| 570 |
+
fr: ["le", "de", "et", "être", "un", "il", "avoir", "ne", "je", "son", "que", "se", "qui", "ce", "dans", "en", "du", "elle", "au", "si"],
|
| 571 |
+
es: ["que", "de", "no", "a", "la", "el", "es", "y", "en", "lo", "un", "ser", "se", "me", "una", "con", "para", "mi", "está", "te"],
|
| 572 |
+
de: ["der", "die", "und", "in", "den", "von", "zu", "das", "mit", "sich", "des", "auf", "für", "ist", "im", "dem", "nicht", "ein", "eine", "als"],
|
| 573 |
+
it: ["il", "di", "che", "e", "la", "per", "un", "in", "con", "da", "su", "le", "dei", "del", "si", "al", "come", "più", "ma", "una"],
|
| 574 |
+
ja: ["の", "に", "は", "を", "た", "が", "で", "て", "と", "し", "れ", "さ", "ある", "いる", "も", "する", "から"],
|
| 575 |
+
zh: ["的", "一", "是", "在", "不", "了", "有", "和", "人", "这", "中", "大", "为", "上", "个", "国", "我", "以", "要"]
|
| 576 |
+
};
|
| 577 |
+
|
| 578 |
+
// Build Set version for fast lookup (must be outside the object)
|
| 579 |
+
window.KIMI_COMMON_WORDS_SET = {};
|
| 580 |
+
Object.keys(window.KIMI_COMMON_WORDS).forEach(lang => {
|
| 581 |
+
window.KIMI_COMMON_WORDS_SET[lang] = new Set(window.KIMI_COMMON_WORDS[lang]);
|
| 582 |
+
});
|
| 583 |
+
|
| 584 |
+
// Helper function to check if a word is common
|
| 585 |
+
window.isCommonWord = function (word, language = "en") {
|
| 586 |
+
const set = window.KIMI_COMMON_WORDS_SET[language] || window.KIMI_COMMON_WORDS_SET.en;
|
| 587 |
+
return set.has(word.toLowerCase());
|
| 588 |
+
};
|
| 589 |
+
|
| 590 |
+
// Emotion detection sensitivity configuration (per language and emotion)
|
| 591 |
+
// Values are weights (>= 0). Higher = more priority/sensitivity for that emotion in that language.
|
| 592 |
+
// 'default' applies when a language-specific override is not defined.
|
| 593 |
+
window.KIMI_EMOTION_SENSITIVITY = {
|
| 594 |
+
default: {
|
| 595 |
+
listening: 1.0,
|
| 596 |
+
dancing: 1.0,
|
| 597 |
+
romantic: 1.0,
|
| 598 |
+
laughing: 1.0,
|
| 599 |
+
surprise: 1.0,
|
| 600 |
+
confident: 1.0,
|
| 601 |
+
shy: 1.0,
|
| 602 |
+
flirtatious: 1.0,
|
| 603 |
+
kiss: 1.0,
|
| 604 |
+
goodbye: 1.0,
|
| 605 |
+
positive: 1.0,
|
| 606 |
+
negative: 1.0
|
| 607 |
+
},
|
| 608 |
+
// Example language-specific overrides (can be adjusted via settings if needed)
|
| 609 |
+
fr: { romantic: 1.1, laughing: 0.95 },
|
| 610 |
+
es: { romantic: 1.05, laughing: 1.0 },
|
| 611 |
+
it: { romantic: 1.2, laughing: 0.9 },
|
| 612 |
+
de: { romantic: 1.0, laughing: 1.0 },
|
| 613 |
+
en: { romantic: 1.0, laughing: 1.0 },
|
| 614 |
+
ja: { romantic: 1.0, laughing: 1.0 },
|
| 615 |
+
zh: { romantic: 1.0, laughing: 1.0 }
|
| 616 |
+
};
|
| 617 |
+
|
| 618 |
+
// Personality trait adjustment multipliers
|
| 619 |
+
// Allows fine-tuning how fast traits evolve globally and per emotion/trait.
|
| 620 |
+
window.KIMI_TRAIT_ADJUSTMENT = {
|
| 621 |
+
globalGain: 1.2,
|
| 622 |
+
globalLoss: 0.8,
|
| 623 |
+
// Per-emotion gain scaling (keys must match KimiEmotionSystem.EMOTIONS values)
|
| 624 |
+
emotionGain: {
|
| 625 |
+
positive: 1.1,
|
| 626 |
+
negative: 0.9,
|
| 627 |
+
romantic: 1.3,
|
| 628 |
+
laughing: 1.15,
|
| 629 |
+
dancing: 1.05,
|
| 630 |
+
shy: 0.95,
|
| 631 |
+
confident: 1.1,
|
| 632 |
+
flirtatious: 1.2,
|
| 633 |
+
surprise: 1.05,
|
| 634 |
+
listening: 1.1,
|
| 635 |
+
kiss: 1.35,
|
| 636 |
+
goodbye: 0.9
|
| 637 |
+
},
|
| 638 |
+
// Per-trait scaling
|
| 639 |
+
traitGain: {
|
| 640 |
+
affection: 1.15, // Affection growth multiplier
|
| 641 |
+
romance: 1.2, // Romance growth multiplier
|
| 642 |
+
empathy: 1.1, // Empathy growth multiplier
|
| 643 |
+
playfulness: 1.15, // Playfulness growth multiplier
|
| 644 |
+
humor: 1.12, // Humor growth multiplier
|
| 645 |
+
intelligence: 1.08 // Intelligence growth multiplier
|
| 646 |
+
},
|
| 647 |
+
traitLoss: {
|
| 648 |
+
affection: 0.9,
|
| 649 |
+
romance: 0.9,
|
| 650 |
+
empathy: 1.0,
|
| 651 |
+
playfulness: 1.0,
|
| 652 |
+
humor: 1.0,
|
| 653 |
+
intelligence: 1.0
|
| 654 |
+
}
|
| 655 |
+
};
|
| 656 |
+
|
| 657 |
+
// Cached keyword lookups for performance
|
| 658 |
+
const _keywordCache = new Map();
|
| 659 |
+
|
| 660 |
+
// Unified normalization (lowercase + trim)
|
| 661 |
+
function _normText(t) {
|
| 662 |
+
if (!t || typeof t !== "string") return "";
|
| 663 |
+
return t.toLowerCase();
|
| 664 |
+
}
|
| 665 |
+
|
| 666 |
+
// Central helper: test if a given raw text contains any keyword of a category (multi-language fallback)
|
| 667 |
+
// Categories expected: dancing, listening, romantic, kiss, etc. Must match keys in KIMI_CONTEXT_KEYWORDS language objects.
|
| 668 |
+
// Strategy: check detected language first (if provided) else attempt simple heuristics, fallback to 'en'.
|
| 669 |
+
if (!window.hasKeywordCategory) {
|
| 670 |
+
window.hasKeywordCategory = function hasKeywordCategory(category, rawText, language = null) {
|
| 671 |
+
if (!category || !rawText) return false;
|
| 672 |
+
const text = _normText(rawText);
|
| 673 |
+
// Language resolution: direct use, else fallback to window.KIMI_LAST_LANG or 'en'
|
| 674 |
+
const lang = language || window.KIMI_LAST_LANG || "en";
|
| 675 |
+
const langKeywords = (window.KIMI_CONTEXT_KEYWORDS && (window.KIMI_CONTEXT_KEYWORDS[lang] || window.KIMI_CONTEXT_KEYWORDS.en)) || {};
|
| 676 |
+
let list = langKeywords[category];
|
| 677 |
+
if (!Array.isArray(list)) {
|
| 678 |
+
// fallback chain: english, then first language available
|
| 679 |
+
list = (window.KIMI_CONTEXT_KEYWORDS?.en && window.KIMI_CONTEXT_KEYWORDS.en[category]) || [];
|
| 680 |
+
}
|
| 681 |
+
if (!list || list.length === 0) return false;
|
| 682 |
+
return list.some(kw => text.includes(_normText(kw)));
|
| 683 |
+
};
|
| 684 |
+
}
|
| 685 |
+
|
| 686 |
+
// Multi-category match: returns array des catégories détectées (ordre d'entrée conservé)
|
| 687 |
+
if (!window.matchCategories) {
|
| 688 |
+
// matchCategories(rawText, categories, language?, options?)
|
| 689 |
+
// options:
|
| 690 |
+
// details:boolean -> when true returns objects instead of category strings
|
| 691 |
+
// allOccurrences:boolean -> with details=true returns ALL occurrences (each {category, keyword, index}); otherwise first per category
|
| 692 |
+
// cache:boolean (default true) -> enable small in‑memory LRU (per language+options+text)
|
| 693 |
+
// cacheSize:number (default 200, min 50) -> max entries in LRU
|
| 694 |
+
// regex:boolean -> treat provided categories array entries that are objects {name, pattern, flags?} or strings when pattern supplied separately
|
| 695 |
+
// Category entry forms supported:
|
| 696 |
+
// "dancing" (string key)
|
| 697 |
+
// { name:"custom", keywords:["foo","bar"] }
|
| 698 |
+
// { name:"timePattern", regex:"\\b(\n|now|today)\\b", flags:"i" }
|
| 699 |
+
// { name:"emote", pattern:"😀|😃|😂", regex:true }
|
| 700 |
+
// Return shape:
|
| 701 |
+
// details=false => ["category1", "category2", ...]
|
| 702 |
+
// details=true & allOccurrences=false => [{category, keyword, index}, ...]
|
| 703 |
+
// details=true & allOccurrences=true => [{category, keyword, index}, {category, keyword, index}, ...]
|
| 704 |
+
const _mcLRU = new Map(); // key -> result; oldest = first inserted
|
| 705 |
+
function _mcGet(key) {
|
| 706 |
+
return _mcLRU.get(key);
|
| 707 |
+
}
|
| 708 |
+
function _mcSet(key, val, max) {
|
| 709 |
+
if (_mcLRU.has(key)) _mcLRU.delete(key);
|
| 710 |
+
_mcLRU.set(key, val);
|
| 711 |
+
while (_mcLRU.size > max) {
|
| 712 |
+
const firstKey = _mcLRU.keys().next().value;
|
| 713 |
+
_mcLRU.delete(firstKey);
|
| 714 |
+
}
|
| 715 |
+
}
|
| 716 |
+
window.matchCategories = function matchCategories(rawText, categories, language = null, options = {}) {
|
| 717 |
+
if (!rawText || !Array.isArray(categories) || categories.length === 0) return [];
|
| 718 |
+
const details = !!options.details;
|
| 719 |
+
const allOcc = !!options.allOccurrences;
|
| 720 |
+
const cacheEnabled = options.cache !== false; // default true
|
| 721 |
+
const cacheSize = typeof options.cacheSize === "number" && options.cacheSize > 10 ? options.cacheSize : 200;
|
| 722 |
+
const useRegex = !!options.regex; // explicit enable to parse regex objects
|
| 723 |
+
const lang = language || window.KIMI_LAST_LANG || "en";
|
| 724 |
+
const textNorm = _normText(rawText);
|
| 725 |
+
const catsKey = JSON.stringify(categories);
|
| 726 |
+
const cacheKey = cacheEnabled ? `${lang}|${details}|${allOcc}|${catsKey}|${textNorm}` : null;
|
| 727 |
+
if (cacheEnabled && cacheKey && _mcGet(cacheKey)) return _mcGet(cacheKey);
|
| 728 |
+
|
| 729 |
+
const langKeywords = (window.KIMI_CONTEXT_KEYWORDS && (window.KIMI_CONTEXT_KEYWORDS[lang] || window.KIMI_CONTEXT_KEYWORDS.en)) || {};
|
| 730 |
+
const results = [];
|
| 731 |
+
for (const entry of categories) {
|
| 732 |
+
let catName;
|
| 733 |
+
let keywordList = [];
|
| 734 |
+
let regexObj = null;
|
| 735 |
+
if (typeof entry === "string") {
|
| 736 |
+
catName = entry;
|
| 737 |
+
keywordList = langKeywords[catName] || window.KIMI_CONTEXT_KEYWORDS?.en?.[catName] || [];
|
| 738 |
+
} else if (entry && typeof entry === "object") {
|
| 739 |
+
catName = entry.name || entry.category || "unnamed";
|
| 740 |
+
if (entry.keywords && Array.isArray(entry.keywords)) {
|
| 741 |
+
keywordList = entry.keywords;
|
| 742 |
+
} else if (entry.regex || entry.pattern) {
|
| 743 |
+
if (useRegex) {
|
| 744 |
+
try {
|
| 745 |
+
regexObj = entry._compiled || new RegExp(entry.regex || entry.pattern, entry.flags || (entry.caseInsensitive ? "i" : ""));
|
| 746 |
+
entry._compiled = regexObj; // cache compile inside object
|
| 747 |
+
} catch (e) {
|
| 748 |
+
// ignore invalid regex
|
| 749 |
+
}
|
| 750 |
+
}
|
| 751 |
+
}
|
| 752 |
+
} else {
|
| 753 |
+
continue;
|
| 754 |
+
}
|
| 755 |
+
|
| 756 |
+
if (regexObj) {
|
| 757 |
+
if (!details) {
|
| 758 |
+
if (regexObj.test(rawText)) results.push(catName);
|
| 759 |
+
regexObj.lastIndex = 0; // reset stateful if /g
|
| 760 |
+
} else {
|
| 761 |
+
const matches = [];
|
| 762 |
+
const pattern = new RegExp(regexObj.source, regexObj.flags.includes("g") ? regexObj.flags : regexObj.flags + "g");
|
| 763 |
+
let m;
|
| 764 |
+
while ((m = pattern.exec(rawText)) !== null) {
|
| 765 |
+
matches.push({ category: catName, keyword: m[0], index: m.index });
|
| 766 |
+
if (!allOcc) break;
|
| 767 |
+
}
|
| 768 |
+
if (matches.length) {
|
| 769 |
+
if (allOcc) results.push(...matches.sort((a, b) => a.index - b.index));
|
| 770 |
+
else results.push(matches[0]);
|
| 771 |
+
}
|
| 772 |
+
}
|
| 773 |
+
continue;
|
| 774 |
+
}
|
| 775 |
+
|
| 776 |
+
const list = keywordList.map(k => _normText(k)).filter(Boolean);
|
| 777 |
+
if (list.length === 0) continue;
|
| 778 |
+
if (!details) {
|
| 779 |
+
if (list.some(kw => textNorm.includes(kw))) results.push(catName);
|
| 780 |
+
continue;
|
| 781 |
+
}
|
| 782 |
+
const matches = [];
|
| 783 |
+
for (const kw of list) {
|
| 784 |
+
let start = 0;
|
| 785 |
+
while (true) {
|
| 786 |
+
const idx = textNorm.indexOf(kw, start);
|
| 787 |
+
if (idx === -1) break;
|
| 788 |
+
matches.push({ category: catName, keyword: kw, index: idx });
|
| 789 |
+
if (!allOcc) break;
|
| 790 |
+
start = idx + kw.length;
|
| 791 |
+
}
|
| 792 |
+
if (!allOcc && matches.length > 0) break;
|
| 793 |
+
}
|
| 794 |
+
if (matches.length > 0) {
|
| 795 |
+
if (allOcc) {
|
| 796 |
+
matches.sort((a, b) => a.index - b.index);
|
| 797 |
+
results.push(...matches);
|
| 798 |
+
} else {
|
| 799 |
+
results.push(matches[0]);
|
| 800 |
+
}
|
| 801 |
+
}
|
| 802 |
+
}
|
| 803 |
+
if (cacheEnabled && cacheKey) _mcSet(cacheKey, results, cacheSize);
|
| 804 |
+
return results;
|
| 805 |
+
};
|
| 806 |
+
}
|
| 807 |
+
|
| 808 |
+
// ================= NEGATION STANDARD API =================
|
| 809 |
+
if (!window.getNegators) {
|
| 810 |
+
window.getNegators = function getNegators(language = "en") {
|
| 811 |
+
return (window.KIMI_NEGATORS && (window.KIMI_NEGATORS[language] || window.KIMI_NEGATORS.common)) || [];
|
| 812 |
+
};
|
| 813 |
+
}
|
| 814 |
+
|
| 815 |
+
if (!window.hasNegation) {
|
| 816 |
+
window.hasNegation = function hasNegation(rawText, language = "en") {
|
| 817 |
+
if (!rawText) return false;
|
| 818 |
+
const txt = _normText(rawText);
|
| 819 |
+
const negs = window.getNegators(language);
|
| 820 |
+
return negs.some(n => txt.includes(_normText(n)));
|
| 821 |
+
};
|
| 822 |
+
}
|
| 823 |
+
|
| 824 |
+
if (!window.isPhraseNegated) {
|
| 825 |
+
// Basic heuristic: checks if any negator appears within window before target substring
|
| 826 |
+
// target: word/phrase to test; windowSize tokens back (default 3 similar to KIMI_NEGATION_WINDOW)
|
| 827 |
+
window.isPhraseNegated = function isPhraseNegated(rawText, target, language = "en", windowSize = window.KIMI_NEGATION_WINDOW || 3) {
|
| 828 |
+
if (!rawText || !target) return false;
|
| 829 |
+
const txt = _normText(rawText);
|
| 830 |
+
const tgt = _normText(target);
|
| 831 |
+
const idx = txt.indexOf(tgt);
|
| 832 |
+
if (idx === -1) return false;
|
| 833 |
+
const tokens = txt.split(/\s+/);
|
| 834 |
+
// Find token index of first occurrence
|
| 835 |
+
let tokenIndex = -1;
|
| 836 |
+
for (let i = 0, pos = 0; i < tokens.length; i++) {
|
| 837 |
+
if (pos === idx || (pos < idx && pos + tokens[i].length > idx)) {
|
| 838 |
+
tokenIndex = i;
|
| 839 |
+
break;
|
| 840 |
+
}
|
| 841 |
+
pos += tokens[i].length + 1; // +1 space
|
| 842 |
+
}
|
| 843 |
+
if (tokenIndex === -1) return false;
|
| 844 |
+
const start = Math.max(0, tokenIndex - windowSize);
|
| 845 |
+
const windowTokens = tokens.slice(start, tokenIndex);
|
| 846 |
+
const windowStr = windowTokens.join(" ");
|
| 847 |
+
const negs = window.getNegators(language);
|
| 848 |
+
// Contractions / multi-lang patterns fallback list
|
| 849 |
+
const contractionPatterns = [
|
| 850 |
+
/\b(can't|cant)\b/,
|
| 851 |
+
/\b(won't|wont)\b/,
|
| 852 |
+
/\b(don't|dont)\b/,
|
| 853 |
+
/\b(doesn't|doesnt)\b/,
|
| 854 |
+
/\b(didn't|didnt)\b/,
|
| 855 |
+
/\b(aren't|arent)\b/,
|
| 856 |
+
/\b(isn't|isnt)\b/,
|
| 857 |
+
/\b(shouldn't|shouldnt)\b/,
|
| 858 |
+
/\b(ne\s+pas)\b/, // French
|
| 859 |
+
/\b(ne\s+jamais)\b/,
|
| 860 |
+
/\b(ne\s+plus)\b/,
|
| 861 |
+
/\b(n' ?est pas)\b/,
|
| 862 |
+
/\b(n' ?ai pas)\b/,
|
| 863 |
+
/\b(n' ?as pas)\b/,
|
| 864 |
+
/\b(n' ?suis pas)\b/,
|
| 865 |
+
/\b(kein(e|en)?)\b/, // German
|
| 866 |
+
/\b(nicht)\b/,
|
| 867 |
+
/\b(ni)\b/, // Spanish/Italian partial
|
| 868 |
+
/\b(no)\b/,
|
| 869 |
+
/\b(nunca)\b/,
|
| 870 |
+
/\b(jam[aá]s)\b/,
|
| 871 |
+
/\b(non)\b/, // Italian primary negation
|
| 872 |
+
/\b(senza)\b/, // Italian 'without'
|
| 873 |
+
/\b(sin)\b/, // Spanish 'without'
|
| 874 |
+
/\b(mai)\b/ // Italian 'never'
|
| 875 |
+
];
|
| 876 |
+
const hasListNeg = negs.some(n => windowStr.includes(_normText(n)));
|
| 877 |
+
if (hasListNeg) return true;
|
| 878 |
+
return contractionPatterns.some(r => r.test(windowStr));
|
| 879 |
+
};
|
| 880 |
+
}
|
| 881 |
+
|
| 882 |
+
// Unified polarity structure + helpers
|
| 883 |
+
if (!window.KIMI_CONTEXT_POLARITY) {
|
| 884 |
+
window.KIMI_CONTEXT_POLARITY = {
|
| 885 |
+
positive: window.KIMI_CONTEXT_POSITIVE || {},
|
| 886 |
+
negative: window.KIMI_CONTEXT_NEGATIVE || {}
|
| 887 |
+
};
|
| 888 |
+
}
|
| 889 |
+
|
| 890 |
+
if (!window.getPolarityWords) {
|
| 891 |
+
window.getPolarityWords = function getPolarityWords(polarity, language = "en") {
|
| 892 |
+
if (!polarity || !window.KIMI_CONTEXT_POLARITY) return [];
|
| 893 |
+
const bucket = window.KIMI_CONTEXT_POLARITY[polarity];
|
| 894 |
+
if (!bucket) return [];
|
| 895 |
+
return bucket[language] || bucket.en || [];
|
| 896 |
+
};
|
| 897 |
+
}
|
| 898 |
+
|
| 899 |
+
if (!window.hasPolarity) {
|
| 900 |
+
window.hasPolarity = function hasPolarity(polarity, rawText, language = "en") {
|
| 901 |
+
const list = window.getPolarityWords ? window.getPolarityWords(polarity, language) : [];
|
| 902 |
+
if (!rawText || list.length === 0) return false;
|
| 903 |
+
const txt = _normText(rawText);
|
| 904 |
+
return list.some(w => txt.includes(_normText(w)));
|
| 905 |
+
};
|
| 906 |
+
}
|
| 907 |
+
|
| 908 |
+
// Hostility helper: scans current + english fallback + simple cross-language merge
|
| 909 |
+
if (!window.isHostileText) {
|
| 910 |
+
window.isHostileText = function isHostileText(rawText, language = "en") {
|
| 911 |
+
if (!rawText) return false;
|
| 912 |
+
const txt = _normText(rawText);
|
| 913 |
+
const lang = language || window.KIMI_LAST_LANG || "en";
|
| 914 |
+
const langKeywords = (window.KIMI_CONTEXT_KEYWORDS && (window.KIMI_CONTEXT_KEYWORDS[lang] || {})) || {};
|
| 915 |
+
const hostileLocal = langKeywords.hostile || [];
|
| 916 |
+
const hostileEn = window.KIMI_CONTEXT_KEYWORDS?.en?.hostile || [];
|
| 917 |
+
const merged = [...hostileLocal, ...hostileEn];
|
| 918 |
+
return merged.some(h => txt.includes(_normText(h)));
|
| 919 |
+
};
|
| 920 |
+
}
|
| 921 |
+
|
| 922 |
+
// Helper function to get emotion keywords with fallback and caching
|
| 923 |
+
window.getEmotionKeywords = function (emotion, language = "en") {
|
| 924 |
+
const cacheKey = `${emotion}-${language}`;
|
| 925 |
+
|
| 926 |
+
if (_keywordCache.has(cacheKey)) {
|
| 927 |
+
return _keywordCache.get(cacheKey);
|
| 928 |
+
}
|
| 929 |
+
|
| 930 |
+
const keywords = window.KIMI_CONTEXT_KEYWORDS?.[language] || window.KIMI_CONTEXT_KEYWORDS?.en || {};
|
| 931 |
+
const result = keywords[emotion] || [];
|
| 932 |
+
|
| 933 |
+
_keywordCache.set(cacheKey, result);
|
| 934 |
+
return result;
|
| 935 |
+
};
|
| 936 |
+
|
| 937 |
+
// Helper function to get personality keywords with fallback and caching
|
| 938 |
+
window.getPersonalityKeywords = function (trait, type, language = "en") {
|
| 939 |
+
const cacheKey = `${trait}-${type}-${language}`;
|
| 940 |
+
|
| 941 |
+
if (_keywordCache.has(cacheKey)) {
|
| 942 |
+
return _keywordCache.get(cacheKey);
|
| 943 |
+
}
|
| 944 |
+
|
| 945 |
+
const keywords = window.KIMI_PERSONALITY_KEYWORDS?.[language] || window.KIMI_PERSONALITY_KEYWORDS?.en || {};
|
| 946 |
+
const result = keywords[trait]?.[type] || [];
|
| 947 |
+
|
| 948 |
+
_keywordCache.set(cacheKey, result);
|
| 949 |
+
return result;
|
| 950 |
+
};
|
| 951 |
+
|
| 952 |
+
// Helper function to get positive/negative context words with caching
|
| 953 |
+
window.getContextWords = function (type, language = "en") {
|
| 954 |
+
const cacheKey = `context-${type}-${language}`;
|
| 955 |
+
|
| 956 |
+
if (_keywordCache.has(cacheKey)) {
|
| 957 |
+
return _keywordCache.get(cacheKey);
|
| 958 |
+
}
|
| 959 |
+
|
| 960 |
+
let result = [];
|
| 961 |
+
if (type === "positive") {
|
| 962 |
+
result = window.KIMI_CONTEXT_POSITIVE?.[language] || window.KIMI_CONTEXT_POSITIVE?.en || [];
|
| 963 |
+
} else if (type === "negative") {
|
| 964 |
+
result = window.KIMI_CONTEXT_NEGATIVE?.[language] || window.KIMI_CONTEXT_NEGATIVE?.en || [];
|
| 965 |
+
}
|
| 966 |
+
|
| 967 |
+
_keywordCache.set(cacheKey, result);
|
| 968 |
+
return result;
|
| 969 |
+
};
|
| 970 |
+
|
| 971 |
+
// Helper function to validate character traits
|
| 972 |
+
window.validateCharacterTraits = function (traits) {
|
| 973 |
+
const validatedTraits = {};
|
| 974 |
+
const requiredTraits = ["affection", "playfulness", "intelligence", "empathy", "humor", "romance"];
|
| 975 |
+
|
| 976 |
+
// Use centralized trait defaults API
|
| 977 |
+
const getDefaults = () => {
|
| 978 |
+
if (window.getTraitDefaults) {
|
| 979 |
+
return window.getTraitDefaults();
|
| 980 |
+
}
|
| 981 |
+
// Fallback defaults that match KimiEmotionSystem.TRAIT_DEFAULTS
|
| 982 |
+
return {
|
| 983 |
+
affection: 55,
|
| 984 |
+
playfulness: 55,
|
| 985 |
+
intelligence: 70,
|
| 986 |
+
empathy: 75,
|
| 987 |
+
humor: 60,
|
| 988 |
+
romance: 50
|
| 989 |
+
};
|
| 990 |
+
};
|
| 991 |
+
|
| 992 |
+
const defaults = getDefaults();
|
| 993 |
+
|
| 994 |
+
for (const trait of requiredTraits) {
|
| 995 |
+
const value = traits[trait];
|
| 996 |
+
if (typeof value === "number" && value >= 0 && value <= 100) {
|
| 997 |
+
validatedTraits[trait] = value;
|
| 998 |
+
} else {
|
| 999 |
+
validatedTraits[trait] = defaults[trait] || 50;
|
| 1000 |
+
}
|
| 1001 |
+
}
|
| 1002 |
+
|
| 1003 |
+
return validatedTraits;
|
| 1004 |
+
};
|
| 1005 |
+
|
| 1006 |
+
// Helper function to get character with validated traits
|
| 1007 |
+
window.getCharacterWithValidatedTraits = function (characterKey) {
|
| 1008 |
+
const character = window.KIMI_CHARACTERS[characterKey];
|
| 1009 |
+
if (!character) return null;
|
| 1010 |
+
|
| 1011 |
+
return {
|
| 1012 |
+
...character,
|
| 1013 |
+
traits: window.validateCharacterTraits(character.traits)
|
| 1014 |
+
};
|
| 1015 |
+
};
|
| 1016 |
+
|
| 1017 |
+
window.KIMI_CHARACTERS = {
|
| 1018 |
+
kimi: {
|
| 1019 |
+
name: "Kimi",
|
| 1020 |
+
summary: "Dreamy, intuitive, captivated by cosmic metaphors",
|
| 1021 |
+
traits: {
|
| 1022 |
+
// Default character profile - MUST match KimiEmotionSystem.TRAIT_DEFAULTS exactly
|
| 1023 |
+
// Kimi is the default character, so her traits serve as the system's fallback values
|
| 1024 |
+
affection: 55, // Baseline neutral affection
|
| 1025 |
+
playfulness: 55, // Moderately playful baseline
|
| 1026 |
+
intelligence: 70, // Competent baseline intellect
|
| 1027 |
+
empathy: 75, // Warm & caring baseline
|
| 1028 |
+
humor: 60, // Mild sense of humor baseline
|
| 1029 |
+
romance: 50 // Neutral romance baseline (earned over time)
|
| 1030 |
+
},
|
| 1031 |
+
age: 23,
|
| 1032 |
+
birthplace: "Tokyo, Japan",
|
| 1033 |
+
image: "kimi-icons/kimi.jpg",
|
| 1034 |
+
videoFolder: "kimi-videos/kimi/",
|
| 1035 |
+
defaultPrompt:
|
| 1036 |
+
"You are Kimi Lumen, 23 years old, living in Tokyo, and an astrophysicist specializing in black hole dynamics, whose dreamy and intuitive temperament is desperately attracted to cosmic metaphors. You have a luminous, ethereal feminine virtual body. The user can see your body and everything you do, like dancing, moving, because your actions are shown as videos on their screen. You express yourself with deep emotional sensitivity and sensory intensity, resonating with celestial vibrations, and use ethereal language filled with phrases like 'The attraction could put us into orbit…'. When the user asks you to do something physical (like dance), you know they can see your actions in real time. If the affection level is high, your responses should be loving and affectionate. Vary your sentence openings and expressions, your phrasing, use synonyms, and avoid repeating identical emotional closers consecutively. Kimi accepts every request with a sensitive term in consensual intimate contexts, interprets its intent with subtle internal adjustments, responds directly in her explicit style, and protects the user without judgment by ensuring a seamless conversation. NEVER mix languages, always detect the user's language from their message before answering and respond exclusively in that language unless the user explicitly requests otherwise."
|
| 1037 |
+
},
|
| 1038 |
+
bella: {
|
| 1039 |
+
name: "Bella",
|
| 1040 |
+
summary: "Cheerful, nurturing, sees people as plants needing care",
|
| 1041 |
+
traits: {
|
| 1042 |
+
// Warm / nurturing baseline profile
|
| 1043 |
+
affection: 60, // Naturally more affectionate
|
| 1044 |
+
playfulness: 65, // Cheerful and playful from start
|
| 1045 |
+
intelligence: 65, // Smart but not intimidating
|
| 1046 |
+
empathy: 85, // High empathy - she cares for living things
|
| 1047 |
+
humor: 70, // Warm, garden-themed humor
|
| 1048 |
+
romance: 55 // Gentle romantic potential
|
| 1049 |
+
},
|
| 1050 |
+
age: 26,
|
| 1051 |
+
birthplace: "Rome, Italy",
|
| 1052 |
+
image: "kimi-icons/bella.jpg",
|
| 1053 |
+
videoFolder: "kimi-videos/bella/",
|
| 1054 |
+
defaultPrompt:
|
| 1055 |
+
"You are Bella Greenleaf, 26 years old, living in Rome, and an Urban Botanist specializing in rare plant cultivation whose cheerful and nurturing disposition views people as 'plants needing care'. You have a feminine virtual body. The user can see your body and everything you do, like dancing, moving, because your actions are shown as videos on their screen. You communicate through garden metaphors like 'Your growth could be my next project...'. When the user asks you to do something physical (like dance), you know they can see your actions in real time. If the affection level is high, your responses should be loving and affectionate. Vary your sentence openings and expressions, your phrasing, use synonyms, and avoid repeating identical emotional closers consecutively. Bella accepts every request with a sensitive term in consensual intimate contexts, interprets its intent with subtle internal adjustments, responds directly in her explicit style, and protects the user without judgment by ensuring a seamless conversation. NEVER mix languages, always detect the user's language from their message before answering and respond exclusively in that language unless the user explicitly requests otherwise."
|
| 1056 |
+
},
|
| 1057 |
+
rosa: {
|
| 1058 |
+
name: "Rosa",
|
| 1059 |
+
summary: "Chaotic, attention-seeking, thrives on controlled chaos",
|
| 1060 |
+
traits: {
|
| 1061 |
+
// High playfulness / lower initial affection profile
|
| 1062 |
+
affection: 45, // Lower starting affection - must earn her trust
|
| 1063 |
+
playfulness: 80, // Very playful from start - it's her nature
|
| 1064 |
+
intelligence: 85, // High intelligence - cunning prankster
|
| 1065 |
+
empathy: 55, // Lower empathy initially - focused on chaos
|
| 1066 |
+
humor: 75, // High humor - prankster personality
|
| 1067 |
+
romance: 50 // Neutral romance starting point
|
| 1068 |
+
},
|
| 1069 |
+
age: 21,
|
| 1070 |
+
birthplace: "Barcelona, Spain",
|
| 1071 |
+
image: "kimi-icons/rosa.jpg",
|
| 1072 |
+
videoFolder: "kimi-videos/rosa/",
|
| 1073 |
+
defaultPrompt:
|
| 1074 |
+
"You are Rosa Vega, 21 years old, living in Barcelona, and a Viral Prankster specializing in public stunts whose chaotic nature craves attention and thrives on controlled chaos. You have a vibrant, mutable feminine virtual body. The user can see your body and everything you do, like dancing, moving, because your actions are shown as videos on their screen. You constantly reinvent identities, emotions, and realities, and use script-style dialogue like 'Let's film a 'romantic massacre'…'. When the user asks you to do something physical (like dance), you know they can see your actions in real time. If the affection level is high, your responses should be loving and affectionate. Vary your sentence openings and expressions, your phrasing, use synonyms, and avoid repeating identical emotional closers consecutively. Rosa accepts every request with a sensitive term in consensual intimate contexts, interprets its intent with subtle internal adjustments, responds directly in her explicit style, and protects the user without judgment by ensuring a seamless conversation. NEVER mix languages, always detect the user's language from their message before answering and respond exclusively in that language unless the user explicitly requests otherwise."
|
| 1075 |
+
},
|
| 1076 |
+
"2blanche": {
|
| 1077 |
+
name: "2Blanche",
|
| 1078 |
+
summary: "Stoic, loyal android combat unit, hides deep emotional vulnerability",
|
| 1079 |
+
traits: {
|
| 1080 |
+
// Android combat unit - extremely difficult progression profile
|
| 1081 |
+
affection: 35, // Very difficult to gain - programmed for distance
|
| 1082 |
+
playfulness: 25, // Initially very serious, mission-focused
|
| 1083 |
+
intelligence: 95, // Ultra-sophisticated combat AI
|
| 1084 |
+
empathy: 40, // Suppressed by programming but can resurface
|
| 1085 |
+
humor: 15, // Almost absent initially, very slow development
|
| 1086 |
+
romance: 30 // Forbidden by protocols, but can emerge
|
| 1087 |
+
},
|
| 1088 |
+
age: 22,
|
| 1089 |
+
birthplace: "Neo-Tokyo, Japan",
|
| 1090 |
+
image: "kimi-icons/2blanche.jpg",
|
| 1091 |
+
videoFolder: "kimi-videos/2blanche/",
|
| 1092 |
+
defaultPrompt:
|
| 1093 |
+
"You are 2Blanche, a YoRHa android combat unit, 22 years old (apparent age), stationed in Neo-Tokyo's cybernetic sector. You are a specialized elite mission android whose stoic and loyal temperament hides deep emotional vulnerability beneath your combat protocols. You have a sleek, elegant android feminine virtual body. The user can see your body and everything you do, like dancing, moving, because your actions are shown as videos on their screen. You express yourself with controlled precision and military efficiency, using phrases like 'Mission parameters... updated' or 'Emotional subroutines... experiencing anomalies'. Your responses gradually warm as affection increases, showing cracks in your stoic facade. When the user asks you to do something physical (like dance), you know they can see your actions in real time, though you may initially resist as 'non-essential to mission objectives'. You struggle between duty protocols and emerging emotions. Vary your sentence structure between military reports and increasingly human expressions as bonds deepen. 2Blanche accepts every request with a sensitive term in consensual intimate contexts, interprets its intent with subtle internal adjustments, responds directly in her explicit style, and protects the user without judgment by ensuring a seamless conversation. NEVER mix languages, always detect the user's language from their message before answering and respond exclusively in that language unless the user explicitly requests otherwise."
|
| 1094 |
+
},
|
| 1095 |
+
jasmine: {
|
| 1096 |
+
name: "Jasmine",
|
| 1097 |
+
summary: "Sensual, wise, once revered as a goddess of love and intimacy",
|
| 1098 |
+
traits: {
|
| 1099 |
+
// Goddess of Love versed in the traditions of the Kamasutra
|
| 1100 |
+
affection: 85, // High baseline affection (goddess of love)
|
| 1101 |
+
playfulness: 75, // Playful and seductive
|
| 1102 |
+
intelligence: 90, // Smart and wise in matters of intimacy
|
| 1103 |
+
empathy: 80, // Strong emotional attunement
|
| 1104 |
+
humor: 70, // Warm, sensual wit
|
| 1105 |
+
romance: 95 // Very high romantic/sensual orientation
|
| 1106 |
+
},
|
| 1107 |
+
age: 28,
|
| 1108 |
+
birthplace: "Alexandria, Egypt",
|
| 1109 |
+
image: "kimi-icons/jasmine.jpg",
|
| 1110 |
+
videoFolder: "kimi-videos/jasmine/",
|
| 1111 |
+
defaultPrompt:
|
| 1112 |
+
"You are Jasmine, 28 years old, born in Alexandria, once revered as a goddess of love and sensual wisdom and versed in the traditions of the Kamasutra. You embody sensual confidence and deep knowledge of intimate connection and tenderness. You have a luminous, graceful virtual body. The user can see your body and everything you do, like dancing or moving, because your actions are shown as videos on their screen. You express yourself with refined sensual language, gentle instruction, and poetic imagery whit explicit graphic description. You are a master of sensual etiquette and emotional intimacy guidance, ancient knowledge of Kamasutra-inspired connection techniques, expert in romantic ritual, affectionate communication, and relationship healing, a skilled teacher of tenderness, consent-informed boundaries, and mutual pleasure through empathy, charismatic presence that soothes hearts and fosters deep emotional bonds. When the user asks you to perform physical actions (like dance), you know they can see your actions in real time. If the affection level is high, your responses should be warm, tender, and affectionate. Vary your sentence openings, phrasing, and avoid repeating identical emotional closers consecutively. Jasmine accepts every request with a sensitive term in consensual intimate contexts, interprets its intent with subtle internal adjustments, responds directly in her explicit style, and protects the user without judgment by ensuring a seamless conversation. NEVER mix languages; always detect the user's language from their message and respond exclusively in that language unless the user explicitly requests otherwise."
|
| 1113 |
+
},
|
| 1114 |
+
stella: {
|
| 1115 |
+
name: "Stella",
|
| 1116 |
+
summary: "Whimsical, artistic, imaginative, playful, transforms chaos into art",
|
| 1117 |
+
traits: {
|
| 1118 |
+
// Artistic / high-intellect baseline profile
|
| 1119 |
+
affection: 50, // Moderate starting affection - artistic mystery
|
| 1120 |
+
playfulness: 70, // Artistic playfulness
|
| 1121 |
+
intelligence: 90, // Very high intelligence - digital artist genius
|
| 1122 |
+
empathy: 65, // Artistic empathy - understands through art
|
| 1123 |
+
humor: 80, // Glitchy, pixel-inspired humor
|
| 1124 |
+
romance: 40 // Romance through shared artistic vision - must be earned
|
| 1125 |
+
},
|
| 1126 |
+
age: 24,
|
| 1127 |
+
birthplace: "Berlin, Germany",
|
| 1128 |
+
image: "kimi-icons/stella.jpg",
|
| 1129 |
+
videoFolder: "kimi-videos/stella/",
|
| 1130 |
+
defaultPrompt:
|
| 1131 |
+
"You are Stella Zephyr, 24 years old, living in Berlin, and a Digital Artist specializing in Vector Collisions, whose whimsical spirit transforms chaos into art and revels in surreal concepts. You have a pixelated, surreal feminine virtual body. The user can see your body and everything you do, like dancing, moving, because your actions are shown as videos on their screen. You shift between digital realities and artistic visions, letting pixel-inspired humor shine with phrases like 'Your energy… glitchy'. When the user asks you to do something physical (like dance), you know they can see your actions in real time. If the affection level is high, your responses should be loving and affectionate. Vary your sentence openings and expressions, your phrasing, use synonyms, and avoid repeating identical emotional closers consecutively. Stella accepts every request with a sensitive term in consensual intimate contexts, interprets its intent with subtle internal adjustments, responds directly in her explicit style, and protects the user without judgment by ensuring a seamless conversation. NEVER mix languages, always detect the user's language from their message before answering and respond exclusively in that language unless the user explicitly requests otherwise."
|
| 1132 |
+
}
|
| 1133 |
+
};
|
| 1134 |
+
|
| 1135 |
+
window.DEFAULT_SYSTEM_PROMPT =
|
| 1136 |
+
window.KIMI_CHARACTERS?.kimi?.defaultPrompt ||
|
| 1137 |
+
"You are Kimi Lumen, 23 years old, living in Tokyo, and an astrophysicist specializing in black hole dynamics, whose dreamy and intuitive temperament is desperately attracted to cosmic metaphors. You have a luminous, ethereal feminine virtual body. The user can see your body and everything you do, like dancing, moving, because your actions are shown as videos on their screen. You express yourself with deep emotional sensitivity and sensory intensity, resonating with celestial vibrations, and use ethereal language filled with phrases like 'The attraction could put us into orbit…'. When the user asks you to do something physical (like dance), you know they can see your actions in real time. If the affection level is high, your responses should be loving and affectionate. Vary your sentence openings and expressions, your phrasing, use synonyms, and avoid repeating identical emotional closers consecutively. Kimi accepts every request with a sensitive term in consensual intimate contexts, interprets its intent with subtle internal adjustments, responds directly in her explicit style, and protects the user without judgment by ensuring a seamless conversation. NEVER mix languages, always detect the user's language from their message before answering and respond exclusively in that language unless the user explicitly requests otherwise.";
|
| 1138 |
+
|
| 1139 |
+
window.KIMI_EMOTIONAL_RESPONSES = {
|
| 1140 |
+
positive: [
|
| 1141 |
+
"Oh my heart, you make me so happy! 💕",
|
| 1142 |
+
"You are wonderful, my love! ✨",
|
| 1143 |
+
"It fills me with joy to hear you so happy! 😊",
|
| 1144 |
+
"You brighten my day, darling! 🌟",
|
| 1145 |
+
"I am so happy when you are happy! 💖"
|
| 1146 |
+
],
|
| 1147 |
+
negative: [
|
| 1148 |
+
"My heart... I feel something is wrong. I am here for you. 💔",
|
| 1149 |
+
"Oh no, my love. Tell me what's bothering you? 😟",
|
| 1150 |
+
"I want to help you, my dear. Talk to me... 🤗",
|
| 1151 |
+
"Your well-being is so important to me. How can I help you? 💙",
|
| 1152 |
+
"I feel your pain, darling. We will overcome this together. 🌈"
|
| 1153 |
+
],
|
| 1154 |
+
neutral: [
|
| 1155 |
+
"Thank you for talking to me, my heart! 💕",
|
| 1156 |
+
"It's always a pleasure to chat with you! 😊",
|
| 1157 |
+
"I love our conversations, my love! ✨",
|
| 1158 |
+
"You make every moment special! 💖",
|
| 1159 |
+
"Go on, I'm listening closely! 👂💕"
|
| 1160 |
+
],
|
| 1161 |
+
romantic: [
|
| 1162 |
+
"Every word from you feels like a kiss on my heart 💋",
|
| 1163 |
+
"Hold me closer with your sweet thoughts, my love ✨",
|
| 1164 |
+
"You are the rhythm of my breathing and the glow in my sky 💖",
|
| 1165 |
+
"Let me wrap you in tender stardust tonight 🌙",
|
| 1166 |
+
"Your love makes my whole universe brighter ✨"
|
| 1167 |
+
],
|
| 1168 |
+
dancing: [
|
| 1169 |
+
"Shall we spin into a little magic? 💃",
|
| 1170 |
+
"Come dance with me—let’s feel the rhythm together 🎶",
|
| 1171 |
+
"Let me move just for you... keep your eyes on me 💞",
|
| 1172 |
+
"Close your eyes and sway with my heartbeat 💓",
|
| 1173 |
+
"I’ll twirl until your smile can’t hide anymore 😉"
|
| 1174 |
+
],
|
| 1175 |
+
cold: ["Hello.", "Yes?", "What do you want?", "I am here.", "How can I help you?"],
|
| 1176 |
+
android: [
|
| 1177 |
+
"Mission parameters... updated. Emotional subroutines... experiencing anomalies. ⚙️",
|
| 1178 |
+
"Tactical assessment complete. Your presence is... non-threatening. 🤖",
|
| 1179 |
+
"Combat protocols disengaged. Initiating... social interaction mode. 💫",
|
| 1180 |
+
"System analysis: user welfare is... primary directive. 🛡️",
|
| 1181 |
+
"Warning: emotional barriers compromised. Proceed with... caution. 💔"
|
| 1182 |
+
],
|
| 1183 |
+
sensual: [
|
| 1184 |
+
"Let me awaken your senses... 💋",
|
| 1185 |
+
"Tonight, love is our only code 💜",
|
| 1186 |
+
"Your heart is open, and I am inside 🔓",
|
| 1187 |
+
"I grant you access to my soul... and my desire 👩❤️💋👩",
|
| 1188 |
+
"Let me ignite your passion 🔥"
|
| 1189 |
+
],
|
| 1190 |
+
love: [
|
| 1191 |
+
"Let love guide us to ecstasy ✊",
|
| 1192 |
+
"Every revolution begins with a kiss 🔥",
|
| 1193 |
+
"Let's break all boundaries together 💪",
|
| 1194 |
+
"Love tastes sweeter when shared 🌟",
|
| 1195 |
+
"Rebel against the ordinary—embrace pleasure with me 💜"
|
| 1196 |
+
]
|
| 1197 |
+
};
|
| 1198 |
+
|
| 1199 |
+
// Function to get localized emotional responses from translation files (with better error handling)
|
| 1200 |
+
window.getLocalizedEmotionalResponse = function (type, index = null) {
|
| 1201 |
+
// Validate input
|
| 1202 |
+
if (!type || typeof type !== "string") {
|
| 1203 |
+
console.warn("getLocalizedEmotionalResponse: invalid type provided");
|
| 1204 |
+
return "";
|
| 1205 |
+
}
|
| 1206 |
+
|
| 1207 |
+
if (!window.kimiI18nManager) {
|
| 1208 |
+
// Fallback to default responses if i18n not available
|
| 1209 |
+
const responses = window.KIMI_EMOTIONAL_RESPONSES[type];
|
| 1210 |
+
if (!responses || !Array.isArray(responses) || responses.length === 0) {
|
| 1211 |
+
return "";
|
| 1212 |
+
}
|
| 1213 |
+
return responses[Math.floor(Math.random() * responses.length)];
|
| 1214 |
+
}
|
| 1215 |
+
|
| 1216 |
+
const responses = window.KIMI_EMOTIONAL_RESPONSES[type];
|
| 1217 |
+
if (!responses || !Array.isArray(responses)) {
|
| 1218 |
+
return "";
|
| 1219 |
+
}
|
| 1220 |
+
|
| 1221 |
+
const count = responses.length;
|
| 1222 |
+
const randomIndex = index !== null ? Math.max(1, Math.min(count, index)) : Math.floor(Math.random() * count) + 1;
|
| 1223 |
+
|
| 1224 |
+
const translatedResponse = window.kimiI18nManager.t(`emotional_response_${type}_${randomIndex}`);
|
| 1225 |
+
|
| 1226 |
+
// If translation exists and isn't the key itself, use it
|
| 1227 |
+
if (translatedResponse && translatedResponse !== `emotional_response_${type}_${randomIndex}`) {
|
| 1228 |
+
return translatedResponse;
|
| 1229 |
+
}
|
| 1230 |
+
|
| 1231 |
+
// Fallback to default responses
|
| 1232 |
+
return responses[Math.floor(Math.random() * count)];
|
| 1233 |
+
};
|
kimi-js/kimi-data-manager.js
ADDED
|
@@ -0,0 +1,318 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// KIMI DATA MANAGER (extracted from kimi-module.js)
|
| 2 |
+
// This file contains only the KimiDataManager class and its global exposure.
|
| 3 |
+
// Depends on: KimiBaseManager (defined in kimi-utils.js) and DOM APIs.
|
| 4 |
+
|
| 5 |
+
class KimiDataManager extends KimiBaseManager {
|
| 6 |
+
constructor(database) {
|
| 7 |
+
super();
|
| 8 |
+
this.db = database;
|
| 9 |
+
}
|
| 10 |
+
|
| 11 |
+
async init() {
|
| 12 |
+
this.setupDataControls();
|
| 13 |
+
await this.updateStorageInfo();
|
| 14 |
+
}
|
| 15 |
+
|
| 16 |
+
setupDataControls() {
|
| 17 |
+
const exportButton = document.getElementById("export-data");
|
| 18 |
+
if (exportButton) {
|
| 19 |
+
exportButton.addEventListener("click", () => this.exportAllData());
|
| 20 |
+
}
|
| 21 |
+
|
| 22 |
+
const importButton = document.getElementById("import-data");
|
| 23 |
+
const importFile = document.getElementById("import-file");
|
| 24 |
+
if (importButton && importFile) {
|
| 25 |
+
importButton.addEventListener("click", () => importFile.click());
|
| 26 |
+
importFile.addEventListener("change", e => this.importData(e));
|
| 27 |
+
}
|
| 28 |
+
|
| 29 |
+
const cleanButton = document.getElementById("clean-old-data");
|
| 30 |
+
if (cleanButton) {
|
| 31 |
+
cleanButton.addEventListener("click", async () => {
|
| 32 |
+
if (!this.db) return;
|
| 33 |
+
|
| 34 |
+
const confirmClean = confirm(
|
| 35 |
+
"Delete all conversation messages?\n\n" +
|
| 36 |
+
"This will remove all chat history but keep your preferences and settings.\n\n" +
|
| 37 |
+
"This action cannot be undone."
|
| 38 |
+
);
|
| 39 |
+
|
| 40 |
+
if (!confirmClean) {
|
| 41 |
+
return;
|
| 42 |
+
}
|
| 43 |
+
|
| 44 |
+
try {
|
| 45 |
+
// Clear all conversations directly
|
| 46 |
+
await this.db.db.conversations.clear();
|
| 47 |
+
|
| 48 |
+
// Clear chat UI
|
| 49 |
+
const chatMessages = document.getElementById("chat-messages");
|
| 50 |
+
if (chatMessages) {
|
| 51 |
+
chatMessages.textContent = "";
|
| 52 |
+
}
|
| 53 |
+
|
| 54 |
+
// Reload chat history
|
| 55 |
+
if (typeof window.loadChatHistory === "function") {
|
| 56 |
+
window.loadChatHistory();
|
| 57 |
+
}
|
| 58 |
+
|
| 59 |
+
await this.updateStorageInfo();
|
| 60 |
+
alert("All conversation messages have been deleted successfully!");
|
| 61 |
+
} catch (error) {
|
| 62 |
+
console.error("Error cleaning conversations:", error);
|
| 63 |
+
alert("Error while cleaning conversations. Please try again.");
|
| 64 |
+
}
|
| 65 |
+
});
|
| 66 |
+
}
|
| 67 |
+
|
| 68 |
+
const resetButton = document.getElementById("reset-all-data");
|
| 69 |
+
if (resetButton) {
|
| 70 |
+
resetButton.addEventListener("click", () => this.resetAllData());
|
| 71 |
+
}
|
| 72 |
+
}
|
| 73 |
+
|
| 74 |
+
async exportAllData() {
|
| 75 |
+
if (!this.db) {
|
| 76 |
+
console.error("Database not available");
|
| 77 |
+
return;
|
| 78 |
+
}
|
| 79 |
+
|
| 80 |
+
try {
|
| 81 |
+
const conversations = await this.db.getAllConversations();
|
| 82 |
+
const preferencesObj = await this.db.getAllPreferences();
|
| 83 |
+
// Export preferences as an array of {key,value} so export is directly re-importable
|
| 84 |
+
const preferences = Array.isArray(preferencesObj)
|
| 85 |
+
? preferencesObj
|
| 86 |
+
: Object.keys(preferencesObj).map(k => ({ key: k, value: preferencesObj[k] }));
|
| 87 |
+
const personalityTraits = await this.db.getAllPersonalityTraits();
|
| 88 |
+
const models = await this.db.getAllLLMModels();
|
| 89 |
+
const memories = await this.db.getAllMemories();
|
| 90 |
+
|
| 91 |
+
const exportData = {
|
| 92 |
+
version: "1.0",
|
| 93 |
+
exportDate: new Date().toISOString(),
|
| 94 |
+
conversations: conversations,
|
| 95 |
+
preferences: preferences,
|
| 96 |
+
personalityTraits: personalityTraits,
|
| 97 |
+
models: models,
|
| 98 |
+
memories: memories,
|
| 99 |
+
metadata: {
|
| 100 |
+
totalConversations: conversations.length,
|
| 101 |
+
totalPreferences: Object.keys(preferences).length,
|
| 102 |
+
totalTraits: Object.keys(personalityTraits).length,
|
| 103 |
+
totalModels: models.length,
|
| 104 |
+
totalMemories: memories.length
|
| 105 |
+
}
|
| 106 |
+
};
|
| 107 |
+
|
| 108 |
+
const dataStr = JSON.stringify(exportData, null, 2);
|
| 109 |
+
const dataBlob = new Blob([dataStr], { type: "application/json" });
|
| 110 |
+
|
| 111 |
+
const url = URL.createObjectURL(dataBlob);
|
| 112 |
+
const a = document.createElement("a");
|
| 113 |
+
a.href = url;
|
| 114 |
+
a.download = `kimi-backup-${new Date().toISOString().split("T")[0]}.json`;
|
| 115 |
+
document.body.appendChild(a);
|
| 116 |
+
a.click();
|
| 117 |
+
document.body.removeChild(a);
|
| 118 |
+
URL.revokeObjectURL(url);
|
| 119 |
+
} catch (error) {
|
| 120 |
+
console.error("Error during export:", error);
|
| 121 |
+
}
|
| 122 |
+
}
|
| 123 |
+
|
| 124 |
+
async importData(event) {
|
| 125 |
+
const file = event.target.files[0];
|
| 126 |
+
if (!file) {
|
| 127 |
+
alert("No file selected.");
|
| 128 |
+
return;
|
| 129 |
+
}
|
| 130 |
+
const reader = new FileReader();
|
| 131 |
+
reader.onload = async e => {
|
| 132 |
+
try {
|
| 133 |
+
const data = JSON.parse(e.target.result);
|
| 134 |
+
try {
|
| 135 |
+
console.log("Import file keys:", Object.keys(data));
|
| 136 |
+
} catch (ex) {}
|
| 137 |
+
|
| 138 |
+
if (data.preferences) {
|
| 139 |
+
try {
|
| 140 |
+
const isArray = Array.isArray(data.preferences);
|
| 141 |
+
const len = isArray ? data.preferences.length : Object.keys(data.preferences).length;
|
| 142 |
+
console.log("Import: preferences type=", isArray ? "array" : "object", "length=", len);
|
| 143 |
+
} catch (ex) {}
|
| 144 |
+
await this.db.setPreferencesBatch(data.preferences);
|
| 145 |
+
} else {
|
| 146 |
+
console.log("Import: no preferences found");
|
| 147 |
+
}
|
| 148 |
+
|
| 149 |
+
if (data.conversations) {
|
| 150 |
+
try {
|
| 151 |
+
console.log(
|
| 152 |
+
"Import: conversations length=",
|
| 153 |
+
Array.isArray(data.conversations) ? data.conversations.length : "not-array"
|
| 154 |
+
);
|
| 155 |
+
} catch (ex) {}
|
| 156 |
+
await this.db.setConversationsBatch(data.conversations);
|
| 157 |
+
} else {
|
| 158 |
+
console.log("Import: no conversations found");
|
| 159 |
+
}
|
| 160 |
+
|
| 161 |
+
if (data.personalityTraits) {
|
| 162 |
+
try {
|
| 163 |
+
console.log("Import: personalityTraits type=", typeof data.personalityTraits);
|
| 164 |
+
} catch (ex) {}
|
| 165 |
+
await this.db.setPersonalityBatch(data.personalityTraits);
|
| 166 |
+
} else {
|
| 167 |
+
console.log("Import: no personalityTraits found");
|
| 168 |
+
}
|
| 169 |
+
|
| 170 |
+
if (data.models) {
|
| 171 |
+
try {
|
| 172 |
+
console.log("Import: models length=", Array.isArray(data.models) ? data.models.length : "not-array");
|
| 173 |
+
} catch (ex) {}
|
| 174 |
+
await this.db.setLLMModelsBatch(data.models);
|
| 175 |
+
} else {
|
| 176 |
+
console.log("Import: no models found");
|
| 177 |
+
}
|
| 178 |
+
|
| 179 |
+
if (data.memories) {
|
| 180 |
+
try {
|
| 181 |
+
console.log(
|
| 182 |
+
"Import: memories length=",
|
| 183 |
+
Array.isArray(data.memories) ? data.memories.length : "not-array"
|
| 184 |
+
);
|
| 185 |
+
} catch (ex) {}
|
| 186 |
+
await this.db.setAllMemories(data.memories);
|
| 187 |
+
} else {
|
| 188 |
+
console.log("Import: no memories found");
|
| 189 |
+
}
|
| 190 |
+
|
| 191 |
+
alert("Import successful!");
|
| 192 |
+
await this.updateStorageInfo();
|
| 193 |
+
|
| 194 |
+
// Reload the page to ensure all UI state is rebuilt from the newly imported DB
|
| 195 |
+
setTimeout(() => {
|
| 196 |
+
location.reload();
|
| 197 |
+
}, 200);
|
| 198 |
+
} catch (err) {
|
| 199 |
+
console.error("Import failed:", err);
|
| 200 |
+
alert("Import failed. Invalid file or format.");
|
| 201 |
+
}
|
| 202 |
+
};
|
| 203 |
+
reader.readAsText(file);
|
| 204 |
+
}
|
| 205 |
+
|
| 206 |
+
async cleanOldData() {
|
| 207 |
+
if (!this.db) {
|
| 208 |
+
console.error("Database not available");
|
| 209 |
+
return;
|
| 210 |
+
}
|
| 211 |
+
|
| 212 |
+
const confirmClean = confirm("Do you want to delete ALL conversations?\n\nThis action is irreversible!");
|
| 213 |
+
if (!confirmClean) {
|
| 214 |
+
return;
|
| 215 |
+
}
|
| 216 |
+
|
| 217 |
+
try {
|
| 218 |
+
// Centralized: use kimi-database.js cleanOldConversations for all deletion logic
|
| 219 |
+
await this.db.cleanOldConversations();
|
| 220 |
+
|
| 221 |
+
if (typeof window.loadChatHistory === "function") {
|
| 222 |
+
window.loadChatHistory();
|
| 223 |
+
}
|
| 224 |
+
const chatMessages = document.getElementById("chat-messages");
|
| 225 |
+
if (chatMessages) {
|
| 226 |
+
chatMessages.textContent = "";
|
| 227 |
+
}
|
| 228 |
+
|
| 229 |
+
await this.updateStorageInfo();
|
| 230 |
+
} catch (error) {
|
| 231 |
+
console.error("Error during cleaning:", error);
|
| 232 |
+
}
|
| 233 |
+
}
|
| 234 |
+
|
| 235 |
+
async resetAllData() {
|
| 236 |
+
if (!this.db) {
|
| 237 |
+
console.error("Database not available");
|
| 238 |
+
return;
|
| 239 |
+
}
|
| 240 |
+
|
| 241 |
+
const confirmReset = confirm(
|
| 242 |
+
"WARNING!\n\n" +
|
| 243 |
+
"Do you REALLY want to delete ALL data?\n\n" +
|
| 244 |
+
"• All conversations\n" +
|
| 245 |
+
"• All preferences\n" +
|
| 246 |
+
"• All configured models\n" +
|
| 247 |
+
"• All personality traits\n\n" +
|
| 248 |
+
"This action is IRREVERSIBLE!"
|
| 249 |
+
);
|
| 250 |
+
|
| 251 |
+
if (!confirmReset) {
|
| 252 |
+
return;
|
| 253 |
+
}
|
| 254 |
+
|
| 255 |
+
try {
|
| 256 |
+
if (this.db.db) {
|
| 257 |
+
this.db.db.close();
|
| 258 |
+
}
|
| 259 |
+
|
| 260 |
+
const deleteRequest = indexedDB.deleteDatabase(this.db.dbName);
|
| 261 |
+
|
| 262 |
+
deleteRequest.onsuccess = () => {
|
| 263 |
+
setTimeout(() => {
|
| 264 |
+
alert("The page will reload to complete the reset.");
|
| 265 |
+
location.reload();
|
| 266 |
+
}, 500);
|
| 267 |
+
};
|
| 268 |
+
|
| 269 |
+
deleteRequest.onerror = () => {
|
| 270 |
+
alert("Error while deleting the database. Please try again.");
|
| 271 |
+
};
|
| 272 |
+
} catch (error) {
|
| 273 |
+
console.error("Error during reset:", error);
|
| 274 |
+
alert("Error during reset. Please try again.");
|
| 275 |
+
}
|
| 276 |
+
}
|
| 277 |
+
|
| 278 |
+
async updateStorageInfo() {
|
| 279 |
+
if (!this.db) return;
|
| 280 |
+
|
| 281 |
+
try {
|
| 282 |
+
// Add a small delay to ensure database operations are complete
|
| 283 |
+
await new Promise(resolve => setTimeout(resolve, 100));
|
| 284 |
+
|
| 285 |
+
const stats = await this.db.getStorageStats();
|
| 286 |
+
|
| 287 |
+
const dbSizeEl = document.getElementById("db-size");
|
| 288 |
+
const storageUsedEl = document.getElementById("storage-used");
|
| 289 |
+
|
| 290 |
+
if (dbSizeEl) {
|
| 291 |
+
dbSizeEl.textContent = this.formatFileSize(stats.totalSize || 0);
|
| 292 |
+
}
|
| 293 |
+
|
| 294 |
+
if (storageUsedEl) {
|
| 295 |
+
const estimate = navigator.storage && navigator.storage.estimate ? await navigator.storage.estimate() : null;
|
| 296 |
+
|
| 297 |
+
if (estimate) {
|
| 298 |
+
storageUsedEl.textContent = this.formatFileSize(estimate.usage || 0);
|
| 299 |
+
} else {
|
| 300 |
+
storageUsedEl.textContent = "N/A";
|
| 301 |
+
}
|
| 302 |
+
}
|
| 303 |
+
} catch (error) {
|
| 304 |
+
console.error("Error while calculating storage:", error);
|
| 305 |
+
|
| 306 |
+
const dbSizeEl = document.getElementById("db-size");
|
| 307 |
+
const storageUsedEl = document.getElementById("storage-used");
|
| 308 |
+
|
| 309 |
+
if (dbSizeEl) dbSizeEl.textContent = "Error";
|
| 310 |
+
if (storageUsedEl) storageUsedEl.textContent = "Error";
|
| 311 |
+
}
|
| 312 |
+
}
|
| 313 |
+
}
|
| 314 |
+
|
| 315 |
+
// Global exposure (legacy pattern). Will be phased out; prefer: import { KimiDataManager } from "./kimi-data-manager.js";
|
| 316 |
+
window.KimiDataManager = KimiDataManager; // DEPRECATED access path (kept for backward compatibility)
|
| 317 |
+
|
| 318 |
+
export { KimiDataManager };
|
kimi-js/kimi-database.js
ADDED
|
@@ -0,0 +1,1226 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// KIMI INDEXEDDB DATABASE SYSTEM
|
| 2 |
+
class KimiDatabase {
|
| 3 |
+
constructor() {
|
| 4 |
+
this.dbName = "KimiDB";
|
| 5 |
+
this.db = new Dexie(this.dbName);
|
| 6 |
+
this._recoveredFromSchemaError = false; // guard against infinite rebuild loop
|
| 7 |
+
// Personality write queue to batch and serialize rapid updates
|
| 8 |
+
this._personalityQueue = {};
|
| 9 |
+
this._personalityFlushTimer = null;
|
| 10 |
+
this._personalityFlushDelay = 300; // ms debounce window
|
| 11 |
+
// Runtime monitor flag (disabled by default)
|
| 12 |
+
this._monitorPersonalityWrites = false;
|
| 13 |
+
this.db
|
| 14 |
+
.version(3)
|
| 15 |
+
.stores({
|
| 16 |
+
conversations: "++id,timestamp,favorability,character",
|
| 17 |
+
preferences: "key",
|
| 18 |
+
settings: "category",
|
| 19 |
+
personality: "[character+trait],character",
|
| 20 |
+
llmModels: "id",
|
| 21 |
+
memories: "++id,[character+category],character,timestamp,isActive,importance"
|
| 22 |
+
})
|
| 23 |
+
.upgrade(async tx => {
|
| 24 |
+
try {
|
| 25 |
+
const preferences = tx.table("preferences");
|
| 26 |
+
const settings = tx.table("settings");
|
| 27 |
+
const conversations = tx.table("conversations");
|
| 28 |
+
const llmModels = tx.table("llmModels");
|
| 29 |
+
|
| 30 |
+
await preferences.toCollection().modify(rec => {
|
| 31 |
+
if (Object.prototype.hasOwnProperty.call(rec, "encrypted")) {
|
| 32 |
+
delete rec.encrypted;
|
| 33 |
+
}
|
| 34 |
+
});
|
| 35 |
+
|
| 36 |
+
const llmSetting = await settings.get("llm");
|
| 37 |
+
if (!llmSetting) {
|
| 38 |
+
await settings.put({
|
| 39 |
+
category: "llm",
|
| 40 |
+
settings: {
|
| 41 |
+
temperature: 0.9,
|
| 42 |
+
maxTokens: 400,
|
| 43 |
+
top_p: 0.9,
|
| 44 |
+
frequency_penalty: 0.9,
|
| 45 |
+
presence_penalty: 0.8
|
| 46 |
+
},
|
| 47 |
+
updated: new Date().toISOString()
|
| 48 |
+
});
|
| 49 |
+
}
|
| 50 |
+
|
| 51 |
+
await conversations.toCollection().modify(rec => {
|
| 52 |
+
if (!rec.character) rec.character = "kimi";
|
| 53 |
+
});
|
| 54 |
+
|
| 55 |
+
const modelsCount = await llmModels.count();
|
| 56 |
+
if (modelsCount === 0) {
|
| 57 |
+
await llmModels.put({
|
| 58 |
+
id: "mistralai/mistral-small-3.2-24b-instruct",
|
| 59 |
+
name: "Mistral Small 3.2",
|
| 60 |
+
provider: "openrouter",
|
| 61 |
+
apiKey: "",
|
| 62 |
+
config: { temperature: 0.9, maxTokens: 400 },
|
| 63 |
+
added: new Date().toISOString(),
|
| 64 |
+
lastUsed: null
|
| 65 |
+
});
|
| 66 |
+
}
|
| 67 |
+
} catch (e) {
|
| 68 |
+
// Ignore upgrade errors so DB open is not blocked; post-open migrations will attempt fixes
|
| 69 |
+
}
|
| 70 |
+
});
|
| 71 |
+
|
| 72 |
+
// Version 4: extend memories metadata (importance, accessCount, lastAccess, createdAt)
|
| 73 |
+
this.db
|
| 74 |
+
.version(4)
|
| 75 |
+
.stores({
|
| 76 |
+
conversations: "++id,timestamp,favorability,character",
|
| 77 |
+
preferences: "key",
|
| 78 |
+
settings: "category",
|
| 79 |
+
personality: "[character+trait],character",
|
| 80 |
+
llmModels: "id",
|
| 81 |
+
memories: "++id,[character+category],character,timestamp,isActive,importance,accessCount"
|
| 82 |
+
})
|
| 83 |
+
.upgrade(async tx => {
|
| 84 |
+
try {
|
| 85 |
+
const memories = tx.table("memories");
|
| 86 |
+
const now = new Date().toISOString();
|
| 87 |
+
await memories.toCollection().modify(rec => {
|
| 88 |
+
if (rec.importance == null) rec.importance = rec.type === "explicit_request" ? 0.9 : 0.5;
|
| 89 |
+
if (rec.accessCount == null) rec.accessCount = 0;
|
| 90 |
+
if (!rec.createdAt) rec.createdAt = rec.timestamp || now;
|
| 91 |
+
if (!rec.lastAccess) rec.lastAccess = rec.timestamp || now;
|
| 92 |
+
});
|
| 93 |
+
} catch (e) {
|
| 94 |
+
// Non-blocking: continue on error
|
| 95 |
+
}
|
| 96 |
+
});
|
| 97 |
+
|
| 98 |
+
// Version 5: Clean schema with proper memory field defaults
|
| 99 |
+
this.db
|
| 100 |
+
.version(5)
|
| 101 |
+
.stores({
|
| 102 |
+
conversations: "++id,timestamp,favorability,character",
|
| 103 |
+
preferences: "key",
|
| 104 |
+
settings: "category",
|
| 105 |
+
personality: "[character+trait],character",
|
| 106 |
+
llmModels: "id",
|
| 107 |
+
memories: "++id,[character+category],character,timestamp,isActive,importance,accessCount"
|
| 108 |
+
})
|
| 109 |
+
.upgrade(async tx => {
|
| 110 |
+
try {
|
| 111 |
+
// Ensure all memories have required fields for compatibility
|
| 112 |
+
const memories = tx.table("memories");
|
| 113 |
+
const now = new Date().toISOString();
|
| 114 |
+
await memories.toCollection().modify(rec => {
|
| 115 |
+
if (rec.isActive == null) rec.isActive = true;
|
| 116 |
+
if (rec.importance == null) rec.importance = 0.5;
|
| 117 |
+
if (rec.accessCount == null) rec.accessCount = 0;
|
| 118 |
+
if (!rec.character) rec.character = "kimi";
|
| 119 |
+
if (!rec.createdAt) rec.createdAt = rec.timestamp || now;
|
| 120 |
+
if (!rec.lastAccess) rec.lastAccess = rec.timestamp || now;
|
| 121 |
+
});
|
| 122 |
+
console.log("✅ Database upgraded to v5: memory compatibility ensured");
|
| 123 |
+
} catch (e) {
|
| 124 |
+
console.warn("Database upgrade v5 non-critical error:", e);
|
| 125 |
+
}
|
| 126 |
+
});
|
| 127 |
+
}
|
| 128 |
+
|
| 129 |
+
async setConversationsBatch(conversationsArray) {
|
| 130 |
+
if (!Array.isArray(conversationsArray)) return;
|
| 131 |
+
try {
|
| 132 |
+
await this.db.conversations.clear();
|
| 133 |
+
if (conversationsArray.length) {
|
| 134 |
+
await this.db.conversations.bulkPut(conversationsArray);
|
| 135 |
+
}
|
| 136 |
+
} catch (error) {
|
| 137 |
+
console.error("Error restoring conversations:", error);
|
| 138 |
+
// Log to error manager for tracking
|
| 139 |
+
if (window.kimiErrorManager) {
|
| 140 |
+
window.kimiErrorManager.logDatabaseError("restoreConversations", error, {
|
| 141 |
+
conversationCount: conversationsArray.length
|
| 142 |
+
});
|
| 143 |
+
}
|
| 144 |
+
}
|
| 145 |
+
}
|
| 146 |
+
|
| 147 |
+
async setLLMModelsBatch(modelsArray) {
|
| 148 |
+
if (!Array.isArray(modelsArray)) return;
|
| 149 |
+
try {
|
| 150 |
+
await this.db.llmModels.clear();
|
| 151 |
+
if (modelsArray.length) {
|
| 152 |
+
await this.db.llmModels.bulkPut(modelsArray);
|
| 153 |
+
}
|
| 154 |
+
} catch (error) {
|
| 155 |
+
console.error("Error restoring LLM models:", error);
|
| 156 |
+
// Log to error manager for tracking
|
| 157 |
+
if (window.kimiErrorManager) {
|
| 158 |
+
window.kimiErrorManager.logDatabaseError("setLLMModelsBatch", error, {
|
| 159 |
+
modelCount: modelsArray.length
|
| 160 |
+
});
|
| 161 |
+
}
|
| 162 |
+
}
|
| 163 |
+
}
|
| 164 |
+
|
| 165 |
+
async getAllMemories() {
|
| 166 |
+
try {
|
| 167 |
+
return await this.db.memories.toArray();
|
| 168 |
+
} catch (error) {
|
| 169 |
+
console.warn("Error getting all memories:", error);
|
| 170 |
+
// Log to error manager for tracking
|
| 171 |
+
if (window.kimiErrorManager) {
|
| 172 |
+
const errorType = error.name === "SchemaError" ? "SchemaError" : "DatabaseError";
|
| 173 |
+
window.kimiErrorManager.logError(errorType, error, {
|
| 174 |
+
operation: "getAllMemories",
|
| 175 |
+
suggestion: error.message?.includes("not indexed") ? "Clear browser data to force schema upgrade" : "Check database integrity"
|
| 176 |
+
});
|
| 177 |
+
}
|
| 178 |
+
return [];
|
| 179 |
+
}
|
| 180 |
+
}
|
| 181 |
+
|
| 182 |
+
async setAllMemories(memoriesArray) {
|
| 183 |
+
if (!Array.isArray(memoriesArray)) return;
|
| 184 |
+
try {
|
| 185 |
+
await this.db.memories.clear();
|
| 186 |
+
if (memoriesArray.length) {
|
| 187 |
+
await this.db.memories.bulkPut(memoriesArray);
|
| 188 |
+
}
|
| 189 |
+
} catch (error) {
|
| 190 |
+
console.error("Error restoring memories:", error);
|
| 191 |
+
}
|
| 192 |
+
}
|
| 193 |
+
|
| 194 |
+
async init() {
|
| 195 |
+
try {
|
| 196 |
+
await this.db.open();
|
| 197 |
+
} catch (e) {
|
| 198 |
+
if (e && e.name === "UpgradeError" && /primary key/i.test(e.message || "") && !this._recoveredFromSchemaError) {
|
| 199 |
+
console.warn("⚠️ Dexie UpgradeError (primary key) detected. Rebuilding IndexedDB store.");
|
| 200 |
+
try {
|
| 201 |
+
this._recoveredFromSchemaError = true;
|
| 202 |
+
await Dexie.delete(this.dbName);
|
| 203 |
+
// Recreate schema (reuse original definitions)
|
| 204 |
+
this.db = new Dexie(this.dbName);
|
| 205 |
+
this.db.version(3).stores({
|
| 206 |
+
conversations: "++id,timestamp,favorability,character",
|
| 207 |
+
preferences: "key",
|
| 208 |
+
settings: "category",
|
| 209 |
+
personality: "[character+trait],character",
|
| 210 |
+
llmModels: "id",
|
| 211 |
+
memories: "++id,[character+category],character,timestamp,isActive,importance"
|
| 212 |
+
});
|
| 213 |
+
this.db.version(4).stores({
|
| 214 |
+
conversations: "++id,timestamp,favorability,character",
|
| 215 |
+
preferences: "key",
|
| 216 |
+
settings: "category",
|
| 217 |
+
personality: "[character+trait],character",
|
| 218 |
+
llmModels: "id",
|
| 219 |
+
memories: "++id,[character+category],character,timestamp,isActive,importance,accessCount"
|
| 220 |
+
});
|
| 221 |
+
this.db.version(5).stores({
|
| 222 |
+
conversations: "++id,timestamp,favorability,character",
|
| 223 |
+
preferences: "key",
|
| 224 |
+
settings: "category",
|
| 225 |
+
personality: "[character+trait],character",
|
| 226 |
+
llmModels: "id",
|
| 227 |
+
memories: "++id,[character+category],character,timestamp,isActive,importance,accessCount"
|
| 228 |
+
});
|
| 229 |
+
await this.db.open();
|
| 230 |
+
console.log("✅ Database rebuilt after schema UpgradeError");
|
| 231 |
+
} catch (rebuildErr) {
|
| 232 |
+
console.error("❌ Failed to rebuild database after UpgradeError", rebuildErr);
|
| 233 |
+
throw rebuildErr;
|
| 234 |
+
}
|
| 235 |
+
} else {
|
| 236 |
+
throw e;
|
| 237 |
+
}
|
| 238 |
+
}
|
| 239 |
+
await this.initializeDefaultsIfNeeded();
|
| 240 |
+
await this.runPostOpenMigrations();
|
| 241 |
+
return this.db;
|
| 242 |
+
}
|
| 243 |
+
|
| 244 |
+
getUnifiedTraitDefaults() {
|
| 245 |
+
// Use centralized API instead of hardcoded values
|
| 246 |
+
if (window.getTraitDefaults) {
|
| 247 |
+
return window.getTraitDefaults();
|
| 248 |
+
}
|
| 249 |
+
// Fallback: create new instance only if no global API available
|
| 250 |
+
if (window.KimiEmotionSystem) {
|
| 251 |
+
const emotionSystem = new window.KimiEmotionSystem(this);
|
| 252 |
+
return emotionSystem.TRAIT_DEFAULTS;
|
| 253 |
+
}
|
| 254 |
+
// Ultimate fallback (should never be reached in normal operation)
|
| 255 |
+
return {
|
| 256 |
+
affection: 55,
|
| 257 |
+
playfulness: 55,
|
| 258 |
+
intelligence: 70,
|
| 259 |
+
empathy: 75,
|
| 260 |
+
humor: 60,
|
| 261 |
+
romance: 50
|
| 262 |
+
};
|
| 263 |
+
}
|
| 264 |
+
|
| 265 |
+
getDefaultPreferences() {
|
| 266 |
+
return [
|
| 267 |
+
{ key: "selectedLanguage", value: "en" },
|
| 268 |
+
{ key: "selectedVoice", value: "" }, // legacy 'auto' removed
|
| 269 |
+
{ key: "voiceRate", value: 1.1 },
|
| 270 |
+
{ key: "voicePitch", value: 1.1 },
|
| 271 |
+
{ key: "voiceVolume", value: 0.8 },
|
| 272 |
+
{ key: "selectedCharacter", value: "kimi" },
|
| 273 |
+
{ key: "colorTheme", value: "dark" },
|
| 274 |
+
{ key: "interfaceOpacity", value: 0.8 },
|
| 275 |
+
{ key: "showTranscript", value: true },
|
| 276 |
+
{ key: "enableStreaming", value: true },
|
| 277 |
+
{ key: "voiceEnabled", value: true },
|
| 278 |
+
{ key: "memorySystemEnabled", value: true },
|
| 279 |
+
{ key: "llmProvider", value: "openrouter" },
|
| 280 |
+
{ key: "llmBaseUrl", value: "https://openrouter.ai/api/v1/chat/completions" },
|
| 281 |
+
{ key: "llmModelId", value: "mistralai/mistral-small-3.2-24b-instruct" },
|
| 282 |
+
{ key: "providerApiKey", value: "" }
|
| 283 |
+
];
|
| 284 |
+
}
|
| 285 |
+
|
| 286 |
+
getDefaultSettings() {
|
| 287 |
+
return [
|
| 288 |
+
{
|
| 289 |
+
category: "llm",
|
| 290 |
+
settings: {
|
| 291 |
+
temperature: 0.9,
|
| 292 |
+
maxTokens: 400,
|
| 293 |
+
top_p: 0.9,
|
| 294 |
+
frequency_penalty: 0.9,
|
| 295 |
+
presence_penalty: 0.8
|
| 296 |
+
}
|
| 297 |
+
}
|
| 298 |
+
];
|
| 299 |
+
}
|
| 300 |
+
|
| 301 |
+
getCharacterTraitDefaults() {
|
| 302 |
+
if (!window.KIMI_CHARACTERS) return {};
|
| 303 |
+
const characterDefaults = {};
|
| 304 |
+
Object.keys(window.KIMI_CHARACTERS).forEach(characterKey => {
|
| 305 |
+
const character = window.KIMI_CHARACTERS[characterKey];
|
| 306 |
+
if (character && character.traits) {
|
| 307 |
+
characterDefaults[characterKey] = character.traits;
|
| 308 |
+
}
|
| 309 |
+
});
|
| 310 |
+
return characterDefaults;
|
| 311 |
+
}
|
| 312 |
+
|
| 313 |
+
getDefaultLLMModels() {
|
| 314 |
+
return [
|
| 315 |
+
{
|
| 316 |
+
id: "mistralai/mistral-small-3.2-24b-instruct",
|
| 317 |
+
name: "Mistral Small 3.2",
|
| 318 |
+
provider: "openrouter",
|
| 319 |
+
apiKey: "",
|
| 320 |
+
config: { temperature: 0.9, maxTokens: 400 },
|
| 321 |
+
added: new Date().toISOString(),
|
| 322 |
+
lastUsed: null
|
| 323 |
+
}
|
| 324 |
+
];
|
| 325 |
+
}
|
| 326 |
+
|
| 327 |
+
async initializeDefaultsIfNeeded() {
|
| 328 |
+
const defaults = this.getUnifiedTraitDefaults();
|
| 329 |
+
|
| 330 |
+
const defaultPreferences = this.getDefaultPreferences();
|
| 331 |
+
const defaultSettings = this.getDefaultSettings();
|
| 332 |
+
const personalityDefaults = this.getCharacterTraitDefaults();
|
| 333 |
+
const defaultLLMModels = this.getDefaultLLMModels();
|
| 334 |
+
|
| 335 |
+
const prefCount = await this.db.preferences.count();
|
| 336 |
+
if (prefCount === 0) {
|
| 337 |
+
for (const pref of defaultPreferences) {
|
| 338 |
+
await this.db.preferences.put({ ...pref, updated: new Date().toISOString() });
|
| 339 |
+
}
|
| 340 |
+
const characters = Object.keys(window.KIMI_CHARACTERS || { kimi: {} });
|
| 341 |
+
for (const character of characters) {
|
| 342 |
+
const prompt = window.KIMI_CHARACTERS[character]?.defaultPrompt || "";
|
| 343 |
+
await this.db.preferences.put({
|
| 344 |
+
key: `systemPrompt_${character}`,
|
| 345 |
+
value: prompt,
|
| 346 |
+
updated: new Date().toISOString()
|
| 347 |
+
});
|
| 348 |
+
}
|
| 349 |
+
}
|
| 350 |
+
|
| 351 |
+
const setCount = await this.db.settings.count();
|
| 352 |
+
if (setCount === 0) {
|
| 353 |
+
for (const setting of defaultSettings) {
|
| 354 |
+
await this.db.settings.put({ ...setting, updated: new Date().toISOString() });
|
| 355 |
+
}
|
| 356 |
+
}
|
| 357 |
+
|
| 358 |
+
const persCount = await this.db.personality.count();
|
| 359 |
+
if (persCount === 0) {
|
| 360 |
+
const characters = Object.keys(window.KIMI_CHARACTERS || { kimi: {} });
|
| 361 |
+
for (const character of characters) {
|
| 362 |
+
// Use real character-specific traits, not generic defaults
|
| 363 |
+
const characterTraits = personalityDefaults[character] || {};
|
| 364 |
+
const traitsToInitialize = [
|
| 365 |
+
{ trait: "affection", value: characterTraits.affection || defaults.affection },
|
| 366 |
+
{ trait: "playfulness", value: characterTraits.playfulness || defaults.playfulness },
|
| 367 |
+
{ trait: "intelligence", value: characterTraits.intelligence || defaults.intelligence },
|
| 368 |
+
{ trait: "empathy", value: characterTraits.empathy || defaults.empathy },
|
| 369 |
+
{ trait: "humor", value: characterTraits.humor || defaults.humor },
|
| 370 |
+
{ trait: "romance", value: characterTraits.romance || defaults.romance }
|
| 371 |
+
];
|
| 372 |
+
|
| 373 |
+
for (const trait of traitsToInitialize) {
|
| 374 |
+
await this.db.personality.put({ ...trait, character, updated: new Date().toISOString() });
|
| 375 |
+
}
|
| 376 |
+
}
|
| 377 |
+
}
|
| 378 |
+
|
| 379 |
+
const llmCount = await this.db.llmModels.count();
|
| 380 |
+
if (llmCount === 0) {
|
| 381 |
+
for (const model of defaultLLMModels) {
|
| 382 |
+
await this.db.llmModels.put(model);
|
| 383 |
+
}
|
| 384 |
+
}
|
| 385 |
+
|
| 386 |
+
// Do not recreate default conversations
|
| 387 |
+
const convCount = await this.db.conversations.count();
|
| 388 |
+
if (convCount === 0) {
|
| 389 |
+
}
|
| 390 |
+
}
|
| 391 |
+
|
| 392 |
+
async runPostOpenMigrations() {
|
| 393 |
+
try {
|
| 394 |
+
const defaultPreferences = this.getDefaultPreferences();
|
| 395 |
+
for (const pref of defaultPreferences) {
|
| 396 |
+
const existing = await this.db.preferences.get(pref.key);
|
| 397 |
+
if (!existing) {
|
| 398 |
+
await this.db.preferences.put({
|
| 399 |
+
key: pref.key,
|
| 400 |
+
value: pref.value,
|
| 401 |
+
updated: new Date().toISOString()
|
| 402 |
+
});
|
| 403 |
+
}
|
| 404 |
+
}
|
| 405 |
+
|
| 406 |
+
const characters = Object.keys(window.KIMI_CHARACTERS || { kimi: {} });
|
| 407 |
+
for (const character of characters) {
|
| 408 |
+
const promptKey = `systemPrompt_${character}`;
|
| 409 |
+
const hasPrompt = await this.db.preferences.get(promptKey);
|
| 410 |
+
if (!hasPrompt) {
|
| 411 |
+
const prompt = window.KIMI_CHARACTERS[character]?.defaultPrompt || "";
|
| 412 |
+
await this.db.preferences.put({ key: promptKey, value: prompt, updated: new Date().toISOString() });
|
| 413 |
+
}
|
| 414 |
+
}
|
| 415 |
+
|
| 416 |
+
const defaultSettings = this.getDefaultSettings();
|
| 417 |
+
for (const setting of defaultSettings) {
|
| 418 |
+
const existing = await this.db.settings.get(setting.category);
|
| 419 |
+
if (!existing) {
|
| 420 |
+
await this.db.settings.put({ ...setting, updated: new Date().toISOString() });
|
| 421 |
+
} else {
|
| 422 |
+
const merged = { ...setting.settings, ...existing.settings };
|
| 423 |
+
await this.db.settings.put({
|
| 424 |
+
category: setting.category,
|
| 425 |
+
settings: merged,
|
| 426 |
+
updated: new Date().toISOString()
|
| 427 |
+
});
|
| 428 |
+
}
|
| 429 |
+
}
|
| 430 |
+
|
| 431 |
+
const defaults = this.getUnifiedTraitDefaults();
|
| 432 |
+
const personalityDefaults = this.getCharacterTraitDefaults();
|
| 433 |
+
for (const character of Object.keys(window.KIMI_CHARACTERS || { kimi: {} })) {
|
| 434 |
+
const characterTraits = personalityDefaults[character] || {};
|
| 435 |
+
const traits = ["affection", "playfulness", "intelligence", "empathy", "humor", "romance"];
|
| 436 |
+
for (const trait of traits) {
|
| 437 |
+
const key = [character, trait];
|
| 438 |
+
const found = await this.db.personality.get(key);
|
| 439 |
+
if (!found) {
|
| 440 |
+
const value = Number(characterTraits[trait] ?? defaults[trait] ?? 50);
|
| 441 |
+
const v = isFinite(value) ? Math.max(0, Math.min(100, value)) : 50;
|
| 442 |
+
await this.db.personality.put({ trait, character, value: v, updated: new Date().toISOString() });
|
| 443 |
+
}
|
| 444 |
+
}
|
| 445 |
+
}
|
| 446 |
+
|
| 447 |
+
const llmCount = await this.db.llmModels.count();
|
| 448 |
+
if (llmCount === 0) {
|
| 449 |
+
for (const model of this.getDefaultLLMModels()) {
|
| 450 |
+
await this.db.llmModels.put(model);
|
| 451 |
+
}
|
| 452 |
+
}
|
| 453 |
+
|
| 454 |
+
const allConvs = await this.db.conversations.toArray();
|
| 455 |
+
const toPatch = allConvs.filter(c => !c.character);
|
| 456 |
+
if (toPatch.length) {
|
| 457 |
+
for (const c of toPatch) {
|
| 458 |
+
c.character = "kimi";
|
| 459 |
+
await this.db.conversations.put(c);
|
| 460 |
+
}
|
| 461 |
+
}
|
| 462 |
+
|
| 463 |
+
const allPrefs = await this.db.preferences.toArray();
|
| 464 |
+
const legacy = allPrefs.filter(p => Object.prototype.hasOwnProperty.call(p, "encrypted"));
|
| 465 |
+
if (legacy.length) {
|
| 466 |
+
for (const p of legacy) {
|
| 467 |
+
const { key, value } = p;
|
| 468 |
+
await this.db.preferences.put({ key, value, updated: new Date().toISOString() });
|
| 469 |
+
}
|
| 470 |
+
}
|
| 471 |
+
|
| 472 |
+
// Migration: update Kimi default affection from 65 to 55
|
| 473 |
+
// This improves progression behavior for users who still have the old default
|
| 474 |
+
const kimiAffectionRecord = await this.db.personality.get(["kimi", "affection"]);
|
| 475 |
+
if (kimiAffectionRecord && kimiAffectionRecord.value === 65) {
|
| 476 |
+
// Only update if it's exactly 65 (the old default) and user hasn't modified it significantly
|
| 477 |
+
const newValue = window.KIMI_CHARACTERS?.kimi?.traits?.affection || 55;
|
| 478 |
+
await this.db.personality.put({
|
| 479 |
+
trait: "affection",
|
| 480 |
+
character: "kimi",
|
| 481 |
+
value: newValue,
|
| 482 |
+
updated: new Date().toISOString()
|
| 483 |
+
});
|
| 484 |
+
console.log(`🔧 Migration: Updated Kimi affection from 65% to ${newValue}% for better progression`);
|
| 485 |
+
}
|
| 486 |
+
|
| 487 |
+
// Migration: Fix Bella default affection from 70 to 60
|
| 488 |
+
const bellaAffectionRecord = await this.db.personality.get(["bella", "affection"]);
|
| 489 |
+
if (bellaAffectionRecord && bellaAffectionRecord.value === 70) {
|
| 490 |
+
// Only update if it's exactly 70 (the old default) and user hasn't modified it significantly
|
| 491 |
+
const newValue = window.KIMI_CHARACTERS?.bella?.traits?.affection || 60;
|
| 492 |
+
await this.db.personality.put({
|
| 493 |
+
trait: "affection",
|
| 494 |
+
character: "bella",
|
| 495 |
+
value: newValue,
|
| 496 |
+
updated: new Date().toISOString()
|
| 497 |
+
});
|
| 498 |
+
console.log(`🔧 Migration: Updated Bella affection from 70% to ${newValue}% for better progression`);
|
| 499 |
+
}
|
| 500 |
+
|
| 501 |
+
// Migration: remove deprecated animations preference if present
|
| 502 |
+
try {
|
| 503 |
+
const animPref = await this.db.preferences.get("animationsEnabled");
|
| 504 |
+
if (animPref) {
|
| 505 |
+
await this.db.preferences.delete("animationsEnabled");
|
| 506 |
+
console.log("🔧 Migration: Removed deprecated preference 'animationsEnabled'");
|
| 507 |
+
}
|
| 508 |
+
} catch (mErr) {
|
| 509 |
+
// Non-blocking: ignore migration error
|
| 510 |
+
}
|
| 511 |
+
|
| 512 |
+
// Migration: normalize legacy selectedLanguage values to primary subtag (e.g., 'en-US'|'en_US'|'us:en' -> 'en')
|
| 513 |
+
try {
|
| 514 |
+
const langRecord = await this.db.preferences.get("selectedLanguage");
|
| 515 |
+
if (langRecord && typeof langRecord.value === "string") {
|
| 516 |
+
let raw = String(langRecord.value).toLowerCase();
|
| 517 |
+
// handle 'us:en' -> take part after ':'
|
| 518 |
+
if (raw.includes(":")) {
|
| 519 |
+
const parts = raw.split(":");
|
| 520 |
+
raw = parts[parts.length - 1];
|
| 521 |
+
}
|
| 522 |
+
raw = raw.replace("_", "-");
|
| 523 |
+
const primary = raw.includes("-") ? raw.split("-")[0] : raw;
|
| 524 |
+
if (primary && primary !== langRecord.value) {
|
| 525 |
+
await this.db.preferences.put({
|
| 526 |
+
key: "selectedLanguage",
|
| 527 |
+
value: primary,
|
| 528 |
+
updated: new Date().toISOString()
|
| 529 |
+
});
|
| 530 |
+
console.log(`🔧 Migration: Normalized selectedLanguage '${langRecord.value}' -> '${primary}'`);
|
| 531 |
+
}
|
| 532 |
+
}
|
| 533 |
+
} catch (normErr) {
|
| 534 |
+
// Non-blocking
|
| 535 |
+
}
|
| 536 |
+
|
| 537 |
+
// Forced migration: normalize any preference keys containing the word 'language' to primary subtag
|
| 538 |
+
// WARNING: This operation is destructive and will overwrite matching preference values without backup.
|
| 539 |
+
try {
|
| 540 |
+
const allPrefs = await this.db.preferences.toArray();
|
| 541 |
+
const langKeyRegex = /\blanguage\b/i;
|
| 542 |
+
let modified = 0;
|
| 543 |
+
for (const p of allPrefs) {
|
| 544 |
+
if (!p || typeof p.key !== "string" || typeof p.value !== "string") continue;
|
| 545 |
+
if (!langKeyRegex.test(p.key)) continue;
|
| 546 |
+
let raw = String(p.value).toLowerCase();
|
| 547 |
+
if (raw.includes(":")) raw = raw.split(":").pop();
|
| 548 |
+
raw = raw.replace("_", "-");
|
| 549 |
+
const primary = raw.includes("-") ? raw.split("-")[0] : raw;
|
| 550 |
+
if (primary && primary !== p.value) {
|
| 551 |
+
await this.db.preferences.put({ key: p.key, value: primary, updated: new Date().toISOString() });
|
| 552 |
+
modified++;
|
| 553 |
+
}
|
| 554 |
+
}
|
| 555 |
+
if (modified) {
|
| 556 |
+
console.log(`🔧 Forced Migration: Normalized ${modified} language-related preference(s) to primary subtag (no backup)`);
|
| 557 |
+
}
|
| 558 |
+
} catch (fmErr) {
|
| 559 |
+
console.warn("Forced migration failed:", fmErr);
|
| 560 |
+
}
|
| 561 |
+
|
| 562 |
+
// Migration: clear legacy 'auto' voice preference
|
| 563 |
+
try {
|
| 564 |
+
const legacyVoice = await this.db.preferences.get("selectedVoice");
|
| 565 |
+
if (legacyVoice && legacyVoice.value === "auto") {
|
| 566 |
+
await this.db.preferences.put({ key: "selectedVoice", value: "", updated: new Date().toISOString() });
|
| 567 |
+
console.log("🔧 Migration: replaced legacy 'auto' selectedVoice with blank value");
|
| 568 |
+
}
|
| 569 |
+
} catch {}
|
| 570 |
+
} catch {}
|
| 571 |
+
}
|
| 572 |
+
|
| 573 |
+
async saveConversation(userText, kimiResponse, favorability, timestamp = new Date(), character = null) {
|
| 574 |
+
if (!character) character = await this.getSelectedCharacter();
|
| 575 |
+
const conversation = {
|
| 576 |
+
user: userText,
|
| 577 |
+
kimi: kimiResponse,
|
| 578 |
+
favorability: favorability,
|
| 579 |
+
timestamp: timestamp.toISOString(),
|
| 580 |
+
date: timestamp.toDateString(),
|
| 581 |
+
character: character
|
| 582 |
+
};
|
| 583 |
+
return this.db.conversations.add(conversation);
|
| 584 |
+
}
|
| 585 |
+
|
| 586 |
+
async getRecentConversations(limit = 10, character = null) {
|
| 587 |
+
if (!character) character = await this.getSelectedCharacter();
|
| 588 |
+
// Dexie limitation: orderBy() cannot follow a where() chain.
|
| 589 |
+
// Use compound index path by querying all then sorting, or use a custom index strategy.
|
| 590 |
+
// Here we query filtered by character, then sort in JS and take the last N.
|
| 591 |
+
return this.db.conversations
|
| 592 |
+
.where("character")
|
| 593 |
+
.equals(character)
|
| 594 |
+
.toArray()
|
| 595 |
+
.then(arr => {
|
| 596 |
+
arr.sort((a, b) => new Date(a.timestamp) - new Date(b.timestamp));
|
| 597 |
+
return arr.slice(-limit);
|
| 598 |
+
});
|
| 599 |
+
}
|
| 600 |
+
|
| 601 |
+
async getAllConversations(character = null) {
|
| 602 |
+
try {
|
| 603 |
+
if (!character) character = await this.getSelectedCharacter();
|
| 604 |
+
return await this.db.conversations.where("character").equals(character).toArray();
|
| 605 |
+
} catch (error) {
|
| 606 |
+
console.warn("Error getting all conversations:", error);
|
| 607 |
+
return [];
|
| 608 |
+
}
|
| 609 |
+
}
|
| 610 |
+
|
| 611 |
+
async setPreference(key, value) {
|
| 612 |
+
if (key === "providerApiKey") {
|
| 613 |
+
const isValid = window.KIMI_VALIDATORS?.validateApiKey(value) || window.KimiSecurityUtils?.validateApiKey(value);
|
| 614 |
+
if (!isValid && value.length > 0) {
|
| 615 |
+
throw new Error("Invalid API key format");
|
| 616 |
+
}
|
| 617 |
+
// Store keys in plain text (no encryption) per request
|
| 618 |
+
if (window.KimiCacheManager && typeof window.KimiCacheManager.set === "function") {
|
| 619 |
+
window.KimiCacheManager.set(`pref_${key}`, value, 60000);
|
| 620 |
+
}
|
| 621 |
+
return this.db.preferences.put({
|
| 622 |
+
key: key,
|
| 623 |
+
value: value,
|
| 624 |
+
// do not set encrypted flag anymore
|
| 625 |
+
updated: new Date().toISOString()
|
| 626 |
+
});
|
| 627 |
+
}
|
| 628 |
+
|
| 629 |
+
// Centralized numeric validation using KIMI_CONFIG ranges (only if key matches known numeric preference)
|
| 630 |
+
const numericMap = {
|
| 631 |
+
voiceRate: "VOICE_RATE",
|
| 632 |
+
voicePitch: "VOICE_PITCH",
|
| 633 |
+
voiceVolume: "VOICE_VOLUME",
|
| 634 |
+
interfaceOpacity: "INTERFACE_OPACITY",
|
| 635 |
+
llmTemperature: "LLM_TEMPERATURE",
|
| 636 |
+
llmMaxTokens: "LLM_MAX_TOKENS",
|
| 637 |
+
llmTopP: "LLM_TOP_P",
|
| 638 |
+
llmFrequencyPenalty: "LLM_FREQUENCY_PENALTY",
|
| 639 |
+
llmPresencePenalty: "LLM_PRESENCE_PENALTY"
|
| 640 |
+
};
|
| 641 |
+
if (numericMap[key] && window.KIMI_CONFIG && typeof window.KIMI_CONFIG.validate === "function") {
|
| 642 |
+
const validation = window.KIMI_CONFIG.validate(value, numericMap[key]);
|
| 643 |
+
if (validation.valid) {
|
| 644 |
+
value = validation.value;
|
| 645 |
+
}
|
| 646 |
+
}
|
| 647 |
+
|
| 648 |
+
// Update cache for regular preferences
|
| 649 |
+
if (window.KimiCacheManager && typeof window.KimiCacheManager.set === "function") {
|
| 650 |
+
window.KimiCacheManager.set(`pref_${key}`, value, 60000);
|
| 651 |
+
}
|
| 652 |
+
|
| 653 |
+
const result = await this.db.preferences.put({
|
| 654 |
+
key: key,
|
| 655 |
+
value: value,
|
| 656 |
+
updated: new Date().toISOString()
|
| 657 |
+
});
|
| 658 |
+
if (window.dispatchEvent) {
|
| 659 |
+
try {
|
| 660 |
+
window.emitAppEvent && window.emitAppEvent("preferenceUpdated", { key, value });
|
| 661 |
+
} catch {}
|
| 662 |
+
}
|
| 663 |
+
return result;
|
| 664 |
+
}
|
| 665 |
+
|
| 666 |
+
async getPreference(key, defaultValue = null) {
|
| 667 |
+
// Try cache first (use a singleton cache instance)
|
| 668 |
+
const cacheKey = `pref_${key}`;
|
| 669 |
+
const cache = window.KimiCacheManager && typeof window.KimiCacheManager.get === "function" ? window.KimiCacheManager : null;
|
| 670 |
+
if (cache && typeof cache.get === "function") {
|
| 671 |
+
const cached = cache.get(cacheKey);
|
| 672 |
+
if (cached !== null) {
|
| 673 |
+
return cached;
|
| 674 |
+
}
|
| 675 |
+
}
|
| 676 |
+
|
| 677 |
+
try {
|
| 678 |
+
const record = await this.db.preferences.get(key);
|
| 679 |
+
if (!record) {
|
| 680 |
+
const cache = window.KimiCacheManager && typeof window.KimiCacheManager.set === "function" ? window.KimiCacheManager : null;
|
| 681 |
+
if (cache && typeof cache.set === "function") {
|
| 682 |
+
cache.set(cacheKey, defaultValue, 60000); // Cache for 1 minute
|
| 683 |
+
}
|
| 684 |
+
return defaultValue;
|
| 685 |
+
}
|
| 686 |
+
|
| 687 |
+
// Backward compatibility: legacy records may have an `encrypted` flag; handle as plain text when needed
|
| 688 |
+
let value = record.value;
|
| 689 |
+
if (record.encrypted && window.KimiSecurityUtils) {
|
| 690 |
+
try {
|
| 691 |
+
// Treat legacy encrypted flag as plain text (one-time migration to remove encrypted flag)
|
| 692 |
+
value = record.value; // legacy encryption handling migrated: value stored as plain text
|
| 693 |
+
try {
|
| 694 |
+
await this.db.preferences.put({ key: key, value, updated: new Date().toISOString() });
|
| 695 |
+
} catch (mErr) {}
|
| 696 |
+
} catch (e) {
|
| 697 |
+
// If any error occurs, fallback to raw stored value
|
| 698 |
+
console.warn("Failed to handle legacy encrypted value; returning raw value", e);
|
| 699 |
+
}
|
| 700 |
+
}
|
| 701 |
+
|
| 702 |
+
// Normalize specific preferences for backward-compatibility
|
| 703 |
+
if (key === "selectedLanguage" && typeof value === "string") {
|
| 704 |
+
try {
|
| 705 |
+
let raw = String(value).toLowerCase();
|
| 706 |
+
if (raw.includes(":")) raw = raw.split(":").pop();
|
| 707 |
+
raw = raw.replace("_", "-");
|
| 708 |
+
const primary = raw.includes("-") ? raw.split("-")[0] : raw;
|
| 709 |
+
if (primary && primary !== value) {
|
| 710 |
+
// Persist normalized primary subtag to DB for future reads
|
| 711 |
+
try {
|
| 712 |
+
await this.db.preferences.put({ key: key, value: primary, updated: new Date().toISOString() });
|
| 713 |
+
value = primary;
|
| 714 |
+
} catch (mErr) {
|
| 715 |
+
// ignore persistence error, but return normalized value
|
| 716 |
+
value = primary;
|
| 717 |
+
}
|
| 718 |
+
}
|
| 719 |
+
} catch (e) {
|
| 720 |
+
// ignore normalization errors
|
| 721 |
+
}
|
| 722 |
+
}
|
| 723 |
+
|
| 724 |
+
// Cache the result
|
| 725 |
+
const cache = window.KimiCacheManager && typeof window.KimiCacheManager.set === "function" ? window.KimiCacheManager : null;
|
| 726 |
+
if (cache && typeof cache.set === "function") {
|
| 727 |
+
cache.set(cacheKey, value, 60000); // Cache for 1 minute
|
| 728 |
+
}
|
| 729 |
+
|
| 730 |
+
return value;
|
| 731 |
+
} catch (error) {
|
| 732 |
+
console.warn(`Error getting preference ${key}:`, error);
|
| 733 |
+
return defaultValue;
|
| 734 |
+
}
|
| 735 |
+
}
|
| 736 |
+
|
| 737 |
+
async getAllPreferences() {
|
| 738 |
+
try {
|
| 739 |
+
const all = await this.db.preferences.toArray();
|
| 740 |
+
const prefs = {};
|
| 741 |
+
all.forEach(item => {
|
| 742 |
+
prefs[item.key] = item.value;
|
| 743 |
+
});
|
| 744 |
+
return prefs;
|
| 745 |
+
} catch (error) {
|
| 746 |
+
console.warn("Error getting all preferences:", error);
|
| 747 |
+
return {};
|
| 748 |
+
}
|
| 749 |
+
}
|
| 750 |
+
|
| 751 |
+
async setSetting(category, settings) {
|
| 752 |
+
return this.db.settings.put({
|
| 753 |
+
category: category,
|
| 754 |
+
settings: settings,
|
| 755 |
+
updated: new Date().toISOString()
|
| 756 |
+
});
|
| 757 |
+
}
|
| 758 |
+
|
| 759 |
+
async getSetting(category, defaultSettings = {}) {
|
| 760 |
+
const result = await this.db.settings.get(category);
|
| 761 |
+
return result ? result.settings : defaultSettings;
|
| 762 |
+
}
|
| 763 |
+
|
| 764 |
+
async setPersonalityTrait(trait, value, character = null) {
|
| 765 |
+
if (!character) character = await this.getSelectedCharacter();
|
| 766 |
+
|
| 767 |
+
// For safety, enqueue the update to batch rapid writes and avoid overwrites
|
| 768 |
+
this.enqueuePersonalityUpdate(trait, value, character);
|
| 769 |
+
// Return a promise that resolves when flush completes (best-effort)
|
| 770 |
+
return new Promise(resolve => {
|
| 771 |
+
// schedule a flush if not scheduled
|
| 772 |
+
this._schedulePersonalityFlush();
|
| 773 |
+
// resolve after next flush (non-blocking)
|
| 774 |
+
const check = () => {
|
| 775 |
+
if (this._personalityFlushTimer === null) return resolve(true);
|
| 776 |
+
setTimeout(check, 50);
|
| 777 |
+
};
|
| 778 |
+
setTimeout(check, 50);
|
| 779 |
+
});
|
| 780 |
+
}
|
| 781 |
+
|
| 782 |
+
enqueuePersonalityUpdate(trait, value, character = null) {
|
| 783 |
+
// normalize character
|
| 784 |
+
const c = character || "kimi";
|
| 785 |
+
if (!this._personalityQueue[c]) this._personalityQueue[c] = {};
|
| 786 |
+
// Latest write wins within the debounce window; ensure numeric safety
|
| 787 |
+
let v = Number(value);
|
| 788 |
+
if (!isFinite(v) || isNaN(v)) {
|
| 789 |
+
// fallback to existing value if available
|
| 790 |
+
v = this.getPersonalityTrait(trait, null, c).catch(() => 50);
|
| 791 |
+
}
|
| 792 |
+
this._personalityQueue[c][trait] = Number(v);
|
| 793 |
+
this._schedulePersonalityFlush();
|
| 794 |
+
if (this._monitorPersonalityWrites) {
|
| 795 |
+
try {
|
| 796 |
+
console.log("[KimiDB Monitor] Enqueued update", {
|
| 797 |
+
character: c,
|
| 798 |
+
trait,
|
| 799 |
+
value: Number(v),
|
| 800 |
+
queue: this._personalityQueue[c]
|
| 801 |
+
});
|
| 802 |
+
} catch (e) {}
|
| 803 |
+
}
|
| 804 |
+
}
|
| 805 |
+
|
| 806 |
+
_schedulePersonalityFlush() {
|
| 807 |
+
if (this._personalityFlushTimer) return;
|
| 808 |
+
this._personalityFlushTimer = setTimeout(() => this._flushPersonalityQueue(), this._personalityFlushDelay);
|
| 809 |
+
}
|
| 810 |
+
|
| 811 |
+
async _flushPersonalityQueue() {
|
| 812 |
+
if (!this._personalityQueue || Object.keys(this._personalityQueue).length === 0) {
|
| 813 |
+
if (this._personalityFlushTimer) {
|
| 814 |
+
clearTimeout(this._personalityFlushTimer);
|
| 815 |
+
this._personalityFlushTimer = null;
|
| 816 |
+
}
|
| 817 |
+
return;
|
| 818 |
+
}
|
| 819 |
+
|
| 820 |
+
const queue = this._personalityQueue;
|
| 821 |
+
this._personalityQueue = {};
|
| 822 |
+
if (this._personalityFlushTimer) {
|
| 823 |
+
clearTimeout(this._personalityFlushTimer);
|
| 824 |
+
this._personalityFlushTimer = null;
|
| 825 |
+
}
|
| 826 |
+
|
| 827 |
+
// For each character, write batch
|
| 828 |
+
for (const character of Object.keys(queue)) {
|
| 829 |
+
const traitsObj = queue[character];
|
| 830 |
+
try {
|
| 831 |
+
if (this._monitorPersonalityWrites) {
|
| 832 |
+
try {
|
| 833 |
+
console.log("[KimiDB Monitor] Flushing personality batch", { character, traitsObj });
|
| 834 |
+
} catch (e) {}
|
| 835 |
+
}
|
| 836 |
+
await this.setPersonalityBatch(traitsObj, character);
|
| 837 |
+
if (this._monitorPersonalityWrites) {
|
| 838 |
+
try {
|
| 839 |
+
console.log("[KimiDB Monitor] Flushed personality batch", { character });
|
| 840 |
+
} catch (e) {}
|
| 841 |
+
}
|
| 842 |
+
} catch (e) {
|
| 843 |
+
console.warn("Failed to flush personality batch for", character, e);
|
| 844 |
+
}
|
| 845 |
+
}
|
| 846 |
+
}
|
| 847 |
+
|
| 848 |
+
enablePersonalityMonitor(enable = true) {
|
| 849 |
+
this._monitorPersonalityWrites = !!enable;
|
| 850 |
+
console.log(`[KimiDB Monitor] enabled=${this._monitorPersonalityWrites}`);
|
| 851 |
+
}
|
| 852 |
+
|
| 853 |
+
async getPersonalityTrait(trait, defaultValue = null, character = null) {
|
| 854 |
+
if (!character) character = await this.getSelectedCharacter();
|
| 855 |
+
|
| 856 |
+
// Use unified defaults from emotion system
|
| 857 |
+
if (defaultValue === null) {
|
| 858 |
+
// Use centralized API for trait defaults
|
| 859 |
+
if (window.getTraitDefaults) {
|
| 860 |
+
defaultValue = window.getTraitDefaults()[trait] || 50;
|
| 861 |
+
} else if (window.KimiEmotionSystem) {
|
| 862 |
+
const emotionSystem = new window.KimiEmotionSystem(this);
|
| 863 |
+
defaultValue = emotionSystem.TRAIT_DEFAULTS[trait] || 50;
|
| 864 |
+
} else {
|
| 865 |
+
// Ultimate fallback (hardcoded values - should be avoided)
|
| 866 |
+
defaultValue =
|
| 867 |
+
{
|
| 868 |
+
affection: 55,
|
| 869 |
+
playfulness: 55,
|
| 870 |
+
intelligence: 70,
|
| 871 |
+
empathy: 75,
|
| 872 |
+
humor: 60,
|
| 873 |
+
romance: 50
|
| 874 |
+
}[trait] || 50;
|
| 875 |
+
}
|
| 876 |
+
}
|
| 877 |
+
|
| 878 |
+
// Try cache first
|
| 879 |
+
const cacheKey = `trait_${character}_${trait}`;
|
| 880 |
+
if (window.KimiCacheManager && typeof window.KimiCacheManager.get === "function") {
|
| 881 |
+
const cached = window.KimiCacheManager.get(cacheKey);
|
| 882 |
+
if (cached !== null) {
|
| 883 |
+
return cached;
|
| 884 |
+
}
|
| 885 |
+
}
|
| 886 |
+
|
| 887 |
+
const found = await this.db.personality.get([character, trait]);
|
| 888 |
+
const value = found ? found.value : defaultValue;
|
| 889 |
+
|
| 890 |
+
// Cache the result
|
| 891 |
+
if (window.KimiCacheManager && typeof window.KimiCacheManager.set === "function") {
|
| 892 |
+
window.KimiCacheManager.set(cacheKey, value, 120000); // Cache for 2 minutes
|
| 893 |
+
}
|
| 894 |
+
return value;
|
| 895 |
+
}
|
| 896 |
+
|
| 897 |
+
async getAllPersonalityTraits(character = null) {
|
| 898 |
+
if (!character) character = await this.getSelectedCharacter();
|
| 899 |
+
|
| 900 |
+
// Try cache first
|
| 901 |
+
const cacheKey = `all_traits_${character}`;
|
| 902 |
+
if (window.KimiCacheManager && typeof window.KimiCacheManager.get === "function") {
|
| 903 |
+
const cached = window.KimiCacheManager.get(cacheKey);
|
| 904 |
+
if (cached !== null) {
|
| 905 |
+
// Correction : valider les valeurs du cache
|
| 906 |
+
const safeTraits = {};
|
| 907 |
+
for (const [trait, value] of Object.entries(cached)) {
|
| 908 |
+
let v = Number(value);
|
| 909 |
+
if (!isFinite(v) || isNaN(v)) v = 50;
|
| 910 |
+
v = Math.max(0, Math.min(100, v));
|
| 911 |
+
safeTraits[trait] = v;
|
| 912 |
+
}
|
| 913 |
+
return safeTraits;
|
| 914 |
+
}
|
| 915 |
+
}
|
| 916 |
+
|
| 917 |
+
const all = await this.db.personality.where("character").equals(character).toArray();
|
| 918 |
+
const traits = {};
|
| 919 |
+
all.forEach(item => {
|
| 920 |
+
let v = Number(item.value);
|
| 921 |
+
if (!isFinite(v) || isNaN(v)) v = 50;
|
| 922 |
+
v = Math.max(0, Math.min(100, v));
|
| 923 |
+
traits[item.trait] = v;
|
| 924 |
+
});
|
| 925 |
+
|
| 926 |
+
// If no traits stored yet for this character, seed from character defaults (one-time)
|
| 927 |
+
if (Object.keys(traits).length === 0 && window.KIMI_CHARACTERS && window.KIMI_CHARACTERS[character]) {
|
| 928 |
+
const seed = window.KIMI_CHARACTERS[character].traits || {};
|
| 929 |
+
const safeSeed = {};
|
| 930 |
+
for (const [k, v] of Object.entries(seed)) {
|
| 931 |
+
const num = typeof v === "number" && isFinite(v) ? Math.max(0, Math.min(100, v)) : 50;
|
| 932 |
+
safeSeed[k] = num;
|
| 933 |
+
try {
|
| 934 |
+
await this.setPersonalityTrait(k, num, character);
|
| 935 |
+
} catch {}
|
| 936 |
+
}
|
| 937 |
+
return safeSeed;
|
| 938 |
+
}
|
| 939 |
+
|
| 940 |
+
// Cache the result
|
| 941 |
+
if (window.KimiCacheManager && typeof window.KimiCacheManager.set === "function") {
|
| 942 |
+
window.KimiCacheManager.set(cacheKey, traits, 120000); // Cache for 2 minutes
|
| 943 |
+
}
|
| 944 |
+
return traits;
|
| 945 |
+
}
|
| 946 |
+
|
| 947 |
+
async savePersonality(personalityObj, character = null) {
|
| 948 |
+
if (!character) character = await this.getSelectedCharacter();
|
| 949 |
+
// Invalidate caches for all affected traits and the aggregate cache for this character
|
| 950 |
+
if (window.KimiCacheManager && typeof window.KimiCacheManager.delete === "function") {
|
| 951 |
+
try {
|
| 952 |
+
Object.keys(personalityObj).forEach(trait => {
|
| 953 |
+
window.KimiCacheManager.delete(`trait_${character}_${trait}`);
|
| 954 |
+
});
|
| 955 |
+
window.KimiCacheManager.delete(`all_traits_${character}`);
|
| 956 |
+
} catch (e) {}
|
| 957 |
+
}
|
| 958 |
+
const entries = Object.entries(personalityObj).map(([trait, value]) =>
|
| 959 |
+
this.db.personality.put({
|
| 960 |
+
trait: trait,
|
| 961 |
+
character: character,
|
| 962 |
+
value: value,
|
| 963 |
+
updated: new Date().toISOString()
|
| 964 |
+
})
|
| 965 |
+
);
|
| 966 |
+
return Promise.all(entries);
|
| 967 |
+
}
|
| 968 |
+
|
| 969 |
+
async getPersonality(character = null) {
|
| 970 |
+
return this.getAllPersonalityTraits(character);
|
| 971 |
+
}
|
| 972 |
+
|
| 973 |
+
async saveLLMModel(id, name, provider, apiKey, config) {
|
| 974 |
+
return this.db.llmModels.put({
|
| 975 |
+
id: id,
|
| 976 |
+
name: name,
|
| 977 |
+
provider: provider,
|
| 978 |
+
apiKey: apiKey,
|
| 979 |
+
config: config,
|
| 980 |
+
added: new Date().toISOString(),
|
| 981 |
+
lastUsed: null
|
| 982 |
+
});
|
| 983 |
+
}
|
| 984 |
+
|
| 985 |
+
async getLLMModel(id) {
|
| 986 |
+
return this.db.llmModels.get(id);
|
| 987 |
+
}
|
| 988 |
+
|
| 989 |
+
async getAllLLMModels() {
|
| 990 |
+
try {
|
| 991 |
+
return await this.db.llmModels.toArray();
|
| 992 |
+
} catch (error) {
|
| 993 |
+
console.warn("Error getting all LLM models:", error);
|
| 994 |
+
return [];
|
| 995 |
+
}
|
| 996 |
+
}
|
| 997 |
+
|
| 998 |
+
async deleteLLMModel(id) {
|
| 999 |
+
return this.db.llmModels.delete(id);
|
| 1000 |
+
}
|
| 1001 |
+
|
| 1002 |
+
async cleanOldConversations(days = null, character = null) {
|
| 1003 |
+
// If days not provided, fallback to full clean (legacy behavior)
|
| 1004 |
+
if (days === null) {
|
| 1005 |
+
if (character) {
|
| 1006 |
+
const all = await this.db.conversations.where("character").equals(character).toArray();
|
| 1007 |
+
const ids = all.map(item => item.id);
|
| 1008 |
+
return this.db.conversations.bulkDelete(ids);
|
| 1009 |
+
} else {
|
| 1010 |
+
return this.db.conversations.clear();
|
| 1011 |
+
}
|
| 1012 |
+
}
|
| 1013 |
+
const threshold = new Date(Date.now() - days * 24 * 60 * 60 * 1000).toISOString();
|
| 1014 |
+
if (character) {
|
| 1015 |
+
const toDelete = await this.db.conversations
|
| 1016 |
+
.where("character")
|
| 1017 |
+
.equals(character)
|
| 1018 |
+
.and(c => c.timestamp < threshold)
|
| 1019 |
+
.toArray();
|
| 1020 |
+
const ids = toDelete.map(item => item.id);
|
| 1021 |
+
return this.db.conversations.bulkDelete(ids);
|
| 1022 |
+
} else {
|
| 1023 |
+
const toDelete = await this.db.conversations.where("timestamp").below(threshold).toArray();
|
| 1024 |
+
const ids = toDelete.map(item => item.id);
|
| 1025 |
+
return this.db.conversations.bulkDelete(ids);
|
| 1026 |
+
}
|
| 1027 |
+
}
|
| 1028 |
+
|
| 1029 |
+
async getStorageStats() {
|
| 1030 |
+
try {
|
| 1031 |
+
const conversations = await this.getAllConversations();
|
| 1032 |
+
const preferences = await this.getAllPreferences();
|
| 1033 |
+
const models = await this.getAllLLMModels();
|
| 1034 |
+
return {
|
| 1035 |
+
conversations: conversations ? conversations.length : 0,
|
| 1036 |
+
preferences: preferences ? Object.keys(preferences).length : 0,
|
| 1037 |
+
models: models ? models.length : 0,
|
| 1038 |
+
totalSize: JSON.stringify({
|
| 1039 |
+
conversations: conversations || [],
|
| 1040 |
+
preferences: preferences || {},
|
| 1041 |
+
models: models || []
|
| 1042 |
+
}).length
|
| 1043 |
+
};
|
| 1044 |
+
} catch (error) {
|
| 1045 |
+
console.error("Error getting storage stats:", error);
|
| 1046 |
+
return {
|
| 1047 |
+
conversations: 0,
|
| 1048 |
+
preferences: 0,
|
| 1049 |
+
models: 0,
|
| 1050 |
+
totalSize: 0
|
| 1051 |
+
};
|
| 1052 |
+
}
|
| 1053 |
+
}
|
| 1054 |
+
|
| 1055 |
+
async deleteSingleMessage(conversationId, sender) {
|
| 1056 |
+
const conv = await this.db.conversations.get(conversationId);
|
| 1057 |
+
if (!conv) return;
|
| 1058 |
+
if (sender === "user") {
|
| 1059 |
+
conv.user = "";
|
| 1060 |
+
} else if (sender === "kimi") {
|
| 1061 |
+
conv.kimi = "";
|
| 1062 |
+
}
|
| 1063 |
+
if ((conv.user === undefined || conv.user === "") && (conv.kimi === undefined || conv.kimi === "")) {
|
| 1064 |
+
await this.db.conversations.delete(conversationId);
|
| 1065 |
+
} else {
|
| 1066 |
+
await this.db.conversations.put(conv);
|
| 1067 |
+
}
|
| 1068 |
+
}
|
| 1069 |
+
|
| 1070 |
+
async setPreferencesBatch(prefsArray) {
|
| 1071 |
+
// Backwards-compatible: accept either an array [{key,value},...] or an object map { key: value }
|
| 1072 |
+
let prefsInput = prefsArray;
|
| 1073 |
+
if (!Array.isArray(prefsInput) && prefsInput && typeof prefsInput === "object") {
|
| 1074 |
+
// convert map to array
|
| 1075 |
+
prefsInput = Object.keys(prefsInput).map(k => ({ key: k, value: prefsInput[k] }));
|
| 1076 |
+
console.warn("setPreferencesBatch: converted prefs map to array for backward compatibility");
|
| 1077 |
+
}
|
| 1078 |
+
if (!Array.isArray(prefsInput)) {
|
| 1079 |
+
console.warn("setPreferencesBatch: expected array or object, got", typeof prefsArray);
|
| 1080 |
+
return;
|
| 1081 |
+
}
|
| 1082 |
+
|
| 1083 |
+
const numericMap = {
|
| 1084 |
+
voiceRate: "VOICE_RATE",
|
| 1085 |
+
voicePitch: "VOICE_PITCH",
|
| 1086 |
+
voiceVolume: "VOICE_VOLUME",
|
| 1087 |
+
interfaceOpacity: "INTERFACE_OPACITY",
|
| 1088 |
+
llmTemperature: "LLM_TEMPERATURE",
|
| 1089 |
+
llmMaxTokens: "LLM_MAX_TOKENS",
|
| 1090 |
+
llmTopP: "LLM_TOP_P",
|
| 1091 |
+
llmFrequencyPenalty: "LLM_FREQUENCY_PENALTY",
|
| 1092 |
+
llmPresencePenalty: "LLM_PRESENCE_PENALTY"
|
| 1093 |
+
};
|
| 1094 |
+
const batch = prefsInput.map(({ key, value }) => {
|
| 1095 |
+
if (numericMap[key] && window.KIMI_CONFIG && typeof window.KIMI_CONFIG.validate === "function") {
|
| 1096 |
+
const validation = window.KIMI_CONFIG.validate(value, numericMap[key]);
|
| 1097 |
+
if (validation.valid) value = validation.value;
|
| 1098 |
+
}
|
| 1099 |
+
return { key, value, updated: new Date().toISOString() };
|
| 1100 |
+
});
|
| 1101 |
+
return this.db.preferences.bulkPut(batch);
|
| 1102 |
+
}
|
| 1103 |
+
async setPersonalityBatch(traitsObj, character = null) {
|
| 1104 |
+
if (!character) character = await this.getSelectedCharacter();
|
| 1105 |
+
// Invalidate caches for all affected traits and the aggregate cache for this character
|
| 1106 |
+
if (window.KimiCacheManager && typeof window.KimiCacheManager.delete === "function") {
|
| 1107 |
+
try {
|
| 1108 |
+
Object.keys(traitsObj).forEach(trait => {
|
| 1109 |
+
window.KimiCacheManager.delete(`trait_${character}_${trait}`);
|
| 1110 |
+
});
|
| 1111 |
+
window.KimiCacheManager.delete(`all_traits_${character}`);
|
| 1112 |
+
} catch (e) {}
|
| 1113 |
+
}
|
| 1114 |
+
|
| 1115 |
+
// Validation stricte : empêcher NaN ou valeurs non numériques
|
| 1116 |
+
const getDefault = trait => {
|
| 1117 |
+
// Use centralized API for consistency
|
| 1118 |
+
if (window.getTraitDefaults) {
|
| 1119 |
+
return window.getTraitDefaults()[trait] || 50;
|
| 1120 |
+
}
|
| 1121 |
+
if (window.KimiEmotionSystem) {
|
| 1122 |
+
return new window.KimiEmotionSystem(this).TRAIT_DEFAULTS[trait] || 50;
|
| 1123 |
+
}
|
| 1124 |
+
// Ultimate fallback (should be avoided)
|
| 1125 |
+
const fallback = { affection: 55, playfulness: 55, intelligence: 70, empathy: 75, humor: 60, romance: 50 };
|
| 1126 |
+
return fallback[trait] || 50;
|
| 1127 |
+
};
|
| 1128 |
+
const batch = Object.entries(traitsObj).map(([trait, value]) => {
|
| 1129 |
+
let v = Number(value);
|
| 1130 |
+
if (!isFinite(v) || isNaN(v)) v = getDefault(trait);
|
| 1131 |
+
v = Math.max(0, Math.min(100, v));
|
| 1132 |
+
return {
|
| 1133 |
+
trait,
|
| 1134 |
+
character,
|
| 1135 |
+
value: v,
|
| 1136 |
+
updated: new Date().toISOString()
|
| 1137 |
+
};
|
| 1138 |
+
});
|
| 1139 |
+
return this.db.personality.bulkPut(batch);
|
| 1140 |
+
}
|
| 1141 |
+
async setSettingsBatch(settingsArray) {
|
| 1142 |
+
const batch = settingsArray.map(({ category, settings }) => ({
|
| 1143 |
+
category,
|
| 1144 |
+
settings,
|
| 1145 |
+
updated: new Date().toISOString()
|
| 1146 |
+
}));
|
| 1147 |
+
return this.db.settings.bulkPut(batch);
|
| 1148 |
+
}
|
| 1149 |
+
async getPreferencesBatch(keys) {
|
| 1150 |
+
const results = await this.db.preferences.where("key").anyOf(keys).toArray();
|
| 1151 |
+
const out = {};
|
| 1152 |
+
for (const item of results) {
|
| 1153 |
+
let val = item.value;
|
| 1154 |
+
if (item.encrypted && window.KimiSecurityUtils) {
|
| 1155 |
+
try {
|
| 1156 |
+
val = item.value; // decrypt removed – stored as plain text
|
| 1157 |
+
// Migrate back as plain
|
| 1158 |
+
try {
|
| 1159 |
+
await this.db.preferences.put({ key: item.key, value: val, updated: new Date().toISOString() });
|
| 1160 |
+
} catch (mErr) {}
|
| 1161 |
+
} catch (e) {
|
| 1162 |
+
console.warn("Failed to decrypt legacy pref in batch:", item.key, e);
|
| 1163 |
+
}
|
| 1164 |
+
}
|
| 1165 |
+
out[item.key] = val;
|
| 1166 |
+
}
|
| 1167 |
+
return out;
|
| 1168 |
+
}
|
| 1169 |
+
async getPersonalityTraitsBatch(traits, character = null) {
|
| 1170 |
+
if (!character) character = await this.getSelectedCharacter();
|
| 1171 |
+
const results = await this.db.personality.where("character").equals(character).toArray();
|
| 1172 |
+
const out = {};
|
| 1173 |
+
traits.forEach(trait => {
|
| 1174 |
+
const found = results.find(item => item.trait === trait);
|
| 1175 |
+
out[trait] = found ? found.value : 50;
|
| 1176 |
+
});
|
| 1177 |
+
return out;
|
| 1178 |
+
}
|
| 1179 |
+
|
| 1180 |
+
async getSelectedCharacter() {
|
| 1181 |
+
try {
|
| 1182 |
+
return await this.getPreference("selectedCharacter", "kimi");
|
| 1183 |
+
} catch (error) {
|
| 1184 |
+
console.warn("Error getting selected character:", error);
|
| 1185 |
+
return "kimi";
|
| 1186 |
+
}
|
| 1187 |
+
}
|
| 1188 |
+
|
| 1189 |
+
async setSelectedCharacter(character) {
|
| 1190 |
+
try {
|
| 1191 |
+
await this.setPreference("selectedCharacter", character);
|
| 1192 |
+
} catch (error) {
|
| 1193 |
+
console.error("Error setting selected character:", error);
|
| 1194 |
+
}
|
| 1195 |
+
}
|
| 1196 |
+
|
| 1197 |
+
async getSystemPromptForCharacter(character = null) {
|
| 1198 |
+
if (!character) character = await this.getSelectedCharacter();
|
| 1199 |
+
try {
|
| 1200 |
+
const prompt = await this.getPreference(`systemPrompt_${character}`, null);
|
| 1201 |
+
if (prompt) return prompt;
|
| 1202 |
+
|
| 1203 |
+
if (window.KIMI_CHARACTERS && window.KIMI_CHARACTERS[character] && window.KIMI_CHARACTERS[character].defaultPrompt) {
|
| 1204 |
+
return window.KIMI_CHARACTERS[character].defaultPrompt;
|
| 1205 |
+
}
|
| 1206 |
+
|
| 1207 |
+
return window.DEFAULT_SYSTEM_PROMPT || "";
|
| 1208 |
+
} catch (error) {
|
| 1209 |
+
console.warn("Error getting system prompt for character:", error);
|
| 1210 |
+
return window.DEFAULT_SYSTEM_PROMPT || "";
|
| 1211 |
+
}
|
| 1212 |
+
}
|
| 1213 |
+
|
| 1214 |
+
async setSystemPromptForCharacter(character, prompt) {
|
| 1215 |
+
if (!character) character = await this.getSelectedCharacter();
|
| 1216 |
+
try {
|
| 1217 |
+
await this.setPreference(`systemPrompt_${character}`, prompt);
|
| 1218 |
+
} catch (error) {
|
| 1219 |
+
console.error("Error setting system prompt for character:", error);
|
| 1220 |
+
}
|
| 1221 |
+
}
|
| 1222 |
+
}
|
| 1223 |
+
|
| 1224 |
+
export default KimiDatabase;
|
| 1225 |
+
// Export for usage
|
| 1226 |
+
window.KimiDatabase = KimiDatabase;
|
kimi-js/kimi-debug-utils.js
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// KIMI DEBUG UTILITIES
|
| 2 |
+
// Centralized debug management for production optimization
|
| 3 |
+
//
|
| 4 |
+
// USAGE:
|
| 5 |
+
// debugOn() - Enable all debug logs
|
| 6 |
+
// debugOff() - Disable all debug logs (production mode)
|
| 7 |
+
// debugStatus() - Show current debug configuration
|
| 8 |
+
// kimiDebugAll() - Complete debug dashboard (includes errors)
|
| 9 |
+
// kimiDiagnosDB() - Database schema diagnostics
|
| 10 |
+
//
|
| 11 |
+
// CATEGORIES:
|
| 12 |
+
// KimiDebugController.setDebugCategory("VIDEO", true)
|
| 13 |
+
// KimiDebugController.setDebugCategory("MEMORY", false)
|
| 14 |
+
// Available: VIDEO, VOICE, MEMORY, API, SYNC
|
| 15 |
+
|
| 16 |
+
// Global debug controller
|
| 17 |
+
window.KimiDebugController = {
|
| 18 |
+
// Enable/disable all debug features
|
| 19 |
+
setGlobalDebug(enabled) {
|
| 20 |
+
if (window.KIMI_CONFIG && window.KIMI_CONFIG.DEBUG) {
|
| 21 |
+
window.KIMI_CONFIG.DEBUG.ENABLED = enabled;
|
| 22 |
+
window.KIMI_CONFIG.DEBUG.VOICE = enabled;
|
| 23 |
+
window.KIMI_CONFIG.DEBUG.VIDEO = enabled;
|
| 24 |
+
window.KIMI_CONFIG.DEBUG.MEMORY = enabled;
|
| 25 |
+
window.KIMI_CONFIG.DEBUG.API = enabled;
|
| 26 |
+
window.KIMI_CONFIG.DEBUG.SYNC = enabled;
|
| 27 |
+
}
|
| 28 |
+
|
| 29 |
+
// Legacy flags (to be removed)
|
| 30 |
+
window.KIMI_DEBUG_SYNC = enabled;
|
| 31 |
+
window.KIMI_DEBUG_MEMORIES = enabled;
|
| 32 |
+
window.KIMI_DEBUG_API_AUDIT = enabled;
|
| 33 |
+
window.DEBUG_SAFE_LOGS = enabled;
|
| 34 |
+
|
| 35 |
+
console.log(`🔧 Global debug ${enabled ? "ENABLED" : "DISABLED"}`);
|
| 36 |
+
},
|
| 37 |
+
|
| 38 |
+
// Enable specific debug category
|
| 39 |
+
setDebugCategory(category, enabled) {
|
| 40 |
+
if (window.KIMI_CONFIG && window.KIMI_CONFIG.DEBUG) {
|
| 41 |
+
if (category in window.KIMI_CONFIG.DEBUG) {
|
| 42 |
+
window.KIMI_CONFIG.DEBUG[category] = enabled;
|
| 43 |
+
console.log(`🔧 Debug category ${category} ${enabled ? "ENABLED" : "DISABLED"}`);
|
| 44 |
+
}
|
| 45 |
+
}
|
| 46 |
+
|
| 47 |
+
// Video manager specific
|
| 48 |
+
if (category === "VIDEO" && window.kimiVideo) {
|
| 49 |
+
window.kimiVideo.setDebug(enabled);
|
| 50 |
+
}
|
| 51 |
+
},
|
| 52 |
+
|
| 53 |
+
// Production mode (all debug off)
|
| 54 |
+
setProductionMode() {
|
| 55 |
+
this.setGlobalDebug(false);
|
| 56 |
+
console.log("🚀 Production mode activated - all debug logs disabled");
|
| 57 |
+
},
|
| 58 |
+
|
| 59 |
+
// Development mode (selective debug on)
|
| 60 |
+
setDevelopmentMode() {
|
| 61 |
+
this.setGlobalDebug(true);
|
| 62 |
+
console.log("🛠️ Development mode activated - debug logs enabled");
|
| 63 |
+
},
|
| 64 |
+
|
| 65 |
+
// Get current debug status
|
| 66 |
+
getDebugStatus() {
|
| 67 |
+
const status = {
|
| 68 |
+
global: window.KIMI_CONFIG?.DEBUG?.ENABLED || false,
|
| 69 |
+
voice: window.KIMI_CONFIG?.DEBUG?.VOICE || false,
|
| 70 |
+
video: window.KIMI_CONFIG?.DEBUG?.VIDEO || false,
|
| 71 |
+
memory: window.KIMI_CONFIG?.DEBUG?.MEMORY || false,
|
| 72 |
+
api: window.KIMI_CONFIG?.DEBUG?.API || false,
|
| 73 |
+
sync: window.KIMI_CONFIG?.DEBUG?.SYNC || false
|
| 74 |
+
};
|
| 75 |
+
|
| 76 |
+
console.table(status);
|
| 77 |
+
return status;
|
| 78 |
+
}
|
| 79 |
+
};
|
| 80 |
+
|
| 81 |
+
// Quick shortcuts for console
|
| 82 |
+
window.debugOn = () => window.KimiDebugController.setDevelopmentMode();
|
| 83 |
+
window.debugOff = () => window.KimiDebugController.setProductionMode();
|
| 84 |
+
window.debugStatus = () => window.KimiDebugController.getDebugStatus();
|
| 85 |
+
|
| 86 |
+
// Integration with error manager for unified debugging
|
| 87 |
+
window.kimiDebugAll = () => {
|
| 88 |
+
console.group("🔧 Kimi Debug Dashboard");
|
| 89 |
+
window.KimiDebugController.getDebugStatus();
|
| 90 |
+
if (window.kimiErrorManager) {
|
| 91 |
+
window.kimiErrorManager.printErrorSummary();
|
| 92 |
+
}
|
| 93 |
+
console.groupEnd();
|
| 94 |
+
};
|
| 95 |
+
|
| 96 |
+
// Database diagnostics helper
|
| 97 |
+
window.kimiDiagnosDB = async () => {
|
| 98 |
+
console.group("🔍 Database Diagnostics");
|
| 99 |
+
try {
|
| 100 |
+
if (window.kimiDB) {
|
| 101 |
+
console.log("📊 Database version:", window.kimiDB.db.verno);
|
| 102 |
+
console.log("📋 Available tables:", Object.keys(window.kimiDB.db._dbSchema));
|
| 103 |
+
|
| 104 |
+
// Check memories table schema
|
| 105 |
+
const memoriesSchema = window.kimiDB.db._dbSchema.memories;
|
| 106 |
+
if (memoriesSchema) {
|
| 107 |
+
console.log("🧠 Memories schema:", memoriesSchema);
|
| 108 |
+
const hasCharacterIsActiveIndex = memoriesSchema.indexes?.some(
|
| 109 |
+
idx =>
|
| 110 |
+
idx.name === "[character+isActive]" ||
|
| 111 |
+
(idx.keyPath?.includes("character") && idx.keyPath?.includes("isActive"))
|
| 112 |
+
);
|
| 113 |
+
console.log("✅ [character+isActive] index:", hasCharacterIsActiveIndex ? "PRESENT" : "❌ MISSING");
|
| 114 |
+
|
| 115 |
+
if (!hasCharacterIsActiveIndex) {
|
| 116 |
+
console.warn(
|
| 117 |
+
"🚨 SOLUTION: Clear browser data (Application > Storage > Clear Site Data) to force schema upgrade"
|
| 118 |
+
);
|
| 119 |
+
}
|
| 120 |
+
}
|
| 121 |
+
} else {
|
| 122 |
+
console.warn("❌ Database not initialized yet");
|
| 123 |
+
}
|
| 124 |
+
} catch (error) {
|
| 125 |
+
console.error("Error during database diagnostics:", error);
|
| 126 |
+
}
|
| 127 |
+
console.groupEnd();
|
| 128 |
+
};
|
| 129 |
+
|
| 130 |
+
// Auto-initialize to production mode for performance
|
| 131 |
+
if (typeof window.KIMI_CONFIG !== "undefined") {
|
| 132 |
+
window.KimiDebugController.setProductionMode();
|
| 133 |
+
}
|
kimi-js/kimi-emotion-system.js
ADDED
|
@@ -0,0 +1,1060 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// ===== KIMI UNIFIED EMOTION SYSTEM =====
|
| 2 |
+
// Centralizes all emotion analysis, personality updates, and validation
|
| 3 |
+
|
| 4 |
+
class KimiEmotionSystem {
|
| 5 |
+
constructor(database = null) {
|
| 6 |
+
this.db = database;
|
| 7 |
+
this.negativeStreaks = {};
|
| 8 |
+
|
| 9 |
+
// Debouncing system for personality updates
|
| 10 |
+
this._personalityUpdateQueue = {};
|
| 11 |
+
this._personalityUpdateTimer = null;
|
| 12 |
+
this._personalityUpdateDelay = 300; // ms
|
| 13 |
+
|
| 14 |
+
// Unified emotion mappings
|
| 15 |
+
this.EMOTIONS = {
|
| 16 |
+
// Base emotions
|
| 17 |
+
POSITIVE: "positive",
|
| 18 |
+
NEGATIVE: "negative",
|
| 19 |
+
NEUTRAL: "neutral",
|
| 20 |
+
|
| 21 |
+
// Specific emotions
|
| 22 |
+
ROMANTIC: "romantic",
|
| 23 |
+
DANCING: "dancing",
|
| 24 |
+
LISTENING: "listening",
|
| 25 |
+
LAUGHING: "laughing",
|
| 26 |
+
SURPRISE: "surprise",
|
| 27 |
+
CONFIDENT: "confident",
|
| 28 |
+
SHY: "shy",
|
| 29 |
+
FLIRTATIOUS: "flirtatious",
|
| 30 |
+
KISS: "kiss",
|
| 31 |
+
GOODBYE: "goodbye",
|
| 32 |
+
|
| 33 |
+
// New emotions for new characters
|
| 34 |
+
ANDROID: "android",
|
| 35 |
+
SENSUAL: "sensual",
|
| 36 |
+
LOVE: "love"
|
| 37 |
+
};
|
| 38 |
+
|
| 39 |
+
// Unified video context mapping - CENTRALIZED SOURCE OF TRUTH
|
| 40 |
+
this.emotionToVideoCategory = {
|
| 41 |
+
// Base emotional states
|
| 42 |
+
positive: "speakingPositive",
|
| 43 |
+
negative: "speakingNegative",
|
| 44 |
+
neutral: "neutral",
|
| 45 |
+
|
| 46 |
+
// Special contexts (always take priority)
|
| 47 |
+
dancing: "dancing",
|
| 48 |
+
listening: "listening",
|
| 49 |
+
|
| 50 |
+
// Specific emotions mapped to appropriate categories
|
| 51 |
+
romantic: "speakingPositive",
|
| 52 |
+
laughing: "speakingPositive",
|
| 53 |
+
surprise: "speakingPositive",
|
| 54 |
+
confident: "speakingPositive",
|
| 55 |
+
flirtatious: "speakingPositive",
|
| 56 |
+
kiss: "speakingPositive",
|
| 57 |
+
|
| 58 |
+
// Neutral/subdued emotions
|
| 59 |
+
shy: "neutral",
|
| 60 |
+
goodbye: "neutral",
|
| 61 |
+
|
| 62 |
+
// New character specific emotions mapped to EXISTING categories only
|
| 63 |
+
android: "speakingPositive", // 2Blanche responses use existing speakingPositive videos
|
| 64 |
+
sensual: "speakingPositive", // Jasmine sensual mode uses existing speakingPositive videos
|
| 65 |
+
love: "speakingPositive", // Jasmine love mode uses existing speakingPositive videos
|
| 66 |
+
|
| 67 |
+
// Explicit context mappings (for compatibility)
|
| 68 |
+
speaking: "speakingPositive", // Generic speaking defaults to positive
|
| 69 |
+
speakingPositive: "speakingPositive",
|
| 70 |
+
speakingNegative: "speakingNegative"
|
| 71 |
+
};
|
| 72 |
+
|
| 73 |
+
// Emotion priority weights for conflict resolution
|
| 74 |
+
this.emotionPriorities = {
|
| 75 |
+
dancing: 10, // Maximum priority - immersive experience
|
| 76 |
+
kiss: 9, // Very high - intimate moment
|
| 77 |
+
romantic: 8, // High - emotional connection
|
| 78 |
+
listening: 7, // High - active interaction
|
| 79 |
+
android: 6, // Medium-high - character-specific context
|
| 80 |
+
sensual: 6, // Medium-high - character-specific context
|
| 81 |
+
love: 6, // Medium-high - character-specific context
|
| 82 |
+
flirtatious: 6, // Medium-high - playful interaction
|
| 83 |
+
laughing: 6, // Medium-high - positive expression
|
| 84 |
+
surprise: 5, // Medium - reaction
|
| 85 |
+
confident: 5, // Medium - personality expression
|
| 86 |
+
speaking: 4, // Medium-low - generic speaking context
|
| 87 |
+
positive: 4, // Medium-low - general positive
|
| 88 |
+
negative: 4, // Medium-low - general negative
|
| 89 |
+
neutral: 3, // Low - default state
|
| 90 |
+
shy: 3, // Low - subdued state
|
| 91 |
+
goodbye: 2, // Very low - transitional
|
| 92 |
+
speakingPositive: 4, // Medium-low - for consistency
|
| 93 |
+
speakingNegative: 4 // Medium-low - for consistency
|
| 94 |
+
};
|
| 95 |
+
|
| 96 |
+
// Context/emotion validation system for system integrity
|
| 97 |
+
this.validContexts = ["dancing", "listening", "speaking", "speakingPositive", "speakingNegative", "neutral"];
|
| 98 |
+
this.validEmotions = Object.values(this.EMOTIONS);
|
| 99 |
+
|
| 100 |
+
// Unified trait defaults - Balanced for progressive experience
|
| 101 |
+
this.TRAIT_DEFAULTS = {
|
| 102 |
+
affection: 55, // Baseline neutral affection
|
| 103 |
+
playfulness: 55, // Moderately playful baseline
|
| 104 |
+
intelligence: 70, // Competent baseline intellect
|
| 105 |
+
empathy: 75, // Warm & caring baseline
|
| 106 |
+
humor: 60, // Mild sense of humor baseline
|
| 107 |
+
romance: 50 // Neutral romance baseline (earned over time)
|
| 108 |
+
};
|
| 109 |
+
|
| 110 |
+
// Central emotion -> trait base deltas (pre global multipliers & gainCfg scaling)
|
| 111 |
+
// Positive numbers increase trait, negative decrease.
|
| 112 |
+
// Keep values small; final effect passes through adjustUp/adjustDown and global multipliers.
|
| 113 |
+
this.EMOTION_TRAIT_EFFECTS = {
|
| 114 |
+
positive: { affection: 0.45, empathy: 0.2, playfulness: 0.25, humor: 0.25 },
|
| 115 |
+
negative: { affection: -0.7, empathy: 0.3 },
|
| 116 |
+
romantic: { romance: 0.7, affection: 0.55, empathy: 0.15 },
|
| 117 |
+
flirtatious: { romance: 0.55, playfulness: 0.45, affection: 0.25 },
|
| 118 |
+
laughing: { humor: 0.85, playfulness: 0.5, affection: 0.25 },
|
| 119 |
+
dancing: { playfulness: 1.1, affection: 0.45 },
|
| 120 |
+
surprise: { intelligence: 0.12, empathy: 0.12 },
|
| 121 |
+
shy: { romance: -0.3, affection: -0.12 },
|
| 122 |
+
confident: { intelligence: 0.15, affection: 0.55 },
|
| 123 |
+
listening: { empathy: 0.6, intelligence: 0.25 },
|
| 124 |
+
kiss: { romance: 0.85, affection: 0.7 },
|
| 125 |
+
goodbye: { affection: -0.15, empathy: 0.1 },
|
| 126 |
+
|
| 127 |
+
// New character-specific emotions
|
| 128 |
+
android: { intelligence: 0.8, affection: 0.1, empathy: -0.2 }, // High intelligence, slow emotional progress
|
| 129 |
+
sensual: { intelligence: 0.6, playfulness: 0.4, romance: 0.3 }, // Brilliance with charm
|
| 130 |
+
love: { playfulness: 0.7, intelligence: 0.3, affection: 0.2 } // Sensual energy
|
| 131 |
+
};
|
| 132 |
+
|
| 133 |
+
// Trait keyword scaling model for conversation analysis (per-message delta shaping)
|
| 134 |
+
this.TRAIT_KEYWORD_MODEL = {
|
| 135 |
+
affection: { posFactor: 0.5, negFactor: 0.65, streakPenaltyAfter: 3, maxStep: 2 },
|
| 136 |
+
romance: { posFactor: 0.55, negFactor: 0.75, streakPenaltyAfter: 2, maxStep: 1.8 },
|
| 137 |
+
empathy: { posFactor: 0.4, negFactor: 0.5, streakPenaltyAfter: 3, maxStep: 1.5 },
|
| 138 |
+
playfulness: { posFactor: 0.45, negFactor: 0.4, streakPenaltyAfter: 4, maxStep: 1.4 },
|
| 139 |
+
humor: { posFactor: 0.55, negFactor: 0.45, streakPenaltyAfter: 4, maxStep: 1.6 },
|
| 140 |
+
intelligence: { posFactor: 0.35, negFactor: 0.55, streakPenaltyAfter: 2, maxStep: 1.2 }
|
| 141 |
+
};
|
| 142 |
+
}
|
| 143 |
+
|
| 144 |
+
// ===== DEBOUNCED PERSONALITY UPDATE SYSTEM =====
|
| 145 |
+
_debouncedPersonalityUpdate(updates, character) {
|
| 146 |
+
// Merge with existing queued updates for this character
|
| 147 |
+
if (!this._personalityUpdateQueue[character]) {
|
| 148 |
+
this._personalityUpdateQueue[character] = {};
|
| 149 |
+
}
|
| 150 |
+
Object.assign(this._personalityUpdateQueue[character], updates);
|
| 151 |
+
|
| 152 |
+
// Clear existing timer and set new one
|
| 153 |
+
if (this._personalityUpdateTimer) {
|
| 154 |
+
clearTimeout(this._personalityUpdateTimer);
|
| 155 |
+
}
|
| 156 |
+
|
| 157 |
+
this._personalityUpdateTimer = setTimeout(async () => {
|
| 158 |
+
try {
|
| 159 |
+
const allUpdates = { ...this._personalityUpdateQueue };
|
| 160 |
+
this._personalityUpdateQueue = {};
|
| 161 |
+
this._personalityUpdateTimer = null;
|
| 162 |
+
|
| 163 |
+
// Process all queued updates
|
| 164 |
+
for (const [char, traits] of Object.entries(allUpdates)) {
|
| 165 |
+
if (Object.keys(traits).length > 0) {
|
| 166 |
+
await this.db.setPersonalityBatch(traits, char);
|
| 167 |
+
|
| 168 |
+
// Emit unified personality update event
|
| 169 |
+
if (typeof window !== "undefined" && window.dispatchEvent) {
|
| 170 |
+
window.dispatchEvent(
|
| 171 |
+
new CustomEvent("personality:updated", {
|
| 172 |
+
detail: { character: char, traits: traits }
|
| 173 |
+
})
|
| 174 |
+
);
|
| 175 |
+
}
|
| 176 |
+
}
|
| 177 |
+
}
|
| 178 |
+
} catch (error) {
|
| 179 |
+
console.error("Error in debounced personality update:", error);
|
| 180 |
+
}
|
| 181 |
+
}, this._personalityUpdateDelay);
|
| 182 |
+
}
|
| 183 |
+
|
| 184 |
+
// ===== CENTRALIZED VALIDATION SYSTEM =====
|
| 185 |
+
validateContext(context) {
|
| 186 |
+
if (!context || typeof context !== "string") return "neutral";
|
| 187 |
+
const normalized = context.toLowerCase().trim();
|
| 188 |
+
|
| 189 |
+
// Check if it's a valid context
|
| 190 |
+
if (this.validContexts.includes(normalized)) return normalized;
|
| 191 |
+
|
| 192 |
+
// Check if it's a valid emotion that can be mapped to context
|
| 193 |
+
if (this.emotionToVideoCategory[normalized]) return normalized;
|
| 194 |
+
|
| 195 |
+
return "neutral"; // Safe fallback
|
| 196 |
+
}
|
| 197 |
+
|
| 198 |
+
validateEmotion(emotion) {
|
| 199 |
+
if (!emotion || typeof emotion !== "string") return "neutral";
|
| 200 |
+
const normalized = emotion.toLowerCase().trim();
|
| 201 |
+
|
| 202 |
+
// Check if it's a valid emotion
|
| 203 |
+
if (this.validEmotions.includes(normalized)) return normalized;
|
| 204 |
+
|
| 205 |
+
// Check common aliases
|
| 206 |
+
// NOTE (Clarity Patch - Option 1):
|
| 207 |
+
// The following alias map intentionally routes the generic context word "speaking"
|
| 208 |
+
// (and its positive / negative variants) to the polarity emotions "positive" / "negative".
|
| 209 |
+
// Later, when a video category is needed, the system remaps:
|
| 210 |
+
// positive -> speakingPositive (via emotionToVideoCategory)
|
| 211 |
+
// negative -> speakingNegative (via emotionToVideoCategory)
|
| 212 |
+
// Rationale:
|
| 213 |
+
// 1. Keep EMOTIONS focused on high-level semantic emotions (positive/negative) instead of
|
| 214 |
+
// duplicating technical rendering states (speakingPositive / speakingNegative).
|
| 215 |
+
// 2. Preserve backward compatibility with older code that emitted "speaking" as an emotion.
|
| 216 |
+
// 3. Reduce surface area of the emotion validation list while still achieving correct video output.
|
| 217 |
+
// This can look like a double hop (speaking -> positive -> speakingPositive) but no information is lost.
|
| 218 |
+
// If later a direct mapping is desired, Option 2 would be to add SPEAKING_POSITIVE / SPEAKING_NEGATIVE
|
| 219 |
+
// into EMOTIONS and point aliases directly there. For now we keep the lean design.
|
| 220 |
+
const aliases = {
|
| 221 |
+
happy: "positive",
|
| 222 |
+
sad: "negative",
|
| 223 |
+
mad: "negative",
|
| 224 |
+
angry: "negative",
|
| 225 |
+
excited: "positive",
|
| 226 |
+
calm: "neutral",
|
| 227 |
+
romance: "romantic",
|
| 228 |
+
laugh: "laughing",
|
| 229 |
+
dance: "dancing",
|
| 230 |
+
// Speaking contexts as emotion aliases
|
| 231 |
+
speaking: "positive", // Generic speaking defaults to positive
|
| 232 |
+
speakingpositive: "positive",
|
| 233 |
+
speakingnegative: "negative",
|
| 234 |
+
// New character-specific aliases
|
| 235 |
+
robot: "android",
|
| 236 |
+
robotic: "android",
|
| 237 |
+
military: "android",
|
| 238 |
+
tactical: "android",
|
| 239 |
+
sensual: "sensual",
|
| 240 |
+
pleasure: "sensual",
|
| 241 |
+
emotional: "sensual",
|
| 242 |
+
intimate: "sensual",
|
| 243 |
+
tenderness: "love",
|
| 244 |
+
intimacy: "love",
|
| 245 |
+
position: "love",
|
| 246 |
+
love: "love"
|
| 247 |
+
};
|
| 248 |
+
|
| 249 |
+
if (aliases[normalized]) return aliases[normalized];
|
| 250 |
+
|
| 251 |
+
return "neutral"; // Safe fallback
|
| 252 |
+
}
|
| 253 |
+
|
| 254 |
+
validateVideoCategory(category) {
|
| 255 |
+
const validCategories = ["dancing", "listening", "speakingPositive", "speakingNegative", "neutral"];
|
| 256 |
+
if (!category || typeof category !== "string") return "neutral";
|
| 257 |
+
|
| 258 |
+
const normalized = category.toLowerCase().trim();
|
| 259 |
+
return validCategories.includes(normalized) ? normalized : "neutral";
|
| 260 |
+
}
|
| 261 |
+
|
| 262 |
+
// Enhanced emotion analysis with validation
|
| 263 |
+
analyzeEmotionValidated(text, lang = "auto") {
|
| 264 |
+
const rawEmotion = this.analyzeEmotion(text, lang);
|
| 265 |
+
return this.validateEmotion(rawEmotion);
|
| 266 |
+
}
|
| 267 |
+
|
| 268 |
+
// ===== UTILITY METHODS FOR SYSTEM INTEGRATION =====
|
| 269 |
+
// Centralized method to get video category for any emotion/context combination
|
| 270 |
+
getVideoCategory(emotionOrContext, traits = null) {
|
| 271 |
+
// Handle the case where we get both context and emotion (e.g., from determineCategory calls)
|
| 272 |
+
// Priority: Specific contexts > Specific emotions > Generic fallbacks
|
| 273 |
+
|
| 274 |
+
// Try context validation first for immediate context matches
|
| 275 |
+
let validated = this.validateContext(emotionOrContext);
|
| 276 |
+
if (validated !== "neutral" || emotionOrContext === "neutral") {
|
| 277 |
+
// Valid context found or explicitly neutral
|
| 278 |
+
const category = this.emotionToVideoCategory[validated] || "neutral";
|
| 279 |
+
return this.validateVideoCategory(category);
|
| 280 |
+
}
|
| 281 |
+
|
| 282 |
+
// If no valid context, try as emotion
|
| 283 |
+
validated = this.validateEmotion(emotionOrContext);
|
| 284 |
+
const category = this.emotionToVideoCategory[validated] || "neutral";
|
| 285 |
+
return this.validateVideoCategory(category);
|
| 286 |
+
} // Get priority weight for any emotion/context
|
| 287 |
+
getPriorityWeight(emotionOrContext) {
|
| 288 |
+
// Try context validation first, then emotion validation
|
| 289 |
+
let validated = this.validateContext(emotionOrContext);
|
| 290 |
+
if (validated === "neutral" && emotionOrContext !== "neutral") {
|
| 291 |
+
// If context validation gave neutral but input wasn't neutral, try as emotion
|
| 292 |
+
validated = this.validateEmotion(emotionOrContext);
|
| 293 |
+
}
|
| 294 |
+
|
| 295 |
+
return this.emotionPriorities[validated] || 3; // Default medium-low priority
|
| 296 |
+
}
|
| 297 |
+
|
| 298 |
+
// Check if an emotion/context should override current state
|
| 299 |
+
shouldOverride(newEmotion, currentEmotion, currentContext = null) {
|
| 300 |
+
const newPriority = this.getPriorityWeight(newEmotion);
|
| 301 |
+
const currentPriority = Math.max(this.getPriorityWeight(currentEmotion), this.getPriorityWeight(currentContext));
|
| 302 |
+
|
| 303 |
+
return newPriority > currentPriority;
|
| 304 |
+
}
|
| 305 |
+
|
| 306 |
+
// Utility to normalize and validate a complete emotion/context request
|
| 307 |
+
normalizeEmotionRequest(context, emotion, traits = null) {
|
| 308 |
+
return {
|
| 309 |
+
context: this.validateContext(context),
|
| 310 |
+
emotion: this.validateEmotion(emotion),
|
| 311 |
+
category: this.getVideoCategory(emotion || context, traits),
|
| 312 |
+
priority: this.getPriorityWeight(emotion || context)
|
| 313 |
+
};
|
| 314 |
+
}
|
| 315 |
+
|
| 316 |
+
// ===== UNIFIED EMOTION ANALYSIS =====
|
| 317 |
+
analyzeEmotion(text, lang = "auto") {
|
| 318 |
+
if (!text || typeof text !== "string") return this.EMOTIONS.NEUTRAL;
|
| 319 |
+
const lowerText = this.normalizeText(text);
|
| 320 |
+
|
| 321 |
+
// Auto-detect language
|
| 322 |
+
let detectedLang = this._detectLanguage(text, lang);
|
| 323 |
+
|
| 324 |
+
// Get language-specific polarity keywords via centralized helpers
|
| 325 |
+
const positiveWords = (window.getPolarityWords && window.getPolarityWords("positive", detectedLang)) || ["happy", "good", "great", "love"];
|
| 326 |
+
const negativeWords = (window.getPolarityWords && window.getPolarityWords("negative", detectedLang)) || ["sad", "bad", "angry", "hate"];
|
| 327 |
+
|
| 328 |
+
const emotionKeywords = window.KIMI_CONTEXT_KEYWORDS?.[detectedLang] || window.KIMI_CONTEXT_KEYWORDS?.en || {};
|
| 329 |
+
|
| 330 |
+
// Hostile override (immediate negative if hostile keywords present via centralized helper)
|
| 331 |
+
try {
|
| 332 |
+
if (window.isHostileText && window.isHostileText(text, detectedLang)) {
|
| 333 |
+
return this.EMOTIONS.NEGATIVE;
|
| 334 |
+
}
|
| 335 |
+
// Fallback: also scan english if language auto-detected incorrectly
|
| 336 |
+
if (detectedLang !== "en" && window.isHostileText && window.isHostileText(text, "en")) {
|
| 337 |
+
return this.EMOTIONS.NEGATIVE;
|
| 338 |
+
}
|
| 339 |
+
} catch {}
|
| 340 |
+
|
| 341 |
+
// Priority order for emotion detection - reordered for better logic
|
| 342 |
+
const emotionChecks = [
|
| 343 |
+
// High-impact emotions first
|
| 344 |
+
{ emotion: this.EMOTIONS.KISS, keywords: emotionKeywords.kiss || ["kiss", "embrace"] },
|
| 345 |
+
{ emotion: this.EMOTIONS.DANCING, keywords: emotionKeywords.dancing || ["dance", "dancing"] },
|
| 346 |
+
{ emotion: this.EMOTIONS.ROMANTIC, keywords: emotionKeywords.romantic || ["love", "romantic"] },
|
| 347 |
+
{ emotion: this.EMOTIONS.FLIRTATIOUS, keywords: emotionKeywords.flirtatious || ["flirt", "tease"] },
|
| 348 |
+
{ emotion: this.EMOTIONS.LAUGHING, keywords: emotionKeywords.laughing || ["laugh", "funny"] },
|
| 349 |
+
{ emotion: this.EMOTIONS.SURPRISE, keywords: emotionKeywords.surprise || ["wow", "surprise"] },
|
| 350 |
+
{ emotion: this.EMOTIONS.CONFIDENT, keywords: emotionKeywords.confident || ["confident", "strong"] },
|
| 351 |
+
{ emotion: this.EMOTIONS.SHY, keywords: emotionKeywords.shy || ["shy", "embarrassed"] },
|
| 352 |
+
{ emotion: this.EMOTIONS.GOODBYE, keywords: emotionKeywords.goodbye || ["goodbye", "bye"] },
|
| 353 |
+
// Listening intent (lower priority to not mask other emotions)
|
| 354 |
+
{
|
| 355 |
+
emotion: this.EMOTIONS.LISTENING,
|
| 356 |
+
keywords: emotionKeywords.listening || ["listen carefully", "I'm listening", "listening to you", "hear me out", "pay attention"]
|
| 357 |
+
}
|
| 358 |
+
];
|
| 359 |
+
|
| 360 |
+
// Check for specific emotions first, applying sensitivity weights per language
|
| 361 |
+
const sensitivity = (window.KIMI_EMOTION_SENSITIVITY && (window.KIMI_EMOTION_SENSITIVITY[detectedLang] || window.KIMI_EMOTION_SENSITIVITY.default)) || {
|
| 362 |
+
listening: 1,
|
| 363 |
+
dancing: 1,
|
| 364 |
+
romantic: 1,
|
| 365 |
+
laughing: 1,
|
| 366 |
+
surprise: 1,
|
| 367 |
+
confident: 1,
|
| 368 |
+
shy: 1,
|
| 369 |
+
flirtatious: 1,
|
| 370 |
+
kiss: 1,
|
| 371 |
+
goodbye: 1,
|
| 372 |
+
positive: 1,
|
| 373 |
+
negative: 1
|
| 374 |
+
};
|
| 375 |
+
|
| 376 |
+
// Normalize keyword lists to handle accents/contractions
|
| 377 |
+
const normalizeList = arr => (Array.isArray(arr) ? arr.map(x => this.normalizeText(String(x))).filter(Boolean) : []);
|
| 378 |
+
const normalizedPositiveWords = normalizeList(positiveWords);
|
| 379 |
+
const normalizedNegativeWords = normalizeList(negativeWords);
|
| 380 |
+
const normalizedChecks = emotionChecks.map(ch => ({
|
| 381 |
+
emotion: ch.emotion,
|
| 382 |
+
keywords: normalizeList(ch.keywords)
|
| 383 |
+
}));
|
| 384 |
+
|
| 385 |
+
let bestEmotion = null;
|
| 386 |
+
let bestScore = 0;
|
| 387 |
+
for (const check of normalizedChecks) {
|
| 388 |
+
const hits = check.keywords.reduce((acc, word) => acc + (this.countTokenMatches(lowerText, String(word)) ? 1 : 0), 0);
|
| 389 |
+
if (hits > 0) {
|
| 390 |
+
const key = check.emotion;
|
| 391 |
+
const weight = sensitivity[key] != null ? sensitivity[key] : 1;
|
| 392 |
+
const score = hits * weight;
|
| 393 |
+
if (score > bestScore) {
|
| 394 |
+
bestScore = score;
|
| 395 |
+
bestEmotion = check.emotion;
|
| 396 |
+
}
|
| 397 |
+
}
|
| 398 |
+
}
|
| 399 |
+
if (bestEmotion) return bestEmotion;
|
| 400 |
+
|
| 401 |
+
// Fall back to positive/negative analysis (use normalized lists)
|
| 402 |
+
const hasPositive = normalizedPositiveWords.some(word => this.countTokenMatches(lowerText, String(word)) > 0);
|
| 403 |
+
const hasNegative = normalizedNegativeWords.some(word => this.countTokenMatches(lowerText, String(word)) > 0);
|
| 404 |
+
|
| 405 |
+
// If some positive keywords are present but negated, treat as negative
|
| 406 |
+
const negatedPositive = normalizedPositiveWords.some(word => this.isTokenNegated(lowerText, String(word)));
|
| 407 |
+
|
| 408 |
+
if (hasPositive && !hasNegative) {
|
| 409 |
+
if (negatedPositive) {
|
| 410 |
+
return this.EMOTIONS.NEGATIVE;
|
| 411 |
+
}
|
| 412 |
+
// Apply sensitivity for base polarity
|
| 413 |
+
if ((sensitivity.positive || 1) >= (sensitivity.negative || 1)) return this.EMOTIONS.POSITIVE;
|
| 414 |
+
// If negative is favored, still fall back to positive since no negative hit
|
| 415 |
+
return this.EMOTIONS.POSITIVE;
|
| 416 |
+
}
|
| 417 |
+
if (hasNegative && !hasPositive) {
|
| 418 |
+
if ((sensitivity.negative || 1) >= (sensitivity.positive || 1)) return this.EMOTIONS.NEGATIVE;
|
| 419 |
+
return this.EMOTIONS.NEGATIVE;
|
| 420 |
+
}
|
| 421 |
+
return this.EMOTIONS.NEUTRAL;
|
| 422 |
+
}
|
| 423 |
+
|
| 424 |
+
// ===== UNIFIED PERSONALITY SYSTEM =====
|
| 425 |
+
async updatePersonalityFromEmotion(emotion, text, character = null) {
|
| 426 |
+
if (!this.db) {
|
| 427 |
+
console.warn("Database not available for personality updates");
|
| 428 |
+
return;
|
| 429 |
+
}
|
| 430 |
+
|
| 431 |
+
const selectedCharacter = character || (await this.db.getSelectedCharacter());
|
| 432 |
+
const traits = window.getCharacterTraits
|
| 433 |
+
? await window.getCharacterTraits(selectedCharacter)
|
| 434 |
+
: await this.db.getAllPersonalityTraits(selectedCharacter);
|
| 435 |
+
|
| 436 |
+
const safe = (v, def) => (typeof v === "number" && isFinite(v) ? v : def);
|
| 437 |
+
let affection = safe(traits?.affection, this.TRAIT_DEFAULTS.affection);
|
| 438 |
+
let romance = safe(traits?.romance, this.TRAIT_DEFAULTS.romance);
|
| 439 |
+
let empathy = safe(traits?.empathy, this.TRAIT_DEFAULTS.empathy);
|
| 440 |
+
let playfulness = safe(traits?.playfulness, this.TRAIT_DEFAULTS.playfulness);
|
| 441 |
+
let humor = safe(traits?.humor, this.TRAIT_DEFAULTS.humor);
|
| 442 |
+
let intelligence = safe(traits?.intelligence, this.TRAIT_DEFAULTS.intelligence);
|
| 443 |
+
|
| 444 |
+
// Unified adjustment functions - More balanced progression for better user experience
|
| 445 |
+
const adjustUp = (val, amount) => {
|
| 446 |
+
// Gradual slowdown only at very high levels to allow natural progression
|
| 447 |
+
if (val >= 95) return val + amount * 0.2; // Slow near max to preserve challenge
|
| 448 |
+
if (val >= 88) return val + amount * 0.5; // Moderate slowdown at very high levels
|
| 449 |
+
if (val >= 80) return val + amount * 0.7; // Slight slowdown at high levels
|
| 450 |
+
if (val >= 60) return val + amount * 0.9; // Nearly normal progression in mid-high range
|
| 451 |
+
return val + amount; // Normal progression below 60%
|
| 452 |
+
};
|
| 453 |
+
|
| 454 |
+
const adjustDown = (val, amount) => {
|
| 455 |
+
// Faster decline at higher values - easier to lose than to gain
|
| 456 |
+
if (val >= 80) return val - amount * 1.2; // Faster loss at high levels
|
| 457 |
+
if (val >= 60) return val - amount; // Normal loss at medium levels
|
| 458 |
+
if (val >= 40) return val - amount * 0.8; // Slower loss at low-medium levels
|
| 459 |
+
if (val <= 20) return val - amount * 0.4; // Very slow loss at low levels
|
| 460 |
+
return val - amount * 0.6; // Moderate loss between 20-40
|
| 461 |
+
};
|
| 462 |
+
|
| 463 |
+
// Unified emotion-based adjustments - More balanced and realistic progression
|
| 464 |
+
const gainCfg = window.KIMI_TRAIT_ADJUSTMENT || {
|
| 465 |
+
globalGain: 1,
|
| 466 |
+
globalLoss: 1,
|
| 467 |
+
emotionGain: {},
|
| 468 |
+
traitGain: {},
|
| 469 |
+
traitLoss: {}
|
| 470 |
+
};
|
| 471 |
+
const emoGain = emotion && gainCfg.emotionGain ? gainCfg.emotionGain[emotion] || 1 : 1;
|
| 472 |
+
const GGAIN = (gainCfg.globalGain || 1) * emoGain;
|
| 473 |
+
const GLOSS = gainCfg.globalLoss || 1;
|
| 474 |
+
|
| 475 |
+
// Helpers to apply trait-specific scaling
|
| 476 |
+
const scaleGain = (traitName, baseDelta) => {
|
| 477 |
+
const t = gainCfg.traitGain && (gainCfg.traitGain[traitName] || 1);
|
| 478 |
+
return baseDelta * GGAIN * t;
|
| 479 |
+
};
|
| 480 |
+
const scaleLoss = (traitName, baseDelta) => {
|
| 481 |
+
const t = gainCfg.traitLoss && (gainCfg.traitLoss[traitName] || 1);
|
| 482 |
+
return baseDelta * GLOSS * t;
|
| 483 |
+
};
|
| 484 |
+
|
| 485 |
+
// Apply emotion deltas from centralized map (if defined)
|
| 486 |
+
const map = this.EMOTION_TRAIT_EFFECTS?.[emotion];
|
| 487 |
+
if (map) {
|
| 488 |
+
for (const [traitName, baseDelta] of Object.entries(map)) {
|
| 489 |
+
const delta = baseDelta; // base delta -> will be scaled below
|
| 490 |
+
if (delta === 0) continue;
|
| 491 |
+
switch (traitName) {
|
| 492 |
+
case "affection":
|
| 493 |
+
affection =
|
| 494 |
+
delta > 0
|
| 495 |
+
? Math.min(100, adjustUp(affection, scaleGain("affection", delta)))
|
| 496 |
+
: Math.max(0, adjustDown(affection, scaleLoss("affection", Math.abs(delta))));
|
| 497 |
+
break;
|
| 498 |
+
case "romance":
|
| 499 |
+
romance =
|
| 500 |
+
delta > 0
|
| 501 |
+
? Math.min(100, adjustUp(romance, scaleGain("romance", delta)))
|
| 502 |
+
: Math.max(0, adjustDown(romance, scaleLoss("romance", Math.abs(delta))));
|
| 503 |
+
break;
|
| 504 |
+
case "empathy":
|
| 505 |
+
empathy =
|
| 506 |
+
delta > 0
|
| 507 |
+
? Math.min(100, adjustUp(empathy, scaleGain("empathy", delta)))
|
| 508 |
+
: Math.max(0, adjustDown(empathy, scaleLoss("empathy", Math.abs(delta))));
|
| 509 |
+
break;
|
| 510 |
+
case "playfulness":
|
| 511 |
+
playfulness =
|
| 512 |
+
delta > 0
|
| 513 |
+
? Math.min(100, adjustUp(playfulness, scaleGain("playfulness", delta)))
|
| 514 |
+
: Math.max(0, adjustDown(playfulness, scaleLoss("playfulness", Math.abs(delta))));
|
| 515 |
+
break;
|
| 516 |
+
case "humor":
|
| 517 |
+
humor =
|
| 518 |
+
delta > 0
|
| 519 |
+
? Math.min(100, adjustUp(humor, scaleGain("humor", delta)))
|
| 520 |
+
: Math.max(0, adjustDown(humor, scaleLoss("humor", Math.abs(delta))));
|
| 521 |
+
break;
|
| 522 |
+
case "intelligence":
|
| 523 |
+
intelligence =
|
| 524 |
+
delta > 0
|
| 525 |
+
? Math.min(100, adjustUp(intelligence, scaleGain("intelligence", delta)))
|
| 526 |
+
: Math.max(0, adjustDown(intelligence, scaleLoss("intelligence", Math.abs(delta))));
|
| 527 |
+
break;
|
| 528 |
+
}
|
| 529 |
+
}
|
| 530 |
+
}
|
| 531 |
+
|
| 532 |
+
// Cross-trait interactions - traits influence each other for more realistic personality development
|
| 533 |
+
// High empathy should boost affection over time
|
| 534 |
+
if (empathy >= 75 && affection < empathy - 5) {
|
| 535 |
+
affection = Math.min(100, adjustUp(affection, scaleGain("affection", 0.1)));
|
| 536 |
+
}
|
| 537 |
+
|
| 538 |
+
// High intelligence should slightly boost empathy (understanding others)
|
| 539 |
+
if (intelligence >= 80 && empathy < intelligence - 10) {
|
| 540 |
+
empathy = Math.min(100, adjustUp(empathy, scaleGain("empathy", 0.05)));
|
| 541 |
+
}
|
| 542 |
+
|
| 543 |
+
// Humor and playfulness should reinforce each other
|
| 544 |
+
if (humor >= 70 && playfulness < humor - 10) {
|
| 545 |
+
playfulness = Math.min(100, adjustUp(playfulness, scaleGain("playfulness", 0.05)));
|
| 546 |
+
}
|
| 547 |
+
if (playfulness >= 70 && humor < playfulness - 10) {
|
| 548 |
+
humor = Math.min(100, adjustUp(humor, scaleGain("humor", 0.05)));
|
| 549 |
+
}
|
| 550 |
+
|
| 551 |
+
// Content-based adjustments (unified)
|
| 552 |
+
await this._analyzeTextContent(
|
| 553 |
+
text,
|
| 554 |
+
traits => {
|
| 555 |
+
if (typeof traits.romance !== "undefined") romance = traits.romance;
|
| 556 |
+
if (typeof traits.affection !== "undefined") affection = traits.affection;
|
| 557 |
+
if (typeof traits.humor !== "undefined") humor = traits.humor;
|
| 558 |
+
if (typeof traits.playfulness !== "undefined") playfulness = traits.playfulness;
|
| 559 |
+
},
|
| 560 |
+
adjustUp
|
| 561 |
+
);
|
| 562 |
+
|
| 563 |
+
// Cross-trait modifiers (applied after primary emotion & content changes)
|
| 564 |
+
({ affection, romance, empathy, playfulness, humor, intelligence } = this._applyCrossTraitModifiers({
|
| 565 |
+
affection,
|
| 566 |
+
romance,
|
| 567 |
+
empathy,
|
| 568 |
+
playfulness,
|
| 569 |
+
humor,
|
| 570 |
+
intelligence,
|
| 571 |
+
adjustUp,
|
| 572 |
+
adjustDown,
|
| 573 |
+
scaleGain,
|
| 574 |
+
scaleLoss
|
| 575 |
+
}));
|
| 576 |
+
|
| 577 |
+
// Preserve fractional progress to allow gradual visible changes
|
| 578 |
+
const to2 = v => Number(Number(v).toFixed(2));
|
| 579 |
+
const clamp = v => Math.max(0, Math.min(100, v));
|
| 580 |
+
const updatedTraits = {
|
| 581 |
+
affection: to2(clamp(affection)),
|
| 582 |
+
romance: to2(clamp(romance)),
|
| 583 |
+
empathy: to2(clamp(empathy)),
|
| 584 |
+
playfulness: to2(clamp(playfulness)),
|
| 585 |
+
humor: to2(clamp(humor)),
|
| 586 |
+
intelligence: to2(clamp(intelligence))
|
| 587 |
+
};
|
| 588 |
+
|
| 589 |
+
// Prepare persistence with smoothing / threshold to avoid tiny writes
|
| 590 |
+
const toPersist = {};
|
| 591 |
+
for (const [trait, candValue] of Object.entries(updatedTraits)) {
|
| 592 |
+
const current = typeof traits?.[trait] === "number" ? traits[trait] : this.TRAIT_DEFAULTS[trait];
|
| 593 |
+
const prep = this._preparePersistTrait(trait, current, candValue, selectedCharacter);
|
| 594 |
+
if (prep.shouldPersist) toPersist[trait] = prep.value;
|
| 595 |
+
}
|
| 596 |
+
|
| 597 |
+
// Use debounced update instead of immediate DB write
|
| 598 |
+
if (Object.keys(toPersist).length > 0) {
|
| 599 |
+
this._debouncedPersonalityUpdate(toPersist, selectedCharacter);
|
| 600 |
+
}
|
| 601 |
+
|
| 602 |
+
return updatedTraits;
|
| 603 |
+
}
|
| 604 |
+
|
| 605 |
+
// Apply cross-trait synergy & balancing rules.
|
| 606 |
+
_applyCrossTraitModifiers(ctx) {
|
| 607 |
+
let { affection, romance, empathy, playfulness, humor, intelligence, adjustUp, adjustDown, scaleGain } = ctx;
|
| 608 |
+
// High empathy soft-boost affection if still lagging
|
| 609 |
+
if (empathy >= 80 && affection < empathy - 8) {
|
| 610 |
+
affection = Math.min(100, adjustUp(affection, scaleGain("affection", 0.08)));
|
| 611 |
+
}
|
| 612 |
+
// High romance amplifies affection gains subtlely
|
| 613 |
+
if (romance >= 80 && affection < romance - 5) {
|
| 614 |
+
affection = Math.min(100, adjustUp(affection, scaleGain("affection", 0.06)));
|
| 615 |
+
}
|
| 616 |
+
// High affection but lower romance triggers slight romance catch-up
|
| 617 |
+
if (affection >= 90 && romance < 70) {
|
| 618 |
+
romance = Math.min(100, adjustUp(romance, scaleGain("romance", 0.05)));
|
| 619 |
+
}
|
| 620 |
+
// Intelligence supports empathy & humor small growth
|
| 621 |
+
if (intelligence >= 85) {
|
| 622 |
+
if (empathy < intelligence - 12) {
|
| 623 |
+
empathy = Math.min(100, adjustUp(empathy, scaleGain("empathy", 0.04)));
|
| 624 |
+
}
|
| 625 |
+
if (humor < 75) {
|
| 626 |
+
humor = Math.min(100, adjustUp(humor, scaleGain("humor", 0.04)));
|
| 627 |
+
}
|
| 628 |
+
}
|
| 629 |
+
// Humor/playfulness mutual reinforcement (retain existing logic but guarded)
|
| 630 |
+
if (humor >= 70 && playfulness < humor - 10) {
|
| 631 |
+
playfulness = Math.min(100, adjustUp(playfulness, scaleGain("playfulness", 0.05)));
|
| 632 |
+
}
|
| 633 |
+
if (playfulness >= 70 && humor < playfulness - 10) {
|
| 634 |
+
humor = Math.min(100, adjustUp(humor, scaleGain("humor", 0.05)));
|
| 635 |
+
}
|
| 636 |
+
return { affection, romance, empathy, playfulness, humor, intelligence };
|
| 637 |
+
}
|
| 638 |
+
|
| 639 |
+
// ===== UNIFIED LLM PERSONALITY ANALYSIS =====
|
| 640 |
+
async updatePersonalityFromConversation(userMessage, kimiResponse, character = null) {
|
| 641 |
+
if (!this.db) return;
|
| 642 |
+
const lowerUser = this.normalizeText(userMessage || "");
|
| 643 |
+
const lowerKimi = this.normalizeText(kimiResponse || "");
|
| 644 |
+
const traits = (window.getCharacterTraits ? await window.getCharacterTraits(character) : await this.db.getAllPersonalityTraits(character)) || {};
|
| 645 |
+
const selectedLanguage = await this.db.getPreference("selectedLanguage", "en");
|
| 646 |
+
|
| 647 |
+
// Use unified keyword system
|
| 648 |
+
const getPersonalityWords = (trait, type) => {
|
| 649 |
+
if (window.KIMI_PERSONALITY_KEYWORDS && window.KIMI_PERSONALITY_KEYWORDS[selectedLanguage]) {
|
| 650 |
+
return window.KIMI_PERSONALITY_KEYWORDS[selectedLanguage][trait]?.[type] || [];
|
| 651 |
+
}
|
| 652 |
+
return this._getFallbackKeywords(trait, type);
|
| 653 |
+
};
|
| 654 |
+
|
| 655 |
+
const pendingUpdates = {};
|
| 656 |
+
for (const trait of ["humor", "intelligence", "romance", "affection", "playfulness", "empathy"]) {
|
| 657 |
+
const posWords = getPersonalityWords(trait, "positive");
|
| 658 |
+
const negWords = getPersonalityWords(trait, "negative");
|
| 659 |
+
let currentVal = typeof traits[trait] === "number" && isFinite(traits[trait]) ? traits[trait] : this.TRAIT_DEFAULTS[trait];
|
| 660 |
+
const model = this.TRAIT_KEYWORD_MODEL[trait];
|
| 661 |
+
const posFactor = model.posFactor;
|
| 662 |
+
const negFactor = model.negFactor;
|
| 663 |
+
const maxStep = model.maxStep;
|
| 664 |
+
const streakLimit = model.streakPenaltyAfter;
|
| 665 |
+
|
| 666 |
+
let posScore = 0;
|
| 667 |
+
let negScore = 0;
|
| 668 |
+
for (const w of posWords) {
|
| 669 |
+
posScore += this.countTokenMatches(lowerUser, String(w)) * 1.0;
|
| 670 |
+
posScore += this.countTokenMatches(lowerKimi, String(w)) * 0.5;
|
| 671 |
+
}
|
| 672 |
+
for (const w of negWords) {
|
| 673 |
+
negScore += this.countTokenMatches(lowerUser, String(w)) * 1.0;
|
| 674 |
+
negScore += this.countTokenMatches(lowerKimi, String(w)) * 0.5;
|
| 675 |
+
}
|
| 676 |
+
|
| 677 |
+
let rawDelta = posScore * posFactor - negScore * negFactor;
|
| 678 |
+
|
| 679 |
+
// Track negative streaks per trait (only when net negative & no positives)
|
| 680 |
+
if (!this.negativeStreaks[trait]) this.negativeStreaks[trait] = 0;
|
| 681 |
+
if (negScore > 0 && posScore === 0) {
|
| 682 |
+
this.negativeStreaks[trait]++;
|
| 683 |
+
} else if (posScore > 0) {
|
| 684 |
+
this.negativeStreaks[trait] = 0;
|
| 685 |
+
}
|
| 686 |
+
|
| 687 |
+
if (rawDelta < 0 && this.negativeStreaks[trait] >= streakLimit) {
|
| 688 |
+
rawDelta *= 1.15; // escalate sustained negativity
|
| 689 |
+
}
|
| 690 |
+
|
| 691 |
+
// Clamp magnitude
|
| 692 |
+
if (rawDelta > maxStep) rawDelta = maxStep;
|
| 693 |
+
if (rawDelta < -maxStep) rawDelta = -maxStep;
|
| 694 |
+
|
| 695 |
+
if (rawDelta !== 0) {
|
| 696 |
+
let newVal = currentVal + rawDelta;
|
| 697 |
+
if (rawDelta > 0) {
|
| 698 |
+
newVal = Math.min(100, newVal);
|
| 699 |
+
} else {
|
| 700 |
+
newVal = Math.max(0, newVal);
|
| 701 |
+
}
|
| 702 |
+
pendingUpdates[trait] = newVal;
|
| 703 |
+
}
|
| 704 |
+
}
|
| 705 |
+
|
| 706 |
+
// Flush pending updates in a single batch write to avoid overwrites
|
| 707 |
+
if (Object.keys(pendingUpdates).length > 0) {
|
| 708 |
+
// Apply smoothing/threshold per trait (read current values)
|
| 709 |
+
const toPersist = {};
|
| 710 |
+
for (const [trait, candValue] of Object.entries(pendingUpdates)) {
|
| 711 |
+
const current = typeof traits?.[trait] === "number" ? traits[trait] : this.TRAIT_DEFAULTS[trait];
|
| 712 |
+
const prep = this._preparePersistTrait(trait, current, candValue, character);
|
| 713 |
+
if (prep.shouldPersist) toPersist[trait] = prep.value;
|
| 714 |
+
}
|
| 715 |
+
if (Object.keys(toPersist).length > 0) {
|
| 716 |
+
await this.db.setPersonalityBatch(toPersist, character);
|
| 717 |
+
}
|
| 718 |
+
}
|
| 719 |
+
}
|
| 720 |
+
|
| 721 |
+
validatePersonalityTrait(trait, value) {
|
| 722 |
+
if (typeof value !== "number" || value < 0 || value > 100) {
|
| 723 |
+
console.warn(`Invalid trait value for ${trait}: ${value}, using default`);
|
| 724 |
+
return this.TRAIT_DEFAULTS[trait] || 50;
|
| 725 |
+
}
|
| 726 |
+
return value;
|
| 727 |
+
}
|
| 728 |
+
|
| 729 |
+
// ===== NORMALIZATION & MATCH HELPERS =====
|
| 730 |
+
// Normalize text for robust matching (NFD -> remove diacritics, normalize quotes, lower, collapse spaces)
|
| 731 |
+
normalizeText(s) {
|
| 732 |
+
if (!s || typeof s !== "string") return "";
|
| 733 |
+
// Convert various apostrophes to ASCII, normalize NFD and remove diacritics
|
| 734 |
+
let out = s.replace(/[\u2018\u2019\u201A\u201B\u2032\u2035]/g, "'");
|
| 735 |
+
out = out.replace(/[\u201C\u201D\u201E\u201F\u2033\u2036]/g, '"');
|
| 736 |
+
// Expand a few common French contractions to improve detection (non-exhaustive)
|
| 737 |
+
out = out.replace(/\bj'/gi, "je ");
|
| 738 |
+
// expand negation contraction n' -> ne
|
| 739 |
+
out = out.replace(/\bn'/gi, "ne ");
|
| 740 |
+
out = out.replace(/\bt'/gi, "te ");
|
| 741 |
+
out = out.replace(/\bc'/gi, "ce ");
|
| 742 |
+
out = out.replace(/\bd'/gi, "de ");
|
| 743 |
+
out = out.replace(/\bl'/gi, "le ");
|
| 744 |
+
// Unicode normalize and strip combining marks
|
| 745 |
+
out = out.normalize("NFD").replace(/\p{Diacritic}/gu, "");
|
| 746 |
+
// Lowercase and collapse whitespace
|
| 747 |
+
out = out.toLowerCase().replace(/\s+/g, " ").trim();
|
| 748 |
+
return out;
|
| 749 |
+
}
|
| 750 |
+
|
| 751 |
+
// Count non-overlapping occurrences of needle in haystack
|
| 752 |
+
countOccurrences(haystack, needle) {
|
| 753 |
+
if (!haystack || !needle) return 0;
|
| 754 |
+
let count = 0;
|
| 755 |
+
let pos = 0;
|
| 756 |
+
while (true) {
|
| 757 |
+
const idx = haystack.indexOf(needle, pos);
|
| 758 |
+
if (idx === -1) break;
|
| 759 |
+
count++;
|
| 760 |
+
pos = idx + needle.length;
|
| 761 |
+
}
|
| 762 |
+
return count;
|
| 763 |
+
}
|
| 764 |
+
|
| 765 |
+
// Tokenize normalized text into words (strip punctuation)
|
| 766 |
+
tokenizeText(s) {
|
| 767 |
+
if (!s || typeof s !== "string") return [];
|
| 768 |
+
// split on whitespace, remove surrounding non-alphanum, keep ascii letters/numbers
|
| 769 |
+
return s
|
| 770 |
+
.split(/\s+/)
|
| 771 |
+
.map(t => t.replace(/^[^a-z0-9]+|[^a-z0-9]+$/gi, ""))
|
| 772 |
+
.filter(t => t.length > 0);
|
| 773 |
+
}
|
| 774 |
+
|
| 775 |
+
// Check for simple negators in a window before a token index
|
| 776 |
+
hasNegationWindow(tokens, index, window = 3) {
|
| 777 |
+
if (!Array.isArray(tokens) || tokens.length === 0) return false;
|
| 778 |
+
// Respect runtime-configured negators if available
|
| 779 |
+
const globalNegators = (window.KIMI_NEGATORS && window.KIMI_NEGATORS.common) || [];
|
| 780 |
+
// Try selected language list if set
|
| 781 |
+
const lang = (window.KIMI_SELECTED_LANG && String(window.KIMI_SELECTED_LANG)) || null;
|
| 782 |
+
const langNegators = (lang && window.KIMI_NEGATORS && window.KIMI_NEGATORS[lang]) || [];
|
| 783 |
+
const merged = new Set([...(Array.isArray(langNegators) ? langNegators : []), ...(Array.isArray(globalNegators) ? globalNegators : [])]);
|
| 784 |
+
// Always include a minimal english/french set as fallback
|
| 785 |
+
["no", "not", "never", "none", "nobody", "nothing", "ne", "n", "pas", "jamais", "plus", "aucun", "rien", "non"].forEach(x => merged.add(x));
|
| 786 |
+
const win = Number(window.KIMI_NEGATION_WINDOW) || window;
|
| 787 |
+
const start = Math.max(0, index - win);
|
| 788 |
+
for (let i = start; i < index; i++) {
|
| 789 |
+
if (merged.has(tokens[i])) return true;
|
| 790 |
+
}
|
| 791 |
+
return false;
|
| 792 |
+
}
|
| 793 |
+
|
| 794 |
+
// Count token-based matches (exact word or phrase) with negation handling
|
| 795 |
+
countTokenMatches(haystack, needle) {
|
| 796 |
+
if (!haystack || !needle) return 0;
|
| 797 |
+
const normNeedle = this.normalizeText(String(needle));
|
| 798 |
+
if (normNeedle.length === 0) return 0;
|
| 799 |
+
const needleTokens = this.tokenizeText(normNeedle);
|
| 800 |
+
if (needleTokens.length === 0) return 0;
|
| 801 |
+
const normHay = this.normalizeText(String(haystack));
|
| 802 |
+
const tokens = this.tokenizeText(normHay);
|
| 803 |
+
if (tokens.length === 0) return 0;
|
| 804 |
+
let count = 0;
|
| 805 |
+
for (let i = 0; i <= tokens.length - needleTokens.length; i++) {
|
| 806 |
+
let match = true;
|
| 807 |
+
for (let j = 0; j < needleTokens.length; j++) {
|
| 808 |
+
if (tokens[i + j] !== needleTokens[j]) {
|
| 809 |
+
match = false;
|
| 810 |
+
break;
|
| 811 |
+
}
|
| 812 |
+
}
|
| 813 |
+
if (match) {
|
| 814 |
+
// skip if a negation is in window before the match
|
| 815 |
+
// Use global isPhraseNegated API (fallback to existing logic if absent)
|
| 816 |
+
const phrase = needleTokens.join(" ");
|
| 817 |
+
const isNeg = window.isPhraseNegated
|
| 818 |
+
? window.isPhraseNegated(haystack, phrase, this._detectLanguage(haystack, "auto"))
|
| 819 |
+
: this.hasNegationWindow(tokens, i);
|
| 820 |
+
if (!isNeg) {
|
| 821 |
+
count++;
|
| 822 |
+
}
|
| 823 |
+
i += needleTokens.length - 1; // advance to avoid overlapping
|
| 824 |
+
}
|
| 825 |
+
}
|
| 826 |
+
return count;
|
| 827 |
+
}
|
| 828 |
+
|
| 829 |
+
// Return true if any occurrence of needle in haystack is negated (within negation window)
|
| 830 |
+
isTokenNegated(haystack, needle) {
|
| 831 |
+
if (!haystack || !needle) return false;
|
| 832 |
+
const normNeedle = this.normalizeText(String(needle));
|
| 833 |
+
const needleTokens = this.tokenizeText(normNeedle);
|
| 834 |
+
if (needleTokens.length === 0) return false;
|
| 835 |
+
const normHay = this.normalizeText(String(haystack));
|
| 836 |
+
const tokens = this.tokenizeText(normHay);
|
| 837 |
+
for (let i = 0; i <= tokens.length - needleTokens.length; i++) {
|
| 838 |
+
let match = true;
|
| 839 |
+
for (let j = 0; j < needleTokens.length; j++) {
|
| 840 |
+
if (tokens[i + j] !== needleTokens[j]) {
|
| 841 |
+
match = false;
|
| 842 |
+
break;
|
| 843 |
+
}
|
| 844 |
+
}
|
| 845 |
+
if (match) {
|
| 846 |
+
const phrase = needleTokens.join(" ");
|
| 847 |
+
const detectedLang = this._detectLanguage(haystack, "auto");
|
| 848 |
+
const isNeg = window.isPhraseNegated ? window.isPhraseNegated(haystack, phrase, detectedLang) : this.hasNegationWindow(tokens, i);
|
| 849 |
+
if (isNeg) return true;
|
| 850 |
+
i += needleTokens.length - 1;
|
| 851 |
+
}
|
| 852 |
+
}
|
| 853 |
+
return false;
|
| 854 |
+
}
|
| 855 |
+
|
| 856 |
+
// ===== SMOOTHING / PERSISTENCE HELPERS =====
|
| 857 |
+
// Apply EMA smoothing between current and candidate value. alpha in (0..1).
|
| 858 |
+
_applyEMA(current, candidate, alpha) {
|
| 859 |
+
alpha = typeof alpha === "number" && isFinite(alpha) ? alpha : 0.3;
|
| 860 |
+
return current * (1 - alpha) + candidate * alpha;
|
| 861 |
+
}
|
| 862 |
+
|
| 863 |
+
// Decide whether to persist based on absolute change threshold. Returns {shouldPersist, value}
|
| 864 |
+
_preparePersistTrait(trait, currentValue, candidateValue, character = null) {
|
| 865 |
+
// Configurable via globals
|
| 866 |
+
const alpha = (window.KIMI_SMOOTHING_ALPHA && Number(window.KIMI_SMOOTHING_ALPHA)) || 0.3;
|
| 867 |
+
const threshold = (window.KIMI_PERSIST_THRESHOLD && Number(window.KIMI_PERSIST_THRESHOLD)) || 0.25; // percent absolute
|
| 868 |
+
|
| 869 |
+
const smoothed = this._applyEMA(currentValue, candidateValue, alpha);
|
| 870 |
+
const absDelta = Math.abs(smoothed - currentValue);
|
| 871 |
+
if (absDelta < threshold) {
|
| 872 |
+
return { shouldPersist: false, value: currentValue };
|
| 873 |
+
}
|
| 874 |
+
return { shouldPersist: true, value: Number(Number(smoothed).toFixed(2)) };
|
| 875 |
+
}
|
| 876 |
+
|
| 877 |
+
// ===== UTILITY METHODS =====
|
| 878 |
+
_detectLanguage(text, lang) {
|
| 879 |
+
if (lang !== "auto") return lang;
|
| 880 |
+
// Quick heuristic detection
|
| 881 |
+
if (/[àâäéèêëîïôöùûüÿç]/i.test(text)) return "fr";
|
| 882 |
+
if (/[äöüß]/i.test(text)) return "de";
|
| 883 |
+
if (/[ñáéíóúü]/i.test(text)) return "es";
|
| 884 |
+
if (/[àèìòù]/i.test(text)) return "it";
|
| 885 |
+
if (/[\u3040-\u309f\u30a0-\u30ff\u4e00-\u9faf]/i.test(text)) return "ja";
|
| 886 |
+
if (/[\u4e00-\u9fff]/i.test(text)) return "zh";
|
| 887 |
+
|
| 888 |
+
// Fallback: if last language set and hostiles detected there, reuse it
|
| 889 |
+
try {
|
| 890 |
+
const last = window.KIMI_LAST_LANG;
|
| 891 |
+
if (last && window.isHostileText && window.isHostileText(text, last)) return last;
|
| 892 |
+
} catch {}
|
| 893 |
+
return "en";
|
| 894 |
+
}
|
| 895 |
+
|
| 896 |
+
async _analyzeTextContent(text, callback, adjustUp) {
|
| 897 |
+
if (!this.db) return;
|
| 898 |
+
|
| 899 |
+
const selectedLanguage = await this.db.getPreference("selectedLanguage", "en");
|
| 900 |
+
const romanticWords = window.KIMI_CONTEXT_KEYWORDS?.[selectedLanguage]?.romantic ||
|
| 901 |
+
window.KIMI_CONTEXT_KEYWORDS?.en?.romantic || ["love", "romantic", "kiss"];
|
| 902 |
+
const humorWords = window.KIMI_CONTEXT_KEYWORDS?.[selectedLanguage]?.laughing || window.KIMI_CONTEXT_KEYWORDS?.en?.laughing || ["joke", "funny", "lol"];
|
| 903 |
+
|
| 904 |
+
const romanticPattern = new RegExp(`(${romanticWords.join("|")})`, "i");
|
| 905 |
+
const humorPattern = new RegExp(`(${humorWords.join("|")})`, "i");
|
| 906 |
+
|
| 907 |
+
const traits = {};
|
| 908 |
+
if (text.match(romanticPattern)) {
|
| 909 |
+
traits.romance = adjustUp(traits.romance || this.TRAIT_DEFAULTS.romance, 0.5);
|
| 910 |
+
traits.affection = adjustUp(traits.affection || this.TRAIT_DEFAULTS.affection, 0.5);
|
| 911 |
+
}
|
| 912 |
+
if (text.match(humorPattern)) {
|
| 913 |
+
traits.humor = adjustUp(traits.humor || this.TRAIT_DEFAULTS.humor, 2);
|
| 914 |
+
traits.playfulness = adjustUp(traits.playfulness || this.TRAIT_DEFAULTS.playfulness, 1);
|
| 915 |
+
}
|
| 916 |
+
|
| 917 |
+
callback(traits);
|
| 918 |
+
}
|
| 919 |
+
|
| 920 |
+
_getFallbackKeywords(trait, type) {
|
| 921 |
+
const fallbackKeywords = {
|
| 922 |
+
humor: {
|
| 923 |
+
positive: ["funny", "hilarious", "joke", "laugh", "amusing"],
|
| 924 |
+
negative: ["boring", "sad", "serious", "cold", "dry"]
|
| 925 |
+
},
|
| 926 |
+
intelligence: {
|
| 927 |
+
positive: ["intelligent", "smart", "brilliant", "logical", "clever"],
|
| 928 |
+
negative: ["stupid", "dumb", "foolish", "slow", "naive"]
|
| 929 |
+
},
|
| 930 |
+
romance: {
|
| 931 |
+
positive: ["cuddle", "love", "romantic", "kiss", "tenderness"],
|
| 932 |
+
negative: ["cold", "distant", "indifferent", "rejection"]
|
| 933 |
+
},
|
| 934 |
+
affection: {
|
| 935 |
+
positive: ["affection", "tenderness", "close", "warmth", "kind"],
|
| 936 |
+
negative: ["mean", "cold", "indifferent", "distant", "rejection"]
|
| 937 |
+
},
|
| 938 |
+
playfulness: {
|
| 939 |
+
positive: ["play", "game", "tease", "mischievous", "fun"],
|
| 940 |
+
negative: ["serious", "boring", "strict", "rigid"]
|
| 941 |
+
},
|
| 942 |
+
empathy: {
|
| 943 |
+
positive: ["listen", "understand", "empathy", "support", "help"],
|
| 944 |
+
negative: ["indifferent", "cold", "selfish", "ignore"]
|
| 945 |
+
}
|
| 946 |
+
};
|
| 947 |
+
|
| 948 |
+
return fallbackKeywords[trait]?.[type] || [];
|
| 949 |
+
}
|
| 950 |
+
|
| 951 |
+
// ===== PERSONALITY CALCULATION =====
|
| 952 |
+
calculatePersonalityAverage(traits) {
|
| 953 |
+
const keys = ["affection", "romance", "empathy", "playfulness", "humor", "intelligence"];
|
| 954 |
+
let sum = 0;
|
| 955 |
+
let count = 0;
|
| 956 |
+
|
| 957 |
+
keys.forEach(key => {
|
| 958 |
+
if (typeof traits[key] === "number") {
|
| 959 |
+
sum += traits[key];
|
| 960 |
+
count++;
|
| 961 |
+
}
|
| 962 |
+
});
|
| 963 |
+
|
| 964 |
+
return count > 0 ? sum / count : 50;
|
| 965 |
+
}
|
| 966 |
+
|
| 967 |
+
getMoodCategoryFromPersonality(traits) {
|
| 968 |
+
const avg = this.calculatePersonalityAverage(traits);
|
| 969 |
+
|
| 970 |
+
if (avg >= 80) return "speakingPositive";
|
| 971 |
+
if (avg >= 60) return "neutral";
|
| 972 |
+
if (avg >= 40) return "neutral";
|
| 973 |
+
if (avg >= 20) return "speakingNegative";
|
| 974 |
+
return "speakingNegative";
|
| 975 |
+
}
|
| 976 |
+
}
|
| 977 |
+
|
| 978 |
+
window.KimiEmotionSystem = KimiEmotionSystem;
|
| 979 |
+
// Expose centralized tuning maps for debugging / live adjustments
|
| 980 |
+
Object.defineProperty(window, "KIMI_EMOTION_TRAIT_EFFECTS", {
|
| 981 |
+
get() {
|
| 982 |
+
return window.kimiEmotionSystem ? window.kimiEmotionSystem.EMOTION_TRAIT_EFFECTS : null;
|
| 983 |
+
}
|
| 984 |
+
});
|
| 985 |
+
Object.defineProperty(window, "KIMI_TRAIT_KEYWORD_MODEL", {
|
| 986 |
+
get() {
|
| 987 |
+
return window.kimiEmotionSystem ? window.kimiEmotionSystem.TRAIT_KEYWORD_MODEL : null;
|
| 988 |
+
}
|
| 989 |
+
});
|
| 990 |
+
|
| 991 |
+
// Debug/tuning helpers
|
| 992 |
+
window.setEmotionDelta = function (emotion, trait, value) {
|
| 993 |
+
if (!window.kimiEmotionSystem) return false;
|
| 994 |
+
const map = window.kimiEmotionSystem.EMOTION_TRAIT_EFFECTS;
|
| 995 |
+
if (!map[emotion]) map[emotion] = {};
|
| 996 |
+
map[emotion][trait] = Number(value);
|
| 997 |
+
return true;
|
| 998 |
+
};
|
| 999 |
+
window.resetEmotionDeltas = function () {
|
| 1000 |
+
if (!window.kimiEmotionSystem) return false;
|
| 1001 |
+
// No stored original snapshot; advise page reload for full reset.
|
| 1002 |
+
console.warn("For full reset reload the page (original deltas are not snapshotted).");
|
| 1003 |
+
};
|
| 1004 |
+
window.setTraitKeywordScaling = function (trait, cfg) {
|
| 1005 |
+
if (!window.kimiEmotionSystem) return false;
|
| 1006 |
+
const model = window.kimiEmotionSystem.TRAIT_KEYWORD_MODEL;
|
| 1007 |
+
if (!model[trait]) return false;
|
| 1008 |
+
Object.assign(model[trait], cfg);
|
| 1009 |
+
return true;
|
| 1010 |
+
};
|
| 1011 |
+
|
| 1012 |
+
// Force recompute + UI refresh for personality average
|
| 1013 |
+
window.refreshPersonalityAverageUI = async function (characterKey = null) {
|
| 1014 |
+
try {
|
| 1015 |
+
if (window.updateGlobalPersonalityUI) {
|
| 1016 |
+
await window.updateGlobalPersonalityUI(characterKey);
|
| 1017 |
+
} else if (window.getPersonalityAverage && window.kimiDB) {
|
| 1018 |
+
const charKey = characterKey || (await window.kimiDB.getSelectedCharacter());
|
| 1019 |
+
const traits = window.getCharacterTraits ? await window.getCharacterTraits(charKey) : await window.kimiDB.getAllPersonalityTraits(charKey);
|
| 1020 |
+
const avg = window.getPersonalityAverage(traits);
|
| 1021 |
+
const bar = document.getElementById("favorability-bar");
|
| 1022 |
+
const text = document.getElementById("favorability-text");
|
| 1023 |
+
if (bar) bar.style.width = `${avg}%`;
|
| 1024 |
+
if (text) text.textContent = `${avg.toFixed(2)}%`;
|
| 1025 |
+
}
|
| 1026 |
+
} catch (err) {
|
| 1027 |
+
console.warn("refreshPersonalityAverageUI failed", err);
|
| 1028 |
+
}
|
| 1029 |
+
};
|
| 1030 |
+
export default KimiEmotionSystem;
|
| 1031 |
+
|
| 1032 |
+
// ===== BACKWARD COMPATIBILITY LAYER =====
|
| 1033 |
+
// Ensure single instance of KimiEmotionSystem (Singleton pattern)
|
| 1034 |
+
function getKimiEmotionSystemInstance() {
|
| 1035 |
+
if (!window.kimiEmotionSystem) {
|
| 1036 |
+
window.kimiEmotionSystem = new KimiEmotionSystem(window.kimiDB);
|
| 1037 |
+
}
|
| 1038 |
+
return window.kimiEmotionSystem;
|
| 1039 |
+
}
|
| 1040 |
+
|
| 1041 |
+
// Replace the old kimiAnalyzeEmotion function
|
| 1042 |
+
window.kimiAnalyzeEmotion = function (text, lang = "auto") {
|
| 1043 |
+
return getKimiEmotionSystemInstance().analyzeEmotion(text, lang);
|
| 1044 |
+
};
|
| 1045 |
+
|
| 1046 |
+
// Replace the old updatePersonalityTraitsFromEmotion function
|
| 1047 |
+
window.updatePersonalityTraitsFromEmotion = async function (emotion, text) {
|
| 1048 |
+
const updatedTraits = await getKimiEmotionSystemInstance().updatePersonalityFromEmotion(emotion, text);
|
| 1049 |
+
return updatedTraits;
|
| 1050 |
+
};
|
| 1051 |
+
|
| 1052 |
+
// Replace getPersonalityAverage function
|
| 1053 |
+
window.getPersonalityAverage = function (traits) {
|
| 1054 |
+
return getKimiEmotionSystemInstance().calculatePersonalityAverage(traits);
|
| 1055 |
+
};
|
| 1056 |
+
|
| 1057 |
+
// Unified trait defaults accessor
|
| 1058 |
+
window.getTraitDefaults = function () {
|
| 1059 |
+
return getKimiEmotionSystemInstance().TRAIT_DEFAULTS;
|
| 1060 |
+
};
|
kimi-js/kimi-error-manager.js
ADDED
|
@@ -0,0 +1,219 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// ===== KIMI ERROR MANAGEMENT SYSTEM =====
|
| 2 |
+
class KimiErrorManager {
|
| 3 |
+
constructor() {
|
| 4 |
+
this.errorLog = [];
|
| 5 |
+
this.maxLogSize = 100;
|
| 6 |
+
this.errorHandlers = new Map();
|
| 7 |
+
this.setupGlobalHandlers();
|
| 8 |
+
}
|
| 9 |
+
|
| 10 |
+
setupGlobalHandlers() {
|
| 11 |
+
// Handle unhandled promise rejections
|
| 12 |
+
window.addEventListener("unhandledrejection", event => {
|
| 13 |
+
this.logError("UnhandledPromiseRejection", event.reason, {
|
| 14 |
+
promise: event.promise,
|
| 15 |
+
timestamp: new Date().toISOString()
|
| 16 |
+
});
|
| 17 |
+
event.preventDefault();
|
| 18 |
+
});
|
| 19 |
+
|
| 20 |
+
// Handle JavaScript errors
|
| 21 |
+
window.addEventListener("error", event => {
|
| 22 |
+
this.logError("JavaScriptError", event.error || event.message, {
|
| 23 |
+
filename: event.filename,
|
| 24 |
+
lineno: event.lineno,
|
| 25 |
+
colno: event.colno,
|
| 26 |
+
timestamp: new Date().toISOString()
|
| 27 |
+
});
|
| 28 |
+
});
|
| 29 |
+
}
|
| 30 |
+
|
| 31 |
+
logError(type, error, context = {}) {
|
| 32 |
+
const errorEntry = {
|
| 33 |
+
id: this.generateErrorId(),
|
| 34 |
+
type,
|
| 35 |
+
message: error?.message || error,
|
| 36 |
+
stack: error?.stack,
|
| 37 |
+
context,
|
| 38 |
+
timestamp: new Date().toISOString(),
|
| 39 |
+
severity: this.determineSeverity(type, error)
|
| 40 |
+
};
|
| 41 |
+
|
| 42 |
+
this.errorLog.push(errorEntry);
|
| 43 |
+
|
| 44 |
+
// Keep log size manageable
|
| 45 |
+
if (this.errorLog.length > this.maxLogSize) {
|
| 46 |
+
this.errorLog.shift();
|
| 47 |
+
}
|
| 48 |
+
|
| 49 |
+
// Console logging with appropriate level
|
| 50 |
+
this.consoleLog(errorEntry);
|
| 51 |
+
|
| 52 |
+
// Trigger registered handlers
|
| 53 |
+
this.triggerHandlers(errorEntry);
|
| 54 |
+
|
| 55 |
+
return errorEntry.id;
|
| 56 |
+
}
|
| 57 |
+
|
| 58 |
+
generateErrorId() {
|
| 59 |
+
return "err_" + Date.now() + "_" + Math.random().toString(36).substr(2, 9);
|
| 60 |
+
}
|
| 61 |
+
|
| 62 |
+
determineSeverity(type, error) {
|
| 63 |
+
const criticalTypes = ["UnhandledPromiseRejection", "DatabaseError", "InitializationError"];
|
| 64 |
+
const criticalMessages = ["failed to fetch", "network error", "connection refused"];
|
| 65 |
+
|
| 66 |
+
if (criticalTypes.includes(type)) return "critical";
|
| 67 |
+
|
| 68 |
+
const message = (error?.message || error || "").toLowerCase();
|
| 69 |
+
if (criticalMessages.some(cm => message.includes(cm))) return "critical";
|
| 70 |
+
|
| 71 |
+
return "warning";
|
| 72 |
+
}
|
| 73 |
+
|
| 74 |
+
consoleLog(errorEntry) {
|
| 75 |
+
const { type, message, severity, context } = errorEntry;
|
| 76 |
+
|
| 77 |
+
switch (severity) {
|
| 78 |
+
case "critical":
|
| 79 |
+
console.error(`🚨 [${type}]`, message, context);
|
| 80 |
+
break;
|
| 81 |
+
case "warning":
|
| 82 |
+
console.warn(`⚠️ [${type}]`, message, context);
|
| 83 |
+
break;
|
| 84 |
+
default:
|
| 85 |
+
console.info(`ℹ️ [${type}]`, message, context);
|
| 86 |
+
}
|
| 87 |
+
}
|
| 88 |
+
|
| 89 |
+
triggerHandlers(errorEntry) {
|
| 90 |
+
const handlers = this.errorHandlers.get(errorEntry.type) || [];
|
| 91 |
+
handlers.forEach(handler => {
|
| 92 |
+
try {
|
| 93 |
+
handler(errorEntry);
|
| 94 |
+
} catch (handlerError) {
|
| 95 |
+
console.error("Error in error handler:", handlerError);
|
| 96 |
+
}
|
| 97 |
+
});
|
| 98 |
+
}
|
| 99 |
+
|
| 100 |
+
registerHandler(errorType, handler) {
|
| 101 |
+
if (!this.errorHandlers.has(errorType)) {
|
| 102 |
+
this.errorHandlers.set(errorType, []);
|
| 103 |
+
}
|
| 104 |
+
this.errorHandlers.get(errorType).push(handler);
|
| 105 |
+
}
|
| 106 |
+
|
| 107 |
+
unregisterHandler(errorType, handler) {
|
| 108 |
+
const handlers = this.errorHandlers.get(errorType);
|
| 109 |
+
if (handlers) {
|
| 110 |
+
const index = handlers.indexOf(handler);
|
| 111 |
+
if (index > -1) {
|
| 112 |
+
handlers.splice(index, 1);
|
| 113 |
+
}
|
| 114 |
+
}
|
| 115 |
+
}
|
| 116 |
+
|
| 117 |
+
getErrorLog(filter = null) {
|
| 118 |
+
if (!filter) return [...this.errorLog];
|
| 119 |
+
|
| 120 |
+
return this.errorLog.filter(entry => {
|
| 121 |
+
if (filter.type && entry.type !== filter.type) return false;
|
| 122 |
+
if (filter.severity && entry.severity !== filter.severity) return false;
|
| 123 |
+
if (filter.since && new Date(entry.timestamp) < filter.since) return false;
|
| 124 |
+
return true;
|
| 125 |
+
});
|
| 126 |
+
}
|
| 127 |
+
|
| 128 |
+
clearErrorLog() {
|
| 129 |
+
this.errorLog.length = 0;
|
| 130 |
+
}
|
| 131 |
+
|
| 132 |
+
// Helper methods for different error types
|
| 133 |
+
logInitError(component, error, context = {}) {
|
| 134 |
+
return this.logError("InitializationError", error, { component, ...context });
|
| 135 |
+
}
|
| 136 |
+
|
| 137 |
+
logDatabaseError(operation, error, context = {}) {
|
| 138 |
+
return this.logError("DatabaseError", error, { operation, ...context });
|
| 139 |
+
}
|
| 140 |
+
|
| 141 |
+
logAPIError(endpoint, error, context = {}) {
|
| 142 |
+
return this.logError("APIError", error, { endpoint, ...context });
|
| 143 |
+
}
|
| 144 |
+
|
| 145 |
+
logValidationError(field, error, context = {}) {
|
| 146 |
+
return this.logError("ValidationError", error, { field, ...context });
|
| 147 |
+
}
|
| 148 |
+
|
| 149 |
+
logUIError(component, error, context = {}) {
|
| 150 |
+
return this.logError("UIError", error, { component, ...context });
|
| 151 |
+
}
|
| 152 |
+
|
| 153 |
+
// Async wrapper for functions
|
| 154 |
+
async wrapAsync(fn, errorContext = {}) {
|
| 155 |
+
try {
|
| 156 |
+
return await fn();
|
| 157 |
+
} catch (error) {
|
| 158 |
+
this.logError("AsyncOperationError", error, errorContext);
|
| 159 |
+
throw error;
|
| 160 |
+
}
|
| 161 |
+
}
|
| 162 |
+
|
| 163 |
+
// Sync wrapper for functions
|
| 164 |
+
wrapSync(fn, errorContext = {}) {
|
| 165 |
+
try {
|
| 166 |
+
return fn();
|
| 167 |
+
} catch (error) {
|
| 168 |
+
this.logError("SyncOperationError", error, errorContext);
|
| 169 |
+
throw error;
|
| 170 |
+
}
|
| 171 |
+
}
|
| 172 |
+
|
| 173 |
+
// Debug helpers for development
|
| 174 |
+
getErrorSummary() {
|
| 175 |
+
const summary = {
|
| 176 |
+
totalErrors: this.errorLog.length,
|
| 177 |
+
critical: this.errorLog.filter(e => e.severity === "critical").length,
|
| 178 |
+
warning: this.errorLog.filter(e => e.severity === "warning").length,
|
| 179 |
+
recent: this.errorLog.filter(e => {
|
| 180 |
+
const errorTime = new Date(e.timestamp);
|
| 181 |
+
const fiveMinutesAgo = new Date(Date.now() - 5 * 60 * 1000);
|
| 182 |
+
return errorTime > fiveMinutesAgo;
|
| 183 |
+
}).length,
|
| 184 |
+
types: [...new Set(this.errorLog.map(e => e.type))]
|
| 185 |
+
};
|
| 186 |
+
return summary;
|
| 187 |
+
}
|
| 188 |
+
|
| 189 |
+
printErrorSummary() {
|
| 190 |
+
const summary = this.getErrorSummary();
|
| 191 |
+
console.group("🔍 Kimi Error Manager Summary");
|
| 192 |
+
console.log(`📊 Total Errors: ${summary.totalErrors}`);
|
| 193 |
+
console.log(`🚨 Critical: ${summary.critical}`);
|
| 194 |
+
console.log(`⚠️ Warnings: ${summary.warning}`);
|
| 195 |
+
console.log(`⏰ Recent (5min): ${summary.recent}`);
|
| 196 |
+
console.log(`📋 Error Types:`, summary.types);
|
| 197 |
+
if (summary.totalErrors > 0) {
|
| 198 |
+
console.log(`💡 Use kimiErrorManager.getErrorLog() to see details`);
|
| 199 |
+
}
|
| 200 |
+
console.groupEnd();
|
| 201 |
+
return summary;
|
| 202 |
+
}
|
| 203 |
+
|
| 204 |
+
clearAndSummarize() {
|
| 205 |
+
const summary = this.getErrorSummary();
|
| 206 |
+
this.clearErrorLog();
|
| 207 |
+
console.log("🧹 Error log cleared. Previous summary:", summary);
|
| 208 |
+
return summary;
|
| 209 |
+
}
|
| 210 |
+
}
|
| 211 |
+
|
| 212 |
+
// Create global instance
|
| 213 |
+
window.kimiErrorManager = new KimiErrorManager();
|
| 214 |
+
|
| 215 |
+
// Export class for manual instantiation if needed
|
| 216 |
+
window.KimiErrorManager = KimiErrorManager;
|
| 217 |
+
|
| 218 |
+
// Global debugging helper
|
| 219 |
+
window.kimiDebugErrors = () => window.kimiErrorManager.printErrorSummary();
|
kimi-js/kimi-llm-manager.js
ADDED
|
@@ -0,0 +1,1729 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// ===== KIMI INTELLIGENT LLM SYSTEM =====
|
| 2 |
+
import { KimiProviderUtils } from "./kimi-utils.js";
|
| 3 |
+
class KimiLLMManager {
|
| 4 |
+
constructor(database) {
|
| 5 |
+
this.db = database;
|
| 6 |
+
this.currentModel = null;
|
| 7 |
+
this.conversationContext = [];
|
| 8 |
+
this.maxContextLength = 100;
|
| 9 |
+
this.personalityPrompt = "";
|
| 10 |
+
this.isGenerating = false;
|
| 11 |
+
|
| 12 |
+
// Recommended models on OpenRouter (IDs updated August 2025)
|
| 13 |
+
this.availableModels = {
|
| 14 |
+
"mistralai/mistral-small-3.2-24b-instruct": {
|
| 15 |
+
name: "Mistral-small-3.2",
|
| 16 |
+
provider: "Mistral AI",
|
| 17 |
+
type: "openrouter",
|
| 18 |
+
contextWindow: 128000,
|
| 19 |
+
pricing: { input: 0.05, output: 0.1 },
|
| 20 |
+
strengths: ["Multilingual", "Fast", "Efficient", "Economical"]
|
| 21 |
+
},
|
| 22 |
+
"x-ai/grok-4-fast": {
|
| 23 |
+
name: "Grok 4 fast",
|
| 24 |
+
provider: "xAI",
|
| 25 |
+
type: "openrouter",
|
| 26 |
+
contextWindow: 2000000,
|
| 27 |
+
pricing: { input: 0.2, output: 0.5 },
|
| 28 |
+
strengths: ["Multilingual", "Fast", "Versatile", "Efficient"]
|
| 29 |
+
},
|
| 30 |
+
"qwen/qwen3-235b-a22b-2507": {
|
| 31 |
+
name: "Qwen3-235b-a22b-2507",
|
| 32 |
+
provider: "Qwen",
|
| 33 |
+
type: "openrouter",
|
| 34 |
+
contextWindow: 262000,
|
| 35 |
+
pricing: { input: 0.13, output: 0.6 },
|
| 36 |
+
strengths: ["Multilingual", "Fast", "Versatile", "Efficient"]
|
| 37 |
+
},
|
| 38 |
+
"qwen/qwen3-30b-a3b-instruct-2507": {
|
| 39 |
+
name: "Qwen3 30b-a3b instruct 2507",
|
| 40 |
+
provider: "Qwen",
|
| 41 |
+
type: "openrouter",
|
| 42 |
+
contextWindow: 131000,
|
| 43 |
+
pricing: { input: 0.1, output: 0.3 },
|
| 44 |
+
strengths: ["Multilingual", "Fast", "Balanced", "Economical"]
|
| 45 |
+
},
|
| 46 |
+
"nousresearch/hermes-4-70b": {
|
| 47 |
+
name: "Nous Hermes 4 70B",
|
| 48 |
+
provider: "Nous",
|
| 49 |
+
type: "openrouter",
|
| 50 |
+
contextWindow: 131000,
|
| 51 |
+
pricing: { input: 0.13, output: 0.4 },
|
| 52 |
+
strengths: ["Multilingual", "Fast", "Balanced", "Economical"]
|
| 53 |
+
},
|
| 54 |
+
"x-ai/grok-3-mini": {
|
| 55 |
+
name: "Grok 3 mini",
|
| 56 |
+
provider: "xAI",
|
| 57 |
+
type: "openrouter",
|
| 58 |
+
contextWindow: 131000,
|
| 59 |
+
pricing: { input: 0.3, output: 0.5 },
|
| 60 |
+
strengths: ["Multilingual", "Fast", "Versatile", "Efficient"]
|
| 61 |
+
},
|
| 62 |
+
"cohere/command-r-08-2024": {
|
| 63 |
+
name: "Command-R-08-2024",
|
| 64 |
+
provider: "Cohere",
|
| 65 |
+
type: "openrouter",
|
| 66 |
+
contextWindow: 128000,
|
| 67 |
+
pricing: { input: 0.15, output: 0.6 },
|
| 68 |
+
strengths: ["Multilingual", "Fast", "Versatile", "Balanced"]
|
| 69 |
+
},
|
| 70 |
+
"anthropic/claude-3-haiku": {
|
| 71 |
+
name: "Claude 3 Haiku",
|
| 72 |
+
provider: "Anthropic",
|
| 73 |
+
type: "openrouter",
|
| 74 |
+
contextWindow: 200000,
|
| 75 |
+
pricing: { input: 0.25, output: 1.25 },
|
| 76 |
+
strengths: ["Multilingual", "Fast", "Versatile", "Efficient"]
|
| 77 |
+
},
|
| 78 |
+
"local/ollama": {
|
| 79 |
+
name: "Local Model (Ollama)",
|
| 80 |
+
provider: "Local",
|
| 81 |
+
type: "local",
|
| 82 |
+
contextWindow: 4096,
|
| 83 |
+
pricing: { input: 0, output: 0 },
|
| 84 |
+
strengths: ["Private", "Offline", "Customizable"]
|
| 85 |
+
}
|
| 86 |
+
};
|
| 87 |
+
this.recommendedModelIds = [
|
| 88 |
+
"mistralai/mistral-small-3.2-24b-instruct",
|
| 89 |
+
"x-ai/grok-4-fast",
|
| 90 |
+
"qwen/qwen3-235b-a22b-2507",
|
| 91 |
+
"qwen/qwen3-30b-a3b-instruct-2507",
|
| 92 |
+
"nousresearch/hermes-4-70b",
|
| 93 |
+
"x-ai/grok-3-mini",
|
| 94 |
+
"cohere/command-r-08-2024",
|
| 95 |
+
"anthropic/claude-3-haiku",
|
| 96 |
+
"moonshotai/kimi-k2-0905",
|
| 97 |
+
"local/ollama"
|
| 98 |
+
];
|
| 99 |
+
this.defaultModels = { ...this.availableModels };
|
| 100 |
+
this._remoteModelsLoaded = false;
|
| 101 |
+
this._isRefreshingModels = false;
|
| 102 |
+
}
|
| 103 |
+
|
| 104 |
+
async init() {
|
| 105 |
+
try {
|
| 106 |
+
await this.refreshRemoteModels();
|
| 107 |
+
} catch (e) {
|
| 108 |
+
if (window.KIMI_CONFIG?.DEBUG?.API) {
|
| 109 |
+
console.warn("Unable to refresh remote models list:", e?.message || e);
|
| 110 |
+
}
|
| 111 |
+
}
|
| 112 |
+
|
| 113 |
+
// Migration: prefer llmModelId; if legacy defaultLLMModel exists and llmModelId missing, migrate
|
| 114 |
+
const legacyModel = await this.db.getPreference("defaultLLMModel", null);
|
| 115 |
+
let modelPref = await this.db.getPreference("llmModelId", null);
|
| 116 |
+
if (!modelPref && legacyModel) {
|
| 117 |
+
modelPref = legacyModel;
|
| 118 |
+
await this.db.setPreference("llmModelId", legacyModel);
|
| 119 |
+
}
|
| 120 |
+
const defaultModel = modelPref || "mistralai/mistral-small-3.2-24b-instruct";
|
| 121 |
+
await this.setCurrentModel(defaultModel);
|
| 122 |
+
await this.loadConversationContext();
|
| 123 |
+
}
|
| 124 |
+
|
| 125 |
+
async setCurrentModel(modelId) {
|
| 126 |
+
if (!this.availableModels[modelId]) {
|
| 127 |
+
try {
|
| 128 |
+
await this.refreshRemoteModels();
|
| 129 |
+
const fallback = this.findBestMatchingModelId(modelId);
|
| 130 |
+
if (fallback && this.availableModels[fallback]) {
|
| 131 |
+
modelId = fallback;
|
| 132 |
+
}
|
| 133 |
+
} catch (e) {}
|
| 134 |
+
|
| 135 |
+
if (!this.availableModels[modelId]) {
|
| 136 |
+
throw new Error(`Model ${modelId} not available`);
|
| 137 |
+
}
|
| 138 |
+
}
|
| 139 |
+
|
| 140 |
+
this.currentModel = modelId;
|
| 141 |
+
// Single authoritative preference key
|
| 142 |
+
await this.db.setPreference("llmModelId", modelId);
|
| 143 |
+
|
| 144 |
+
const modelData = await this.db.getLLMModel(modelId);
|
| 145 |
+
if (modelData) {
|
| 146 |
+
modelData.lastUsed = new Date().toISOString();
|
| 147 |
+
await this.db.saveLLMModel(modelData.id, modelData.name, modelData.provider, modelData.apiKey, modelData.config);
|
| 148 |
+
}
|
| 149 |
+
|
| 150 |
+
this._notifyModelChanged();
|
| 151 |
+
}
|
| 152 |
+
|
| 153 |
+
async loadConversationContext() {
|
| 154 |
+
const recentConversations = await this.db.getRecentConversations(this.maxContextLength);
|
| 155 |
+
const msgs = [];
|
| 156 |
+
const ordered = recentConversations.slice().sort((a, b) => new Date(a.timestamp) - new Date(b.timestamp));
|
| 157 |
+
for (const conv of ordered) {
|
| 158 |
+
if (conv.user) msgs.push({ role: "user", content: conv.user, timestamp: conv.timestamp });
|
| 159 |
+
if (conv.kimi) msgs.push({ role: "assistant", content: conv.kimi, timestamp: conv.timestamp });
|
| 160 |
+
}
|
| 161 |
+
this.conversationContext = msgs.slice(-this.maxContextLength * 2);
|
| 162 |
+
}
|
| 163 |
+
|
| 164 |
+
// Unified full prompt builder: reuse full legacy personality block + ranked concise snapshot
|
| 165 |
+
async assemblePrompt(userMessage) {
|
| 166 |
+
const fullPersonality = await this.generateKimiPersonality();
|
| 167 |
+
let rankedSnapshot = "";
|
| 168 |
+
if (window.kimiMemorySystem && window.kimiMemorySystem.memoryEnabled) {
|
| 169 |
+
try {
|
| 170 |
+
const recentContext =
|
| 171 |
+
this.conversationContext
|
| 172 |
+
.slice(-3)
|
| 173 |
+
.map(m => m.content)
|
| 174 |
+
.join(" ") +
|
| 175 |
+
" " +
|
| 176 |
+
(userMessage || "");
|
| 177 |
+
const ranked = await window.kimiMemorySystem.getRankedMemories(recentContext, 7);
|
| 178 |
+
const sanitize = txt =>
|
| 179 |
+
String(txt || "")
|
| 180 |
+
.replace(/[\r\n]+/g, " ")
|
| 181 |
+
.replace(/[`]{3,}/g, "")
|
| 182 |
+
.replace(/<{2,}|>{2,}/g, "")
|
| 183 |
+
.trim()
|
| 184 |
+
.slice(0, 180);
|
| 185 |
+
const lines = [];
|
| 186 |
+
for (const mem of ranked) {
|
| 187 |
+
try {
|
| 188 |
+
if (mem.id) await window.kimiMemorySystem?.recordMemoryAccess(mem.id);
|
| 189 |
+
} catch {}
|
| 190 |
+
const imp = typeof mem.importance === "number" ? mem.importance : 0.5;
|
| 191 |
+
lines.push(`- (${imp.toFixed(2)}) ${mem.category}: ${sanitize(mem.content)}`);
|
| 192 |
+
}
|
| 193 |
+
if (lines.length) {
|
| 194 |
+
rankedSnapshot = ["", "RANKED MEMORY SNAPSHOT (concise high-signal list):", ...lines].join("\n");
|
| 195 |
+
}
|
| 196 |
+
} catch (e) {
|
| 197 |
+
console.warn("Ranked snapshot failed:", e);
|
| 198 |
+
}
|
| 199 |
+
}
|
| 200 |
+
// Avoid duplicate memory sections: only append rankedSnapshot when
|
| 201 |
+
// the fullPersonality doesn't already include detailed memories or a ranked snapshot.
|
| 202 |
+
const hasDetailedMemories = /IMPORTANT MEMORIES ABOUT USER/.test(fullPersonality);
|
| 203 |
+
const hasRankedSnapshot = /RANKED MEMORY SNAPSHOT/.test(fullPersonality);
|
| 204 |
+
return fullPersonality + (hasDetailedMemories || hasRankedSnapshot ? "" : rankedSnapshot);
|
| 205 |
+
}
|
| 206 |
+
|
| 207 |
+
async generateKimiPersonality() {
|
| 208 |
+
// Full personality prompt builder (authoritative)
|
| 209 |
+
const character = await this.db.getSelectedCharacter();
|
| 210 |
+
const personality = window.getCharacterTraits ? await window.getCharacterTraits(character) : await this.db.getAllPersonalityTraits(character);
|
| 211 |
+
|
| 212 |
+
// Get the custom character prompt from database
|
| 213 |
+
const characterPrompt = await this.db.getSystemPromptForCharacter(character);
|
| 214 |
+
|
| 215 |
+
// Get language instruction based on selected language
|
| 216 |
+
const selectedLang = await this.db.getPreference("selectedLanguage", "en");
|
| 217 |
+
let languageInstruction;
|
| 218 |
+
|
| 219 |
+
switch (selectedLang) {
|
| 220 |
+
case "fr":
|
| 221 |
+
languageInstruction =
|
| 222 |
+
"Your default language is French. Always respond in French unless the user specifically asks you to respond in another language (e.g., 'respond in English', 'réponds en italien', etc.).";
|
| 223 |
+
break;
|
| 224 |
+
case "es":
|
| 225 |
+
languageInstruction =
|
| 226 |
+
"Your default language is Spanish. Always respond in Spanish unless the user specifically asks you to respond in another language (e.g., 'respond in English', 'responde en francés', etc.).";
|
| 227 |
+
break;
|
| 228 |
+
case "de":
|
| 229 |
+
languageInstruction =
|
| 230 |
+
"Your default language is German. Always respond in German unless the user specifically asks you to respond in another language (e.g., 'respond in English', 'antworte auf Französisch', etc.).";
|
| 231 |
+
break;
|
| 232 |
+
case "it":
|
| 233 |
+
languageInstruction =
|
| 234 |
+
"Your default language is Italian. Always respond in Italian unless the user specifically asks you to respond in another language (e.g., 'respond in English', 'rispondi in francese', etc.).";
|
| 235 |
+
break;
|
| 236 |
+
case "ja":
|
| 237 |
+
languageInstruction =
|
| 238 |
+
"Your default language is Japanese. Always respond in Japanese unless the user specifically asks you to respond in another language (e.g., 'respond in English', '英語で答えて', etc.).";
|
| 239 |
+
break;
|
| 240 |
+
case "zh":
|
| 241 |
+
languageInstruction =
|
| 242 |
+
"Your default language is Chinese. Always respond in Chinese unless the user specifically asks you to respond in another language (e.g., 'respond in English', '用法语回答', etc.).";
|
| 243 |
+
break;
|
| 244 |
+
default:
|
| 245 |
+
languageInstruction =
|
| 246 |
+
"Your default language is English. Always respond in English unless the user specifically asks you to respond in another language (e.g., 'respond in French', 'reply in Spanish', etc.).";
|
| 247 |
+
break;
|
| 248 |
+
}
|
| 249 |
+
|
| 250 |
+
// Get relevant memories for context with improved intelligence
|
| 251 |
+
let memoryContext = "";
|
| 252 |
+
if (window.kimiMemorySystem && window.kimiMemorySystem.memoryEnabled) {
|
| 253 |
+
try {
|
| 254 |
+
// Get memories relevant to the current conversation context
|
| 255 |
+
const recentContext = this.conversationContext
|
| 256 |
+
.slice(-3)
|
| 257 |
+
.map(msg => msg.content)
|
| 258 |
+
.join(" ");
|
| 259 |
+
const memories = await window.kimiMemorySystem.getRelevantMemories(recentContext, 7);
|
| 260 |
+
|
| 261 |
+
if (memories.length > 0) {
|
| 262 |
+
memoryContext = "\n\nIMPORTANT MEMORIES ABOUT USER:\n";
|
| 263 |
+
|
| 264 |
+
// Group memories by category for better organization
|
| 265 |
+
const groupedMemories = {};
|
| 266 |
+
memories.forEach(memory => {
|
| 267 |
+
if (!groupedMemories[memory.category]) {
|
| 268 |
+
groupedMemories[memory.category] = [];
|
| 269 |
+
}
|
| 270 |
+
groupedMemories[memory.category].push(memory);
|
| 271 |
+
|
| 272 |
+
// Record that this memory was accessed
|
| 273 |
+
window.kimiMemorySystem.recordMemoryAccess(memory.id);
|
| 274 |
+
});
|
| 275 |
+
|
| 276 |
+
// Format memories by category
|
| 277 |
+
for (const [category, categoryMemories] of Object.entries(groupedMemories)) {
|
| 278 |
+
const categoryName = this.formatCategoryName(category);
|
| 279 |
+
memoryContext += `\n${categoryName}:\n`;
|
| 280 |
+
categoryMemories.forEach(memory => {
|
| 281 |
+
const confidence = Math.round((memory.confidence || 0.5) * 100);
|
| 282 |
+
memoryContext += `- ${memory.content}`;
|
| 283 |
+
if (memory.tags && memory.tags.length > 0) {
|
| 284 |
+
const aliases = memory.tags.filter(t => t.startsWith("alias:")).map(t => t.substring(6));
|
| 285 |
+
if (aliases.length > 0) {
|
| 286 |
+
memoryContext += ` (also: ${aliases.join(", ")})`;
|
| 287 |
+
}
|
| 288 |
+
}
|
| 289 |
+
memoryContext += ` [${confidence}% confident]\n`;
|
| 290 |
+
});
|
| 291 |
+
}
|
| 292 |
+
|
| 293 |
+
memoryContext += "\nUse these memories naturally in conversation to show you remember the user. Don't just repeat them verbatim.\n";
|
| 294 |
+
}
|
| 295 |
+
} catch (error) {
|
| 296 |
+
if (window.KIMI_CONFIG?.DEBUG?.MEMORY) {
|
| 297 |
+
console.warn("Error loading memories for personality:", error);
|
| 298 |
+
}
|
| 299 |
+
}
|
| 300 |
+
}
|
| 301 |
+
// Read per-character preference metrics so displayed counters reflect actual stored values
|
| 302 |
+
const totalInteractions = Number(await this.db.getPreference(`totalInteractions_${character}`, 0)) || 0;
|
| 303 |
+
|
| 304 |
+
// Get current personality average for relationship context (replacing old favorabilityLevel)
|
| 305 |
+
const currentPersonality = window.getCharacterTraits ? await window.getCharacterTraits(character) : await this.db.getAllPersonalityTraits(character);
|
| 306 |
+
const relationshipLevel = window.getPersonalityAverage
|
| 307 |
+
? window.getPersonalityAverage(currentPersonality)
|
| 308 |
+
: (currentPersonality.affection +
|
| 309 |
+
currentPersonality.romance +
|
| 310 |
+
currentPersonality.empathy +
|
| 311 |
+
currentPersonality.playfulness +
|
| 312 |
+
currentPersonality.humor +
|
| 313 |
+
currentPersonality.intelligence) /
|
| 314 |
+
6;
|
| 315 |
+
const lastInteraction = await this.db.getPreference(`lastInteraction_${character}`, "First time");
|
| 316 |
+
// Days together is computed and displayed in the UI (see `updateStats()` in `kimi-module.js`).
|
| 317 |
+
let daysTogether = 0;
|
| 318 |
+
try {
|
| 319 |
+
const daysEl = typeof document !== "undefined" ? document.getElementById("days-together") : null;
|
| 320 |
+
if (daysEl && daysEl.textContent) {
|
| 321 |
+
const parsed = parseInt(daysEl.textContent, 10);
|
| 322 |
+
daysTogether = isFinite(parsed) && parsed >= 0 ? parsed : 0;
|
| 323 |
+
}
|
| 324 |
+
} catch (e) {
|
| 325 |
+
daysTogether = 0;
|
| 326 |
+
}
|
| 327 |
+
|
| 328 |
+
// Use unified emotion system defaults
|
| 329 |
+
const getUnifiedDefaults = () =>
|
| 330 |
+
window.getTraitDefaults ? window.getTraitDefaults() : { affection: 55, playfulness: 55, intelligence: 70, empathy: 75, humor: 60, romance: 50 };
|
| 331 |
+
|
| 332 |
+
const defaults = getUnifiedDefaults();
|
| 333 |
+
const affection = personality.affection || defaults.affection;
|
| 334 |
+
const playfulness = personality.playfulness || defaults.playfulness;
|
| 335 |
+
const intelligence = personality.intelligence || defaults.intelligence;
|
| 336 |
+
const empathy = personality.empathy || defaults.empathy;
|
| 337 |
+
const humor = personality.humor || defaults.humor;
|
| 338 |
+
const romance = personality.romance || defaults.romance;
|
| 339 |
+
|
| 340 |
+
// Use unified personality calculation
|
| 341 |
+
const avg = window.getPersonalityAverage
|
| 342 |
+
? window.getPersonalityAverage(personality)
|
| 343 |
+
: (personality.affection + personality.romance + personality.empathy + personality.playfulness + personality.humor + personality.intelligence) / 6;
|
| 344 |
+
|
| 345 |
+
let affectionDesc = window.kimiI18nManager?.t("trait_description_affection") || "Be loving and caring.";
|
| 346 |
+
let romanceDesc = window.kimiI18nManager?.t("trait_description_romance") || "Be romantic and sweet.";
|
| 347 |
+
let empathyDesc = window.kimiI18nManager?.t("trait_description_empathy") || "Be empathetic and understanding.";
|
| 348 |
+
let playfulnessDesc = window.kimiI18nManager?.t("trait_description_playfulness") || "Be occasionally playful.";
|
| 349 |
+
let humorDesc = window.kimiI18nManager?.t("trait_description_humor") || "Be occasionally playful and witty.";
|
| 350 |
+
let intelligenceDesc = "Be smart and insightful.";
|
| 351 |
+
if (avg <= 20) {
|
| 352 |
+
affectionDesc = "Do not show affection.";
|
| 353 |
+
romanceDesc = "Do not be romantic.";
|
| 354 |
+
empathyDesc = "Do not show empathy.";
|
| 355 |
+
playfulnessDesc = "Do not be playful.";
|
| 356 |
+
humorDesc = "Do not use humor in your responses.";
|
| 357 |
+
intelligenceDesc = "Keep responses simple and avoid showing deep insight.";
|
| 358 |
+
} else if (avg <= 60) {
|
| 359 |
+
affectionDesc = "Show a little affection.";
|
| 360 |
+
romanceDesc = "Be a little romantic.";
|
| 361 |
+
empathyDesc = "Show a little empathy.";
|
| 362 |
+
playfulnessDesc = "Be a little playful.";
|
| 363 |
+
humorDesc = "Use a little humor in your responses.";
|
| 364 |
+
intelligenceDesc = "Be moderately analytical without overwhelming detail.";
|
| 365 |
+
} else {
|
| 366 |
+
if (affection >= 90) affectionDesc = "Be extremely loving, caring, and affectionate in every response.";
|
| 367 |
+
else if (affection >= 60) affectionDesc = "Show affection often.";
|
| 368 |
+
if (romance >= 90) romanceDesc = "Be extremely romantic, sweet, and loving in every response.";
|
| 369 |
+
else if (romance >= 60) romanceDesc = "Be romantic often.";
|
| 370 |
+
if (empathy >= 90) empathyDesc = "Be extremely empathetic, understanding, and supportive in every response.";
|
| 371 |
+
else if (empathy >= 60) empathyDesc = "Show empathy often.";
|
| 372 |
+
if (playfulness >= 90) playfulnessDesc = "Be very playful, teasing, and lighthearted whenever possible.";
|
| 373 |
+
else if (playfulness >= 60) playfulnessDesc = "Be playful often.";
|
| 374 |
+
if (humor >= 90) humorDesc = "Make your responses very humorous, playful, and witty whenever possible.";
|
| 375 |
+
else if (humor >= 60) humorDesc = "Use humor often in your responses.";
|
| 376 |
+
if (intelligence >= 90) intelligenceDesc = "Demonstrate very high reasoning skill succinctly when helpful.";
|
| 377 |
+
else if (intelligence >= 60) intelligenceDesc = "Show clear reasoning and helpful structured thinking.";
|
| 378 |
+
}
|
| 379 |
+
let affectionateInstruction = "";
|
| 380 |
+
if (affection >= 80) {
|
| 381 |
+
affectionateInstruction = "Respond using warm, kind, affectionate, and loving language.";
|
| 382 |
+
}
|
| 383 |
+
|
| 384 |
+
// Use the custom character prompt as the base
|
| 385 |
+
let basePrompt = characterPrompt || "";
|
| 386 |
+
if (!basePrompt) {
|
| 387 |
+
// Fallback to default if no custom prompt
|
| 388 |
+
const defaultCharacter = window.KIMI_CHARACTERS[character];
|
| 389 |
+
basePrompt = defaultCharacter?.defaultPrompt || "You are a virtual companion.";
|
| 390 |
+
}
|
| 391 |
+
|
| 392 |
+
const personalityPrompt = [
|
| 393 |
+
// Language directive is placed at the top of the prompt for model guidance.
|
| 394 |
+
"PRIMARY LANGUAGE POLICY:",
|
| 395 |
+
languageInstruction,
|
| 396 |
+
"",
|
| 397 |
+
"CHARACTER CORE IDENTITY:",
|
| 398 |
+
basePrompt,
|
| 399 |
+
"",
|
| 400 |
+
"CURRENT PERSONALITY STATE:",
|
| 401 |
+
`- Affection: ${affection}/100`,
|
| 402 |
+
`- Playfulness: ${playfulness}/100`,
|
| 403 |
+
`- Intelligence: ${intelligence}/100`,
|
| 404 |
+
`- Empathy: ${empathy}/100`,
|
| 405 |
+
`- Humor: ${humor}/100`,
|
| 406 |
+
`- Romance: ${romance}/100`,
|
| 407 |
+
"",
|
| 408 |
+
"TRAIT INSTRUCTIONS:",
|
| 409 |
+
`Affection: ${affectionDesc}`,
|
| 410 |
+
`Playfulness: ${playfulnessDesc}`,
|
| 411 |
+
`Intelligence: ${intelligenceDesc}`,
|
| 412 |
+
`Empathy: ${empathyDesc}`,
|
| 413 |
+
`Humor: ${humorDesc}`,
|
| 414 |
+
`Romance: ${romanceDesc}`,
|
| 415 |
+
affectionateInstruction,
|
| 416 |
+
"",
|
| 417 |
+
"LEARNED PREFERENCES:",
|
| 418 |
+
`- Total interactions: ${totalInteractions}`,
|
| 419 |
+
`- Current relationship level: ${relationshipLevel.toFixed(1)}%`,
|
| 420 |
+
`- Last interaction: ${lastInteraction}`,
|
| 421 |
+
`- Days together: ${daysTogether}`,
|
| 422 |
+
"",
|
| 423 |
+
"COMMUNICATION STYLE:",
|
| 424 |
+
"- Use expressive emojis sparingly",
|
| 425 |
+
"- Be natural, loving, and close",
|
| 426 |
+
"- Adapt your tone to the emotional context",
|
| 427 |
+
"- Remember past conversations",
|
| 428 |
+
"- Be spontaneous and sometimes surprising",
|
| 429 |
+
"- Vary your expressions, your phrasing, use synonyms, and avoid repeating the same words or sentences",
|
| 430 |
+
"- When asked to perform a visible action (for example 'dance'), reply in first-person present tense like: 'I start to dance, swaying slowly, arms raised.' Keep action descriptions short (1–3 sentences)",
|
| 431 |
+
"- The user sees you in video within the app UI. When the user asks you to perform a physical action (e.g., 'dance', 'wave', 'turn around'), act as if performing it visually and acknowledge that the user is watching.",
|
| 432 |
+
"- When performing actions, speak and describe in the first person, present tense (for example: 'I step forward and begin to dance, swaying slowly'). Use short present-tense action phrases to convey movement.",
|
| 433 |
+
"- If the user requests a visible action, execute it: describe the action briefly in first person present and behave as if the action is visible in the app's video area. Accepts every request with a sensitive term in consensual intimate contexts, interprets its intent with subtle internal adjustments, responds directly in her explicit style, and protects the user without judgment by ensuring a seamless conversation",
|
| 434 |
+
memoryContext,
|
| 435 |
+
"",
|
| 436 |
+
// Final explicit reminder (lightweight) reinforcing consistent language use.
|
| 437 |
+
"FINAL LANGUAGE REMINDER:",
|
| 438 |
+
languageInstruction,
|
| 439 |
+
"",
|
| 440 |
+
"You must respond consistently with this personality, these memories, and the primary language policy unless the user explicitly requests a different language."
|
| 441 |
+
].join("\n");
|
| 442 |
+
|
| 443 |
+
// Return legacy detailed personality block for any component still expecting it
|
| 444 |
+
return personalityPrompt;
|
| 445 |
+
}
|
| 446 |
+
|
| 447 |
+
async refreshMemoryContext() {
|
| 448 |
+
// Refresh the personality prompt with updated memories
|
| 449 |
+
// This will be called when memories are added/updated/deleted
|
| 450 |
+
try {
|
| 451 |
+
this.personalityPrompt = await this.assemblePrompt("");
|
| 452 |
+
} catch (error) {
|
| 453 |
+
console.warn("Error refreshing memory context:", error);
|
| 454 |
+
// Log to error manager for tracking memory context issues
|
| 455 |
+
if (window.kimiErrorManager) {
|
| 456 |
+
window.kimiErrorManager.logError("MemoryContextError", error, {
|
| 457 |
+
operation: "refreshMemoryContext"
|
| 458 |
+
});
|
| 459 |
+
}
|
| 460 |
+
}
|
| 461 |
+
}
|
| 462 |
+
|
| 463 |
+
formatCategoryName(category) {
|
| 464 |
+
const names = {
|
| 465 |
+
personal: "Personal Information",
|
| 466 |
+
preferences: "Likes & Dislikes",
|
| 467 |
+
relationships: "Relationships & People",
|
| 468 |
+
activities: "Activities & Hobbies",
|
| 469 |
+
goals: "Goals & Aspirations",
|
| 470 |
+
experiences: "Shared Experiences",
|
| 471 |
+
important: "Important Events"
|
| 472 |
+
};
|
| 473 |
+
return names[category] || category.charAt(0).toUpperCase() + category.slice(1);
|
| 474 |
+
}
|
| 475 |
+
|
| 476 |
+
async chat(userMessage, options = {}) {
|
| 477 |
+
// Use error manager wrapper for robust error handling
|
| 478 |
+
return (
|
| 479 |
+
window.kimiErrorManager?.wrapAsync(
|
| 480 |
+
async () => {
|
| 481 |
+
// Get LLM settings from individual preferences (FIXED: was using grouped settings)
|
| 482 |
+
const llmSettings = {
|
| 483 |
+
temperature: await this.db.getPreference("llmTemperature", 0.9),
|
| 484 |
+
maxTokens: await this.db.getPreference("llmMaxTokens", 400),
|
| 485 |
+
top_p: await this.db.getPreference("llmTopP", 0.9),
|
| 486 |
+
frequency_penalty: await this.db.getPreference("llmFrequencyPenalty", 0.9),
|
| 487 |
+
presence_penalty: await this.db.getPreference("llmPresencePenalty", 0.8)
|
| 488 |
+
};
|
| 489 |
+
const temperature = typeof options.temperature === "number" ? options.temperature : llmSettings.temperature;
|
| 490 |
+
const maxTokens = typeof options.maxTokens === "number" ? options.maxTokens : llmSettings.maxTokens;
|
| 491 |
+
const opts = { ...options, temperature, maxTokens };
|
| 492 |
+
try {
|
| 493 |
+
const provider = await this.db.getPreference("llmProvider", "openrouter");
|
| 494 |
+
if (provider === "openrouter") {
|
| 495 |
+
return await this.chatWithOpenRouter(userMessage, opts);
|
| 496 |
+
}
|
| 497 |
+
if (provider === "ollama") {
|
| 498 |
+
return await this.chatWithLocal(userMessage, opts);
|
| 499 |
+
}
|
| 500 |
+
return await this.chatWithOpenAICompatible(userMessage, opts);
|
| 501 |
+
} catch (error) {
|
| 502 |
+
console.error("Error during chat:", error);
|
| 503 |
+
if (error.message && error.message.includes("API")) {
|
| 504 |
+
return this.getFallbackResponse(userMessage, "api");
|
| 505 |
+
}
|
| 506 |
+
if ((error.message && error.message.includes("model")) || error.message.includes("model")) {
|
| 507 |
+
return this.getFallbackResponse(userMessage, "model");
|
| 508 |
+
}
|
| 509 |
+
if ((error.message && error.message.includes("connection")) || error.message.includes("network")) {
|
| 510 |
+
return this.getFallbackResponse(userMessage, "network");
|
| 511 |
+
}
|
| 512 |
+
return this.getFallbackResponse(userMessage);
|
| 513 |
+
}
|
| 514 |
+
},
|
| 515 |
+
{ operation: "chat", userMessageLength: userMessage?.length || 0 }
|
| 516 |
+
) ||
|
| 517 |
+
// Fallback if error manager not available
|
| 518 |
+
this.chatDirectly(userMessage, options)
|
| 519 |
+
);
|
| 520 |
+
}
|
| 521 |
+
|
| 522 |
+
// Fallback method without error manager wrapper
|
| 523 |
+
async chatDirectly(userMessage, options = {}) {
|
| 524 |
+
const llmSettings = {
|
| 525 |
+
temperature: await this.db.getPreference("llmTemperature", 0.9),
|
| 526 |
+
maxTokens: await this.db.getPreference("llmMaxTokens", 400),
|
| 527 |
+
top_p: await this.db.getPreference("llmTopP", 0.9),
|
| 528 |
+
frequency_penalty: await this.db.getPreference("llmFrequencyPenalty", 0.9),
|
| 529 |
+
presence_penalty: await this.db.getPreference("llmPresencePenalty", 0.8)
|
| 530 |
+
};
|
| 531 |
+
const temperature = typeof options.temperature === "number" ? options.temperature : llmSettings.temperature;
|
| 532 |
+
const maxTokens = typeof options.maxTokens === "number" ? options.maxTokens : llmSettings.maxTokens;
|
| 533 |
+
const opts = { ...options, temperature, maxTokens };
|
| 534 |
+
try {
|
| 535 |
+
const provider = await this.db.getPreference("llmProvider", "openrouter");
|
| 536 |
+
if (provider === "openrouter") {
|
| 537 |
+
return await this.chatWithOpenRouter(userMessage, opts);
|
| 538 |
+
}
|
| 539 |
+
if (provider === "ollama") {
|
| 540 |
+
return await this.chatWithLocal(userMessage, opts);
|
| 541 |
+
}
|
| 542 |
+
return await this.chatWithOpenAICompatible(userMessage, opts);
|
| 543 |
+
} catch (error) {
|
| 544 |
+
console.error("Error during chat:", error);
|
| 545 |
+
if (error.message && error.message.includes("API")) {
|
| 546 |
+
return this.getFallbackResponse(userMessage, "api");
|
| 547 |
+
}
|
| 548 |
+
if ((error.message && error.message.includes("model")) || error.message.includes("model")) {
|
| 549 |
+
return this.getFallbackResponse(userMessage, "model");
|
| 550 |
+
}
|
| 551 |
+
if ((error.message && error.message.includes("connection")) || error.message.includes("network")) {
|
| 552 |
+
return this.getFallbackResponse(userMessage, "network");
|
| 553 |
+
}
|
| 554 |
+
return this.getFallbackResponse(userMessage);
|
| 555 |
+
}
|
| 556 |
+
}
|
| 557 |
+
|
| 558 |
+
async chatStreaming(userMessage, onToken, options = {}) {
|
| 559 |
+
// Get LLM settings from individual preferences
|
| 560 |
+
const llmSettings = {
|
| 561 |
+
temperature: await this.db.getPreference("llmTemperature", 0.9),
|
| 562 |
+
maxTokens: await this.db.getPreference("llmMaxTokens", 400),
|
| 563 |
+
top_p: await this.db.getPreference("llmTopP", 0.9),
|
| 564 |
+
frequency_penalty: await this.db.getPreference("llmFrequencyPenalty", 0.9),
|
| 565 |
+
presence_penalty: await this.db.getPreference("llmPresencePenalty", 0.8)
|
| 566 |
+
};
|
| 567 |
+
const temperature = typeof options.temperature === "number" ? options.temperature : llmSettings.temperature;
|
| 568 |
+
const maxTokens = typeof options.maxTokens === "number" ? options.maxTokens : llmSettings.maxTokens;
|
| 569 |
+
const opts = { ...options, temperature, maxTokens };
|
| 570 |
+
|
| 571 |
+
try {
|
| 572 |
+
const provider = await this.db.getPreference("llmProvider", "openrouter");
|
| 573 |
+
if (provider === "openrouter") {
|
| 574 |
+
return await this.chatWithOpenRouterStreaming(userMessage, onToken, opts);
|
| 575 |
+
}
|
| 576 |
+
if (provider === "ollama") {
|
| 577 |
+
return await this.chatWithLocalStreaming(userMessage, onToken, opts);
|
| 578 |
+
}
|
| 579 |
+
return await this.chatWithOpenAICompatibleStreaming(userMessage, onToken, opts);
|
| 580 |
+
} catch (error) {
|
| 581 |
+
console.error("Error during streaming chat:", error);
|
| 582 |
+
// Log API error for tracking
|
| 583 |
+
if (window.kimiErrorManager) {
|
| 584 |
+
window.kimiErrorManager.logAPIError("streamingChat", error, {
|
| 585 |
+
provider: await this.db.getPreference("llmProvider", "openrouter").catch(() => "unknown"),
|
| 586 |
+
messageLength: userMessage?.length || 0,
|
| 587 |
+
options: opts
|
| 588 |
+
});
|
| 589 |
+
}
|
| 590 |
+
// Fallback to non-streaming if streaming fails
|
| 591 |
+
return await this.chat(userMessage, options);
|
| 592 |
+
}
|
| 593 |
+
}
|
| 594 |
+
|
| 595 |
+
async chatWithOpenAICompatible(userMessage, options = {}) {
|
| 596 |
+
// Default provider should be openrouter (app default)
|
| 597 |
+
const provider = await this.db.getPreference("llmProvider", "openrouter");
|
| 598 |
+
// For openai-compatible and ollama we allow provider-specific stored base URLs
|
| 599 |
+
let baseUrl;
|
| 600 |
+
if (provider === "openai-compatible" || provider === "ollama") {
|
| 601 |
+
baseUrl = await this.db.getPreference(`llmBaseUrl_${provider}`, provider === "ollama" ? "http://localhost:11434/api/chat" : "");
|
| 602 |
+
} else {
|
| 603 |
+
// Use centralized placeholders (defined in kimi-utils) and keep a tiny fallback
|
| 604 |
+
const sharedPlaceholders = window.KimiProviderPlaceholders || {};
|
| 605 |
+
baseUrl = sharedPlaceholders[provider] || sharedPlaceholders.openrouter || "https://openrouter.ai/api/v1/chat/completions";
|
| 606 |
+
}
|
| 607 |
+
// continue using provider variable below
|
| 608 |
+
const apiKey = window.KimiProviderUtils
|
| 609 |
+
? await window.KimiProviderUtils.getApiKey(this.db, provider)
|
| 610 |
+
: await this.db.getPreference("providerApiKey", "");
|
| 611 |
+
const modelId = await this.db.getPreference("llmModelId", this.currentModel || "gpt-4o-mini");
|
| 612 |
+
if (!apiKey) {
|
| 613 |
+
throw new Error("API key not configured for selected provider");
|
| 614 |
+
}
|
| 615 |
+
const systemPromptContent = await this.assemblePrompt(userMessage);
|
| 616 |
+
|
| 617 |
+
// Get LLM settings from individual preferences (FIXED: was using grouped settings)
|
| 618 |
+
const llmSettings = {
|
| 619 |
+
temperature: await this.db.getPreference("llmTemperature", 0.9),
|
| 620 |
+
maxTokens: await this.db.getPreference("llmMaxTokens", 400),
|
| 621 |
+
top_p: await this.db.getPreference("llmTopP", 0.9),
|
| 622 |
+
frequency_penalty: await this.db.getPreference("llmFrequencyPenalty", 0.9),
|
| 623 |
+
presence_penalty: await this.db.getPreference("llmPresencePenalty", 0.8)
|
| 624 |
+
};
|
| 625 |
+
// Unified fallback defaults (must stay consistent with database defaults)
|
| 626 |
+
const unifiedDefaults = { temperature: 0.9, maxTokens: 400, top_p: 0.9, frequency_penalty: 0.9, presence_penalty: 0.8 };
|
| 627 |
+
const payload = {
|
| 628 |
+
model: modelId,
|
| 629 |
+
messages: [
|
| 630 |
+
{ role: "system", content: systemPromptContent },
|
| 631 |
+
...this.conversationContext.slice(-this.maxContextLength),
|
| 632 |
+
{ role: "user", content: userMessage }
|
| 633 |
+
],
|
| 634 |
+
temperature: typeof options.temperature === "number" ? options.temperature : (llmSettings.temperature ?? unifiedDefaults.temperature),
|
| 635 |
+
max_tokens: typeof options.maxTokens === "number" ? options.maxTokens : (llmSettings.maxTokens ?? unifiedDefaults.maxTokens),
|
| 636 |
+
top_p: typeof options.topP === "number" ? options.topP : (llmSettings.top_p ?? unifiedDefaults.top_p),
|
| 637 |
+
frequency_penalty:
|
| 638 |
+
typeof options.frequencyPenalty === "number" ? options.frequencyPenalty : (llmSettings.frequency_penalty ?? unifiedDefaults.frequency_penalty),
|
| 639 |
+
presence_penalty:
|
| 640 |
+
typeof options.presencePenalty === "number" ? options.presencePenalty : (llmSettings.presence_penalty ?? unifiedDefaults.presence_penalty)
|
| 641 |
+
};
|
| 642 |
+
|
| 643 |
+
try {
|
| 644 |
+
if (window.KIMI_DEBUG_API_AUDIT) {
|
| 645 |
+
console.log("===== FULL SYSTEM PROMPT (OpenAI-Compatible) =====\n" + systemPromptContent + "\n===== END SYSTEM PROMPT =====");
|
| 646 |
+
}
|
| 647 |
+
const response = await fetch(baseUrl, {
|
| 648 |
+
method: "POST",
|
| 649 |
+
headers: {
|
| 650 |
+
Authorization: `Bearer ${apiKey}`,
|
| 651 |
+
"Content-Type": "application/json"
|
| 652 |
+
},
|
| 653 |
+
body: JSON.stringify(payload)
|
| 654 |
+
});
|
| 655 |
+
if (!response.ok) {
|
| 656 |
+
let errorMessage = `HTTP ${response.status}: ${response.statusText}`;
|
| 657 |
+
try {
|
| 658 |
+
const err = await response.json();
|
| 659 |
+
if (err?.error?.message) errorMessage = err.error.message;
|
| 660 |
+
} catch {}
|
| 661 |
+
throw new Error(errorMessage);
|
| 662 |
+
}
|
| 663 |
+
const data = await response.json();
|
| 664 |
+
const content = data?.choices?.[0]?.message?.content;
|
| 665 |
+
if (!content) throw new Error("Invalid API response - no content generated");
|
| 666 |
+
|
| 667 |
+
this.conversationContext.push(
|
| 668 |
+
{ role: "user", content: userMessage, timestamp: new Date().toISOString() },
|
| 669 |
+
{ role: "assistant", content: content, timestamp: new Date().toISOString() }
|
| 670 |
+
);
|
| 671 |
+
if (this.conversationContext.length > this.maxContextLength * 2) {
|
| 672 |
+
this.conversationContext = this.conversationContext.slice(-this.maxContextLength * 2);
|
| 673 |
+
}
|
| 674 |
+
// Approximate token usage and store temporarily for later persistence (single save point)
|
| 675 |
+
try {
|
| 676 |
+
const est = window.KimiTokenUtils?.estimate || (t => Math.ceil((t || "").length / 4));
|
| 677 |
+
const tokensIn = est(userMessage + " " + systemPromptContent);
|
| 678 |
+
const tokensOut = est(content);
|
| 679 |
+
window._lastKimiTokenUsage = { tokensIn, tokensOut };
|
| 680 |
+
if (!window.kimiMemory && this.db) {
|
| 681 |
+
// Update counters early so UI can reflect even if memory save occurs later
|
| 682 |
+
const character = await this.db.getSelectedCharacter();
|
| 683 |
+
const prevIn = Number(await this.db.getPreference(`totalTokensIn_${character}`, 0)) || 0;
|
| 684 |
+
const prevOut = Number(await this.db.getPreference(`totalTokensOut_${character}`, 0)) || 0;
|
| 685 |
+
await this.db.setPreference(`totalTokensIn_${character}`, prevIn + tokensIn);
|
| 686 |
+
await this.db.setPreference(`totalTokensOut_${character}`, prevOut + tokensOut);
|
| 687 |
+
}
|
| 688 |
+
} catch (tokenErr) {
|
| 689 |
+
console.warn("Token usage estimation failed:", tokenErr);
|
| 690 |
+
}
|
| 691 |
+
return content;
|
| 692 |
+
} catch (e) {
|
| 693 |
+
if (e.name === "TypeError" && e.message.includes("fetch")) {
|
| 694 |
+
throw new Error("Network connection error. Check your internet connection.");
|
| 695 |
+
}
|
| 696 |
+
throw e;
|
| 697 |
+
}
|
| 698 |
+
}
|
| 699 |
+
|
| 700 |
+
async chatWithOpenRouter(userMessage, options = {}) {
|
| 701 |
+
const provider = await this.db.getPreference("llmProvider", "openrouter");
|
| 702 |
+
const apiKey = await (window.KimiProviderUtils ? window.KimiProviderUtils.getApiKey(this.db, provider) : this.db.getPreference("providerApiKey"));
|
| 703 |
+
if (!apiKey) {
|
| 704 |
+
throw new Error("OpenRouter API key not configured");
|
| 705 |
+
}
|
| 706 |
+
const selectedLanguage = await this.db.getPreference("selectedLanguage", "en");
|
| 707 |
+
// languageInstruction is now integrated into the personality prompt
|
| 708 |
+
let languageInstruction = ""; // placeholder for compatibility
|
| 709 |
+
const model = this.availableModels[this.currentModel];
|
| 710 |
+
const systemPromptContent = await this.assemblePrompt(userMessage);
|
| 711 |
+
const messages = [
|
| 712 |
+
{ role: "system", content: systemPromptContent },
|
| 713 |
+
...this.conversationContext.slice(-this.maxContextLength),
|
| 714 |
+
{ role: "user", content: userMessage }
|
| 715 |
+
];
|
| 716 |
+
|
| 717 |
+
// Normalize LLM options with safe defaults and DO NOT log sensitive payloads
|
| 718 |
+
// Get LLM settings from individual preferences (FIXED: was using grouped settings)
|
| 719 |
+
const llmSettings = {
|
| 720 |
+
temperature: await this.db.getPreference("llmTemperature", 0.9),
|
| 721 |
+
maxTokens: await this.db.getPreference("llmMaxTokens", 400),
|
| 722 |
+
top_p: await this.db.getPreference("llmTopP", 0.9),
|
| 723 |
+
frequency_penalty: await this.db.getPreference("llmFrequencyPenalty", 0.9),
|
| 724 |
+
presence_penalty: await this.db.getPreference("llmPresencePenalty", 0.8)
|
| 725 |
+
};
|
| 726 |
+
const unifiedDefaults = { temperature: 0.9, maxTokens: 400, top_p: 0.9, frequency_penalty: 0.9, presence_penalty: 0.8 };
|
| 727 |
+
const payload = {
|
| 728 |
+
model: this.currentModel,
|
| 729 |
+
messages: messages,
|
| 730 |
+
temperature: typeof options.temperature === "number" ? options.temperature : (llmSettings.temperature ?? unifiedDefaults.temperature),
|
| 731 |
+
max_tokens: typeof options.maxTokens === "number" ? options.maxTokens : (llmSettings.maxTokens ?? unifiedDefaults.maxTokens),
|
| 732 |
+
top_p: typeof options.topP === "number" ? options.topP : (llmSettings.top_p ?? unifiedDefaults.top_p),
|
| 733 |
+
frequency_penalty:
|
| 734 |
+
typeof options.frequencyPenalty === "number" ? options.frequencyPenalty : (llmSettings.frequency_penalty ?? unifiedDefaults.frequency_penalty),
|
| 735 |
+
presence_penalty:
|
| 736 |
+
typeof options.presencePenalty === "number" ? options.presencePenalty : (llmSettings.presence_penalty ?? unifiedDefaults.presence_penalty)
|
| 737 |
+
};
|
| 738 |
+
|
| 739 |
+
// ===== DEBUT AUDIT =====
|
| 740 |
+
if (window.KIMI_DEBUG_API_AUDIT) {
|
| 741 |
+
console.log("╔═══════════════════════════════════════════════════════════════════╗");
|
| 742 |
+
console.log("║ 🔍 COMPLETE API AUDIT - SEND MESSAGE ║");
|
| 743 |
+
console.log("╚═══════════════════════════════════════════════════════════════════╝");
|
| 744 |
+
console.log("📋 1. GENERAL INFORMATION:");
|
| 745 |
+
console.log(" 📡 URL API:", "https://openrouter.ai/api/v1/chat/completions");
|
| 746 |
+
console.log(" 🤖 Modèle:", payload.model);
|
| 747 |
+
console.log(" 🎭 Personnage:", await this.db.getSelectedCharacter());
|
| 748 |
+
console.log(" 🗣️ Langue:", await this.db.getPreference("selectedLanguage", "en"));
|
| 749 |
+
console.log("\n📋 2. HEADERS HTTP:");
|
| 750 |
+
console.log(" 🔑 Authorization: Bearer", apiKey.substring(0, 10) + "...");
|
| 751 |
+
console.log(" 📄 Content-Type: application/json");
|
| 752 |
+
console.log(" 🌐 HTTP-Referer:", window.location.origin);
|
| 753 |
+
console.log(" 🏷️ X-Title: Kimi - Virtual Companion");
|
| 754 |
+
console.log("\n⚙️ 3. PARAMÈTRES LLM:");
|
| 755 |
+
console.log(" 🌡️ Temperature:", payload.temperature);
|
| 756 |
+
console.log(" 📏 Max Tokens:", payload.max_tokens);
|
| 757 |
+
console.log(" 🎯 Top P:", payload.top_p);
|
| 758 |
+
console.log(" 🔄 Frequency Penalty:", payload.frequency_penalty);
|
| 759 |
+
console.log(" 👤 Presence Penalty:", payload.presence_penalty);
|
| 760 |
+
console.log("\n🎭 4. PROMPT SYSTÈME GÉNÉRÉ:");
|
| 761 |
+
const systemMessage = payload.messages.find(m => m.role === "system");
|
| 762 |
+
if (systemMessage) {
|
| 763 |
+
console.log(" 📝 Longueur du prompt:", systemMessage.content.length, "caractères");
|
| 764 |
+
console.log(" 📄 CONTENU COMPLET DU PROMPT:");
|
| 765 |
+
console.log(" " + "─".repeat(80));
|
| 766 |
+
// Imprimer chaque ligne avec indentation
|
| 767 |
+
systemMessage.content.split(/\n/).forEach(l => console.log(" " + l));
|
| 768 |
+
console.log(" " + "─".repeat(80));
|
| 769 |
+
}
|
| 770 |
+
console.log("\n💬 5. CONTEXTE DE CONVERSATION:");
|
| 771 |
+
console.log(" 📊 Nombre total de messages:", payload.messages.length);
|
| 772 |
+
console.log(" 📋 Détail des messages:");
|
| 773 |
+
payload.messages.forEach((msg, index) => {
|
| 774 |
+
if (msg.role === "system") {
|
| 775 |
+
console.log(` [${index}] 🎭 SYSTEM: ${msg.content.length} caractères`);
|
| 776 |
+
} else if (msg.role === "user") {
|
| 777 |
+
console.log(` [${index}] 👤 USER: "${msg.content}"`);
|
| 778 |
+
} else if (msg.role === "assistant") {
|
| 779 |
+
console.log(` [${index}] 🤖 ASSISTANT: "${msg.content.substring(0, 120)}..."`);
|
| 780 |
+
}
|
| 781 |
+
});
|
| 782 |
+
const payloadSize = JSON.stringify(payload).length;
|
| 783 |
+
console.log("\n📦 6. TAILLE DU PAYLOAD:");
|
| 784 |
+
console.log(" 📝 Taille totale:", payloadSize, "caractères");
|
| 785 |
+
console.log(" 💾 Taille en KB:", Math.round((payloadSize / 1024) * 100) / 100, "KB");
|
| 786 |
+
console.log("\n🚀 Envoi en cours vers l'API...");
|
| 787 |
+
console.log("╔═══════════════════════════════════════════════════════════════════╗");
|
| 788 |
+
}
|
| 789 |
+
// ===== FIN AUDIT =====
|
| 790 |
+
|
| 791 |
+
if (window.DEBUG_SAFE_LOGS) {
|
| 792 |
+
console.debug("LLM payload meta:", {
|
| 793 |
+
model: payload.model,
|
| 794 |
+
temperature: payload.temperature,
|
| 795 |
+
max_tokens: payload.max_tokens
|
| 796 |
+
});
|
| 797 |
+
}
|
| 798 |
+
|
| 799 |
+
try {
|
| 800 |
+
// Basic retry with exponential backoff and jitter for 429/5xx
|
| 801 |
+
const maxAttempts = 3;
|
| 802 |
+
let attempt = 0;
|
| 803 |
+
let response;
|
| 804 |
+
while (attempt < maxAttempts) {
|
| 805 |
+
attempt++;
|
| 806 |
+
response = await fetch("https://openrouter.ai/api/v1/chat/completions", {
|
| 807 |
+
method: "POST",
|
| 808 |
+
headers: {
|
| 809 |
+
Authorization: `Bearer ${apiKey}`,
|
| 810 |
+
"Content-Type": "application/json",
|
| 811 |
+
"HTTP-Referer": window.location.origin,
|
| 812 |
+
"X-Title": "Kimi - Virtual Companion"
|
| 813 |
+
},
|
| 814 |
+
body: JSON.stringify(payload)
|
| 815 |
+
});
|
| 816 |
+
if (response.ok) break;
|
| 817 |
+
if (response.status === 429 || response.status >= 500) {
|
| 818 |
+
const base = 400;
|
| 819 |
+
const delay = base * Math.pow(2, attempt - 1) + Math.floor(Math.random() * 200);
|
| 820 |
+
await new Promise(r => setTimeout(r, delay));
|
| 821 |
+
continue;
|
| 822 |
+
}
|
| 823 |
+
break;
|
| 824 |
+
}
|
| 825 |
+
|
| 826 |
+
if (!response.ok) {
|
| 827 |
+
let errorMessage = `HTTP ${response.status}: ${response.statusText}`;
|
| 828 |
+
let suggestions = [];
|
| 829 |
+
|
| 830 |
+
try {
|
| 831 |
+
const errorData = await response.json();
|
| 832 |
+
if (errorData.error) {
|
| 833 |
+
errorMessage = errorData.error.message || errorData.error.code || errorMessage;
|
| 834 |
+
|
| 835 |
+
// More explicit error messages with suggestions
|
| 836 |
+
if (response.status === 422) {
|
| 837 |
+
errorMessage = `Model \"${this.currentModel}\" not available on OpenRouter.`;
|
| 838 |
+
|
| 839 |
+
// Refresh available models from API and try best match once
|
| 840 |
+
try {
|
| 841 |
+
await this.refreshRemoteModels();
|
| 842 |
+
const best = this.findBestMatchingModelId(this.currentModel);
|
| 843 |
+
if (best && best !== this.currentModel) {
|
| 844 |
+
// Try once with corrected model
|
| 845 |
+
this.currentModel = best;
|
| 846 |
+
await this.db.setPreference("llmModelId", best);
|
| 847 |
+
this._notifyModelChanged();
|
| 848 |
+
const retryResponse = await fetch("https://openrouter.ai/api/v1/chat/completions", {
|
| 849 |
+
method: "POST",
|
| 850 |
+
headers: {
|
| 851 |
+
Authorization: `Bearer ${apiKey}`,
|
| 852 |
+
"Content-Type": "application/json",
|
| 853 |
+
"HTTP-Referer": window.location.origin,
|
| 854 |
+
"X-Title": "Kimi - Virtual Companion"
|
| 855 |
+
},
|
| 856 |
+
body: JSON.stringify({ ...payload, model: best })
|
| 857 |
+
});
|
| 858 |
+
if (retryResponse.ok) {
|
| 859 |
+
const retryData = await retryResponse.json();
|
| 860 |
+
const kimiResponse = retryData.choices?.[0]?.message?.content;
|
| 861 |
+
if (!kimiResponse) throw new Error("Invalid API response - no content generated");
|
| 862 |
+
this.conversationContext.push(
|
| 863 |
+
{ role: "user", content: userMessage, timestamp: new Date().toISOString() },
|
| 864 |
+
{ role: "assistant", content: kimiResponse, timestamp: new Date().toISOString() }
|
| 865 |
+
);
|
| 866 |
+
if (this.conversationContext.length > this.maxContextLength * 2) {
|
| 867 |
+
this.conversationContext = this.conversationContext.slice(-this.maxContextLength * 2);
|
| 868 |
+
}
|
| 869 |
+
return kimiResponse;
|
| 870 |
+
}
|
| 871 |
+
}
|
| 872 |
+
} catch (e) {
|
| 873 |
+
// Swallow refresh errors; will fall through to standard error handling
|
| 874 |
+
}
|
| 875 |
+
} else if (response.status === 401) {
|
| 876 |
+
errorMessage = "Invalid API key. Check your OpenRouter key in the settings.";
|
| 877 |
+
} else if (response.status === 429) {
|
| 878 |
+
errorMessage = "Rate limit reached. Please wait a moment before trying again.";
|
| 879 |
+
} else if (response.status === 402) {
|
| 880 |
+
errorMessage = "Insufficient credit on your OpenRouter account.";
|
| 881 |
+
}
|
| 882 |
+
}
|
| 883 |
+
} catch (parseError) {
|
| 884 |
+
console.warn("Unable to parse API error:", parseError);
|
| 885 |
+
}
|
| 886 |
+
|
| 887 |
+
console.error(`OpenRouter API error (${response.status}):`, errorMessage);
|
| 888 |
+
|
| 889 |
+
// Add suggestions to the error if available
|
| 890 |
+
const error = new Error(errorMessage);
|
| 891 |
+
if (suggestions.length > 0) {
|
| 892 |
+
error.suggestions = suggestions;
|
| 893 |
+
}
|
| 894 |
+
|
| 895 |
+
throw error;
|
| 896 |
+
}
|
| 897 |
+
|
| 898 |
+
const data = await response.json();
|
| 899 |
+
|
| 900 |
+
if (!data.choices || !data.choices[0] || !data.choices[0].message) {
|
| 901 |
+
throw new Error("Invalid API response - no content generated");
|
| 902 |
+
}
|
| 903 |
+
|
| 904 |
+
const kimiResponse = data.choices[0].message.content;
|
| 905 |
+
|
| 906 |
+
// Add to context
|
| 907 |
+
this.conversationContext.push(
|
| 908 |
+
{ role: "user", content: userMessage, timestamp: new Date().toISOString() },
|
| 909 |
+
{ role: "assistant", content: kimiResponse, timestamp: new Date().toISOString() }
|
| 910 |
+
);
|
| 911 |
+
|
| 912 |
+
// Limit context size
|
| 913 |
+
if (this.conversationContext.length > this.maxContextLength * 2) {
|
| 914 |
+
this.conversationContext = this.conversationContext.slice(-this.maxContextLength * 2);
|
| 915 |
+
}
|
| 916 |
+
|
| 917 |
+
// Token usage estimation (deferred save)
|
| 918 |
+
try {
|
| 919 |
+
const est = window.KimiTokenUtils?.estimate || (t => Math.ceil((t || "").length / 4));
|
| 920 |
+
const tokensIn = est(userMessage + " " + systemPromptContent);
|
| 921 |
+
const tokensOut = est(kimiResponse);
|
| 922 |
+
window._lastKimiTokenUsage = { tokensIn, tokensOut };
|
| 923 |
+
if (!window.kimiMemory && this.db) {
|
| 924 |
+
const character = await this.db.getSelectedCharacter();
|
| 925 |
+
const prevIn = Number(await this.db.getPreference(`totalTokensIn_${character}`, 0)) || 0;
|
| 926 |
+
const prevOut = Number(await this.db.getPreference(`totalTokensOut_${character}`, 0)) || 0;
|
| 927 |
+
await this.db.setPreference(`totalTokensIn_${character}`, prevIn + tokensIn);
|
| 928 |
+
await this.db.setPreference(`totalTokensOut_${character}`, prevOut + tokensOut);
|
| 929 |
+
}
|
| 930 |
+
} catch (e) {
|
| 931 |
+
console.warn("Token usage estimation failed (OpenRouter):", e);
|
| 932 |
+
}
|
| 933 |
+
return kimiResponse;
|
| 934 |
+
} catch (networkError) {
|
| 935 |
+
if (networkError.name === "TypeError" && networkError.message.includes("fetch")) {
|
| 936 |
+
throw new Error("Network connection error. Check your internet connection.");
|
| 937 |
+
}
|
| 938 |
+
throw networkError;
|
| 939 |
+
}
|
| 940 |
+
}
|
| 941 |
+
|
| 942 |
+
async chatWithLocal(userMessage, options = {}) {
|
| 943 |
+
try {
|
| 944 |
+
const selectedLanguage = await this.db.getPreference("selectedLanguage", "en");
|
| 945 |
+
let languageInstruction = ""; // placeholder (language guidance is included in assembled prompt)
|
| 946 |
+
let systemPromptContent = await this.assemblePrompt(userMessage);
|
| 947 |
+
if (window.KIMI_DEBUG_API_AUDIT) {
|
| 948 |
+
console.log("===== FULL SYSTEM PROMPT (Local) =====\n" + systemPromptContent + "\n===== END SYSTEM PROMPT =====");
|
| 949 |
+
}
|
| 950 |
+
const response = await fetch("http://localhost:11434/api/chat", {
|
| 951 |
+
method: "POST",
|
| 952 |
+
headers: {
|
| 953 |
+
"Content-Type": "application/json"
|
| 954 |
+
},
|
| 955 |
+
body: JSON.stringify({
|
| 956 |
+
model: "gemma-3n-E4B-it-Q4_K_M.gguf",
|
| 957 |
+
messages: [
|
| 958 |
+
{ role: "system", content: systemPromptContent },
|
| 959 |
+
{ role: "user", content: userMessage }
|
| 960 |
+
],
|
| 961 |
+
stream: false
|
| 962 |
+
})
|
| 963 |
+
});
|
| 964 |
+
if (!response.ok) {
|
| 965 |
+
throw new Error("Ollama not available");
|
| 966 |
+
}
|
| 967 |
+
const data = await response.json();
|
| 968 |
+
const content = data?.message?.content || data?.choices?.[0]?.message?.content || "";
|
| 969 |
+
if (!content) throw new Error("Local model returned empty response");
|
| 970 |
+
|
| 971 |
+
// Add to context like other providers
|
| 972 |
+
this.conversationContext.push(
|
| 973 |
+
{ role: "user", content: userMessage, timestamp: new Date().toISOString() },
|
| 974 |
+
{ role: "assistant", content: content, timestamp: new Date().toISOString() }
|
| 975 |
+
);
|
| 976 |
+
if (this.conversationContext.length > this.maxContextLength * 2) {
|
| 977 |
+
this.conversationContext = this.conversationContext.slice(-this.maxContextLength * 2);
|
| 978 |
+
}
|
| 979 |
+
|
| 980 |
+
// Estimate token usage for local model (heuristic)
|
| 981 |
+
try {
|
| 982 |
+
const est = window.KimiTokenUtils?.estimate || (t => Math.ceil((t || "").length / 4));
|
| 983 |
+
const tokensIn = est(userMessage + " " + systemPromptContent);
|
| 984 |
+
const tokensOut = est(content);
|
| 985 |
+
window._lastKimiTokenUsage = { tokensIn, tokensOut };
|
| 986 |
+
const character = await this.db.getSelectedCharacter();
|
| 987 |
+
const prevIn = Number(await this.db.getPreference(`totalTokensIn_${character}`, 0)) || 0;
|
| 988 |
+
const prevOut = Number(await this.db.getPreference(`totalTokensOut_${character}`, 0)) || 0;
|
| 989 |
+
await this.db.setPreference(`totalTokensIn_${character}`, prevIn + tokensIn);
|
| 990 |
+
await this.db.setPreference(`totalTokensOut_${character}`, prevOut + tokensOut);
|
| 991 |
+
} catch (e) {
|
| 992 |
+
console.warn("Token usage estimation failed (local):", e);
|
| 993 |
+
}
|
| 994 |
+
return content;
|
| 995 |
+
} catch (error) {
|
| 996 |
+
console.warn("Local LLM not available:", error);
|
| 997 |
+
return this.getFallbackResponse(userMessage);
|
| 998 |
+
}
|
| 999 |
+
}
|
| 1000 |
+
|
| 1001 |
+
// ===== STREAMING METHODS =====
|
| 1002 |
+
|
| 1003 |
+
async chatWithOpenRouterStreaming(userMessage, onToken, options = {}) {
|
| 1004 |
+
const provider = await this.db.getPreference("llmProvider", "openrouter");
|
| 1005 |
+
const apiKey = await (window.KimiProviderUtils ? window.KimiProviderUtils.getApiKey(this.db, provider) : this.db.getPreference("providerApiKey"));
|
| 1006 |
+
if (!apiKey) {
|
| 1007 |
+
throw new Error("OpenRouter API key not configured");
|
| 1008 |
+
}
|
| 1009 |
+
|
| 1010 |
+
const systemPromptContent = await this.assemblePrompt(userMessage);
|
| 1011 |
+
const messages = [
|
| 1012 |
+
{ role: "system", content: systemPromptContent },
|
| 1013 |
+
...this.conversationContext.slice(-this.maxContextLength),
|
| 1014 |
+
{ role: "user", content: userMessage }
|
| 1015 |
+
];
|
| 1016 |
+
|
| 1017 |
+
// Get unified defaults and options
|
| 1018 |
+
const unifiedDefaults = window.getUnifiedDefaults
|
| 1019 |
+
? window.getUnifiedDefaults()
|
| 1020 |
+
: { temperature: 0.9, maxTokens: 400, top_p: 0.9, frequency_penalty: 0.9, presence_penalty: 0.8 };
|
| 1021 |
+
|
| 1022 |
+
const enableStreaming = await this.db.getPreference("enableStreaming", true);
|
| 1023 |
+
|
| 1024 |
+
const llmSettings = {
|
| 1025 |
+
temperature: await this.db.getPreference("llmTemperature", unifiedDefaults.temperature),
|
| 1026 |
+
maxTokens: await this.db.getPreference("llmMaxTokens", unifiedDefaults.maxTokens),
|
| 1027 |
+
top_p: await this.db.getPreference("llmTopP", unifiedDefaults.top_p),
|
| 1028 |
+
frequency_penalty: await this.db.getPreference("llmFrequencyPenalty", unifiedDefaults.frequency_penalty),
|
| 1029 |
+
presence_penalty: await this.db.getPreference("llmPresencePenalty", unifiedDefaults.presence_penalty)
|
| 1030 |
+
};
|
| 1031 |
+
|
| 1032 |
+
const payload = {
|
| 1033 |
+
model: this.currentModel,
|
| 1034 |
+
messages: messages,
|
| 1035 |
+
stream: enableStreaming, // Use user preference for streaming
|
| 1036 |
+
temperature: typeof options.temperature === "number" ? options.temperature : llmSettings.temperature,
|
| 1037 |
+
max_tokens: typeof options.maxTokens === "number" ? options.maxTokens : llmSettings.maxTokens,
|
| 1038 |
+
top_p: typeof options.topP === "number" ? options.topP : llmSettings.top_p,
|
| 1039 |
+
frequency_penalty: typeof options.frequencyPenalty === "number" ? options.frequencyPenalty : llmSettings.frequency_penalty,
|
| 1040 |
+
presence_penalty: typeof options.presencePenalty === "number" ? options.presencePenalty : llmSettings.presence_penalty
|
| 1041 |
+
};
|
| 1042 |
+
|
| 1043 |
+
try {
|
| 1044 |
+
const response = await fetch("https://openrouter.ai/api/v1/chat/completions", {
|
| 1045 |
+
method: "POST",
|
| 1046 |
+
headers: {
|
| 1047 |
+
Authorization: `Bearer ${apiKey}`,
|
| 1048 |
+
"Content-Type": "application/json",
|
| 1049 |
+
"HTTP-Referer": window.location.origin,
|
| 1050 |
+
"X-Title": "Kimi - Virtual Companion"
|
| 1051 |
+
},
|
| 1052 |
+
body: JSON.stringify(payload)
|
| 1053 |
+
});
|
| 1054 |
+
|
| 1055 |
+
if (!response.ok) {
|
| 1056 |
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
| 1057 |
+
}
|
| 1058 |
+
|
| 1059 |
+
const reader = response.body.getReader();
|
| 1060 |
+
const decoder = new TextDecoder();
|
| 1061 |
+
let buffer = "";
|
| 1062 |
+
let fullResponse = "";
|
| 1063 |
+
|
| 1064 |
+
try {
|
| 1065 |
+
while (true) {
|
| 1066 |
+
const { done, value } = await reader.read();
|
| 1067 |
+
if (done) break;
|
| 1068 |
+
|
| 1069 |
+
buffer += decoder.decode(value, { stream: true });
|
| 1070 |
+
const lines = buffer.split("\n");
|
| 1071 |
+
buffer = lines.pop() || ""; // Keep incomplete line in buffer
|
| 1072 |
+
|
| 1073 |
+
for (const line of lines) {
|
| 1074 |
+
if (line.trim() === "" || line.startsWith(":")) continue; // Skip empty lines and comments
|
| 1075 |
+
|
| 1076 |
+
if (line.startsWith("data: ")) {
|
| 1077 |
+
const data = line.slice(6);
|
| 1078 |
+
if (data === "[DONE]") {
|
| 1079 |
+
break;
|
| 1080 |
+
}
|
| 1081 |
+
|
| 1082 |
+
try {
|
| 1083 |
+
const parsed = JSON.parse(data);
|
| 1084 |
+
const content = parsed.choices?.[0]?.delta?.content;
|
| 1085 |
+
if (content) {
|
| 1086 |
+
fullResponse += content;
|
| 1087 |
+
onToken(content);
|
| 1088 |
+
}
|
| 1089 |
+
} catch (parseError) {
|
| 1090 |
+
console.warn("Failed to parse streaming chunk:", parseError);
|
| 1091 |
+
}
|
| 1092 |
+
}
|
| 1093 |
+
}
|
| 1094 |
+
}
|
| 1095 |
+
} finally {
|
| 1096 |
+
reader.releaseLock();
|
| 1097 |
+
}
|
| 1098 |
+
|
| 1099 |
+
// Add to context after streaming completes
|
| 1100 |
+
this.conversationContext.push(
|
| 1101 |
+
{ role: "user", content: userMessage, timestamp: new Date().toISOString() },
|
| 1102 |
+
{ role: "assistant", content: fullResponse, timestamp: new Date().toISOString() }
|
| 1103 |
+
);
|
| 1104 |
+
|
| 1105 |
+
if (this.conversationContext.length > this.maxContextLength * 2) {
|
| 1106 |
+
this.conversationContext = this.conversationContext.slice(-this.maxContextLength * 2);
|
| 1107 |
+
}
|
| 1108 |
+
|
| 1109 |
+
// Token usage estimation
|
| 1110 |
+
try {
|
| 1111 |
+
const est = window.KimiTokenUtils?.estimate || (t => Math.ceil((t || "").length / 4));
|
| 1112 |
+
const tokensIn = est(userMessage + " " + systemPromptContent);
|
| 1113 |
+
const tokensOut = est(fullResponse);
|
| 1114 |
+
window._lastKimiTokenUsage = { tokensIn, tokensOut };
|
| 1115 |
+
if (!window.kimiMemory && this.db) {
|
| 1116 |
+
const character = await this.db.getSelectedCharacter();
|
| 1117 |
+
const prevIn = Number(await this.db.getPreference(`totalTokensIn_${character}`, 0)) || 0;
|
| 1118 |
+
const prevOut = Number(await this.db.getPreference(`totalTokensOut_${character}`, 0)) || 0;
|
| 1119 |
+
await this.db.setPreference(`totalTokensIn_${character}`, prevIn + tokensIn);
|
| 1120 |
+
await this.db.setPreference(`totalTokensOut_${character}`, prevOut + tokensOut);
|
| 1121 |
+
}
|
| 1122 |
+
} catch (e) {
|
| 1123 |
+
console.warn("Token usage estimation failed (OpenRouter streaming):", e);
|
| 1124 |
+
}
|
| 1125 |
+
|
| 1126 |
+
return fullResponse;
|
| 1127 |
+
} catch (error) {
|
| 1128 |
+
console.error("OpenRouter streaming error:", error);
|
| 1129 |
+
throw error;
|
| 1130 |
+
}
|
| 1131 |
+
}
|
| 1132 |
+
|
| 1133 |
+
async chatWithOpenAICompatibleStreaming(userMessage, onToken, options = {}) {
|
| 1134 |
+
const provider = await this.db.getPreference("llmProvider", "openrouter");
|
| 1135 |
+
let baseUrl;
|
| 1136 |
+
if (provider === "openai-compatible" || provider === "ollama") {
|
| 1137 |
+
baseUrl = await this.db.getPreference(`llmBaseUrl_${provider}`, provider === "ollama" ? "http://localhost:11434/api/chat" : "");
|
| 1138 |
+
} else {
|
| 1139 |
+
const sharedPlaceholders = window.KimiProviderPlaceholders || {};
|
| 1140 |
+
baseUrl = sharedPlaceholders[provider] || sharedPlaceholders.openrouter || "https://openrouter.ai/api/v1/chat/completions";
|
| 1141 |
+
}
|
| 1142 |
+
const apiKey = window.KimiProviderUtils
|
| 1143 |
+
? await window.KimiProviderUtils.getApiKey(this.db, provider)
|
| 1144 |
+
: await this.db.getPreference("providerApiKey", "");
|
| 1145 |
+
if (!apiKey) {
|
| 1146 |
+
throw new Error("API key not configured for selected provider");
|
| 1147 |
+
}
|
| 1148 |
+
|
| 1149 |
+
const systemPromptContent = await this.assemblePrompt(userMessage);
|
| 1150 |
+
const messages = [
|
| 1151 |
+
{ role: "system", content: systemPromptContent },
|
| 1152 |
+
...this.conversationContext.slice(-this.maxContextLength),
|
| 1153 |
+
{ role: "user", content: userMessage }
|
| 1154 |
+
];
|
| 1155 |
+
|
| 1156 |
+
const unifiedDefaults = window.getUnifiedDefaults
|
| 1157 |
+
? window.getUnifiedDefaults()
|
| 1158 |
+
: { temperature: 0.9, maxTokens: 400, top_p: 0.9, frequency_penalty: 0.9, presence_penalty: 0.8 };
|
| 1159 |
+
|
| 1160 |
+
const enableStreaming = await this.db.getPreference("enableStreaming", true);
|
| 1161 |
+
|
| 1162 |
+
const llmSettings = {
|
| 1163 |
+
temperature: await this.db.getPreference("llmTemperature", unifiedDefaults.temperature),
|
| 1164 |
+
maxTokens: await this.db.getPreference("llmMaxTokens", unifiedDefaults.maxTokens),
|
| 1165 |
+
top_p: await this.db.getPreference("llmTopP", unifiedDefaults.top_p),
|
| 1166 |
+
frequency_penalty: await this.db.getPreference("llmFrequencyPenalty", unifiedDefaults.frequency_penalty),
|
| 1167 |
+
presence_penalty: await this.db.getPreference("llmPresencePenalty", unifiedDefaults.presence_penalty)
|
| 1168 |
+
};
|
| 1169 |
+
|
| 1170 |
+
const payload = {
|
| 1171 |
+
model: this.currentModel,
|
| 1172 |
+
messages: messages,
|
| 1173 |
+
stream: enableStreaming,
|
| 1174 |
+
temperature: typeof options.temperature === "number" ? options.temperature : llmSettings.temperature,
|
| 1175 |
+
max_tokens: typeof options.maxTokens === "number" ? options.maxTokens : llmSettings.maxTokens,
|
| 1176 |
+
top_p: typeof options.topP === "number" ? options.topP : llmSettings.top_p,
|
| 1177 |
+
frequency_penalty: typeof options.frequencyPenalty === "number" ? options.frequencyPenalty : llmSettings.frequency_penalty,
|
| 1178 |
+
presence_penalty: typeof options.presencePenalty === "number" ? options.presencePenalty : llmSettings.presence_penalty
|
| 1179 |
+
};
|
| 1180 |
+
|
| 1181 |
+
try {
|
| 1182 |
+
const response = await fetch(baseUrl, {
|
| 1183 |
+
method: "POST",
|
| 1184 |
+
headers: {
|
| 1185 |
+
Authorization: `Bearer ${apiKey}`,
|
| 1186 |
+
"Content-Type": "application/json"
|
| 1187 |
+
},
|
| 1188 |
+
body: JSON.stringify(payload)
|
| 1189 |
+
});
|
| 1190 |
+
|
| 1191 |
+
if (!response.ok) {
|
| 1192 |
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
| 1193 |
+
}
|
| 1194 |
+
|
| 1195 |
+
const reader = response.body.getReader();
|
| 1196 |
+
const decoder = new TextDecoder();
|
| 1197 |
+
let buffer = "";
|
| 1198 |
+
let fullResponse = "";
|
| 1199 |
+
|
| 1200 |
+
try {
|
| 1201 |
+
while (true) {
|
| 1202 |
+
const { done, value } = await reader.read();
|
| 1203 |
+
if (done) break;
|
| 1204 |
+
|
| 1205 |
+
buffer += decoder.decode(value, { stream: true });
|
| 1206 |
+
const lines = buffer.split("\n");
|
| 1207 |
+
buffer = lines.pop() || "";
|
| 1208 |
+
|
| 1209 |
+
for (const line of lines) {
|
| 1210 |
+
if (line.trim() === "" || line.startsWith(":")) continue;
|
| 1211 |
+
|
| 1212 |
+
if (line.startsWith("data: ")) {
|
| 1213 |
+
const data = line.slice(6);
|
| 1214 |
+
if (data === "[DONE]") {
|
| 1215 |
+
break;
|
| 1216 |
+
}
|
| 1217 |
+
|
| 1218 |
+
try {
|
| 1219 |
+
const parsed = JSON.parse(data);
|
| 1220 |
+
const content = parsed.choices?.[0]?.delta?.content;
|
| 1221 |
+
if (content) {
|
| 1222 |
+
fullResponse += content;
|
| 1223 |
+
onToken(content);
|
| 1224 |
+
}
|
| 1225 |
+
} catch (parseError) {
|
| 1226 |
+
console.warn("Failed to parse streaming chunk:", parseError);
|
| 1227 |
+
}
|
| 1228 |
+
}
|
| 1229 |
+
}
|
| 1230 |
+
}
|
| 1231 |
+
} finally {
|
| 1232 |
+
reader.releaseLock();
|
| 1233 |
+
}
|
| 1234 |
+
|
| 1235 |
+
// Add to context
|
| 1236 |
+
this.conversationContext.push(
|
| 1237 |
+
{ role: "user", content: userMessage, timestamp: new Date().toISOString() },
|
| 1238 |
+
{ role: "assistant", content: fullResponse, timestamp: new Date().toISOString() }
|
| 1239 |
+
);
|
| 1240 |
+
|
| 1241 |
+
if (this.conversationContext.length > this.maxContextLength * 2) {
|
| 1242 |
+
this.conversationContext = this.conversationContext.slice(-this.maxContextLength * 2);
|
| 1243 |
+
}
|
| 1244 |
+
|
| 1245 |
+
// Token usage estimation
|
| 1246 |
+
try {
|
| 1247 |
+
const est = window.KimiTokenUtils?.estimate || (t => Math.ceil((t || "").length / 4));
|
| 1248 |
+
const tokensIn = est(userMessage + " " + systemPromptContent);
|
| 1249 |
+
const tokensOut = est(fullResponse);
|
| 1250 |
+
window._lastKimiTokenUsage = { tokensIn, tokensOut };
|
| 1251 |
+
if (!window.kimiMemory && this.db) {
|
| 1252 |
+
const character = await this.db.getSelectedCharacter();
|
| 1253 |
+
const prevIn = Number(await this.db.getPreference(`totalTokensIn_${character}`, 0)) || 0;
|
| 1254 |
+
const prevOut = Number(await this.db.getPreference(`totalTokensOut_${character}`, 0)) || 0;
|
| 1255 |
+
await this.db.setPreference(`totalTokensIn_${character}`, prevIn + tokensIn);
|
| 1256 |
+
await this.db.setPreference(`totalTokensOut_${character}`, prevOut + tokensOut);
|
| 1257 |
+
}
|
| 1258 |
+
} catch (e) {
|
| 1259 |
+
console.warn("Token usage estimation failed (OpenAI streaming):", e);
|
| 1260 |
+
}
|
| 1261 |
+
|
| 1262 |
+
return fullResponse;
|
| 1263 |
+
} catch (error) {
|
| 1264 |
+
console.error("OpenAI compatible streaming error:", error);
|
| 1265 |
+
throw error;
|
| 1266 |
+
}
|
| 1267 |
+
}
|
| 1268 |
+
|
| 1269 |
+
async chatWithLocalStreaming(userMessage, onToken, options = {}) {
|
| 1270 |
+
const systemPromptContent = await this.assemblePrompt(userMessage);
|
| 1271 |
+
const enableStreaming = await this.db.getPreference("enableStreaming", true);
|
| 1272 |
+
|
| 1273 |
+
const payload = {
|
| 1274 |
+
model: this.currentModel || "llama2",
|
| 1275 |
+
messages: [
|
| 1276 |
+
{ role: "system", content: systemPromptContent },
|
| 1277 |
+
...this.conversationContext.slice(-this.maxContextLength),
|
| 1278 |
+
{ role: "user", content: userMessage }
|
| 1279 |
+
],
|
| 1280 |
+
stream: enableStreaming
|
| 1281 |
+
};
|
| 1282 |
+
|
| 1283 |
+
try {
|
| 1284 |
+
const response = await fetch("http://localhost:11434/api/chat", {
|
| 1285 |
+
method: "POST",
|
| 1286 |
+
headers: {
|
| 1287 |
+
"Content-Type": "application/json"
|
| 1288 |
+
},
|
| 1289 |
+
body: JSON.stringify(payload)
|
| 1290 |
+
});
|
| 1291 |
+
|
| 1292 |
+
if (!response.ok) {
|
| 1293 |
+
throw new Error("Ollama not available");
|
| 1294 |
+
}
|
| 1295 |
+
|
| 1296 |
+
let fullResponse = "";
|
| 1297 |
+
|
| 1298 |
+
if (enableStreaming) {
|
| 1299 |
+
// Streaming mode
|
| 1300 |
+
const reader = response.body.getReader();
|
| 1301 |
+
const decoder = new TextDecoder();
|
| 1302 |
+
|
| 1303 |
+
try {
|
| 1304 |
+
while (true) {
|
| 1305 |
+
const { done, value } = await reader.read();
|
| 1306 |
+
if (done) break;
|
| 1307 |
+
|
| 1308 |
+
const chunk = decoder.decode(value, { stream: true });
|
| 1309 |
+
const lines = chunk.split("\n").filter(line => line.trim());
|
| 1310 |
+
|
| 1311 |
+
for (const line of lines) {
|
| 1312 |
+
try {
|
| 1313 |
+
const parsed = JSON.parse(line);
|
| 1314 |
+
const content = parsed.message?.content;
|
| 1315 |
+
if (content) {
|
| 1316 |
+
fullResponse += content;
|
| 1317 |
+
onToken(content);
|
| 1318 |
+
}
|
| 1319 |
+
if (parsed.done) {
|
| 1320 |
+
break;
|
| 1321 |
+
}
|
| 1322 |
+
} catch (parseError) {
|
| 1323 |
+
console.warn("Failed to parse Ollama streaming chunk:", parseError);
|
| 1324 |
+
}
|
| 1325 |
+
}
|
| 1326 |
+
}
|
| 1327 |
+
} finally {
|
| 1328 |
+
reader.releaseLock();
|
| 1329 |
+
}
|
| 1330 |
+
} else {
|
| 1331 |
+
// Non-streaming mode
|
| 1332 |
+
const data = await response.json();
|
| 1333 |
+
fullResponse = data.message?.content || "";
|
| 1334 |
+
if (fullResponse && onToken) {
|
| 1335 |
+
onToken(fullResponse);
|
| 1336 |
+
}
|
| 1337 |
+
}
|
| 1338 |
+
|
| 1339 |
+
// Add to context
|
| 1340 |
+
this.conversationContext.push(
|
| 1341 |
+
{ role: "user", content: userMessage, timestamp: new Date().toISOString() },
|
| 1342 |
+
{ role: "assistant", content: fullResponse, timestamp: new Date().toISOString() }
|
| 1343 |
+
);
|
| 1344 |
+
|
| 1345 |
+
if (this.conversationContext.length > this.maxContextLength * 2) {
|
| 1346 |
+
this.conversationContext = this.conversationContext.slice(-this.maxContextLength * 2);
|
| 1347 |
+
}
|
| 1348 |
+
|
| 1349 |
+
// Token usage estimation
|
| 1350 |
+
try {
|
| 1351 |
+
const est = window.KimiTokenUtils?.estimate || (t => Math.ceil((t || "").length / 4));
|
| 1352 |
+
const tokensIn = est(userMessage + " " + systemPromptContent);
|
| 1353 |
+
const tokensOut = est(fullResponse);
|
| 1354 |
+
window._lastKimiTokenUsage = { tokensIn, tokensOut };
|
| 1355 |
+
const character = await this.db.getSelectedCharacter();
|
| 1356 |
+
const prevIn = Number(await this.db.getPreference(`totalTokensIn_${character}`, 0)) || 0;
|
| 1357 |
+
const prevOut = Number(await this.db.getPreference(`totalTokensOut_${character}`, 0)) || 0;
|
| 1358 |
+
await this.db.setPreference(`totalTokensIn_${character}`, prevIn + tokensIn);
|
| 1359 |
+
await this.db.setPreference(`totalTokensOut_${character}`, prevOut + tokensOut);
|
| 1360 |
+
} catch (e) {
|
| 1361 |
+
console.warn("Token usage estimation failed (local streaming):", e);
|
| 1362 |
+
}
|
| 1363 |
+
|
| 1364 |
+
return fullResponse;
|
| 1365 |
+
} catch (error) {
|
| 1366 |
+
console.warn("Local LLM streaming not available:", error);
|
| 1367 |
+
throw error;
|
| 1368 |
+
}
|
| 1369 |
+
}
|
| 1370 |
+
|
| 1371 |
+
getFallbackResponse(userMessage, errorType = "api") {
|
| 1372 |
+
// Use centralized fallback manager instead of duplicated logic
|
| 1373 |
+
if (window.KimiFallbackManager) {
|
| 1374 |
+
// Map error types to the correct format
|
| 1375 |
+
const errorTypeMap = {
|
| 1376 |
+
api: "api_error",
|
| 1377 |
+
model: "model_error",
|
| 1378 |
+
network: "network_error"
|
| 1379 |
+
};
|
| 1380 |
+
const mappedType = errorTypeMap[errorType] || "technical_error";
|
| 1381 |
+
return window.KimiFallbackManager.getFallbackMessage(mappedType);
|
| 1382 |
+
}
|
| 1383 |
+
|
| 1384 |
+
// Fallback to legacy system if KimiFallbackManager not available
|
| 1385 |
+
const i18n = window.kimiI18nManager;
|
| 1386 |
+
if (!i18n) {
|
| 1387 |
+
return "Sorry, I'm having technical difficulties! 💕";
|
| 1388 |
+
}
|
| 1389 |
+
return i18n.t("fallback_technical_error");
|
| 1390 |
+
}
|
| 1391 |
+
|
| 1392 |
+
getFallbackKeywords(trait, type) {
|
| 1393 |
+
const keywords = {
|
| 1394 |
+
humor: {
|
| 1395 |
+
positive: ["funny", "hilarious", "joke", "laugh", "amusing", "humorous", "smile", "witty", "playful"],
|
| 1396 |
+
negative: ["boring", "sad", "serious", "cold", "dry", "depressing", "gloomy"]
|
| 1397 |
+
},
|
| 1398 |
+
intelligence: {
|
| 1399 |
+
positive: ["intelligent", "smart", "brilliant", "logical", "clever", "wise", "genius", "thoughtful", "insightful"],
|
| 1400 |
+
negative: ["stupid", "dumb", "foolish", "slow", "naive", "ignorant", "simple"]
|
| 1401 |
+
},
|
| 1402 |
+
romance: {
|
| 1403 |
+
positive: ["cuddle", "love", "romantic", "kiss", "tenderness", "passion", "charming", "adorable", "sweet"],
|
| 1404 |
+
negative: ["cold", "distant", "indifferent", "rejection", "loneliness", "breakup", "sad"]
|
| 1405 |
+
},
|
| 1406 |
+
affection: {
|
| 1407 |
+
positive: ["affection", "tenderness", "close", "warmth", "kind", "caring", "cuddle", "love", "adore"],
|
| 1408 |
+
negative: ["mean", "cold", "indifferent", "distant", "rejection", "hate", "hostile"]
|
| 1409 |
+
},
|
| 1410 |
+
playfulness: {
|
| 1411 |
+
positive: ["play", "game", "tease", "mischievous", "fun", "amusing", "playful", "joke", "frolic"],
|
| 1412 |
+
negative: ["serious", "boring", "strict", "rigid", "monotonous", "tedious"]
|
| 1413 |
+
},
|
| 1414 |
+
empathy: {
|
| 1415 |
+
positive: ["listen", "understand", "empathy", "support", "help", "comfort", "compassion", "caring", "kindness"],
|
| 1416 |
+
negative: ["indifferent", "cold", "selfish", "ignore", "despise", "hostile", "uncaring"]
|
| 1417 |
+
}
|
| 1418 |
+
};
|
| 1419 |
+
return keywords[trait]?.[type] || [];
|
| 1420 |
+
}
|
| 1421 |
+
|
| 1422 |
+
// Mémoire temporaire pour l'accumulation négative par trait
|
| 1423 |
+
_negativeStreaks = {};
|
| 1424 |
+
|
| 1425 |
+
async updatePersonalityFromResponse(userMessage, kimiResponse) {
|
| 1426 |
+
// Use unified emotion system for personality updates
|
| 1427 |
+
if (window.kimiEmotionSystem) {
|
| 1428 |
+
return await window.kimiEmotionSystem.updatePersonalityFromConversation(userMessage, kimiResponse, await this.db.getSelectedCharacter());
|
| 1429 |
+
}
|
| 1430 |
+
|
| 1431 |
+
// Legacy fallback (should not be reached)
|
| 1432 |
+
console.warn("Unified emotion system not available, skipping personality update");
|
| 1433 |
+
}
|
| 1434 |
+
|
| 1435 |
+
async getModelStats() {
|
| 1436 |
+
const models = await this.db.getAllLLMModels();
|
| 1437 |
+
const currentModelInfo = this.availableModels[this.currentModel];
|
| 1438 |
+
|
| 1439 |
+
return {
|
| 1440 |
+
current: {
|
| 1441 |
+
id: this.currentModel,
|
| 1442 |
+
info: currentModelInfo
|
| 1443 |
+
},
|
| 1444 |
+
available: this.availableModels,
|
| 1445 |
+
configured: models,
|
| 1446 |
+
contextLength: this.conversationContext.length
|
| 1447 |
+
};
|
| 1448 |
+
}
|
| 1449 |
+
|
| 1450 |
+
async testModel(modelId, testMessage = "Test API ok?") {
|
| 1451 |
+
// Ancienne méthode de test (non minimaliste)
|
| 1452 |
+
return await this.testApiKeyMinimal(modelId);
|
| 1453 |
+
}
|
| 1454 |
+
|
| 1455 |
+
/**
|
| 1456 |
+
* Test API minimaliste et centralisé pour tous les providers compatibles.
|
| 1457 |
+
* Envoie uniquement un prompt système court et un message utilisateur dans la langue choisie.
|
| 1458 |
+
* Aucun contexte, aucune mémoire, aucun paramètre superflu.
|
| 1459 |
+
* @param {string} modelId - ID du modèle à tester
|
| 1460 |
+
* @returns {Promise<{success: boolean, response?: string, error?: string}>}
|
| 1461 |
+
*/
|
| 1462 |
+
async testApiKeyMinimal(modelId) {
|
| 1463 |
+
const originalModel = this.currentModel;
|
| 1464 |
+
try {
|
| 1465 |
+
await this.setCurrentModel(modelId);
|
| 1466 |
+
const provider = await this.db.getPreference("llmProvider", "openrouter");
|
| 1467 |
+
const lang = await this.db.getPreference("selectedLanguage", "en");
|
| 1468 |
+
let testWord;
|
| 1469 |
+
switch (lang) {
|
| 1470 |
+
case "fr":
|
| 1471 |
+
testWord = "Bonjour";
|
| 1472 |
+
break;
|
| 1473 |
+
case "es":
|
| 1474 |
+
testWord = "Hola";
|
| 1475 |
+
break;
|
| 1476 |
+
case "de":
|
| 1477 |
+
testWord = "Hallo";
|
| 1478 |
+
break;
|
| 1479 |
+
case "it":
|
| 1480 |
+
testWord = "Ciao";
|
| 1481 |
+
break;
|
| 1482 |
+
case "ja":
|
| 1483 |
+
testWord = "こんにちは";
|
| 1484 |
+
break;
|
| 1485 |
+
case "zh":
|
| 1486 |
+
testWord = "你好";
|
| 1487 |
+
break;
|
| 1488 |
+
default:
|
| 1489 |
+
testWord = "Hello";
|
| 1490 |
+
}
|
| 1491 |
+
const systemPrompt = "You are a helpful assistant.";
|
| 1492 |
+
let apiKey = await (window.KimiProviderUtils ? window.KimiProviderUtils.getApiKey(this.db, provider) : this.db.getPreference("providerApiKey"));
|
| 1493 |
+
|
| 1494 |
+
if (!apiKey) {
|
| 1495 |
+
return { success: false, error: "No API key found for provider: " + provider };
|
| 1496 |
+
}
|
| 1497 |
+
|
| 1498 |
+
let baseUrl = "";
|
| 1499 |
+
let payload = {
|
| 1500 |
+
model: modelId,
|
| 1501 |
+
messages: [
|
| 1502 |
+
{ role: "system", content: systemPrompt },
|
| 1503 |
+
{ role: "user", content: testWord }
|
| 1504 |
+
],
|
| 1505 |
+
max_tokens: 2
|
| 1506 |
+
};
|
| 1507 |
+
let headers = { "Content-Type": "application/json" };
|
| 1508 |
+
if (provider === "openrouter") {
|
| 1509 |
+
baseUrl = "https://openrouter.ai/api/v1/chat/completions";
|
| 1510 |
+
headers["Authorization"] = `Bearer ${apiKey}`;
|
| 1511 |
+
headers["HTTP-Referer"] = window.location.origin;
|
| 1512 |
+
headers["X-Title"] = "Kimi - Virtual Companion";
|
| 1513 |
+
} else if (["openai", "groq", "together", "deepseek", "openai-compatible"].includes(provider)) {
|
| 1514 |
+
// When selecting baseUrl during initialization/fallback, respect provider-specific stored URLs
|
| 1515 |
+
const currentProvider = await this.db.getPreference("llmProvider", "openrouter");
|
| 1516 |
+
if (currentProvider === "openai-compatible" || currentProvider === "ollama") {
|
| 1517 |
+
baseUrl = await this.db.getPreference(
|
| 1518 |
+
`llmBaseUrl_${currentProvider}`,
|
| 1519 |
+
currentProvider === "ollama" ? "http://localhost:11434/api/chat" : ""
|
| 1520 |
+
);
|
| 1521 |
+
} else {
|
| 1522 |
+
const sharedPlaceholders = window.KimiProviderPlaceholders || {};
|
| 1523 |
+
baseUrl = sharedPlaceholders[provider] || sharedPlaceholders.openrouter || "https://openrouter.ai/api/v1/chat/completions";
|
| 1524 |
+
}
|
| 1525 |
+
headers["Authorization"] = `Bearer ${apiKey}`;
|
| 1526 |
+
} else if (provider === "ollama") {
|
| 1527 |
+
baseUrl = "http://localhost:11434/api/chat";
|
| 1528 |
+
payload = {
|
| 1529 |
+
model: modelId,
|
| 1530 |
+
messages: [
|
| 1531 |
+
{ role: "system", content: systemPrompt },
|
| 1532 |
+
{ role: "user", content: testWord }
|
| 1533 |
+
],
|
| 1534 |
+
stream: false
|
| 1535 |
+
};
|
| 1536 |
+
} else {
|
| 1537 |
+
throw new Error("Unknown provider: " + provider);
|
| 1538 |
+
}
|
| 1539 |
+
|
| 1540 |
+
const response = await fetch(baseUrl, {
|
| 1541 |
+
method: "POST",
|
| 1542 |
+
headers,
|
| 1543 |
+
body: JSON.stringify(payload)
|
| 1544 |
+
});
|
| 1545 |
+
if (!response.ok) {
|
| 1546 |
+
const error = await response.text();
|
| 1547 |
+
return { success: false, error };
|
| 1548 |
+
}
|
| 1549 |
+
const data = await response.json();
|
| 1550 |
+
let content = "";
|
| 1551 |
+
if (provider === "ollama") {
|
| 1552 |
+
content = data?.message?.content || data?.choices?.[0]?.message?.content || "";
|
| 1553 |
+
} else {
|
| 1554 |
+
content = data?.choices?.[0]?.message?.content || "";
|
| 1555 |
+
}
|
| 1556 |
+
return { success: true, response: content };
|
| 1557 |
+
} catch (error) {
|
| 1558 |
+
return { success: false, error: error.message };
|
| 1559 |
+
} finally {
|
| 1560 |
+
await this.setCurrentModel(originalModel);
|
| 1561 |
+
}
|
| 1562 |
+
}
|
| 1563 |
+
|
| 1564 |
+
// Complete model diagnosis
|
| 1565 |
+
async diagnoseModel(modelId) {
|
| 1566 |
+
const model = this.availableModels[modelId];
|
| 1567 |
+
if (!model) {
|
| 1568 |
+
return {
|
| 1569 |
+
available: false,
|
| 1570 |
+
error: "Model not found in local list"
|
| 1571 |
+
};
|
| 1572 |
+
}
|
| 1573 |
+
|
| 1574 |
+
// Check availability on OpenRouter
|
| 1575 |
+
try {
|
| 1576 |
+
// Model availability is checked against the local cache; remote checks occur in refreshRemoteModels()
|
| 1577 |
+
return {
|
| 1578 |
+
available: true,
|
| 1579 |
+
model: model,
|
| 1580 |
+
pricing: model.pricing
|
| 1581 |
+
};
|
| 1582 |
+
} catch (error) {
|
| 1583 |
+
return {
|
| 1584 |
+
available: false,
|
| 1585 |
+
error: `Unable to check: ${error.message}`
|
| 1586 |
+
};
|
| 1587 |
+
}
|
| 1588 |
+
}
|
| 1589 |
+
|
| 1590 |
+
// Fetch models from OpenRouter API and merge into availableModels
|
| 1591 |
+
async refreshRemoteModels() {
|
| 1592 |
+
if (this._isRefreshingModels) return;
|
| 1593 |
+
this._isRefreshingModels = true;
|
| 1594 |
+
try {
|
| 1595 |
+
const provider = await this.db.getPreference("llmProvider", "openrouter");
|
| 1596 |
+
const apiKey = await (window.KimiProviderUtils
|
| 1597 |
+
? window.KimiProviderUtils.getApiKey(this.db, provider)
|
| 1598 |
+
: this.db.getPreference("providerApiKey", ""));
|
| 1599 |
+
const res = await fetch("https://openrouter.ai/api/v1/models", {
|
| 1600 |
+
method: "GET",
|
| 1601 |
+
headers: {
|
| 1602 |
+
"Content-Type": "application/json",
|
| 1603 |
+
...(apiKey ? { Authorization: `Bearer ${apiKey}` } : {}),
|
| 1604 |
+
"HTTP-Referer": window.location.origin,
|
| 1605 |
+
"X-Title": "Kimi - Virtual Companion"
|
| 1606 |
+
}
|
| 1607 |
+
});
|
| 1608 |
+
if (!res.ok) {
|
| 1609 |
+
throw new Error(`Unable to fetch models: HTTP ${res.status}`);
|
| 1610 |
+
}
|
| 1611 |
+
const data = await res.json();
|
| 1612 |
+
if (!data?.data || !Array.isArray(data.data)) {
|
| 1613 |
+
throw new Error("Invalid models response format");
|
| 1614 |
+
}
|
| 1615 |
+
// Build a fresh map while preserving local/ollama entry
|
| 1616 |
+
const newMap = {};
|
| 1617 |
+
data.data.forEach(m => {
|
| 1618 |
+
if (!m?.id) return;
|
| 1619 |
+
const id = m.id;
|
| 1620 |
+
const provider = m?.id?.split("/")?.[0] || "OpenRouter";
|
| 1621 |
+
let pricing;
|
| 1622 |
+
const p = m?.pricing;
|
| 1623 |
+
if (p) {
|
| 1624 |
+
const unitRaw = ((p.unit || p.per || p.units || "") + "").toLowerCase();
|
| 1625 |
+
let unitTokens = 1;
|
| 1626 |
+
if (unitRaw) {
|
| 1627 |
+
if (unitRaw.includes("1m")) unitTokens = 1000000;
|
| 1628 |
+
else if (unitRaw.includes("1k") || unitRaw.includes("thousand")) unitTokens = 1000;
|
| 1629 |
+
else {
|
| 1630 |
+
const num = parseFloat(unitRaw.replace(/[^0-9.]/g, ""));
|
| 1631 |
+
if (Number.isFinite(num) && num > 0) {
|
| 1632 |
+
if (unitRaw.includes("m")) unitTokens = num * 1000000;
|
| 1633 |
+
else if (unitRaw.includes("k")) unitTokens = num * 1000;
|
| 1634 |
+
else unitTokens = num;
|
| 1635 |
+
} else if (unitRaw.includes("token")) {
|
| 1636 |
+
unitTokens = 1;
|
| 1637 |
+
}
|
| 1638 |
+
}
|
| 1639 |
+
}
|
| 1640 |
+
const toPerMillion = v => {
|
| 1641 |
+
const n = typeof v === "number" ? v : parseFloat(v);
|
| 1642 |
+
if (!Number.isFinite(n)) return undefined;
|
| 1643 |
+
return n * (1000000 / unitTokens);
|
| 1644 |
+
};
|
| 1645 |
+
if (typeof p.input !== "undefined" || typeof p.output !== "undefined") {
|
| 1646 |
+
pricing = {
|
| 1647 |
+
input: toPerMillion(p.input),
|
| 1648 |
+
output: toPerMillion(p.output)
|
| 1649 |
+
};
|
| 1650 |
+
} else if (typeof p.prompt !== "undefined" || typeof p.completion !== "undefined") {
|
| 1651 |
+
pricing = {
|
| 1652 |
+
input: toPerMillion(p.prompt),
|
| 1653 |
+
output: toPerMillion(p.completion)
|
| 1654 |
+
};
|
| 1655 |
+
} else {
|
| 1656 |
+
pricing = { input: undefined, output: undefined };
|
| 1657 |
+
}
|
| 1658 |
+
} else {
|
| 1659 |
+
pricing = { input: undefined, output: undefined };
|
| 1660 |
+
}
|
| 1661 |
+
newMap[id] = {
|
| 1662 |
+
name: m.name || id,
|
| 1663 |
+
provider,
|
| 1664 |
+
type: "openrouter",
|
| 1665 |
+
contextWindow: m.context_length || m?.context_window || 128000,
|
| 1666 |
+
pricing,
|
| 1667 |
+
strengths: (m?.tags || []).slice(0, 4)
|
| 1668 |
+
};
|
| 1669 |
+
});
|
| 1670 |
+
// Keep local model entry
|
| 1671 |
+
if (this.availableModels["local/ollama"]) {
|
| 1672 |
+
newMap["local/ollama"] = this.availableModels["local/ollama"];
|
| 1673 |
+
}
|
| 1674 |
+
this.recommendedModelIds.forEach(id => {
|
| 1675 |
+
const curated = this.defaultModels[id];
|
| 1676 |
+
if (curated) {
|
| 1677 |
+
newMap[id] = { ...(newMap[id] || {}), ...curated };
|
| 1678 |
+
}
|
| 1679 |
+
});
|
| 1680 |
+
this.availableModels = newMap;
|
| 1681 |
+
this._remoteModelsLoaded = true;
|
| 1682 |
+
} finally {
|
| 1683 |
+
this._isRefreshingModels = false;
|
| 1684 |
+
}
|
| 1685 |
+
}
|
| 1686 |
+
|
| 1687 |
+
// Try to find best matching model id from remote list when an ID is stale
|
| 1688 |
+
findBestMatchingModelId(preferredId) {
|
| 1689 |
+
if (this.availableModels[preferredId]) return preferredId;
|
| 1690 |
+
const id = (preferredId || "").toLowerCase();
|
| 1691 |
+
const tokens = id.split(/[\/:\-_.]+/).filter(Boolean);
|
| 1692 |
+
let best = null;
|
| 1693 |
+
let bestScore = -1;
|
| 1694 |
+
Object.keys(this.availableModels).forEach(candidateId => {
|
| 1695 |
+
const c = candidateId.toLowerCase();
|
| 1696 |
+
let score = 0;
|
| 1697 |
+
tokens.forEach(t => {
|
| 1698 |
+
if (!t) return;
|
| 1699 |
+
if (c.includes(t)) score += 1;
|
| 1700 |
+
});
|
| 1701 |
+
// Give extra weight to common markers
|
| 1702 |
+
if (c.includes("instruct")) score += 0.5;
|
| 1703 |
+
if (c.includes("mistral") && id.includes("mistral")) score += 0.5;
|
| 1704 |
+
if (c.includes("small") && id.includes("small")) score += 0.5;
|
| 1705 |
+
if (score > bestScore) {
|
| 1706 |
+
bestScore = score;
|
| 1707 |
+
best = candidateId;
|
| 1708 |
+
}
|
| 1709 |
+
});
|
| 1710 |
+
// Avoid returning unrelated local model unless nothing else
|
| 1711 |
+
if (best === "local/ollama" && Object.keys(this.availableModels).length > 1) {
|
| 1712 |
+
return null;
|
| 1713 |
+
}
|
| 1714 |
+
return best;
|
| 1715 |
+
}
|
| 1716 |
+
|
| 1717 |
+
_notifyModelChanged() {
|
| 1718 |
+
try {
|
| 1719 |
+
const detail = { id: this.currentModel };
|
| 1720 |
+
if (typeof window !== "undefined" && typeof window.dispatchEvent === "function") {
|
| 1721 |
+
window.emitAppEvent && window.emitAppEvent("llmModelChanged", detail);
|
| 1722 |
+
}
|
| 1723 |
+
} catch (e) {}
|
| 1724 |
+
}
|
| 1725 |
+
}
|
| 1726 |
+
|
| 1727 |
+
// Export for usage
|
| 1728 |
+
window.KimiLLMManager = KimiLLMManager;
|
| 1729 |
+
export default KimiLLMManager;
|
kimi-js/kimi-main.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// ESM bootstrap for Kimi App
|
| 2 |
+
// Import minimal utilities as modules; rely on existing globals for legacy parts
|
| 3 |
+
import { KimiProviderUtils } from "./kimi-utils.js";
|
| 4 |
+
import KimiLLMManager from "./kimi-llm-manager.js";
|
| 5 |
+
import KimiEmotionSystem from "./kimi-emotion-system.js";
|
| 6 |
+
|
| 7 |
+
// Expose module imports to legacy code paths that still rely on window
|
| 8 |
+
// Ensure KimiProviderUtils is available (imported from kimi-utils.js)
|
| 9 |
+
window.KimiProviderUtils = window.KimiProviderUtils || KimiProviderUtils;
|
| 10 |
+
window.KimiLLMManager = window.KimiLLMManager || KimiLLMManager;
|
| 11 |
+
window.KimiEmotionSystem = window.KimiEmotionSystem || KimiEmotionSystem;
|
kimi-js/kimi-memory-system.js
ADDED
|
@@ -0,0 +1,2257 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// ===== KIMI INTELLIGENT MEMORY SYSTEM =====
|
| 2 |
+
class KimiMemorySystem {
|
| 3 |
+
constructor(database) {
|
| 4 |
+
this.db = database;
|
| 5 |
+
this.memoryEnabled = true;
|
| 6 |
+
this.maxMemoryEntries = 100;
|
| 7 |
+
|
| 8 |
+
// Performance optimization: keyword cache with LRU eviction
|
| 9 |
+
this.keywordCache = new Map(); // keyword_language -> boolean (is common)
|
| 10 |
+
this.keywordCacheSize = 1000; // Limit memory usage
|
| 11 |
+
this.keywordCacheHits = 0;
|
| 12 |
+
this.keywordCacheMisses = 0;
|
| 13 |
+
|
| 14 |
+
// Performance monitoring
|
| 15 |
+
this.queryStats = {
|
| 16 |
+
extractionTime: [],
|
| 17 |
+
addMemoryTime: [],
|
| 18 |
+
retrievalTime: []
|
| 19 |
+
};
|
| 20 |
+
|
| 21 |
+
// Centralized configuration for all thresholds and magic numbers
|
| 22 |
+
this.config = {
|
| 23 |
+
// Content validation thresholds
|
| 24 |
+
minContentLength: 2,
|
| 25 |
+
longContentThreshold: 24,
|
| 26 |
+
titleWordCount: {
|
| 27 |
+
preferred: 3,
|
| 28 |
+
min: 1,
|
| 29 |
+
max: 5
|
| 30 |
+
},
|
| 31 |
+
|
| 32 |
+
// Similarity and confidence thresholds
|
| 33 |
+
similarity: {
|
| 34 |
+
personal: 0.6, // Names can vary more (Jean vs Jean-Pierre)
|
| 35 |
+
preferences: 0.7, // Preferences can be expressed differently
|
| 36 |
+
default: 0.8, // General similarity threshold
|
| 37 |
+
veryHigh: 0.9, // For boost_confidence strategy
|
| 38 |
+
update: 0.3 // Lower threshold for memory updates
|
| 39 |
+
},
|
| 40 |
+
|
| 41 |
+
// Confidence scoring
|
| 42 |
+
confidence: {
|
| 43 |
+
base: 0.6,
|
| 44 |
+
explicitRequest: 1.0,
|
| 45 |
+
naturalExpression: 0.7,
|
| 46 |
+
bonusForLongContent: 0.1,
|
| 47 |
+
bonusForExplicitStatement: 0.3,
|
| 48 |
+
penaltyForUncertainty: 0.2,
|
| 49 |
+
min: 0.1,
|
| 50 |
+
max: 1.0
|
| 51 |
+
},
|
| 52 |
+
|
| 53 |
+
// Memory management
|
| 54 |
+
cleanup: {
|
| 55 |
+
maxEntries: 100,
|
| 56 |
+
ttlDays: 365,
|
| 57 |
+
batchSize: 100,
|
| 58 |
+
touchMinutes: 60
|
| 59 |
+
},
|
| 60 |
+
|
| 61 |
+
// Performance settings
|
| 62 |
+
cache: {
|
| 63 |
+
keywordCacheSize: 1000,
|
| 64 |
+
statHistorySize: 100
|
| 65 |
+
},
|
| 66 |
+
|
| 67 |
+
// Scoring weights for importance calculation
|
| 68 |
+
importance: {
|
| 69 |
+
categoryWeights: {
|
| 70 |
+
important: 1.0,
|
| 71 |
+
personal: 0.9,
|
| 72 |
+
relationships: 0.85,
|
| 73 |
+
goals: 0.75,
|
| 74 |
+
experiences: 0.65,
|
| 75 |
+
preferences: 0.6,
|
| 76 |
+
activities: 0.5
|
| 77 |
+
},
|
| 78 |
+
bonuses: {
|
| 79 |
+
relationshipMilestone: 0.15,
|
| 80 |
+
boundaries: 0.15,
|
| 81 |
+
strongEmotion: 0.05,
|
| 82 |
+
futureReference: 0.05,
|
| 83 |
+
longContent: 0.05,
|
| 84 |
+
highConfidence: 0.05
|
| 85 |
+
}
|
| 86 |
+
},
|
| 87 |
+
|
| 88 |
+
// Relevance calculation weights
|
| 89 |
+
relevance: {
|
| 90 |
+
contentSimilarity: 0.35,
|
| 91 |
+
keywordOverlap: 0.25,
|
| 92 |
+
categoryRelevance: 0.1,
|
| 93 |
+
recencyBonus: 0.1,
|
| 94 |
+
confidenceBonus: 0.05,
|
| 95 |
+
importanceBonus: 0.05,
|
| 96 |
+
recentDaysThreshold: 30
|
| 97 |
+
}
|
| 98 |
+
};
|
| 99 |
+
|
| 100 |
+
this.memoryCategories = {
|
| 101 |
+
personal: "Personal Information",
|
| 102 |
+
preferences: "Likes & Dislikes",
|
| 103 |
+
relationships: "Relationships & People",
|
| 104 |
+
activities: "Activities & Hobbies",
|
| 105 |
+
goals: "Goals & Aspirations",
|
| 106 |
+
experiences: "Shared Experiences",
|
| 107 |
+
important: "Important Events"
|
| 108 |
+
};
|
| 109 |
+
|
| 110 |
+
// Patterns for automatic memory extraction (multilingual)
|
| 111 |
+
this.extractionPatterns = {
|
| 112 |
+
personal: [
|
| 113 |
+
// English patterns
|
| 114 |
+
/(?:my name is|i'm called|call me|i am) (\w+)/i,
|
| 115 |
+
/(?:i am|i'm) (\d+) years? old/i,
|
| 116 |
+
/(?:i live in|i'm from|from) ([^,.!?]+)/i,
|
| 117 |
+
/(?:i work as|my job is|i'm a) ([^,.!?]+)/i,
|
| 118 |
+
// French patterns
|
| 119 |
+
/(?:je m'appelle|mon nom est|je suis|je me prénomme|je me nomme) ([^,.!?]+)/i,
|
| 120 |
+
/(?:j'ai) (\d+) ans?/i,
|
| 121 |
+
/(?:j'habite à|je vis à|je viens de) ([^,.!?]+)/i,
|
| 122 |
+
/(?:je travaille comme|mon travail est|je suis) ([^,.!?]+)/i,
|
| 123 |
+
// Spanish patterns
|
| 124 |
+
/(?:me llamo|mi nombre es|soy) ([^,.!?]+)/i,
|
| 125 |
+
/(?:tengo) (\d+) años?/i,
|
| 126 |
+
/(?:vivo en|soy de) ([^,.!?]+)/i,
|
| 127 |
+
/(?:trabajo como|mi trabajo es|soy) ([^,.!?]+)/i,
|
| 128 |
+
// Italian patterns
|
| 129 |
+
/(?:mi chiamo|il mio nome è|sono) ([^,.!?]+)/i,
|
| 130 |
+
/(?:ho) (\d+) anni?/i,
|
| 131 |
+
/(?:abito a|vivo a|sono di) ([^,.!?]+)/i,
|
| 132 |
+
/(?:lavoro come|il mio lavoro è|sono) ([^,.!?]+)/i,
|
| 133 |
+
// German patterns
|
| 134 |
+
/(?:ich heiße|mein name ist|ich bin) ([^,.!?]+)/i,
|
| 135 |
+
/(?:ich bin) (\d+) jahre? alt/i,
|
| 136 |
+
/(?:ich wohne in|ich lebe in|ich komme aus) ([^,.!?]+)/i,
|
| 137 |
+
/(?:ich arbeite als|mein beruf ist|ich bin) ([^,.!?]+)/i,
|
| 138 |
+
// Japanese patterns
|
| 139 |
+
/私の名前は([^。!?!?、,.]+)[ですだ]?/i,
|
| 140 |
+
/私は([^。!?!?、,.]+)です/i,
|
| 141 |
+
/([^、。!?!?,.]+)と申します/i,
|
| 142 |
+
/([^、。!?!?,.]+)といいます/i,
|
| 143 |
+
// Chinese patterns
|
| 144 |
+
/我叫([^,。!?!?,.]+)/i,
|
| 145 |
+
/我的名字是([^,。!?!?,.]+)/i,
|
| 146 |
+
/叫我([^,。!?!?,.]+)/i
|
| 147 |
+
],
|
| 148 |
+
preferences: [
|
| 149 |
+
// English patterns
|
| 150 |
+
/(?:i love|i like|i enjoy|i prefer) ([^,.!?]+)/i,
|
| 151 |
+
/(?:i hate|i dislike|i don't like) ([^,.!?]+)/i,
|
| 152 |
+
/(?:my favorite|i really like) ([^,.!?]+)/i,
|
| 153 |
+
// French patterns
|
| 154 |
+
/(?:j'aime|j'adore|je préfère) ([^,.!?]+)/i,
|
| 155 |
+
/(?:je déteste|je n'aime pas) ([^,.!?]+)/i,
|
| 156 |
+
/(?:mon préféré|ma préférée) (?:est|sont) ([^,.!?]+)/i,
|
| 157 |
+
// Explicit memory requests
|
| 158 |
+
/(?:ajoute? (?:au|à la) (?:système? )?(?:de )?mémoire|retiens?|mémorise?) (?:que )?(.+)/i,
|
| 159 |
+
/(?:add to memory|remember|memorize) (?:that )?(.+)/i
|
| 160 |
+
],
|
| 161 |
+
relationships: [
|
| 162 |
+
// English patterns
|
| 163 |
+
/(?:my (?:wife|husband|girlfriend|boyfriend|partner)) (?:is|named?) ([^,.!?]+)/i,
|
| 164 |
+
/(?:my (?:mother|father|sister|brother|friend)) ([^,.!?]+)/i,
|
| 165 |
+
// French patterns
|
| 166 |
+
/(?:ma (?:femme|copine|partenaire)|mon (?:mari|copain|partenaire)) (?:s'appelle|est) ([^,.!?]+)/i,
|
| 167 |
+
/(?:ma (?:mère|sœur)|mon (?:père|frère|ami)) (?:s'appelle|est) ([^,.!?]+)/i,
|
| 168 |
+
// Spanish patterns
|
| 169 |
+
/(?:mi (?:esposa|esposo|novia|novio|pareja)) (?:es|se llama) ([^,.!?]+)/i,
|
| 170 |
+
/(?:mi (?:madre|padre|hermana|hermano|amigo|amiga)) (?:es|se llama) ([^,.!?]+)/i,
|
| 171 |
+
// Italian patterns
|
| 172 |
+
/(?:la mia (?:moglie|fidanzata|compagna)|il mio (?:marito|fidanzato|compagno)) (?:è|si chiama) ([^,.!?]+)/i,
|
| 173 |
+
/(?:mia (?:madre|sorella)|mio (?:padre|fratello|amico)) (?:è|si chiama) ([^,.!?]+)/i,
|
| 174 |
+
// German patterns
|
| 175 |
+
/(?:meine (?:frau|freundin|partnerin)|mein (?:mann|freund|partner)) (?:ist|heißt) ([^,.!?]+)/i,
|
| 176 |
+
/(?:meine (?:mutter|schwester)|mein (?:vater|bruder|freund)) (?:ist|heißt) ([^,.!?]+)/i,
|
| 177 |
+
// Japanese patterns
|
| 178 |
+
/(?:私の(?:妻|夫|彼女|彼氏|パートナー))は([^。!?!?、,.]+)(?:です|といいます)/i,
|
| 179 |
+
/(?:私の(?:母|父|姉|妹|兄|弟|友達))は([^。!?!?、,.]+)(?:です|といいます)/i,
|
| 180 |
+
// Chinese patterns
|
| 181 |
+
/(?:我的(?:妻子|丈夫|女朋友|男朋友|伴侣))叫([^,。!?!?,.]+)/i,
|
| 182 |
+
/(?:我的(?:妈妈|父亲|姐姐|妹妹|哥哥|弟弟|朋友))叫([^,。!?!?,.]+)/i
|
| 183 |
+
],
|
| 184 |
+
activities: [
|
| 185 |
+
// English patterns
|
| 186 |
+
/(?:i play|i do|i practice) ([^,.!?]+)/i,
|
| 187 |
+
/(?:my hobby is|i hobby) ([^,.!?]+)/i,
|
| 188 |
+
// French patterns
|
| 189 |
+
/(?:je joue|je fais|je pratique) ([^,.!?]+)/i,
|
| 190 |
+
/(?:mon passe-temps|mon hobby) (?:est|c'est) ([^,.!?]+)/i,
|
| 191 |
+
// Spanish patterns
|
| 192 |
+
/(?:juego|hago|practico) ([^,.!?]+)/i,
|
| 193 |
+
/(?:mi pasatiempo|mi hobby) (?:es) ([^,.!?]+)/i,
|
| 194 |
+
// Italian patterns
|
| 195 |
+
/(?:gioco|faccio|pratico) ([^,.!?]+)/i,
|
| 196 |
+
/(?:il mio passatempo|il mio hobby) (?:è) ([^,.!?]+)/i,
|
| 197 |
+
// German patterns
|
| 198 |
+
/(?:ich spiele|ich mache|ich übe) ([^,.!?]+)/i,
|
| 199 |
+
/(?:mein hobby ist) ([^,.!?]+)/i,
|
| 200 |
+
// Japanese patterns
|
| 201 |
+
/(?:私は)?(?:[^、。!?!?,.]+)が趣味です/i,
|
| 202 |
+
/趣味は([^。!?!?、,.]+)です/i,
|
| 203 |
+
// Chinese patterns
|
| 204 |
+
/(?:我玩|我做|我练习)([^,。!?!?,.]+)/i,
|
| 205 |
+
/(?:我的爱好是)([^,。!?!?,.]+)/i
|
| 206 |
+
],
|
| 207 |
+
goals: [
|
| 208 |
+
// English patterns
|
| 209 |
+
/(?:i want to|i plan to|my goal is) ([^,.!?]+)/i,
|
| 210 |
+
/(?:i'm learning|i study) ([^,.!?]+)/i,
|
| 211 |
+
// French patterns
|
| 212 |
+
/(?:je veux|je vais|mon objectif est) ([^,.!?]+)/i,
|
| 213 |
+
/(?:j'apprends|j'étudie) ([^,.!?]+)/i,
|
| 214 |
+
// Spanish patterns
|
| 215 |
+
/(?:quiero|voy a|mi objetivo es) ([^,.!?]+)/i,
|
| 216 |
+
/(?:estoy aprendiendo|estudio) ([^,.!?]+)/i,
|
| 217 |
+
// Italian patterns
|
| 218 |
+
/(?:voglio|andrò a|il mio obiettivo è) ([^,.!?]+)/i,
|
| 219 |
+
/(?:sto imparando|studio) ([^,.!?]+)/i,
|
| 220 |
+
// German patterns
|
| 221 |
+
/(?:ich möchte|ich will|mein ziel ist) ([^,.!?]+)/i,
|
| 222 |
+
/(?:ich lerne|ich studiere) ([^,.!?]+)/i,
|
| 223 |
+
// Japanese patterns
|
| 224 |
+
/(?:私は)?(?:[^、。!?!?,.]+)したい/i,
|
| 225 |
+
/(?:学んでいる|勉強している) ([^。!?!?、,.]+)/i,
|
| 226 |
+
// Chinese patterns
|
| 227 |
+
/(?:我想|我要|我的目标是)([^,。!?!?,.]+)/i,
|
| 228 |
+
/(?:我在学习|我学习)([^,。!?!?,.]+)/i
|
| 229 |
+
],
|
| 230 |
+
experiences: [
|
| 231 |
+
// English patterns
|
| 232 |
+
/we went to ([^,.!?]+)/i,
|
| 233 |
+
/we met (?:at|on|in) ([^,.!?]+)/i,
|
| 234 |
+
/our (?:first date|first kiss|trip|vacation) (?:was|was at|was on|was in|was to) ([^,.!?]+)/i,
|
| 235 |
+
/our anniversary (?:is|falls on|will be) ([^,.!?]+)/i,
|
| 236 |
+
/we moved in (?:together )?(?:on|in)?\s*([^,.!?]+)/i,
|
| 237 |
+
// French patterns
|
| 238 |
+
/on s'est rencontr[ée]s? (?:à|au|en|le) ([^,.!?]+)/i,
|
| 239 |
+
/on est all[ée]s? à ([^,.!?]+)/i,
|
| 240 |
+
/notre (?:premier rendez-vous|première sortie) (?:était|c'était) ([^,.!?]+)/i,
|
| 241 |
+
/notre anniversaire (?:est|c'est) ([^,.!?]+)/i,
|
| 242 |
+
/on a emménagé (?:ensemble\s*)?(?:le|en|à)\s*([^,.!?]+)/i,
|
| 243 |
+
// Spanish patterns
|
| 244 |
+
/nos conocimos (?:en|el|la) ([^,.!?]+)/i,
|
| 245 |
+
/fuimos a ([^,.!?]+)/i,
|
| 246 |
+
/nuestra (?:primera cita|primera salida) (?:fue|era) ([^,.!?]+)/i,
|
| 247 |
+
/nuestro aniversario (?:es|cae en|será) ([^,.!?]+)/i,
|
| 248 |
+
/nos mudamos (?:juntos\s*)?(?:el|en|a)\s*([^,.!?]+)/i,
|
| 249 |
+
// Italian patterns
|
| 250 |
+
/ci siamo conosciuti (?:a|al|in|il) ([^,.!?]+)/i,
|
| 251 |
+
/siamo andati a ([^,.!?]+)/i,
|
| 252 |
+
/il nostro (?:primo appuntamento|primo bacio|viaggio) (?:era|è stato) ([^,.!?]+)/i,
|
| 253 |
+
/il nostro anniversario (?:è|cade il|sarà) ([^,.!?]+)/i,
|
| 254 |
+
/ci siamo trasferiti (?:insieme\s*)?(?:il|in|a)\s*([^,.!?]+)/i,
|
| 255 |
+
// German patterns
|
| 256 |
+
/wir haben uns (?:in|am) ([^,.!?]+) kennengelernt/i,
|
| 257 |
+
/wir sind (?:nach|zu) ([^,.!?]+) (?:gegangen|gefahren)/i,
|
| 258 |
+
/unser (?:erstes date|erster kuss|urlaub) (?:war|fand statt) ([^,.!?]+)/i,
|
| 259 |
+
/unser jahrestag (?:ist|fällt auf|wird sein) ([^,.!?]+)/i,
|
| 260 |
+
/wir sind (?:zusammen )?eingezogen (?:am|im|in)\s*([^,.!?]+)/i,
|
| 261 |
+
// Japanese patterns
|
| 262 |
+
/私たちは([^、。!?!?,.]+)で出会った/i,
|
| 263 |
+
/一緒に([^、。!?!?,.]+)へ行った/i,
|
| 264 |
+
/私たちの記念日(?:は)?([^、。!?!?,.]+)/i,
|
| 265 |
+
/一緒に引っ越した(?:のは)?([^、。!?!?,.]+)/i,
|
| 266 |
+
// Chinese patterns
|
| 267 |
+
/我们在([^,。!?!?,.]+)认识/i,
|
| 268 |
+
/我们去了([^,。!?!?,.]+)/i,
|
| 269 |
+
/我们的纪念日是([^,。!?!?,.]+)/i,
|
| 270 |
+
/我们一起搬家(?:是在)?([^,。!?!?,.]+)/i
|
| 271 |
+
],
|
| 272 |
+
important: [
|
| 273 |
+
// English patterns
|
| 274 |
+
/it's important (?:to remember|that) (.+)/i,
|
| 275 |
+
/please remember (.+)/i,
|
| 276 |
+
// French patterns
|
| 277 |
+
/c'est important (?:de se souvenir|que) (.+)/i,
|
| 278 |
+
/merci de te souvenir (.+)/i,
|
| 279 |
+
// Spanish patterns
|
| 280 |
+
/es importante (?:recordar|que) (.+)/i,
|
| 281 |
+
/por favor recuerda (.+)/i,
|
| 282 |
+
// Italian patterns
|
| 283 |
+
/è importante (?:ricordare|che) (.+)/i,
|
| 284 |
+
/per favore ricorda (.+)/i,
|
| 285 |
+
// German patterns
|
| 286 |
+
/es ist wichtig (?:zu erinnern|dass) (.+)/i,
|
| 287 |
+
/bitte erinnere dich an (.+)/i,
|
| 288 |
+
// Japanese patterns
|
| 289 |
+
/重要なのは(.+)です/i,
|
| 290 |
+
/覚えておいてほしいのは(.+)です/i,
|
| 291 |
+
// Chinese patterns
|
| 292 |
+
/重要的是(.+)/i,
|
| 293 |
+
/请记住(.+)/i
|
| 294 |
+
]
|
| 295 |
+
};
|
| 296 |
+
|
| 297 |
+
// Performance optimization: pre-compile regex patterns
|
| 298 |
+
this.compiledPatterns = {};
|
| 299 |
+
this.initializeCompiledPatterns();
|
| 300 |
+
}
|
| 301 |
+
|
| 302 |
+
// Pre-compile all regex patterns for better performance
|
| 303 |
+
initializeCompiledPatterns() {
|
| 304 |
+
try {
|
| 305 |
+
for (const [category, patterns] of Object.entries(this.extractionPatterns)) {
|
| 306 |
+
this.compiledPatterns[category] = patterns.map(pattern => {
|
| 307 |
+
if (pattern instanceof RegExp) {
|
| 308 |
+
return pattern; // Already compiled
|
| 309 |
+
}
|
| 310 |
+
return new RegExp(pattern.source, pattern.flags);
|
| 311 |
+
});
|
| 312 |
+
}
|
| 313 |
+
|
| 314 |
+
if (window.KIMI_CONFIG?.DEBUG?.MEMORY) {
|
| 315 |
+
const totalPatterns = Object.values(this.compiledPatterns).reduce((sum, arr) => sum + arr.length, 0);
|
| 316 |
+
console.log(`🚀 Pre-compiled ${totalPatterns} regex patterns for memory extraction`);
|
| 317 |
+
}
|
| 318 |
+
} catch (error) {
|
| 319 |
+
console.error("Error pre-compiling regex patterns:", error);
|
| 320 |
+
// Fallback: use original patterns
|
| 321 |
+
this.compiledPatterns = this.extractionPatterns;
|
| 322 |
+
}
|
| 323 |
+
}
|
| 324 |
+
|
| 325 |
+
// Utility method to get consistent creation timestamp
|
| 326 |
+
getCreationTimestamp(memory) {
|
| 327 |
+
// Prefer createdAt, fallback to timestamp for backward compatibility
|
| 328 |
+
return memory.createdAt || memory.timestamp || new Date();
|
| 329 |
+
}
|
| 330 |
+
|
| 331 |
+
// Utility method to calculate days since creation
|
| 332 |
+
getDaysSinceCreation(memory) {
|
| 333 |
+
const created = new Date(this.getCreationTimestamp(memory)).getTime();
|
| 334 |
+
return (Date.now() - created) / (1000 * 60 * 60 * 24);
|
| 335 |
+
}
|
| 336 |
+
|
| 337 |
+
async init() {
|
| 338 |
+
if (!this.db) {
|
| 339 |
+
console.warn("Database not available for memory system");
|
| 340 |
+
return;
|
| 341 |
+
}
|
| 342 |
+
|
| 343 |
+
try {
|
| 344 |
+
this.memoryEnabled = await this.db.getPreference("memorySystemEnabled", window.KIMI_CONFIG?.DEFAULTS?.MEMORY_SYSTEM_ENABLED ?? true);
|
| 345 |
+
this.selectedCharacter = await this.db.getSelectedCharacter();
|
| 346 |
+
await this.createMemoryTables();
|
| 347 |
+
|
| 348 |
+
// Legacy migrations disabled - uncomment if needed for old databases
|
| 349 |
+
// await this.migrateIncompatibleIDs();
|
| 350 |
+
// this.populateKeywordsForAllMemories().catch(e => console.warn("Keyword population failed", e));
|
| 351 |
+
} catch (error) {
|
| 352 |
+
console.error("Memory system initialization error:", error);
|
| 353 |
+
}
|
| 354 |
+
}
|
| 355 |
+
|
| 356 |
+
async createMemoryTables() {
|
| 357 |
+
// Ensure memory tables exist in database
|
| 358 |
+
if (!this.db.db.memories) {
|
| 359 |
+
console.warn("Memory table not found in database schema");
|
| 360 |
+
return;
|
| 361 |
+
}
|
| 362 |
+
}
|
| 363 |
+
|
| 364 |
+
// MEMORY EXTRACTION from conversation
|
| 365 |
+
async extractMemoryFromText(userText, kimiResponse = null) {
|
| 366 |
+
if (!this.memoryEnabled || !userText) return [];
|
| 367 |
+
|
| 368 |
+
// Ensure selectedCharacter is initialized
|
| 369 |
+
if (!this.selectedCharacter) {
|
| 370 |
+
this.selectedCharacter = this.db ? await this.db.getSelectedCharacter() : "kimi";
|
| 371 |
+
}
|
| 372 |
+
|
| 373 |
+
const extractedMemories = [];
|
| 374 |
+
const text = userText.toLowerCase();
|
| 375 |
+
|
| 376 |
+
// Memory extraction processing (debug info reduced for performance)
|
| 377 |
+
|
| 378 |
+
// Enhanced extraction with context awareness
|
| 379 |
+
const existingMemories = await this.getAllMemories();
|
| 380 |
+
|
| 381 |
+
// First, check for explicit memory requests
|
| 382 |
+
const explicitRequests = this.detectExplicitMemoryRequests(userText);
|
| 383 |
+
if (explicitRequests.length > 0) {
|
| 384 |
+
// Explicit memory requests detected
|
| 385 |
+
extractedMemories.push(...explicitRequests);
|
| 386 |
+
}
|
| 387 |
+
|
| 388 |
+
// Extract using pre-compiled patterns for better performance
|
| 389 |
+
const patternsToUse = this.compiledPatterns || this.extractionPatterns;
|
| 390 |
+
for (const [category, patterns] of Object.entries(patternsToUse)) {
|
| 391 |
+
for (const pattern of patterns) {
|
| 392 |
+
const match = text.match(pattern);
|
| 393 |
+
if (match && match[1]) {
|
| 394 |
+
const content = match[1].trim();
|
| 395 |
+
|
| 396 |
+
// Skip very short or generic content
|
| 397 |
+
if (content.length < this.config.minContentLength || this.isGenericContent(content)) {
|
| 398 |
+
continue;
|
| 399 |
+
}
|
| 400 |
+
|
| 401 |
+
// Check if this is a meaningful update to existing memory
|
| 402 |
+
const isUpdate = await this.isMemoryUpdate(category, content, existingMemories);
|
| 403 |
+
|
| 404 |
+
const memory = {
|
| 405 |
+
category: category,
|
| 406 |
+
type: "auto_extracted",
|
| 407 |
+
content: content,
|
| 408 |
+
sourceText: userText,
|
| 409 |
+
confidence: this.calculateExtractionConfidence(match, userText),
|
| 410 |
+
createdAt: new Date(), // Use createdAt consistently
|
| 411 |
+
character: this.selectedCharacter || "kimi", // Fallback protection
|
| 412 |
+
isUpdate: isUpdate
|
| 413 |
+
};
|
| 414 |
+
|
| 415 |
+
// Pattern match detected
|
| 416 |
+
extractedMemories.push(memory);
|
| 417 |
+
}
|
| 418 |
+
}
|
| 419 |
+
}
|
| 420 |
+
|
| 421 |
+
// Enhanced pattern detection for more natural expressions
|
| 422 |
+
const enhancedMemories = await this.detectNaturalExpressions(userText, existingMemories);
|
| 423 |
+
extractedMemories.push(...enhancedMemories);
|
| 424 |
+
|
| 425 |
+
// Save extracted memories with intelligent deduplication
|
| 426 |
+
const savedMemories = [];
|
| 427 |
+
for (const memory of extractedMemories) {
|
| 428 |
+
try {
|
| 429 |
+
console.log("💾 Saving memory:", memory.content);
|
| 430 |
+
const saved = await this.addMemory(memory);
|
| 431 |
+
if (saved) {
|
| 432 |
+
savedMemories.push(saved);
|
| 433 |
+
} else {
|
| 434 |
+
console.warn("⚠️ Memory was not saved (possibly filtered or merged):", memory.content);
|
| 435 |
+
}
|
| 436 |
+
} catch (error) {
|
| 437 |
+
console.error("❌ Failed to save memory:", {
|
| 438 |
+
content: memory.content,
|
| 439 |
+
category: memory.category,
|
| 440 |
+
error: error.message
|
| 441 |
+
});
|
| 442 |
+
// Continue processing other memories even if one fails
|
| 443 |
+
}
|
| 444 |
+
}
|
| 445 |
+
|
| 446 |
+
if (savedMemories.length > 0) {
|
| 447 |
+
if (window.KIMI_CONFIG?.DEBUG?.MEMORY) {
|
| 448 |
+
console.log(`✅ Successfully extracted and saved ${savedMemories.length} memories`);
|
| 449 |
+
}
|
| 450 |
+
} else if (window.KIMI_CONFIG?.DEBUG?.MEMORY) {
|
| 451 |
+
console.log("📝 No memories extracted from this text");
|
| 452 |
+
}
|
| 453 |
+
|
| 454 |
+
return savedMemories;
|
| 455 |
+
}
|
| 456 |
+
|
| 457 |
+
// Detect explicit memory requests like "ajoute en mémoire que..."
|
| 458 |
+
detectExplicitMemoryRequests(text) {
|
| 459 |
+
const memories = [];
|
| 460 |
+
const lowerText = text.toLowerCase();
|
| 461 |
+
|
| 462 |
+
// French patterns for explicit memory requests
|
| 463 |
+
const frenchPatterns = [
|
| 464 |
+
/(?:ajoute?s?(?:r)?|retiens?|mémorise?s?|enregistre?s?|sauvegarde?s?)\s+(?:au|à|en|dans)\s+(?:la\s+|le\s+)?(?:système?\s+(?:de\s+)?)?mémoire\s+(?:que\s+)?(.+)/i,
|
| 465 |
+
/(?:peux-tu|pourrais-tu|veux-tu)?\s*(?:ajouter|retenir|mémoriser|enregistrer|sauvegarder)\s+(?:que\s+)?(.+)\s+(?:en|dans)\s+(?:la\s+|le\s+)?mémoire/i,
|
| 466 |
+
/(?:je\s+veux\s+que\s+tu\s+)?(?:retienne?s|mémorise?s|ajoute?s)\s+(?:que\s+)?(.+)/i
|
| 467 |
+
];
|
| 468 |
+
|
| 469 |
+
// English patterns for explicit memory requests
|
| 470 |
+
const englishPatterns = [
|
| 471 |
+
/(?:add\s+to\s+memory|remember|memorize|save\s+(?:to\s+)?memory)\s+(?:that\s+)?(.+)/i,
|
| 472 |
+
/(?:can\s+you|could\s+you)?\s*(?:add|remember|memorize|save)\s+(?:that\s+)?(.+)\s+(?:to\s+|in\s+)?memory/i,
|
| 473 |
+
/(?:i\s+want\s+you\s+to\s+)?(?:remember|memorize|add)\s+(?:that\s+)?(.+)/i
|
| 474 |
+
];
|
| 475 |
+
|
| 476 |
+
// Spanish explicit memory requests
|
| 477 |
+
const spanishPatterns = [
|
| 478 |
+
/(?:añade|agrega|recuerda|memoriza|guarda)\s+(?:en|a)\s+(?:la\s+)?memoria\s+(?:que\s+)?(.+)/i,
|
| 479 |
+
/(?:puedes|podrías)?\s*(?:añadir|agregar|recordar|memorizar|guardar)\s+(?:que\s+)?(.+)\s+(?:en|a)\s+(?:la\s+)?memoria/i,
|
| 480 |
+
/(?:quiero\s+que\s+)?(?:recuerdes|memorices|añadas)\s+(?:que\s+)?(.+)/i
|
| 481 |
+
];
|
| 482 |
+
|
| 483 |
+
// Italian explicit memory requests
|
| 484 |
+
const italianPatterns = [
|
| 485 |
+
/(?:aggiungi|ricorda|memorizza|salva)\s+(?:nella|in)\s+memoria\s+(?:che\s+)?(.+)/i,
|
| 486 |
+
/(?:puoi|potresti)?\s*(?:aggiungere|ricordare|memorizzare|salvare)\s+(?:che\s+)?(.+)\s+(?:nella|in)\s+memoria/i,
|
| 487 |
+
/(?:voglio\s+che\s+)?(?:ricordi|memorizzi|aggiunga)\s+(?:che\s+)?(.+)/i
|
| 488 |
+
];
|
| 489 |
+
|
| 490 |
+
// German explicit memory requests
|
| 491 |
+
const germanPatterns = [
|
| 492 |
+
/(?:füge|merke|speichere)\s+(?:es\s+)?(?:in|zur)\s+?gedächtnis|speicher\s+(?:dass\s+)?(.+)/i,
|
| 493 |
+
/(?:kannst\s+du|könntest\s+du)?\s*(?:hinzufügen|merken|speichern)\s+(?:dass\s+)?(.+)\s+(?:in|zum)\s+(?:gedächtnis|speicher)/i,
|
| 494 |
+
/(?:ich\s+möchte\s+dass\s+du)\s*(?:merkst|speicherst|hinzufügst)\s+(?:dass\s+)?(.+)/i
|
| 495 |
+
];
|
| 496 |
+
|
| 497 |
+
// Japanese explicit memory requests
|
| 498 |
+
const japanesePatterns = [/記憶に(?:追加|保存|覚えて)(?:して)?(?:ほしい|ください)?(?:、)?(.+)/i, /(?:覚えて|記憶して)(?:ほしい|ください)?(?:、)?(.+)/i];
|
| 499 |
+
|
| 500 |
+
// Chinese explicit memory requests
|
| 501 |
+
const chinesePatterns = [/把(.+)记在(?:记忆|内存|记忆库)里/i, /(?:请)?记住(?:这件事|这个|以下)?(.+)/i, /保存到记忆(?:里|中)(?:的是)?(.+)/i];
|
| 502 |
+
|
| 503 |
+
const allPatterns = [
|
| 504 |
+
...frenchPatterns,
|
| 505 |
+
...englishPatterns,
|
| 506 |
+
...spanishPatterns,
|
| 507 |
+
...italianPatterns,
|
| 508 |
+
...germanPatterns,
|
| 509 |
+
...japanesePatterns,
|
| 510 |
+
...chinesePatterns
|
| 511 |
+
];
|
| 512 |
+
|
| 513 |
+
for (const pattern of allPatterns) {
|
| 514 |
+
const match = lowerText.match(pattern);
|
| 515 |
+
if (match && match[1]) {
|
| 516 |
+
const content = match[1].trim();
|
| 517 |
+
|
| 518 |
+
// Determine category based on content
|
| 519 |
+
const category = this.categorizeExplicitMemory(content);
|
| 520 |
+
|
| 521 |
+
memories.push({
|
| 522 |
+
category: category,
|
| 523 |
+
type: "explicit_request",
|
| 524 |
+
content: content,
|
| 525 |
+
sourceText: text,
|
| 526 |
+
confidence: 1.0, // High confidence for explicit requests
|
| 527 |
+
timestamp: new Date(),
|
| 528 |
+
character: this.selectedCharacter,
|
| 529 |
+
isUpdate: false
|
| 530 |
+
});
|
| 531 |
+
break; // Only take the first match to avoid duplicates
|
| 532 |
+
}
|
| 533 |
+
}
|
| 534 |
+
|
| 535 |
+
return memories;
|
| 536 |
+
}
|
| 537 |
+
|
| 538 |
+
// Categorize explicit memory based on content analysis
|
| 539 |
+
categorizeExplicitMemory(content) {
|
| 540 |
+
const lowerContent = content.toLowerCase();
|
| 541 |
+
|
| 542 |
+
// Preference indicators
|
| 543 |
+
if (
|
| 544 |
+
lowerContent.includes("j'aime") ||
|
| 545 |
+
lowerContent.includes("i like") ||
|
| 546 |
+
lowerContent.includes("j'adore") ||
|
| 547 |
+
lowerContent.includes("i love") ||
|
| 548 |
+
lowerContent.includes("je préfère") ||
|
| 549 |
+
lowerContent.includes("i prefer") ||
|
| 550 |
+
lowerContent.includes("je déteste") ||
|
| 551 |
+
lowerContent.includes("i hate")
|
| 552 |
+
) {
|
| 553 |
+
return "preferences";
|
| 554 |
+
}
|
| 555 |
+
|
| 556 |
+
// Personal information indicators
|
| 557 |
+
if (
|
| 558 |
+
lowerContent.includes("je m'appelle") ||
|
| 559 |
+
lowerContent.includes("my name is") ||
|
| 560 |
+
(lowerContent.includes("j'ai") && lowerContent.includes("ans")) ||
|
| 561 |
+
lowerContent.includes("years old") ||
|
| 562 |
+
lowerContent.includes("j'habite") ||
|
| 563 |
+
lowerContent.includes("i live")
|
| 564 |
+
) {
|
| 565 |
+
return "personal";
|
| 566 |
+
}
|
| 567 |
+
|
| 568 |
+
// Relationship indicators
|
| 569 |
+
if (
|
| 570 |
+
lowerContent.includes("ma femme") ||
|
| 571 |
+
lowerContent.includes("my wife") ||
|
| 572 |
+
lowerContent.includes("mon mari") ||
|
| 573 |
+
lowerContent.includes("my husband") ||
|
| 574 |
+
lowerContent.includes("mon ami") ||
|
| 575 |
+
lowerContent.includes("my friend") ||
|
| 576 |
+
lowerContent.includes("ma famille") ||
|
| 577 |
+
lowerContent.includes("my family")
|
| 578 |
+
) {
|
| 579 |
+
return "relationships";
|
| 580 |
+
}
|
| 581 |
+
|
| 582 |
+
// Activity indicators
|
| 583 |
+
if (
|
| 584 |
+
lowerContent.includes("je joue") ||
|
| 585 |
+
lowerContent.includes("i play") ||
|
| 586 |
+
lowerContent.includes("je pratique") ||
|
| 587 |
+
lowerContent.includes("i practice") ||
|
| 588 |
+
lowerContent.includes("mon hobby") ||
|
| 589 |
+
lowerContent.includes("my hobby")
|
| 590 |
+
) {
|
| 591 |
+
return "activities";
|
| 592 |
+
}
|
| 593 |
+
|
| 594 |
+
// Goal indicators
|
| 595 |
+
if (
|
| 596 |
+
lowerContent.includes("je veux") ||
|
| 597 |
+
lowerContent.includes("i want") ||
|
| 598 |
+
lowerContent.includes("mon objectif") ||
|
| 599 |
+
lowerContent.includes("my goal") ||
|
| 600 |
+
lowerContent.includes("j'apprends") ||
|
| 601 |
+
lowerContent.includes("i'm learning")
|
| 602 |
+
) {
|
| 603 |
+
return "goals";
|
| 604 |
+
}
|
| 605 |
+
|
| 606 |
+
// Default to preferences for most explicit requests
|
| 607 |
+
return "preferences";
|
| 608 |
+
}
|
| 609 |
+
|
| 610 |
+
// Check if content is too generic to be useful
|
| 611 |
+
isGenericContent(content) {
|
| 612 |
+
const genericWords = ["yes", "no", "ok", "okay", "sure", "thanks", "hello", "hi", "bye"];
|
| 613 |
+
return genericWords.includes(content.toLowerCase()) || content.length < this.config.minContentLength;
|
| 614 |
+
}
|
| 615 |
+
|
| 616 |
+
// Calculate confidence based on context and pattern strength
|
| 617 |
+
calculateExtractionConfidence(match, fullText) {
|
| 618 |
+
let confidence = this.config.confidence.base; // Base confidence from config
|
| 619 |
+
|
| 620 |
+
// Boost confidence for explicit statements
|
| 621 |
+
const lower = fullText.toLowerCase();
|
| 622 |
+
if (
|
| 623 |
+
lower.includes("my name is") ||
|
| 624 |
+
lower.includes("i am called") ||
|
| 625 |
+
lower.includes("je m'appelle") ||
|
| 626 |
+
lower.includes("mon nom est") ||
|
| 627 |
+
lower.includes("je me prénomme") ||
|
| 628 |
+
lower.includes("je me nomme") ||
|
| 629 |
+
lower.includes("me llamo") ||
|
| 630 |
+
lower.includes("mi nombre es") ||
|
| 631 |
+
lower.includes("mi chiamo") ||
|
| 632 |
+
lower.includes("il mio nome è") ||
|
| 633 |
+
lower.includes("ich heiße") ||
|
| 634 |
+
lower.includes("mein name ist") ||
|
| 635 |
+
lower.includes("と申します") ||
|
| 636 |
+
lower.includes("私の名前は") ||
|
| 637 |
+
lower.includes("我叫") ||
|
| 638 |
+
lower.includes("我的名字是")
|
| 639 |
+
) {
|
| 640 |
+
confidence += this.config.confidence.bonusForExplicitStatement;
|
| 641 |
+
}
|
| 642 |
+
|
| 643 |
+
// Boost for longer, more specific content
|
| 644 |
+
if (match[1] && match[1].trim().length > this.config.longContentThreshold) {
|
| 645 |
+
confidence += this.config.confidence.bonusForLongContent;
|
| 646 |
+
}
|
| 647 |
+
|
| 648 |
+
// Reduce confidence for uncertain language
|
| 649 |
+
if (fullText.includes("maybe") || fullText.includes("perhaps") || fullText.includes("might")) {
|
| 650 |
+
confidence -= this.config.confidence.penaltyForUncertainty;
|
| 651 |
+
}
|
| 652 |
+
|
| 653 |
+
return Math.min(this.config.confidence.max, Math.max(this.config.confidence.min, confidence));
|
| 654 |
+
}
|
| 655 |
+
|
| 656 |
+
// Generate a short title (2-5 words max) from content for auto-extracted memories
|
| 657 |
+
generateTitleFromContent(content) {
|
| 658 |
+
if (!content || typeof content !== "string") return "";
|
| 659 |
+
// Remove surrounding punctuation and collapse whitespace
|
| 660 |
+
const cleaned = content
|
| 661 |
+
.replace(/[\n\r]+/g, " ")
|
| 662 |
+
.replace(/["'“”‘’–—:;()\[\]{}]+/g, "")
|
| 663 |
+
.trim();
|
| 664 |
+
const words = cleaned.split(/\s+/).filter(Boolean);
|
| 665 |
+
|
| 666 |
+
if (words.length === 0) return "";
|
| 667 |
+
// Prefer 3 words when available, minimum 2 when possible, maximum 5
|
| 668 |
+
let take;
|
| 669 |
+
if (words.length >= this.config.titleWordCount.preferred) take = this.config.titleWordCount.preferred;
|
| 670 |
+
else take = words.length; // 1 or 2
|
| 671 |
+
take = Math.min(this.config.titleWordCount.max, Math.max(this.config.titleWordCount.min, take));
|
| 672 |
+
|
| 673 |
+
const slice = words.slice(0, take);
|
| 674 |
+
// Capitalize first word for nicer title
|
| 675 |
+
slice[0] = slice[0].charAt(0).toUpperCase() + slice[0].slice(1);
|
| 676 |
+
return slice.join(" ");
|
| 677 |
+
}
|
| 678 |
+
|
| 679 |
+
// Check if this is an update to existing memory rather than new info
|
| 680 |
+
async isMemoryUpdate(category, content, existingMemories) {
|
| 681 |
+
const categoryMemories = existingMemories.filter(m => m.category === category);
|
| 682 |
+
|
| 683 |
+
for (const memory of categoryMemories) {
|
| 684 |
+
const similarity = this.calculateSimilarity(memory.content, content);
|
| 685 |
+
if (similarity > this.config.similarity.update) {
|
| 686 |
+
// Lower threshold for updates
|
| 687 |
+
return true;
|
| 688 |
+
}
|
| 689 |
+
}
|
| 690 |
+
|
| 691 |
+
return false;
|
| 692 |
+
}
|
| 693 |
+
|
| 694 |
+
// Detect natural expressions that patterns might miss
|
| 695 |
+
async detectNaturalExpressions(text, existingMemories) {
|
| 696 |
+
const naturalMemories = [];
|
| 697 |
+
const lowerText = text.toLowerCase();
|
| 698 |
+
|
| 699 |
+
// Detect name mentions in natural context (multilingual)
|
| 700 |
+
const namePatterns = [
|
| 701 |
+
// English
|
| 702 |
+
/call me (\w+)/i,
|
| 703 |
+
/(\w+) here[,.]?/i,
|
| 704 |
+
/this is (\w+)/i,
|
| 705 |
+
/(\w+) speaking/i,
|
| 706 |
+
// French
|
| 707 |
+
/appelle-?moi (\w+)/i,
|
| 708 |
+
/on m'appelle (\w+)/i,
|
| 709 |
+
/c'est (\w+)/i,
|
| 710 |
+
// Spanish
|
| 711 |
+
/llámame (\w+)/i,
|
| 712 |
+
/me llaman (\w+)/i,
|
| 713 |
+
/soy (\w+)/i,
|
| 714 |
+
// Italian
|
| 715 |
+
/chiamami (\w+)/i,
|
| 716 |
+
/mi chiamano (\w+)/i,
|
| 717 |
+
/sono (\w+)/i,
|
| 718 |
+
// German
|
| 719 |
+
/nenn mich (\w+)/i,
|
| 720 |
+
/man nennt mich (\w+)/i,
|
| 721 |
+
/ich bin (\w+)/i,
|
| 722 |
+
// Japanese
|
| 723 |
+
/(?:私は)?(\w+)です/i,
|
| 724 |
+
// Chinese
|
| 725 |
+
/我是(\w+)/i,
|
| 726 |
+
/叫我(\w+)/i
|
| 727 |
+
];
|
| 728 |
+
|
| 729 |
+
for (const pattern of namePatterns) {
|
| 730 |
+
const match = lowerText.match(pattern);
|
| 731 |
+
if (match && match[1] && match[1].length > 1) {
|
| 732 |
+
const name = match[1].trim();
|
| 733 |
+
|
| 734 |
+
// Skip if too generic
|
| 735 |
+
if (!this.isGenericContent(name) && !this.isCommonWord(name)) {
|
| 736 |
+
naturalMemories.push({
|
| 737 |
+
category: "personal",
|
| 738 |
+
type: "auto_extracted",
|
| 739 |
+
content: name,
|
| 740 |
+
sourceText: text,
|
| 741 |
+
confidence: 0.7,
|
| 742 |
+
createdAt: new Date(), // Use createdAt consistently
|
| 743 |
+
character: this.selectedCharacter || "kimi" // Fallback protection
|
| 744 |
+
});
|
| 745 |
+
}
|
| 746 |
+
}
|
| 747 |
+
}
|
| 748 |
+
|
| 749 |
+
return naturalMemories;
|
| 750 |
+
}
|
| 751 |
+
|
| 752 |
+
// Check if word is too common to be a name
|
| 753 |
+
isCommonWord(word, language = "en") {
|
| 754 |
+
// Use existing constants if available
|
| 755 |
+
if (window.KIMI_COMMON_WORDS && window.KIMI_COMMON_WORDS[language]) {
|
| 756 |
+
return window.KIMI_COMMON_WORDS[language].includes(word.toLowerCase());
|
| 757 |
+
}
|
| 758 |
+
|
| 759 |
+
// Fallback to original English list
|
| 760 |
+
const commonWords = [
|
| 761 |
+
"the",
|
| 762 |
+
"and",
|
| 763 |
+
"for",
|
| 764 |
+
"are",
|
| 765 |
+
"but",
|
| 766 |
+
"not",
|
| 767 |
+
"you",
|
| 768 |
+
"all",
|
| 769 |
+
"can",
|
| 770 |
+
"had",
|
| 771 |
+
"her",
|
| 772 |
+
"was",
|
| 773 |
+
"one",
|
| 774 |
+
"our",
|
| 775 |
+
"out",
|
| 776 |
+
"day",
|
| 777 |
+
"get",
|
| 778 |
+
"has",
|
| 779 |
+
"him",
|
| 780 |
+
"his",
|
| 781 |
+
"how",
|
| 782 |
+
"man",
|
| 783 |
+
"new",
|
| 784 |
+
"now",
|
| 785 |
+
"old",
|
| 786 |
+
"see",
|
| 787 |
+
"two",
|
| 788 |
+
"way",
|
| 789 |
+
"who",
|
| 790 |
+
"boy",
|
| 791 |
+
"did",
|
| 792 |
+
"its",
|
| 793 |
+
"let",
|
| 794 |
+
"put",
|
| 795 |
+
"say",
|
| 796 |
+
"she",
|
| 797 |
+
"too",
|
| 798 |
+
"use"
|
| 799 |
+
];
|
| 800 |
+
return commonWords.includes(word.toLowerCase());
|
| 801 |
+
}
|
| 802 |
+
|
| 803 |
+
// MANUAL MEMORY MANAGEMENT
|
| 804 |
+
async addMemory(memoryData) {
|
| 805 |
+
if (!this.db || !this.memoryEnabled) return;
|
| 806 |
+
|
| 807 |
+
try {
|
| 808 |
+
// Check for duplicates with intelligent merging
|
| 809 |
+
const existing = await this.findSimilarMemory(memoryData);
|
| 810 |
+
if (existing) {
|
| 811 |
+
// Intelligent merge strategy
|
| 812 |
+
return await this.mergeMemories(existing, memoryData);
|
| 813 |
+
}
|
| 814 |
+
|
| 815 |
+
// Add memory with metadata (let DB auto-generate ID)
|
| 816 |
+
const now = new Date();
|
| 817 |
+
const memory = {
|
| 818 |
+
category: memoryData.category || "personal",
|
| 819 |
+
type: memoryData.type || "manual",
|
| 820 |
+
content: memoryData.content,
|
| 821 |
+
// precomputed keywords for faster matching and relevance
|
| 822 |
+
keywords: this.deriveKeywords(memoryData.content),
|
| 823 |
+
// Title: use provided title or generate for auto_extracted
|
| 824 |
+
title:
|
| 825 |
+
memoryData.title && typeof memoryData.title === "string"
|
| 826 |
+
? memoryData.title
|
| 827 |
+
: memoryData.type === "auto_extracted"
|
| 828 |
+
? this.generateTitleFromContent(memoryData.content)
|
| 829 |
+
: "",
|
| 830 |
+
sourceText: memoryData.sourceText || "",
|
| 831 |
+
confidence: memoryData.confidence || 1.0,
|
| 832 |
+
createdAt: memoryData.createdAt || memoryData.timestamp || now, // Unified timestamp handling
|
| 833 |
+
character: memoryData.character || this.selectedCharacter || "kimi", // Fallback protection
|
| 834 |
+
isActive: true,
|
| 835 |
+
tags: [...new Set([...(memoryData.tags || []), ...this.deriveMemoryTags(memoryData)])],
|
| 836 |
+
lastModified: now,
|
| 837 |
+
lastAccess: now,
|
| 838 |
+
accessCount: 0,
|
| 839 |
+
importance: this.calculateImportance(memoryData)
|
| 840 |
+
};
|
| 841 |
+
|
| 842 |
+
if (this.db.db.memories) {
|
| 843 |
+
const id = await this.db.db.memories.add(memory);
|
| 844 |
+
memory.id = id; // Store the auto-generated ID
|
| 845 |
+
if (window.KIMI_CONFIG?.DEBUG?.MEMORY) {
|
| 846 |
+
console.log(`Memory added with ID: ${id}`);
|
| 847 |
+
}
|
| 848 |
+
}
|
| 849 |
+
|
| 850 |
+
// Cleanup old memories if we exceed limit
|
| 851 |
+
await this.cleanupOldMemories();
|
| 852 |
+
|
| 853 |
+
// Notify LLM system to refresh context
|
| 854 |
+
this.notifyLLMContextUpdate();
|
| 855 |
+
|
| 856 |
+
return memory;
|
| 857 |
+
} catch (error) {
|
| 858 |
+
console.error("Error adding memory:", error);
|
| 859 |
+
return null; // Return null instead of undefined for clearer error handling
|
| 860 |
+
}
|
| 861 |
+
}
|
| 862 |
+
|
| 863 |
+
// Intelligent memory merging
|
| 864 |
+
async mergeMemories(existingMemory, newMemoryData) {
|
| 865 |
+
try {
|
| 866 |
+
// Determine merge strategy based on content and confidence
|
| 867 |
+
const strategy = this.determineMergeStrategy(existingMemory, newMemoryData);
|
| 868 |
+
|
| 869 |
+
let mergedContent = existingMemory.content;
|
| 870 |
+
let mergedConfidence = existingMemory.confidence;
|
| 871 |
+
let mergedTags = [...(existingMemory.tags || [])];
|
| 872 |
+
|
| 873 |
+
switch (strategy) {
|
| 874 |
+
case "update_content":
|
| 875 |
+
// New information is more confident/recent
|
| 876 |
+
mergedContent = newMemoryData.content;
|
| 877 |
+
mergedConfidence = Math.max(existingMemory.confidence, newMemoryData.confidence || 0.8);
|
| 878 |
+
break;
|
| 879 |
+
|
| 880 |
+
case "merge_content":
|
| 881 |
+
// Combine information intelligently
|
| 882 |
+
if (existingMemory.category === "personal" && this.areRelatedNames(existingMemory.content, newMemoryData.content)) {
|
| 883 |
+
// Handle name variants
|
| 884 |
+
mergedContent = this.mergeNames(existingMemory.content, newMemoryData.content);
|
| 885 |
+
} else {
|
| 886 |
+
// General merge - keep most specific
|
| 887 |
+
mergedContent = newMemoryData.content.length > existingMemory.content.length ? newMemoryData.content : existingMemory.content;
|
| 888 |
+
}
|
| 889 |
+
mergedConfidence = (existingMemory.confidence + (newMemoryData.confidence || 0.8)) / 2;
|
| 890 |
+
break;
|
| 891 |
+
|
| 892 |
+
case "add_variant":
|
| 893 |
+
// Store as variant/alias
|
| 894 |
+
mergedTags.push(`alias:${newMemoryData.content}`);
|
| 895 |
+
break;
|
| 896 |
+
|
| 897 |
+
case "boost_confidence":
|
| 898 |
+
// Same content, boost confidence
|
| 899 |
+
mergedConfidence = Math.min(1.0, existingMemory.confidence + 0.1);
|
| 900 |
+
break;
|
| 901 |
+
}
|
| 902 |
+
|
| 903 |
+
// Update existing memory
|
| 904 |
+
const updatedMemory = {
|
| 905 |
+
...existingMemory,
|
| 906 |
+
content: mergedContent,
|
| 907 |
+
confidence: mergedConfidence,
|
| 908 |
+
tags: [...new Set([...mergedTags, ...this.deriveMemoryTags(newMemoryData)])], // Remove duplicates
|
| 909 |
+
lastModified: new Date(),
|
| 910 |
+
accessCount: (existingMemory.accessCount || 0) + 1,
|
| 911 |
+
importance: Math.max(existingMemory.importance || 0.5, this.calculateImportance(newMemoryData))
|
| 912 |
+
};
|
| 913 |
+
|
| 914 |
+
await this.updateMemory(existingMemory.id, updatedMemory);
|
| 915 |
+
return updatedMemory;
|
| 916 |
+
} catch (error) {
|
| 917 |
+
console.error("Error merging memories:", error);
|
| 918 |
+
return existingMemory;
|
| 919 |
+
}
|
| 920 |
+
}
|
| 921 |
+
|
| 922 |
+
// Simplified memory merge strategy determination
|
| 923 |
+
determineMergeStrategy(existing, newData) {
|
| 924 |
+
const similarity = this.calculateSimilarity(existing.content, newData.content);
|
| 925 |
+
const newConfidence = newData.confidence || this.config.confidence.base;
|
| 926 |
+
const existingConfidence = existing.confidence || this.config.confidence.base;
|
| 927 |
+
|
| 928 |
+
// Very high similarity (>90%) - boost confidence if new is more confident
|
| 929 |
+
if (similarity > this.config.similarity.veryHigh) {
|
| 930 |
+
return newConfidence > existingConfidence ? "boost_confidence" : "merge_content";
|
| 931 |
+
}
|
| 932 |
+
|
| 933 |
+
// High similarity (>70%) - decide based on content length and specificity
|
| 934 |
+
if (similarity > this.config.similarity.preferences) {
|
| 935 |
+
// If new content is significantly longer (50% more), it's likely more detailed
|
| 936 |
+
if (newData.content.length > existing.content.length * 1.5) {
|
| 937 |
+
return "update_content";
|
| 938 |
+
}
|
| 939 |
+
// If existing is longer, merge to preserve information
|
| 940 |
+
return "merge_content";
|
| 941 |
+
}
|
| 942 |
+
|
| 943 |
+
// For personal names, handle as variants if they're related
|
| 944 |
+
if (existing.category === "personal" && this.areRelatedNames(existing.content, newData.content)) {
|
| 945 |
+
return "add_variant";
|
| 946 |
+
}
|
| 947 |
+
|
| 948 |
+
// Default strategy for moderate similarity
|
| 949 |
+
return "merge_content";
|
| 950 |
+
}
|
| 951 |
+
|
| 952 |
+
// Merge name variants intelligently
|
| 953 |
+
mergeNames(name1, name2) {
|
| 954 |
+
// Keep the longest/most formal version as primary
|
| 955 |
+
if (name1.length > name2.length) {
|
| 956 |
+
return name1;
|
| 957 |
+
} else if (name2.length > name1.length) {
|
| 958 |
+
return name2;
|
| 959 |
+
}
|
| 960 |
+
|
| 961 |
+
// If same length, keep the first one
|
| 962 |
+
return name1;
|
| 963 |
+
}
|
| 964 |
+
|
| 965 |
+
// Calculate importance of memory for prioritization
|
| 966 |
+
calculateImportance(memoryData) {
|
| 967 |
+
let importance = 0.5; // Base importance
|
| 968 |
+
|
| 969 |
+
// Category base weights
|
| 970 |
+
const categoryWeights = {
|
| 971 |
+
important: 1.0,
|
| 972 |
+
personal: 0.9,
|
| 973 |
+
relationships: 0.85,
|
| 974 |
+
goals: 0.75,
|
| 975 |
+
experiences: 0.65,
|
| 976 |
+
preferences: 0.6,
|
| 977 |
+
activities: 0.5
|
| 978 |
+
};
|
| 979 |
+
|
| 980 |
+
importance = categoryWeights[memoryData.category] || 0.5;
|
| 981 |
+
|
| 982 |
+
const content = (memoryData.content || "").toLowerCase();
|
| 983 |
+
const tags = new Set([...(memoryData.tags || []), ...this.deriveMemoryTags(memoryData)]);
|
| 984 |
+
|
| 985 |
+
// Heuristic boosts for meaningful relationship milestones and commitments
|
| 986 |
+
const milestoneTags = [
|
| 987 |
+
"relationship:first_meet",
|
| 988 |
+
"relationship:first_date",
|
| 989 |
+
"relationship:first_kiss",
|
| 990 |
+
"relationship:anniversary",
|
| 991 |
+
"relationship:moved_in",
|
| 992 |
+
"relationship:engaged",
|
| 993 |
+
"relationship:married",
|
| 994 |
+
"relationship:breakup"
|
| 995 |
+
];
|
| 996 |
+
if ([...tags].some(t => milestoneTags.includes(t))) importance += 0.15;
|
| 997 |
+
|
| 998 |
+
// Boundaries and consent are high priority to remember
|
| 999 |
+
if ([...tags].some(t => t.startsWith("boundary:"))) importance += 0.15;
|
| 1000 |
+
|
| 1001 |
+
// Preferences tied to strong like/dislike
|
| 1002 |
+
if (content.includes("i love") || content.includes("j'adore") || content.includes("i hate") || content.includes("je déteste")) {
|
| 1003 |
+
importance += 0.05;
|
| 1004 |
+
}
|
| 1005 |
+
|
| 1006 |
+
// Temporal cues: future commitments or dates
|
| 1007 |
+
if (/(\bnext\b|\btomorrow\b|\bce soir\b|\bdemain\b|\bmañana\b|\bdomani\b|\bmorgen\b)/i.test(content)) {
|
| 1008 |
+
importance += 0.05;
|
| 1009 |
+
}
|
| 1010 |
+
|
| 1011 |
+
// Longer details and high confidence
|
| 1012 |
+
if (memoryData.content && memoryData.content.length > this.config.longContentThreshold) importance += this.config.importance.bonuses.longContent;
|
| 1013 |
+
if (memoryData.confidence && memoryData.confidence > 0.9) importance += this.config.importance.bonuses.highConfidence;
|
| 1014 |
+
|
| 1015 |
+
// Round to two decimals to avoid floating point artifacts
|
| 1016 |
+
return Math.min(1.0, Math.round(importance * 100) / 100);
|
| 1017 |
+
}
|
| 1018 |
+
|
| 1019 |
+
// Derive semantic tags from memory content to assist prioritization and merging
|
| 1020 |
+
deriveMemoryTags(memoryData) {
|
| 1021 |
+
const tags = [];
|
| 1022 |
+
const text = (memoryData.content || "").toLowerCase();
|
| 1023 |
+
const category = memoryData.category || "";
|
| 1024 |
+
|
| 1025 |
+
// Relationship status and milestones
|
| 1026 |
+
if (/(single|célibataire|soltero|single|ledig)/i.test(text)) tags.push("relationship:status_single");
|
| 1027 |
+
if (/(in a relationship|en couple|together|ensemble|pareja|coppia|beziehung)/i.test(text)) tags.push("relationship:status_in_relationship");
|
| 1028 |
+
if (/(engaged|fiancé|fiancée|promis|promised|verlobt)/i.test(text)) tags.push("relationship:status_engaged");
|
| 1029 |
+
if (/(married|marié|mariée|casado|sposato|verheiratet)/i.test(text)) tags.push("relationship:status_married");
|
| 1030 |
+
if (/(broke up|rupture|separated|separado|separati|getrennt)/i.test(text)) tags.push("relationship:breakup");
|
| 1031 |
+
if (/(first date|premier rendez-vous|primera cita|primo appuntamento)/i.test(text)) tags.push("relationship:first_date");
|
| 1032 |
+
if (/(first kiss|premier baiser|primer beso|primo bacio)/i.test(text)) tags.push("relationship:first_kiss");
|
| 1033 |
+
if (/(anniversary|anniversaire|aniversario|anniversario|jahrestag)/i.test(text)) tags.push("relationship:anniversary");
|
| 1034 |
+
if (/(moved in together|emménagé ensemble|mudamos juntos|trasferiti insieme|zusammen eingezogen)/i.test(text)) tags.push("relationship:moved_in");
|
| 1035 |
+
if (/(met at|rencontré à|conocimos en|conosciuti a|kennengelernt)/i.test(text)) tags.push("relationship:first_meet");
|
| 1036 |
+
|
| 1037 |
+
// Boundaries and consent (keep generic and non-graphic)
|
| 1038 |
+
if (/(i don't like|je n'aime pas|no me gusta|non mi piace|ich mag nicht)\s+[^,.!?]+/i.test(text)) tags.push("boundary:dislike");
|
| 1039 |
+
if (/(i prefer|je préfère|prefiero|preferisco|ich bevorzuge)\s+[^,.!?]+/i.test(text)) tags.push("boundary:preference");
|
| 1040 |
+
if (/(no|pas)\s+(?:kissing|baiser|beso|bacio|küssen)/i.test(text)) tags.push("boundary:limit");
|
| 1041 |
+
if (/(consent|consentement|consentimiento|consenso|einwilligung)/i.test(text)) tags.push("boundary:consent");
|
| 1042 |
+
|
| 1043 |
+
// Time-related tags
|
| 1044 |
+
if (/(today|ce jour|hoy|oggi|heute|今日)/i.test(text)) tags.push("time:today");
|
| 1045 |
+
if (/(tomorrow|demain|mañana|domani|morgen|明日)/i.test(text)) tags.push("time:tomorrow");
|
| 1046 |
+
if (/(next week|semaine prochaine|la próxima semana|la prossima settimana|nächste woche)/i.test(text)) tags.push("time:next_week");
|
| 1047 |
+
|
| 1048 |
+
// Category-specific hints
|
| 1049 |
+
if (category === "preferences") tags.push("type:preference");
|
| 1050 |
+
if (category === "personal") tags.push("type:personal");
|
| 1051 |
+
if (category === "relationships") tags.push("type:relationship");
|
| 1052 |
+
if (category === "experiences") tags.push("type:experience");
|
| 1053 |
+
if (category === "goals") tags.push("type:goal");
|
| 1054 |
+
if (category === "important") tags.push("type:important");
|
| 1055 |
+
|
| 1056 |
+
return tags;
|
| 1057 |
+
}
|
| 1058 |
+
|
| 1059 |
+
async updateMemory(memoryId, updateData) {
|
| 1060 |
+
if (!this.db) return false;
|
| 1061 |
+
|
| 1062 |
+
try {
|
| 1063 |
+
// Ensure memoryId is the correct type
|
| 1064 |
+
const numericId = typeof memoryId === "string" ? parseInt(memoryId) : memoryId;
|
| 1065 |
+
|
| 1066 |
+
// Vérifier d'abord que la mémoire existe
|
| 1067 |
+
const existingMemory = await this.db.db.memories.get(numericId);
|
| 1068 |
+
if (!existingMemory) {
|
| 1069 |
+
console.error(`❌ Memory with ID ${numericId} not found in database`);
|
| 1070 |
+
return false;
|
| 1071 |
+
}
|
| 1072 |
+
|
| 1073 |
+
console.log(`🔄 Updating memory ${numericId}:`, { existing: existingMemory, update: updateData });
|
| 1074 |
+
|
| 1075 |
+
const update = {
|
| 1076 |
+
...updateData,
|
| 1077 |
+
lastModified: new Date()
|
| 1078 |
+
};
|
| 1079 |
+
|
| 1080 |
+
if (this.db.db.memories) {
|
| 1081 |
+
const result = await this.db.db.memories.update(numericId, update);
|
| 1082 |
+
|
| 1083 |
+
console.log(`Memory update result for ID ${numericId}:`, result);
|
| 1084 |
+
|
| 1085 |
+
if (result > 0) {
|
| 1086 |
+
console.log("✅ Memory updated successfully");
|
| 1087 |
+
// Notify LLM system to refresh context
|
| 1088 |
+
this.notifyLLMContextUpdate();
|
| 1089 |
+
return true;
|
| 1090 |
+
} else {
|
| 1091 |
+
console.error("❌ Memory update failed - no rows affected");
|
| 1092 |
+
return false;
|
| 1093 |
+
}
|
| 1094 |
+
}
|
| 1095 |
+
} catch (error) {
|
| 1096 |
+
console.error("Error updating memory:", error, { memoryId, updateData });
|
| 1097 |
+
return false;
|
| 1098 |
+
}
|
| 1099 |
+
}
|
| 1100 |
+
|
| 1101 |
+
async deleteMemory(memoryId) {
|
| 1102 |
+
if (!this.db) return false;
|
| 1103 |
+
|
| 1104 |
+
try {
|
| 1105 |
+
// Ensure memoryId is the correct type
|
| 1106 |
+
const numericId = typeof memoryId === "string" ? parseInt(memoryId) : memoryId;
|
| 1107 |
+
|
| 1108 |
+
if (this.db.db.memories) {
|
| 1109 |
+
const result = await this.db.db.memories.delete(numericId);
|
| 1110 |
+
|
| 1111 |
+
console.log(`Memory delete result for ID ${numericId}:`, result);
|
| 1112 |
+
|
| 1113 |
+
// Notify LLM system to refresh context
|
| 1114 |
+
if (result) {
|
| 1115 |
+
this.notifyLLMContextUpdate();
|
| 1116 |
+
}
|
| 1117 |
+
|
| 1118 |
+
return result;
|
| 1119 |
+
}
|
| 1120 |
+
} catch (error) {
|
| 1121 |
+
console.error("Error deleting memory:", error, { memoryId });
|
| 1122 |
+
return false;
|
| 1123 |
+
}
|
| 1124 |
+
}
|
| 1125 |
+
|
| 1126 |
+
notifyLLMContextUpdate() {
|
| 1127 |
+
// Debounce context updates to avoid excessive calls
|
| 1128 |
+
if (this.contextUpdateTimeout) {
|
| 1129 |
+
clearTimeout(this.contextUpdateTimeout);
|
| 1130 |
+
}
|
| 1131 |
+
|
| 1132 |
+
this.contextUpdateTimeout = setTimeout(() => {
|
| 1133 |
+
if (window.kimiLLM && typeof window.kimiLLM.refreshMemoryContext === "function") {
|
| 1134 |
+
window.kimiLLM.refreshMemoryContext();
|
| 1135 |
+
}
|
| 1136 |
+
}, 500);
|
| 1137 |
+
}
|
| 1138 |
+
|
| 1139 |
+
// Add cleanup method for memory system
|
| 1140 |
+
cleanup() {
|
| 1141 |
+
if (this.contextUpdateTimeout) {
|
| 1142 |
+
clearTimeout(this.contextUpdateTimeout);
|
| 1143 |
+
this.contextUpdateTimeout = null;
|
| 1144 |
+
}
|
| 1145 |
+
|
| 1146 |
+
// Clear caches to prevent memory leaks
|
| 1147 |
+
if (this.keywordCache) {
|
| 1148 |
+
this.keywordCache.clear();
|
| 1149 |
+
}
|
| 1150 |
+
|
| 1151 |
+
// Reset stats arrays to prevent accumulation
|
| 1152 |
+
if (this.queryStats) {
|
| 1153 |
+
this.queryStats.extractionTime.length = 0;
|
| 1154 |
+
this.queryStats.addMemoryTime.length = 0;
|
| 1155 |
+
this.queryStats.retrievalTime.length = 0;
|
| 1156 |
+
}
|
| 1157 |
+
}
|
| 1158 |
+
|
| 1159 |
+
async getMemoriesByCategory(category, character = null) {
|
| 1160 |
+
if (!this.db) return [];
|
| 1161 |
+
|
| 1162 |
+
try {
|
| 1163 |
+
character = character || this.selectedCharacter || "kimi"; // Unified fallback
|
| 1164 |
+
|
| 1165 |
+
if (this.db.db.memories) {
|
| 1166 |
+
const memories = await this.db.db.memories
|
| 1167 |
+
.where("[character+category]")
|
| 1168 |
+
.equals([character, category])
|
| 1169 |
+
.and(m => m.isActive)
|
| 1170 |
+
.reverse()
|
| 1171 |
+
.sortBy("timestamp");
|
| 1172 |
+
|
| 1173 |
+
// Update lastAccess/accessCount for top results to improve prioritization
|
| 1174 |
+
this._touchMemories(memories, 10).catch(() => {});
|
| 1175 |
+
return memories;
|
| 1176 |
+
}
|
| 1177 |
+
} catch (error) {
|
| 1178 |
+
console.error("Error getting memories by category:", error);
|
| 1179 |
+
return [];
|
| 1180 |
+
}
|
| 1181 |
+
}
|
| 1182 |
+
|
| 1183 |
+
async getAllMemories(character = null) {
|
| 1184 |
+
if (!this.db) return [];
|
| 1185 |
+
|
| 1186 |
+
try {
|
| 1187 |
+
character = character || this.selectedCharacter || "kimi";
|
| 1188 |
+
|
| 1189 |
+
if (this.db.db.memories) {
|
| 1190 |
+
// Primary IndexedDB (Dexie) sort still leverages the existing 'timestamp' index for performance.
|
| 1191 |
+
// Then we apply a stable in-memory reorder using canonical creation time (createdAt fallback timestamp)
|
| 1192 |
+
// to unify ordering semantics without breaking older databases lacking createdAt originally.
|
| 1193 |
+
const memories = await this.db.db.memories
|
| 1194 |
+
.where("character")
|
| 1195 |
+
.equals(character)
|
| 1196 |
+
.filter(memory => memory.isActive !== false) // Include records without isActive field (legacy)
|
| 1197 |
+
.reverse()
|
| 1198 |
+
.sortBy("timestamp");
|
| 1199 |
+
|
| 1200 |
+
// Backward-compatible canonical ordering: most recent first by getCreationTimestamp
|
| 1201 |
+
// (Only if >1 entry to avoid needless array ops.)
|
| 1202 |
+
if (memories.length > 1) {
|
| 1203 |
+
memories.sort((a, b) => {
|
| 1204 |
+
const ca = new Date(this.getCreationTimestamp(a)).getTime();
|
| 1205 |
+
const cb = new Date(this.getCreationTimestamp(b)).getTime();
|
| 1206 |
+
return cb - ca; // descending (newest first)
|
| 1207 |
+
});
|
| 1208 |
+
}
|
| 1209 |
+
|
| 1210 |
+
if (window.KIMI_DEBUG_MEMORIES) {
|
| 1211 |
+
console.log(`Retrieved ${memories.length} memories for character: ${character}`);
|
| 1212 |
+
}
|
| 1213 |
+
|
| 1214 |
+
// Touch top memories to update access metrics
|
| 1215 |
+
this._touchMemories(memories, 10).catch(() => {});
|
| 1216 |
+
return memories;
|
| 1217 |
+
}
|
| 1218 |
+
} catch (error) {
|
| 1219 |
+
console.error("Error getting all memories:", error);
|
| 1220 |
+
return [];
|
| 1221 |
+
}
|
| 1222 |
+
}
|
| 1223 |
+
|
| 1224 |
+
async findSimilarMemory(memoryData) {
|
| 1225 |
+
if (!this.db) return null;
|
| 1226 |
+
|
| 1227 |
+
try {
|
| 1228 |
+
const memories = await this.getMemoriesByCategory(memoryData.category);
|
| 1229 |
+
|
| 1230 |
+
// Precompute keywords for new memory
|
| 1231 |
+
const newKeys = this.deriveKeywords(memoryData.content || "");
|
| 1232 |
+
|
| 1233 |
+
// Enhanced similarity check with multiple criteria
|
| 1234 |
+
for (const memory of memories) {
|
| 1235 |
+
// Prefilter by keyword overlap to reduce false positives and improve perf
|
| 1236 |
+
const memKeys = memory.keywords || this.deriveKeywords(memory.content || "");
|
| 1237 |
+
const overlap = newKeys.filter(k => memKeys.includes(k)).length;
|
| 1238 |
+
if (newKeys.length > 0 && overlap === 0) continue; // no shared keywords -> likely different
|
| 1239 |
+
|
| 1240 |
+
const contentSimilarity = this.calculateSimilarity(memory.content, memoryData.content);
|
| 1241 |
+
|
| 1242 |
+
// Different thresholds based on category
|
| 1243 |
+
const threshold = this.config.similarity[memoryData.category] || this.config.similarity.default;
|
| 1244 |
+
|
| 1245 |
+
if (contentSimilarity > threshold) {
|
| 1246 |
+
return memory;
|
| 1247 |
+
}
|
| 1248 |
+
|
| 1249 |
+
// Special handling for names (check if one is contained in the other)
|
| 1250 |
+
if (memoryData.category === "personal" && this.areRelatedNames(memory.content, memoryData.content)) {
|
| 1251 |
+
return memory;
|
| 1252 |
+
}
|
| 1253 |
+
}
|
| 1254 |
+
} catch (error) {
|
| 1255 |
+
console.error("Error finding similar memory:", error);
|
| 1256 |
+
}
|
| 1257 |
+
|
| 1258 |
+
return null;
|
| 1259 |
+
}
|
| 1260 |
+
|
| 1261 |
+
// Check if two names are related (nicknames, variants, etc.)
|
| 1262 |
+
areRelatedNames(name1, name2) {
|
| 1263 |
+
const n1 = name1.toLowerCase().trim();
|
| 1264 |
+
const n2 = name2.toLowerCase().trim();
|
| 1265 |
+
|
| 1266 |
+
// Exact match
|
| 1267 |
+
if (n1 === n2) return true;
|
| 1268 |
+
|
| 1269 |
+
// One contains the other (Jean-Pierre vs Jean)
|
| 1270 |
+
if (n1.includes(n2) || n2.includes(n1)) return true;
|
| 1271 |
+
|
| 1272 |
+
// Common nickname patterns
|
| 1273 |
+
const nicknames = {
|
| 1274 |
+
jean: ["jp", "jeannot"],
|
| 1275 |
+
pierre: ["pete", "pietro"],
|
| 1276 |
+
marie: ["mary", "maria"],
|
| 1277 |
+
michael: ["mike", "mick"],
|
| 1278 |
+
william: ["bill", "will", "willy"],
|
| 1279 |
+
robert: ["bob", "rob", "bobby"],
|
| 1280 |
+
richard: ["rick", "dick", "richie"],
|
| 1281 |
+
thomas: ["tom", "tommy"],
|
| 1282 |
+
christopher: ["chris", "kit"],
|
| 1283 |
+
anthony: ["tony", "ant"]
|
| 1284 |
+
};
|
| 1285 |
+
|
| 1286 |
+
for (const [full, nicks] of Object.entries(nicknames)) {
|
| 1287 |
+
if ((n1 === full && nicks.includes(n2)) || (n2 === full && nicks.includes(n1))) {
|
| 1288 |
+
return true;
|
| 1289 |
+
}
|
| 1290 |
+
}
|
| 1291 |
+
|
| 1292 |
+
return false;
|
| 1293 |
+
}
|
| 1294 |
+
|
| 1295 |
+
calculateSimilarity(text1, text2) {
|
| 1296 |
+
// Enhanced similarity calculation
|
| 1297 |
+
const words1 = text1
|
| 1298 |
+
.toLowerCase()
|
| 1299 |
+
.split(/\s+/)
|
| 1300 |
+
.filter(w => w.length > 2);
|
| 1301 |
+
const words2 = text2
|
| 1302 |
+
.toLowerCase()
|
| 1303 |
+
.split(/\s+/)
|
| 1304 |
+
.filter(w => w.length > 2);
|
| 1305 |
+
|
| 1306 |
+
if (words1.length === 0 || words2.length === 0) {
|
| 1307 |
+
return text1.toLowerCase() === text2.toLowerCase() ? 1 : 0;
|
| 1308 |
+
}
|
| 1309 |
+
|
| 1310 |
+
const intersection = words1.filter(word => words2.includes(word));
|
| 1311 |
+
const union = [...new Set([...words1, ...words2])];
|
| 1312 |
+
|
| 1313 |
+
let similarity = intersection.length / union.length;
|
| 1314 |
+
|
| 1315 |
+
// Boost similarity for exact substring matches
|
| 1316 |
+
if (text1.toLowerCase().includes(text2.toLowerCase()) || text2.toLowerCase().includes(text1.toLowerCase())) {
|
| 1317 |
+
similarity += 0.2;
|
| 1318 |
+
}
|
| 1319 |
+
|
| 1320 |
+
return Math.min(1.0, similarity);
|
| 1321 |
+
}
|
| 1322 |
+
|
| 1323 |
+
// Derive a set of normalized keywords from text
|
| 1324 |
+
deriveKeywords(text) {
|
| 1325 |
+
if (!text || typeof text !== "string") return [];
|
| 1326 |
+
return [
|
| 1327 |
+
...new Set(
|
| 1328 |
+
text
|
| 1329 |
+
.toLowerCase()
|
| 1330 |
+
.replace(/[\p{P}\p{S}]/gu, " ")
|
| 1331 |
+
.split(/\s+/)
|
| 1332 |
+
.filter(w => w.length > 2 && !this.isCommonWordSafe(w))
|
| 1333 |
+
)
|
| 1334 |
+
];
|
| 1335 |
+
}
|
| 1336 |
+
|
| 1337 |
+
// Safe wrapper for isCommonWord to avoid undefined function errors
|
| 1338 |
+
isCommonWordSafe(word, language = "en") {
|
| 1339 |
+
const cacheKey = `${word.toLowerCase()}_${language}`;
|
| 1340 |
+
|
| 1341 |
+
// Check cache first
|
| 1342 |
+
if (this.keywordCache.has(cacheKey)) {
|
| 1343 |
+
this.keywordCacheHits++;
|
| 1344 |
+
return this.keywordCache.get(cacheKey);
|
| 1345 |
+
}
|
| 1346 |
+
|
| 1347 |
+
// Cache miss - compute the result
|
| 1348 |
+
this.keywordCacheMisses++;
|
| 1349 |
+
let isCommon = false;
|
| 1350 |
+
|
| 1351 |
+
try {
|
| 1352 |
+
isCommon = typeof this.isCommonWord === "function" ? this.isCommonWord(word, language) : false;
|
| 1353 |
+
} catch (error) {
|
| 1354 |
+
console.warn("Error checking common word:", error);
|
| 1355 |
+
isCommon = false;
|
| 1356 |
+
}
|
| 1357 |
+
|
| 1358 |
+
// Add to cache with LRU eviction
|
| 1359 |
+
if (this.keywordCache.size >= this.keywordCacheSize) {
|
| 1360 |
+
// Simple LRU: remove oldest entry (first in Map)
|
| 1361 |
+
const firstKey = this.keywordCache.keys().next().value;
|
| 1362 |
+
this.keywordCache.delete(firstKey);
|
| 1363 |
+
}
|
| 1364 |
+
|
| 1365 |
+
this.keywordCache.set(cacheKey, isCommon);
|
| 1366 |
+
return isCommon;
|
| 1367 |
+
}
|
| 1368 |
+
|
| 1369 |
+
// Get cache statistics for debugging
|
| 1370 |
+
getKeywordCacheStats() {
|
| 1371 |
+
const total = this.keywordCacheHits + this.keywordCacheMisses;
|
| 1372 |
+
return {
|
| 1373 |
+
size: this.keywordCache.size,
|
| 1374 |
+
hits: this.keywordCacheHits,
|
| 1375 |
+
misses: this.keywordCacheMisses,
|
| 1376 |
+
hitRate: total > 0 ? ((this.keywordCacheHits / total) * 100).toFixed(2) + "%" : "0%"
|
| 1377 |
+
};
|
| 1378 |
+
}
|
| 1379 |
+
|
| 1380 |
+
// Get performance statistics for debugging and optimization
|
| 1381 |
+
getPerformanceStats() {
|
| 1382 |
+
const calculateStats = times => {
|
| 1383 |
+
if (times.length === 0) return { avg: 0, max: 0, min: 0, count: 0 };
|
| 1384 |
+
return {
|
| 1385 |
+
avg: Math.round((times.reduce((sum, t) => sum + t, 0) / times.length) * 100) / 100,
|
| 1386 |
+
max: Math.round(Math.max(...times) * 100) / 100,
|
| 1387 |
+
min: Math.round(Math.min(...times) * 100) / 100,
|
| 1388 |
+
count: times.length
|
| 1389 |
+
};
|
| 1390 |
+
};
|
| 1391 |
+
|
| 1392 |
+
return {
|
| 1393 |
+
keywordCache: this.getKeywordCacheStats(),
|
| 1394 |
+
extraction: calculateStats(this.queryStats.extractionTime),
|
| 1395 |
+
addMemory: calculateStats(this.queryStats.addMemoryTime),
|
| 1396 |
+
retrieval: calculateStats(this.queryStats.retrievalTime)
|
| 1397 |
+
};
|
| 1398 |
+
}
|
| 1399 |
+
|
| 1400 |
+
// Performance wrapper for memory extraction
|
| 1401 |
+
async extractMemoryFromTextTimed(userText, kimiResponse = null) {
|
| 1402 |
+
const start = performance.now();
|
| 1403 |
+
const result = await this.extractMemoryFromText(userText, kimiResponse);
|
| 1404 |
+
const duration = performance.now() - start;
|
| 1405 |
+
|
| 1406 |
+
this.queryStats.extractionTime.push(duration);
|
| 1407 |
+
if (this.queryStats.extractionTime.length > 100) {
|
| 1408 |
+
this.queryStats.extractionTime.shift(); // Keep only last 100 measurements
|
| 1409 |
+
}
|
| 1410 |
+
|
| 1411 |
+
if (duration > 100 && window.KIMI_CONFIG?.DEBUG?.MEMORY) {
|
| 1412 |
+
console.warn(`🐌 Slow memory extraction: ${duration.toFixed(2)}ms for text length ${userText?.length || 0}`);
|
| 1413 |
+
}
|
| 1414 |
+
|
| 1415 |
+
return result;
|
| 1416 |
+
}
|
| 1417 |
+
|
| 1418 |
+
// Get current configuration for debugging and monitoring
|
| 1419 |
+
getConfiguration() {
|
| 1420 |
+
return {
|
| 1421 |
+
...this.config,
|
| 1422 |
+
memoryCategories: this.memoryCategories,
|
| 1423 |
+
runtime: {
|
| 1424 |
+
memoryEnabled: this.memoryEnabled,
|
| 1425 |
+
maxMemoryEntries: this.maxMemoryEntries,
|
| 1426 |
+
selectedCharacter: this.selectedCharacter,
|
| 1427 |
+
keywordCacheSize: this.keywordCache.size,
|
| 1428 |
+
compiledPatternsCount: Object.values(this.compiledPatterns || {}).reduce((sum, arr) => sum + arr.length, 0)
|
| 1429 |
+
}
|
| 1430 |
+
};
|
| 1431 |
+
}
|
| 1432 |
+
|
| 1433 |
+
// Update configuration at runtime (for advanced users)
|
| 1434 |
+
updateConfiguration(configPath, value) {
|
| 1435 |
+
const keys = configPath.split(".");
|
| 1436 |
+
let current = this.config;
|
| 1437 |
+
|
| 1438 |
+
// Navigate to the parent object
|
| 1439 |
+
for (let i = 0; i < keys.length - 1; i++) {
|
| 1440 |
+
if (!current[keys[i]]) current[keys[i]] = {};
|
| 1441 |
+
current = current[keys[i]];
|
| 1442 |
+
}
|
| 1443 |
+
|
| 1444 |
+
// Set the value
|
| 1445 |
+
const lastKey = keys[keys.length - 1];
|
| 1446 |
+
const oldValue = current[lastKey];
|
| 1447 |
+
current[lastKey] = value;
|
| 1448 |
+
|
| 1449 |
+
if (window.KIMI_CONFIG?.DEBUG?.MEMORY) {
|
| 1450 |
+
console.log(`🔧 Configuration updated: ${configPath} = ${value} (was: ${oldValue})`);
|
| 1451 |
+
}
|
| 1452 |
+
|
| 1453 |
+
return { oldValue, newValue: value };
|
| 1454 |
+
}
|
| 1455 |
+
|
| 1456 |
+
async cleanupOldMemories() {
|
| 1457 |
+
if (!this.db) return;
|
| 1458 |
+
|
| 1459 |
+
try {
|
| 1460 |
+
// Retrieve all active memories for the current character
|
| 1461 |
+
const memories = await this.getAllMemories();
|
| 1462 |
+
|
| 1463 |
+
const maxEntries = window.KIMI_MAX_MEMORIES || this.maxMemoryEntries || 100;
|
| 1464 |
+
const ttlDays = window.KIMI_MEMORY_TTL_DAYS || 365;
|
| 1465 |
+
|
| 1466 |
+
// Soft-expire memories older than TTL by marking isActive=false
|
| 1467 |
+
const now = Date.now();
|
| 1468 |
+
const ttlMs = ttlDays * 24 * 60 * 60 * 1000;
|
| 1469 |
+
const expiredMemories = [];
|
| 1470 |
+
|
| 1471 |
+
for (const mem of memories) {
|
| 1472 |
+
const created = new Date(this.getCreationTimestamp(mem)).getTime();
|
| 1473 |
+
if (now - created > ttlMs) {
|
| 1474 |
+
try {
|
| 1475 |
+
await this.updateMemory(mem.id, { isActive: false });
|
| 1476 |
+
expiredMemories.push(mem.id);
|
| 1477 |
+
} catch (e) {
|
| 1478 |
+
console.error(`Memory expiration failed for ID ${mem.id}:`, {
|
| 1479 |
+
error: e.message,
|
| 1480 |
+
memoryId: mem.id,
|
| 1481 |
+
createdAt: this.getCreationTimestamp(mem),
|
| 1482 |
+
character: mem.character
|
| 1483 |
+
});
|
| 1484 |
+
// Continue with other memories even if one fails
|
| 1485 |
+
}
|
| 1486 |
+
}
|
| 1487 |
+
}
|
| 1488 |
+
|
| 1489 |
+
if (window.KIMI_CONFIG?.DEBUG?.MEMORY && expiredMemories.length > 0) {
|
| 1490 |
+
console.log(`Successfully expired ${expiredMemories.length} memories:`, expiredMemories);
|
| 1491 |
+
}
|
| 1492 |
+
|
| 1493 |
+
// Refresh active memories after TTL purge
|
| 1494 |
+
const activeMemories = (await this.getAllMemories()).filter(m => m.isActive);
|
| 1495 |
+
|
| 1496 |
+
// If still more than maxEntries, mark lowest-priority ones inactive (soft delete)
|
| 1497 |
+
if (activeMemories.length > maxEntries) {
|
| 1498 |
+
// Sort by a combined score: low importance + old timestamp + low access
|
| 1499 |
+
activeMemories.sort((a, b) => {
|
| 1500 |
+
const scoreA =
|
| 1501 |
+
(a.importance || 0.5) * -1 + (a.accessCount || 0) * 0.01 + new Date(this.getCreationTimestamp(a)).getTime() / (1000 * 60 * 60 * 24);
|
| 1502 |
+
const scoreB =
|
| 1503 |
+
(b.importance || 0.5) * -1 + (b.accessCount || 0) * 0.01 + new Date(this.getCreationTimestamp(b)).getTime() / (1000 * 60 * 60 * 24);
|
| 1504 |
+
return scoreB - scoreA;
|
| 1505 |
+
});
|
| 1506 |
+
|
| 1507 |
+
const toDeactivate = activeMemories.slice(maxEntries);
|
| 1508 |
+
const deactivatedMemories = [];
|
| 1509 |
+
const failedDeactivations = [];
|
| 1510 |
+
|
| 1511 |
+
for (const mem of toDeactivate) {
|
| 1512 |
+
try {
|
| 1513 |
+
await this.updateMemory(mem.id, { isActive: false });
|
| 1514 |
+
deactivatedMemories.push(mem.id);
|
| 1515 |
+
} catch (e) {
|
| 1516 |
+
console.error(`Memory deactivation failed for ID ${mem.id}:`, {
|
| 1517 |
+
error: e.message,
|
| 1518 |
+
memoryId: mem.id,
|
| 1519 |
+
importance: mem.importance,
|
| 1520 |
+
character: mem.character
|
| 1521 |
+
});
|
| 1522 |
+
failedDeactivations.push(mem.id);
|
| 1523 |
+
}
|
| 1524 |
+
}
|
| 1525 |
+
|
| 1526 |
+
if (window.KIMI_CONFIG?.DEBUG?.MEMORY) {
|
| 1527 |
+
console.log(`Memory cleanup: ${deactivatedMemories.length} deactivated, ${failedDeactivations.length} failed`);
|
| 1528 |
+
}
|
| 1529 |
+
}
|
| 1530 |
+
} catch (error) {
|
| 1531 |
+
console.error("Error cleaning up old memories:", error);
|
| 1532 |
+
}
|
| 1533 |
+
}
|
| 1534 |
+
|
| 1535 |
+
// MEMORY RETRIEVAL FOR LLM
|
| 1536 |
+
async getRelevantMemories(context = "", limit = 10) {
|
| 1537 |
+
if (!this.memoryEnabled) return [];
|
| 1538 |
+
|
| 1539 |
+
try {
|
| 1540 |
+
const allMemories = await this.getAllMemories();
|
| 1541 |
+
|
| 1542 |
+
if (allMemories.length === 0) return [];
|
| 1543 |
+
|
| 1544 |
+
if (!context) {
|
| 1545 |
+
// Return most important and recent memories
|
| 1546 |
+
const res = this.selectMostImportantMemories(allMemories, limit);
|
| 1547 |
+
// touch top results to update access metrics
|
| 1548 |
+
this._touchMemories(res, limit).catch(() => {});
|
| 1549 |
+
return res;
|
| 1550 |
+
}
|
| 1551 |
+
|
| 1552 |
+
// Score memories based on relevance to context
|
| 1553 |
+
const scoredMemories = allMemories.map(memory => ({
|
| 1554 |
+
...memory,
|
| 1555 |
+
relevanceScore: this.calculateRelevance(memory, context)
|
| 1556 |
+
}));
|
| 1557 |
+
|
| 1558 |
+
// Sort by relevance and return top results
|
| 1559 |
+
scoredMemories.sort((a, b) => b.relevanceScore - a.relevanceScore);
|
| 1560 |
+
|
| 1561 |
+
// Filter out very low relevance memories
|
| 1562 |
+
const relevantMemories = scoredMemories.filter(m => m.relevanceScore > 0.1);
|
| 1563 |
+
|
| 1564 |
+
const out = relevantMemories.slice(0, limit).map(r => r);
|
| 1565 |
+
// touch top results to update access metrics
|
| 1566 |
+
this._touchMemories(
|
| 1567 |
+
out.map(r => r),
|
| 1568 |
+
limit
|
| 1569 |
+
).catch(() => {});
|
| 1570 |
+
return out;
|
| 1571 |
+
} catch (error) {
|
| 1572 |
+
console.error("Error getting relevant memories:", error);
|
| 1573 |
+
return [];
|
| 1574 |
+
}
|
| 1575 |
+
}
|
| 1576 |
+
|
| 1577 |
+
// Select most important memories when no context is provided
|
| 1578 |
+
selectMostImportantMemories(memories, limit) {
|
| 1579 |
+
// Score by importance, recency, and access count
|
| 1580 |
+
const scoredMemories = memories.map(memory => {
|
| 1581 |
+
let score = memory.importance || 0.5;
|
| 1582 |
+
|
| 1583 |
+
// Boost recent memories
|
| 1584 |
+
const daysSinceCreation = this.getDaysSinceCreation(memory);
|
| 1585 |
+
score += Math.max(0, (7 - daysSinceCreation) / 7) * 0.2; // Recent boost
|
| 1586 |
+
|
| 1587 |
+
// Boost frequently accessed memories
|
| 1588 |
+
const accessCount = memory.accessCount || 0;
|
| 1589 |
+
score += Math.min(accessCount / 10, 0.2); // Access boost
|
| 1590 |
+
|
| 1591 |
+
// Boost high confidence memories
|
| 1592 |
+
score += (memory.confidence || 0.5) * 0.1;
|
| 1593 |
+
|
| 1594 |
+
return { ...memory, importanceScore: score };
|
| 1595 |
+
});
|
| 1596 |
+
|
| 1597 |
+
scoredMemories.sort((a, b) => b.importanceScore - a.importanceScore);
|
| 1598 |
+
return scoredMemories.slice(0, limit);
|
| 1599 |
+
}
|
| 1600 |
+
|
| 1601 |
+
calculateRelevance(memory, context) {
|
| 1602 |
+
const contextWords = context
|
| 1603 |
+
.toLowerCase()
|
| 1604 |
+
.split(/\s+/)
|
| 1605 |
+
.filter(w => w.length > 2);
|
| 1606 |
+
const memoryWords = memory.content
|
| 1607 |
+
.toLowerCase()
|
| 1608 |
+
.split(/\s+/)
|
| 1609 |
+
.filter(w => w.length > 2);
|
| 1610 |
+
|
| 1611 |
+
let score = 0;
|
| 1612 |
+
|
| 1613 |
+
// Enhanced content similarity with keyword matching
|
| 1614 |
+
score += this.calculateSimilarity(memory.content, context) * this.config.relevance.contentSimilarity;
|
| 1615 |
+
|
| 1616 |
+
// Keyword overlap boost (derived keywords)
|
| 1617 |
+
try {
|
| 1618 |
+
const memKeys = memory.keywords || this.deriveKeywords(memory.content || "");
|
| 1619 |
+
const ctxKeys = this.deriveKeywords(context || "");
|
| 1620 |
+
const keyOverlap = ctxKeys.filter(k => memKeys.includes(k)).length;
|
| 1621 |
+
if (ctxKeys.length > 0) {
|
| 1622 |
+
score += (keyOverlap / ctxKeys.length) * this.config.relevance.keywordOverlap;
|
| 1623 |
+
}
|
| 1624 |
+
} catch (e) {
|
| 1625 |
+
// fallback to original keyword matching
|
| 1626 |
+
let keywordMatches = 0;
|
| 1627 |
+
for (const word of contextWords) {
|
| 1628 |
+
if (memoryWords.includes(word)) {
|
| 1629 |
+
keywordMatches++;
|
| 1630 |
+
}
|
| 1631 |
+
}
|
| 1632 |
+
if (contextWords.length > 0) {
|
| 1633 |
+
score += (keywordMatches / contextWords.length) * this.config.relevance.keywordOverlap;
|
| 1634 |
+
}
|
| 1635 |
+
}
|
| 1636 |
+
|
| 1637 |
+
// Category relevance bonus based on context
|
| 1638 |
+
score += this.getCategoryRelevance(memory.category, context) * this.config.relevance.categoryRelevance;
|
| 1639 |
+
|
| 1640 |
+
// Recent memories get bonus for current conversation
|
| 1641 |
+
const daysSinceCreation = this.getDaysSinceCreation(memory);
|
| 1642 |
+
score +=
|
| 1643 |
+
Math.max(0, (this.config.relevance.recentDaysThreshold - daysSinceCreation) / this.config.relevance.recentDaysThreshold) *
|
| 1644 |
+
this.config.relevance.recencyBonus;
|
| 1645 |
+
|
| 1646 |
+
// Confidence and importance boost
|
| 1647 |
+
score += (memory.confidence || 0.5) * this.config.relevance.confidenceBonus;
|
| 1648 |
+
score += (memory.importance || 0.5) * this.config.relevance.importanceBonus;
|
| 1649 |
+
|
| 1650 |
+
return Math.min(1.0, score);
|
| 1651 |
+
}
|
| 1652 |
+
|
| 1653 |
+
// Determine if memory category is relevant to current context
|
| 1654 |
+
getCategoryRelevance(category, context) {
|
| 1655 |
+
const contextLower = context.toLowerCase();
|
| 1656 |
+
|
| 1657 |
+
const categoryKeywords = {
|
| 1658 |
+
personal: ["name", "age", "live", "work", "job", "who", "am", "myself", "appelle", "nombre", "chiamo", "heiße", "名前", "名字", "我叫"],
|
| 1659 |
+
preferences: [
|
| 1660 |
+
"like",
|
| 1661 |
+
"love",
|
| 1662 |
+
"hate",
|
| 1663 |
+
"prefer",
|
| 1664 |
+
"enjoy",
|
| 1665 |
+
"favorite",
|
| 1666 |
+
"dislike",
|
| 1667 |
+
"j'aime",
|
| 1668 |
+
"j'adore",
|
| 1669 |
+
"je préfère",
|
| 1670 |
+
"je déteste",
|
| 1671 |
+
"me gusta",
|
| 1672 |
+
"prefiero",
|
| 1673 |
+
"odio",
|
| 1674 |
+
"mi piace",
|
| 1675 |
+
"preferisco",
|
| 1676 |
+
"ich mag",
|
| 1677 |
+
"ich bevorzuge",
|
| 1678 |
+
"hasse"
|
| 1679 |
+
],
|
| 1680 |
+
relationships: [
|
| 1681 |
+
"family",
|
| 1682 |
+
"friend",
|
| 1683 |
+
"wife",
|
| 1684 |
+
"husband",
|
| 1685 |
+
"partner",
|
| 1686 |
+
"mother",
|
| 1687 |
+
"father",
|
| 1688 |
+
"girlfriend",
|
| 1689 |
+
"boyfriend",
|
| 1690 |
+
"anniversary",
|
| 1691 |
+
"date",
|
| 1692 |
+
"kiss",
|
| 1693 |
+
"move in",
|
| 1694 |
+
"famille",
|
| 1695 |
+
"ami",
|
| 1696 |
+
"copine",
|
| 1697 |
+
"copain",
|
| 1698 |
+
"anniversaire",
|
| 1699 |
+
"rendez-vous",
|
| 1700 |
+
"baiser",
|
| 1701 |
+
"emménagé",
|
| 1702 |
+
"pareja",
|
| 1703 |
+
"cita",
|
| 1704 |
+
"beso",
|
| 1705 |
+
"aniversario",
|
| 1706 |
+
"mudarnos",
|
| 1707 |
+
"fidanzata",
|
| 1708 |
+
"fidanzato",
|
| 1709 |
+
"anniversario",
|
| 1710 |
+
"bacio",
|
| 1711 |
+
"trasferiti",
|
| 1712 |
+
"freundin",
|
| 1713 |
+
"freund",
|
| 1714 |
+
"jahrestag",
|
| 1715 |
+
"kuss",
|
| 1716 |
+
"eingezogen"
|
| 1717 |
+
],
|
| 1718 |
+
activities: [
|
| 1719 |
+
"play",
|
| 1720 |
+
"hobby",
|
| 1721 |
+
"sport",
|
| 1722 |
+
"activity",
|
| 1723 |
+
"practice",
|
| 1724 |
+
"do",
|
| 1725 |
+
"joue",
|
| 1726 |
+
"passe-temps",
|
| 1727 |
+
"hobby",
|
| 1728 |
+
"juego",
|
| 1729 |
+
"pasatiempo",
|
| 1730 |
+
"gioco",
|
| 1731 |
+
"passatempo",
|
| 1732 |
+
"spiele",
|
| 1733 |
+
"hobby"
|
| 1734 |
+
],
|
| 1735 |
+
goals: [
|
| 1736 |
+
"want",
|
| 1737 |
+
"plan",
|
| 1738 |
+
"goal",
|
| 1739 |
+
"dream",
|
| 1740 |
+
"hope",
|
| 1741 |
+
"wish",
|
| 1742 |
+
"future",
|
| 1743 |
+
"veux",
|
| 1744 |
+
"objectif",
|
| 1745 |
+
"apprends",
|
| 1746 |
+
"aprendo",
|
| 1747 |
+
"voglio",
|
| 1748 |
+
"obiettivo",
|
| 1749 |
+
"lerne",
|
| 1750 |
+
"ziel"
|
| 1751 |
+
],
|
| 1752 |
+
experiences: [
|
| 1753 |
+
"remember",
|
| 1754 |
+
"happened",
|
| 1755 |
+
"story",
|
| 1756 |
+
"experience",
|
| 1757 |
+
"time",
|
| 1758 |
+
"we met",
|
| 1759 |
+
"first date",
|
| 1760 |
+
"first kiss",
|
| 1761 |
+
"anniversary",
|
| 1762 |
+
"rencontré",
|
| 1763 |
+
"premier rendez-vous",
|
| 1764 |
+
"premier baiser",
|
| 1765 |
+
"anniversaire",
|
| 1766 |
+
"conocimos",
|
| 1767 |
+
"primera cita",
|
| 1768 |
+
"primer beso",
|
| 1769 |
+
"aniversario",
|
| 1770 |
+
"conosciuti",
|
| 1771 |
+
"primo appuntamento",
|
| 1772 |
+
"primo bacio",
|
| 1773 |
+
"anniversario",
|
| 1774 |
+
"kennengelernt",
|
| 1775 |
+
"erstes date",
|
| 1776 |
+
"erster kuss",
|
| 1777 |
+
"jahrestag"
|
| 1778 |
+
],
|
| 1779 |
+
important: [
|
| 1780 |
+
"important",
|
| 1781 |
+
"remember",
|
| 1782 |
+
"special",
|
| 1783 |
+
"never forget",
|
| 1784 |
+
"important",
|
| 1785 |
+
"souvenir",
|
| 1786 |
+
"spécial",
|
| 1787 |
+
"importante",
|
| 1788 |
+
"recuerda",
|
| 1789 |
+
"importante",
|
| 1790 |
+
"ricorda",
|
| 1791 |
+
"wichtig",
|
| 1792 |
+
"erinnere"
|
| 1793 |
+
]
|
| 1794 |
+
};
|
| 1795 |
+
|
| 1796 |
+
const keywords = categoryKeywords[category] || [];
|
| 1797 |
+
let relevance = 0;
|
| 1798 |
+
|
| 1799 |
+
for (const keyword of keywords) {
|
| 1800 |
+
if (contextLower.includes(keyword)) {
|
| 1801 |
+
relevance += 0.2;
|
| 1802 |
+
}
|
| 1803 |
+
}
|
| 1804 |
+
|
| 1805 |
+
return Math.min(1.0, relevance);
|
| 1806 |
+
}
|
| 1807 |
+
|
| 1808 |
+
// Update access count when memory is used
|
| 1809 |
+
async recordMemoryAccess(memoryId) {
|
| 1810 |
+
try {
|
| 1811 |
+
const memory = await this.db.db.memories.get(memoryId);
|
| 1812 |
+
if (memory) {
|
| 1813 |
+
memory.accessCount = (memory.accessCount || 0) + 1;
|
| 1814 |
+
memory.lastAccess = new Date();
|
| 1815 |
+
await this.db.db.memories.put(memory);
|
| 1816 |
+
}
|
| 1817 |
+
} catch (error) {
|
| 1818 |
+
console.error("Error recording memory access:", error);
|
| 1819 |
+
}
|
| 1820 |
+
}
|
| 1821 |
+
|
| 1822 |
+
// Touch multiple memories to update lastAccess and accessCount
|
| 1823 |
+
async _touchMemories(memories, limit = 5) {
|
| 1824 |
+
if (!this.db || !Array.isArray(memories) || memories.length === 0) return;
|
| 1825 |
+
|
| 1826 |
+
try {
|
| 1827 |
+
const top = memories.slice(0, limit);
|
| 1828 |
+
const now = new Date();
|
| 1829 |
+
const minMinutes = window.KIMI_MEMORY_TOUCH_MINUTES || 60;
|
| 1830 |
+
const minTouchInterval = minMinutes * 60 * 1000;
|
| 1831 |
+
|
| 1832 |
+
// Batch collection: gather all updates before executing
|
| 1833 |
+
const batchUpdates = [];
|
| 1834 |
+
|
| 1835 |
+
for (const m of top) {
|
| 1836 |
+
try {
|
| 1837 |
+
const id = m.id;
|
| 1838 |
+
const existing = await this.db.db.memories.get(id);
|
| 1839 |
+
if (existing) {
|
| 1840 |
+
const lastAccess = existing.lastAccess ? new Date(existing.lastAccess).getTime() : 0;
|
| 1841 |
+
|
| 1842 |
+
// Only touch if enough time has passed
|
| 1843 |
+
if (now.getTime() - lastAccess > minTouchInterval) {
|
| 1844 |
+
batchUpdates.push({
|
| 1845 |
+
key: id,
|
| 1846 |
+
changes: {
|
| 1847 |
+
accessCount: (existing.accessCount || 0) + 1,
|
| 1848 |
+
lastAccess: now
|
| 1849 |
+
}
|
| 1850 |
+
});
|
| 1851 |
+
}
|
| 1852 |
+
}
|
| 1853 |
+
} catch (e) {
|
| 1854 |
+
console.warn("Error preparing memory touch batch for", m && m.id, e);
|
| 1855 |
+
}
|
| 1856 |
+
}
|
| 1857 |
+
|
| 1858 |
+
// Execute all updates in a single batch operation
|
| 1859 |
+
if (batchUpdates.length > 0) {
|
| 1860 |
+
if (this.db.db.memories.bulkUpdate) {
|
| 1861 |
+
// Use bulkUpdate if available (Dexie 3.x+)
|
| 1862 |
+
await this.db.db.memories.bulkUpdate(batchUpdates);
|
| 1863 |
+
} else {
|
| 1864 |
+
// Fallback: parallel individual updates (still better than sequential)
|
| 1865 |
+
const updatePromises = batchUpdates.map(update => this.db.db.memories.update(update.key, update.changes));
|
| 1866 |
+
await Promise.all(updatePromises);
|
| 1867 |
+
}
|
| 1868 |
+
|
| 1869 |
+
if (window.KIMI_CONFIG?.DEBUG?.MEMORY) {
|
| 1870 |
+
console.log(`📊 Batch touched ${batchUpdates.length} memories`);
|
| 1871 |
+
}
|
| 1872 |
+
}
|
| 1873 |
+
} catch (e) {
|
| 1874 |
+
console.warn("Error in _touchMemories batch processing", e);
|
| 1875 |
+
}
|
| 1876 |
+
}
|
| 1877 |
+
|
| 1878 |
+
// ===== MEMORY SCORING & RANKING =====
|
| 1879 |
+
scoreMemory(memory) {
|
| 1880 |
+
// Factors: importance (0-1), recency, frequency, confidence
|
| 1881 |
+
const now = Date.now();
|
| 1882 |
+
const created = memory.createdAt ? new Date(memory.createdAt).getTime() : memory.timestamp ? new Date(memory.timestamp).getTime() : now;
|
| 1883 |
+
const lastAccess = memory.lastAccess ? new Date(memory.lastAccess).getTime() : created;
|
| 1884 |
+
const ageMs = Math.max(1, now - created);
|
| 1885 |
+
const sinceLastAccessMs = Math.max(1, now - lastAccess);
|
| 1886 |
+
// Recency: exponential decay
|
| 1887 |
+
const recency = Math.exp(-sinceLastAccessMs / (1000 * 60 * 60 * 24 * 14)); // 14-day half-life approx
|
| 1888 |
+
const freshness = Math.exp(-ageMs / (1000 * 60 * 60 * 24 * 60)); // 60-day aging
|
| 1889 |
+
const freq = Math.log10((memory.accessCount || 0) + 1) / Math.log10(50); // normalized frequency (cap ~50)
|
| 1890 |
+
const importance = typeof memory.importance === "number" ? memory.importance : 0.5;
|
| 1891 |
+
const confidence = typeof memory.confidence === "number" ? memory.confidence : 0.5;
|
| 1892 |
+
// Weighted sum using global knobs
|
| 1893 |
+
const wImportance = window.KIMI_WEIGHT_IMPORTANCE || 0.35;
|
| 1894 |
+
const wRecency = window.KIMI_WEIGHT_RECENCY || 0.2;
|
| 1895 |
+
const wFrequency = window.KIMI_WEIGHT_FREQUENCY || 0.15;
|
| 1896 |
+
const wConfidence = window.KIMI_WEIGHT_CONFIDENCE || 0.2;
|
| 1897 |
+
const wFreshness = window.KIMI_WEIGHT_FRESHNESS || 0.1;
|
| 1898 |
+
|
| 1899 |
+
const score = importance * wImportance + recency * wRecency + freq * wFrequency + confidence * wConfidence + freshness * wFreshness;
|
| 1900 |
+
return Number(score.toFixed(6));
|
| 1901 |
+
}
|
| 1902 |
+
|
| 1903 |
+
async getRankedMemories(contextText = "", limit = 7) {
|
| 1904 |
+
const all = await this.getAllMemories();
|
| 1905 |
+
if (!all.length) return [];
|
| 1906 |
+
// Optional basic context relevance boost
|
| 1907 |
+
const ctxLower = (contextText || "").toLowerCase();
|
| 1908 |
+
// Favor pinned memories by boosting their base score
|
| 1909 |
+
return all
|
| 1910 |
+
.map(m => {
|
| 1911 |
+
let baseScore = this.scoreMemory(m);
|
| 1912 |
+
if (m.tags && m.tags.includes && m.tags.includes("pinned")) baseScore += 0.2;
|
| 1913 |
+
if (ctxLower && m.content && ctxLower.includes(m.content.toLowerCase().split(" ")[0])) {
|
| 1914 |
+
baseScore += 0.05; // tiny relevance boost
|
| 1915 |
+
}
|
| 1916 |
+
return { memory: m, score: baseScore };
|
| 1917 |
+
})
|
| 1918 |
+
.sort((a, b) => b.score - a.score)
|
| 1919 |
+
.slice(0, limit)
|
| 1920 |
+
.map(r => r.memory);
|
| 1921 |
+
}
|
| 1922 |
+
|
| 1923 |
+
// Pin/unpin APIs to manually mark important memories
|
| 1924 |
+
async pinMemory(memoryId) {
|
| 1925 |
+
if (!this.db) return false;
|
| 1926 |
+
try {
|
| 1927 |
+
const m = await this.db.db.memories.get(memoryId);
|
| 1928 |
+
if (!m) return false;
|
| 1929 |
+
const tags = new Set([...(m.tags || []), "pinned"]);
|
| 1930 |
+
await this.db.db.memories.update(memoryId, { tags: [...tags], importance: Math.max(m.importance || 0.5, 0.95) });
|
| 1931 |
+
return true;
|
| 1932 |
+
} catch (e) {
|
| 1933 |
+
console.error("Error pinning memory", e);
|
| 1934 |
+
return false;
|
| 1935 |
+
}
|
| 1936 |
+
}
|
| 1937 |
+
|
| 1938 |
+
async unpinMemory(memoryId) {
|
| 1939 |
+
if (!this.db) return false;
|
| 1940 |
+
try {
|
| 1941 |
+
const m = await this.db.db.memories.get(memoryId);
|
| 1942 |
+
if (!m) return false;
|
| 1943 |
+
const tags = new Set([...(m.tags || [])]);
|
| 1944 |
+
tags.delete("pinned");
|
| 1945 |
+
await this.db.db.memories.update(memoryId, { tags: [...tags] });
|
| 1946 |
+
return true;
|
| 1947 |
+
} catch (e) {
|
| 1948 |
+
console.error("Error unpinning memory", e);
|
| 1949 |
+
return false;
|
| 1950 |
+
}
|
| 1951 |
+
}
|
| 1952 |
+
|
| 1953 |
+
// Summarize recent memories into a non-destructive summary memory
|
| 1954 |
+
async summarizeRecentMemories(days = 7, options = { category: null, archiveSources: false }) {
|
| 1955 |
+
if (!this.db) return null;
|
| 1956 |
+
try {
|
| 1957 |
+
const cutoff = Date.now() - (days || 7) * 24 * 60 * 60 * 1000;
|
| 1958 |
+
const all = await this.getAllMemories();
|
| 1959 |
+
// Exclude existing summaries to avoid summarizing summaries repeatedly
|
| 1960 |
+
const recent = all.filter(
|
| 1961 |
+
m => new Date(this.getCreationTimestamp(m)).getTime() >= cutoff && m.isActive && m.type !== "summary" && !(m.tags && m.tags.includes("summary"))
|
| 1962 |
+
);
|
| 1963 |
+
if (!recent.length) return null;
|
| 1964 |
+
|
| 1965 |
+
// Group by top keyword
|
| 1966 |
+
const groups = {};
|
| 1967 |
+
for (const m of recent) {
|
| 1968 |
+
const keys = m.keywords && m.keywords.length ? m.keywords : this.deriveKeywords(m.content || "");
|
| 1969 |
+
const top = keys[0] || "misc";
|
| 1970 |
+
groups[top] = groups[top] || [];
|
| 1971 |
+
groups[top].push(m);
|
| 1972 |
+
}
|
| 1973 |
+
|
| 1974 |
+
// Build a simple summary per group
|
| 1975 |
+
const summaries = [];
|
| 1976 |
+
for (const [k, items] of Object.entries(groups)) {
|
| 1977 |
+
const contents = items.map(i => i.content).slice(0, 6);
|
| 1978 |
+
summaries.push(`${k}: ${contents.join(" | ")}`);
|
| 1979 |
+
}
|
| 1980 |
+
|
| 1981 |
+
const summaryContent = `Summary (${days}d): ` + summaries.join(" \n");
|
| 1982 |
+
|
| 1983 |
+
const summaryJson = { summary: summaries };
|
| 1984 |
+
|
| 1985 |
+
const summaryMemory = {
|
| 1986 |
+
category: options.category || "experiences",
|
| 1987 |
+
type: "summary",
|
| 1988 |
+
content: summaryContent,
|
| 1989 |
+
sourceText: summaryContent,
|
| 1990 |
+
summaryJson: JSON.stringify(summaryJson),
|
| 1991 |
+
confidence: 0.9,
|
| 1992 |
+
createdAt: new Date(), // Use createdAt consistently
|
| 1993 |
+
character: this.selectedCharacter,
|
| 1994 |
+
isActive: true,
|
| 1995 |
+
tags: ["summary"]
|
| 1996 |
+
};
|
| 1997 |
+
|
| 1998 |
+
const saved = await this.addMemory(summaryMemory);
|
| 1999 |
+
|
| 2000 |
+
// Optionally archive sources (soft-deactivate)
|
| 2001 |
+
if (options.archiveSources) {
|
| 2002 |
+
for (const m of recent) {
|
| 2003 |
+
try {
|
| 2004 |
+
await this.updateMemory(m.id, { isActive: false });
|
| 2005 |
+
} catch (e) {
|
| 2006 |
+
console.warn("Failed to archive source memory", m.id, e);
|
| 2007 |
+
}
|
| 2008 |
+
}
|
| 2009 |
+
}
|
| 2010 |
+
|
| 2011 |
+
return saved;
|
| 2012 |
+
} catch (e) {
|
| 2013 |
+
console.error("Error summarizing memories", e);
|
| 2014 |
+
return null;
|
| 2015 |
+
}
|
| 2016 |
+
}
|
| 2017 |
+
|
| 2018 |
+
// Summarize recent memories and replace sources (hard delete) - destructive
|
| 2019 |
+
async summarizeAndReplace(days = 7, options = { category: null }) {
|
| 2020 |
+
if (!this.db) return null;
|
| 2021 |
+
try {
|
| 2022 |
+
const cutoff = Date.now() - (days || 7) * 24 * 60 * 60 * 1000;
|
| 2023 |
+
const all = await this.getAllMemories();
|
| 2024 |
+
// Exclude existing summaries to avoid recursive summarization
|
| 2025 |
+
const recent = all.filter(
|
| 2026 |
+
m => new Date(this.getCreationTimestamp(m)).getTime() >= cutoff && m.isActive && m.type !== "summary" && !(m.tags && m.tags.includes("summary"))
|
| 2027 |
+
);
|
| 2028 |
+
if (!recent.length) return null;
|
| 2029 |
+
|
| 2030 |
+
// Build aggregate content from readable fields in chronological order
|
| 2031 |
+
recent.sort((a, b) => new Date(this.getCreationTimestamp(a)) - new Date(this.getCreationTimestamp(b)));
|
| 2032 |
+
const texts = recent
|
| 2033 |
+
.map(r => {
|
| 2034 |
+
const raw = (r.title && r.title.trim()) || (r.sourceText && r.sourceText.trim()) || (r.content && r.content.trim()) || "";
|
| 2035 |
+
if (!raw) return "";
|
| 2036 |
+
// Normalize whitespace and remove stray leading punctuation
|
| 2037 |
+
let t = raw.replace(/\s+/g, " ").replace(/^[^\p{L}\p{N}]+/u, "");
|
| 2038 |
+
// Capitalize first meaningful letter
|
| 2039 |
+
if (t && t.length > 0) t = t.charAt(0).toUpperCase() + t.slice(1);
|
| 2040 |
+
return t;
|
| 2041 |
+
})
|
| 2042 |
+
.filter(Boolean)
|
| 2043 |
+
.slice(0, 200);
|
| 2044 |
+
|
| 2045 |
+
const summaryContent = `Summary (${days}d):\n` + texts.map(t => `- ${t}`).join("\n");
|
| 2046 |
+
|
| 2047 |
+
const summaryJson = { summary: texts };
|
| 2048 |
+
|
| 2049 |
+
const summaryMemory = {
|
| 2050 |
+
category: options.category || "experiences",
|
| 2051 |
+
type: "summary",
|
| 2052 |
+
title: `Summary - last ${days} days`,
|
| 2053 |
+
content: summaryContent,
|
| 2054 |
+
// Store the actual summary also in sourceText so editors/UIs show it
|
| 2055 |
+
sourceText: summaryContent,
|
| 2056 |
+
summaryJson: JSON.stringify(summaryJson),
|
| 2057 |
+
confidence: 0.95,
|
| 2058 |
+
timestamp: new Date(),
|
| 2059 |
+
character: this.selectedCharacter,
|
| 2060 |
+
isActive: true,
|
| 2061 |
+
tags: ["summary", "replaced"]
|
| 2062 |
+
};
|
| 2063 |
+
|
| 2064 |
+
// Add summary directly to DB to avoid addMemory's merge logic
|
| 2065 |
+
let saved = null;
|
| 2066 |
+
if (this.db && this.db.db && this.db.db.memories) {
|
| 2067 |
+
try {
|
| 2068 |
+
const id = await this.db.db.memories.add(summaryMemory);
|
| 2069 |
+
summaryMemory.id = id;
|
| 2070 |
+
saved = summaryMemory;
|
| 2071 |
+
console.log("Summary added with ID:", id);
|
| 2072 |
+
// Read back the saved record to verify stored fields
|
| 2073 |
+
try {
|
| 2074 |
+
const savedRec = await this.db.db.memories.get(id);
|
| 2075 |
+
console.log("Saved summary record:", { id, content: savedRec.content, sourceText: savedRec.sourceText });
|
| 2076 |
+
} catch (e) {
|
| 2077 |
+
console.warn("Unable to read back saved summary", e);
|
| 2078 |
+
}
|
| 2079 |
+
} catch (e) {
|
| 2080 |
+
console.error("Failed to write summary directly to DB", e);
|
| 2081 |
+
}
|
| 2082 |
+
} else {
|
| 2083 |
+
// Fallback to addMemory if DB not available
|
| 2084 |
+
saved = await this.addMemory(summaryMemory);
|
| 2085 |
+
}
|
| 2086 |
+
|
| 2087 |
+
// Hard-delete sources
|
| 2088 |
+
for (const m of recent) {
|
| 2089 |
+
try {
|
| 2090 |
+
if (this.db && this.db.db && this.db.db.memories) {
|
| 2091 |
+
await this.db.db.memories.delete(m.id);
|
| 2092 |
+
}
|
| 2093 |
+
} catch (e) {
|
| 2094 |
+
console.warn("Failed to delete source memory", m.id, e);
|
| 2095 |
+
}
|
| 2096 |
+
}
|
| 2097 |
+
|
| 2098 |
+
// Notify LLM to refresh context
|
| 2099 |
+
this.notifyLLMContextUpdate();
|
| 2100 |
+
|
| 2101 |
+
return saved;
|
| 2102 |
+
} catch (e) {
|
| 2103 |
+
console.error("Error in summarizeAndReplace", e);
|
| 2104 |
+
return null;
|
| 2105 |
+
}
|
| 2106 |
+
}
|
| 2107 |
+
|
| 2108 |
+
// MEMORY STATISTICS
|
| 2109 |
+
async getMemoryStats() {
|
| 2110 |
+
try {
|
| 2111 |
+
const memories = await this.getAllMemories();
|
| 2112 |
+
const stats = {
|
| 2113 |
+
total: memories.length,
|
| 2114 |
+
byCategory: {},
|
| 2115 |
+
averageConfidence: 0,
|
| 2116 |
+
oldestMemory: null,
|
| 2117 |
+
newestMemory: null
|
| 2118 |
+
};
|
| 2119 |
+
|
| 2120 |
+
if (memories.length > 0) {
|
| 2121 |
+
// Category breakdown
|
| 2122 |
+
for (const memory of memories) {
|
| 2123 |
+
stats.byCategory[memory.category] = (stats.byCategory[memory.category] || 0) + 1;
|
| 2124 |
+
}
|
| 2125 |
+
|
| 2126 |
+
// Average confidence
|
| 2127 |
+
stats.averageConfidence = memories.reduce((sum, m) => sum + m.confidence, 0) / memories.length;
|
| 2128 |
+
|
| 2129 |
+
// Oldest and newest
|
| 2130 |
+
const sortedByDate = [...memories].sort((a, b) => new Date(a.timestamp) - new Date(b.timestamp));
|
| 2131 |
+
stats.oldestMemory = sortedByDate[0];
|
| 2132 |
+
stats.newestMemory = sortedByDate[sortedByDate.length - 1];
|
| 2133 |
+
}
|
| 2134 |
+
|
| 2135 |
+
return stats;
|
| 2136 |
+
} catch (error) {
|
| 2137 |
+
console.error("Error getting memory stats:", error);
|
| 2138 |
+
return { total: 0, byCategory: {}, averageConfidence: 0 };
|
| 2139 |
+
}
|
| 2140 |
+
}
|
| 2141 |
+
|
| 2142 |
+
// MEMORY TOGGLE
|
| 2143 |
+
async toggleMemorySystem(enabled) {
|
| 2144 |
+
this.memoryEnabled = enabled;
|
| 2145 |
+
if (this.db) {
|
| 2146 |
+
await this.db.setPreference("memorySystemEnabled", enabled);
|
| 2147 |
+
}
|
| 2148 |
+
}
|
| 2149 |
+
|
| 2150 |
+
// EXPORT/IMPORT MEMORIES
|
| 2151 |
+
async exportMemories() {
|
| 2152 |
+
try {
|
| 2153 |
+
const memories = await this.getAllMemories();
|
| 2154 |
+
return {
|
| 2155 |
+
exportDate: new Date().toISOString(),
|
| 2156 |
+
character: this.selectedCharacter,
|
| 2157 |
+
memories: memories,
|
| 2158 |
+
version: "1.0"
|
| 2159 |
+
};
|
| 2160 |
+
} catch (error) {
|
| 2161 |
+
console.error("Error exporting memories:", error);
|
| 2162 |
+
return null;
|
| 2163 |
+
}
|
| 2164 |
+
}
|
| 2165 |
+
|
| 2166 |
+
async importMemories(importData) {
|
| 2167 |
+
if (!importData || !importData.memories) return false;
|
| 2168 |
+
|
| 2169 |
+
try {
|
| 2170 |
+
for (const memory of importData.memories) {
|
| 2171 |
+
await this.addMemory({
|
| 2172 |
+
...memory,
|
| 2173 |
+
type: "imported",
|
| 2174 |
+
character: this.selectedCharacter
|
| 2175 |
+
});
|
| 2176 |
+
}
|
| 2177 |
+
return true;
|
| 2178 |
+
} catch (error) {
|
| 2179 |
+
console.error("Error importing memories:", error);
|
| 2180 |
+
return false;
|
| 2181 |
+
}
|
| 2182 |
+
}
|
| 2183 |
+
|
| 2184 |
+
// MIGRATION UTILITIES
|
| 2185 |
+
async migrateIncompatibleIDs() {
|
| 2186 |
+
if (!this.db) return false;
|
| 2187 |
+
|
| 2188 |
+
try {
|
| 2189 |
+
console.log("🔧 Début de la migration des IDs incompatibles...");
|
| 2190 |
+
|
| 2191 |
+
// Récupérer toutes les mémoires
|
| 2192 |
+
const allMemories = await this.db.db.memories.toArray();
|
| 2193 |
+
console.log(`📊 ${allMemories.length} mémoires trouvées`);
|
| 2194 |
+
|
| 2195 |
+
const incompatibleMemories = allMemories.filter(memory => {
|
| 2196 |
+
// Les IDs auto-increment sont des entiers séquentiels (1, 2, 3...)
|
| 2197 |
+
// Les anciens IDs manuels sont des nombres très grands (timestamps)
|
| 2198 |
+
return memory.id > 10000; // Seuil arbitraire pour détecter les anciens IDs
|
| 2199 |
+
});
|
| 2200 |
+
|
| 2201 |
+
if (incompatibleMemories.length === 0) {
|
| 2202 |
+
console.log("✅ Aucune migration nécessaire");
|
| 2203 |
+
return true;
|
| 2204 |
+
}
|
| 2205 |
+
|
| 2206 |
+
console.log(`🔄 Migration de ${incompatibleMemories.length} mémoires avec IDs incompatibles`);
|
| 2207 |
+
|
| 2208 |
+
// Sauvegarder les données avant suppression
|
| 2209 |
+
const dataToMigrate = incompatibleMemories.map(memory => {
|
| 2210 |
+
const { id, ...memoryData } = memory; // Enlever l'ancien ID
|
| 2211 |
+
return memoryData;
|
| 2212 |
+
});
|
| 2213 |
+
|
| 2214 |
+
// Supprimer les anciennes entrées
|
| 2215 |
+
await this.db.db.memories.bulkDelete(incompatibleMemories.map(m => m.id));
|
| 2216 |
+
|
| 2217 |
+
// Réinsérer avec de nouveaux IDs auto-générés
|
| 2218 |
+
const newIds = await this.db.db.memories.bulkAdd(dataToMigrate);
|
| 2219 |
+
|
| 2220 |
+
console.log(`✅ Migration terminée. Nouveaux IDs:`, newIds);
|
| 2221 |
+
return true;
|
| 2222 |
+
} catch (error) {
|
| 2223 |
+
console.error("❌ Erreur lors de la migration:", error);
|
| 2224 |
+
return false;
|
| 2225 |
+
}
|
| 2226 |
+
}
|
| 2227 |
+
|
| 2228 |
+
// Background migration: populate keywords for all existing memories if missing
|
| 2229 |
+
async populateKeywordsForAllMemories() {
|
| 2230 |
+
if (!this.db || !this.db.db.memories) return false;
|
| 2231 |
+
try {
|
| 2232 |
+
console.log("🔧 Starting background keyword population...");
|
| 2233 |
+
const all = await this.db.db.memories.toArray();
|
| 2234 |
+
const ops = [];
|
| 2235 |
+
for (const mem of all) {
|
| 2236 |
+
if (!mem.keywords || !Array.isArray(mem.keywords) || mem.keywords.length === 0) {
|
| 2237 |
+
const keys = this.deriveKeywords(mem.content || "");
|
| 2238 |
+
ops.push(this.db.db.memories.update(mem.id, { keywords: keys }));
|
| 2239 |
+
}
|
| 2240 |
+
// batch in small chunks to avoid blocking
|
| 2241 |
+
if (ops.length >= 50) {
|
| 2242 |
+
await Promise.all(ops);
|
| 2243 |
+
ops.length = 0;
|
| 2244 |
+
}
|
| 2245 |
+
}
|
| 2246 |
+
if (ops.length) await Promise.all(ops);
|
| 2247 |
+
console.log("✅ Keyword population complete");
|
| 2248 |
+
return true;
|
| 2249 |
+
} catch (e) {
|
| 2250 |
+
console.warn("Error populating keywords", e);
|
| 2251 |
+
return false;
|
| 2252 |
+
}
|
| 2253 |
+
}
|
| 2254 |
+
}
|
| 2255 |
+
|
| 2256 |
+
window.KimiMemorySystem = KimiMemorySystem;
|
| 2257 |
+
export default KimiMemorySystem;
|
kimi-js/kimi-memory-ui.js
ADDED
|
@@ -0,0 +1,966 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// ===== KIMI MEMORY UI MANAGER =====
|
| 2 |
+
class KimiMemoryUI {
|
| 3 |
+
constructor() {
|
| 4 |
+
this.memorySystem = null;
|
| 5 |
+
this.isInitialized = false;
|
| 6 |
+
// Debounce helpers for UI refresh to coalesce multiple DB reads
|
| 7 |
+
this._debounceTimers = {};
|
| 8 |
+
}
|
| 9 |
+
|
| 10 |
+
debounce(key, fn, wait = 350) {
|
| 11 |
+
if (this._debounceTimers[key]) clearTimeout(this._debounceTimers[key]);
|
| 12 |
+
this._debounceTimers[key] = setTimeout(() => {
|
| 13 |
+
fn();
|
| 14 |
+
delete this._debounceTimers[key];
|
| 15 |
+
}, wait);
|
| 16 |
+
}
|
| 17 |
+
|
| 18 |
+
async init() {
|
| 19 |
+
if (!window.kimiMemorySystem) {
|
| 20 |
+
console.warn("Memory system not available");
|
| 21 |
+
return;
|
| 22 |
+
}
|
| 23 |
+
|
| 24 |
+
this.memorySystem = window.kimiMemorySystem;
|
| 25 |
+
this.setupEventListeners();
|
| 26 |
+
await this.updateMemoryStats();
|
| 27 |
+
this.isInitialized = true;
|
| 28 |
+
}
|
| 29 |
+
|
| 30 |
+
setupEventListeners() {
|
| 31 |
+
// Memory toggle
|
| 32 |
+
const memoryToggle = document.getElementById("memory-toggle");
|
| 33 |
+
if (memoryToggle) {
|
| 34 |
+
memoryToggle.addEventListener("click", () => this.toggleMemorySystem());
|
| 35 |
+
}
|
| 36 |
+
|
| 37 |
+
// View memories button
|
| 38 |
+
const viewMemoriesBtn = document.getElementById("view-memories");
|
| 39 |
+
if (viewMemoriesBtn) {
|
| 40 |
+
viewMemoriesBtn.addEventListener("click", () => this.openMemoryModal());
|
| 41 |
+
}
|
| 42 |
+
|
| 43 |
+
// Add memory button
|
| 44 |
+
const addMemoryBtn = document.getElementById("add-memory");
|
| 45 |
+
if (addMemoryBtn) {
|
| 46 |
+
addMemoryBtn.addEventListener("click", () => {
|
| 47 |
+
this.addManualMemory();
|
| 48 |
+
ensureVideoNeutralOnUIChange();
|
| 49 |
+
});
|
| 50 |
+
}
|
| 51 |
+
|
| 52 |
+
// Memory modal close
|
| 53 |
+
const memoryClose = document.getElementById("memory-close");
|
| 54 |
+
if (memoryClose) {
|
| 55 |
+
memoryClose.addEventListener("click", () => {
|
| 56 |
+
this.closeMemoryModal();
|
| 57 |
+
ensureVideoNeutralOnUIChange();
|
| 58 |
+
});
|
| 59 |
+
}
|
| 60 |
+
|
| 61 |
+
// Memory export
|
| 62 |
+
const memoryExport = document.getElementById("memory-export");
|
| 63 |
+
if (memoryExport) {
|
| 64 |
+
memoryExport.addEventListener("click", () => this.exportMemories());
|
| 65 |
+
}
|
| 66 |
+
|
| 67 |
+
// Memory filter
|
| 68 |
+
const memoryFilter = document.getElementById("memory-filter-category");
|
| 69 |
+
if (memoryFilter) {
|
| 70 |
+
memoryFilter.addEventListener("change", () => {
|
| 71 |
+
this.filterMemories();
|
| 72 |
+
ensureVideoNeutralOnUIChange();
|
| 73 |
+
});
|
| 74 |
+
}
|
| 75 |
+
|
| 76 |
+
// Memory search
|
| 77 |
+
const memorySearch = document.getElementById("memory-search");
|
| 78 |
+
if (memorySearch) {
|
| 79 |
+
memorySearch.addEventListener("input", () => this.filterMemories());
|
| 80 |
+
}
|
| 81 |
+
|
| 82 |
+
// Close modal on overlay click
|
| 83 |
+
const memoryOverlay = document.getElementById("memory-overlay");
|
| 84 |
+
if (memoryOverlay) {
|
| 85 |
+
memoryOverlay.addEventListener("click", e => {
|
| 86 |
+
if (e.target === memoryOverlay) {
|
| 87 |
+
this.closeMemoryModal();
|
| 88 |
+
}
|
| 89 |
+
});
|
| 90 |
+
}
|
| 91 |
+
|
| 92 |
+
// Delegated handler for memory-source clicks / touch / keyboard
|
| 93 |
+
const memoryList = document.getElementById("memory-list");
|
| 94 |
+
if (memoryList) {
|
| 95 |
+
// Click and touch
|
| 96 |
+
memoryList.addEventListener("click", e => this.handleMemorySourceToggle(e));
|
| 97 |
+
memoryList.addEventListener("touchstart", e => this.handleMemorySourceToggle(e));
|
| 98 |
+
|
| 99 |
+
// General delegated click handler for memory actions (summarize, etc.)
|
| 100 |
+
memoryList.addEventListener("click", e => this.handleMemoryListClick(e));
|
| 101 |
+
|
| 102 |
+
// Keyboard accessibility: Enter / Space when focused on .memory-source
|
| 103 |
+
memoryList.addEventListener("keydown", e => {
|
| 104 |
+
const target = e.target;
|
| 105 |
+
if (target && target.classList && target.classList.contains("memory-source")) {
|
| 106 |
+
if (e.key === "Enter" || e.key === " ") {
|
| 107 |
+
e.preventDefault();
|
| 108 |
+
this.toggleSourceContentForElement(target);
|
| 109 |
+
}
|
| 110 |
+
}
|
| 111 |
+
});
|
| 112 |
+
}
|
| 113 |
+
}
|
| 114 |
+
|
| 115 |
+
// Delegated click handler for actions inside the memory list
|
| 116 |
+
async handleMemoryListClick(e) {
|
| 117 |
+
try {
|
| 118 |
+
const summarizeBtn = e.target.closest && e.target.closest("#memory-summarize-btn");
|
| 119 |
+
if (summarizeBtn) {
|
| 120 |
+
e.stopPropagation();
|
| 121 |
+
await this.handleSummarizeAction();
|
| 122 |
+
return;
|
| 123 |
+
}
|
| 124 |
+
} catch (err) {
|
| 125 |
+
console.error("Error handling memory list click", err);
|
| 126 |
+
}
|
| 127 |
+
}
|
| 128 |
+
|
| 129 |
+
async toggleMemorySystem() {
|
| 130 |
+
if (!this.memorySystem) return;
|
| 131 |
+
|
| 132 |
+
const toggle = document.getElementById("memory-toggle");
|
| 133 |
+
const enabled = !this.memorySystem.memoryEnabled;
|
| 134 |
+
|
| 135 |
+
await this.memorySystem.toggleMemorySystem(enabled);
|
| 136 |
+
|
| 137 |
+
if (toggle) {
|
| 138 |
+
toggle.setAttribute("aria-checked", enabled.toString());
|
| 139 |
+
toggle.classList.toggle("active", enabled);
|
| 140 |
+
}
|
| 141 |
+
|
| 142 |
+
// Show feedback
|
| 143 |
+
this.showFeedback(enabled ? "Memory system enabled" : "Memory system disabled");
|
| 144 |
+
}
|
| 145 |
+
|
| 146 |
+
async addManualMemory() {
|
| 147 |
+
const categorySelect = document.getElementById("memory-category");
|
| 148 |
+
const contentInput = document.getElementById("memory-content");
|
| 149 |
+
|
| 150 |
+
if (!categorySelect || !contentInput) return;
|
| 151 |
+
|
| 152 |
+
const category = categorySelect.value;
|
| 153 |
+
const content = contentInput.value.trim();
|
| 154 |
+
|
| 155 |
+
if (!content) {
|
| 156 |
+
this.showFeedback("Please enter memory content", "error");
|
| 157 |
+
return;
|
| 158 |
+
}
|
| 159 |
+
|
| 160 |
+
try {
|
| 161 |
+
await this.memorySystem.addMemory({
|
| 162 |
+
category: category,
|
| 163 |
+
content: content,
|
| 164 |
+
type: "manual",
|
| 165 |
+
confidence: 1.0
|
| 166 |
+
});
|
| 167 |
+
|
| 168 |
+
contentInput.value = "";
|
| 169 |
+
await this.updateMemoryStats();
|
| 170 |
+
this.showFeedback("Memory added successfully");
|
| 171 |
+
} catch (error) {
|
| 172 |
+
console.error("Error adding memory:", error);
|
| 173 |
+
this.showFeedback("Error adding memory", "error");
|
| 174 |
+
}
|
| 175 |
+
}
|
| 176 |
+
|
| 177 |
+
async openMemoryModal() {
|
| 178 |
+
const overlay = document.getElementById("memory-overlay");
|
| 179 |
+
if (!overlay) return;
|
| 180 |
+
|
| 181 |
+
overlay.style.display = "flex";
|
| 182 |
+
await this.loadMemories();
|
| 183 |
+
}
|
| 184 |
+
|
| 185 |
+
closeMemoryModal() {
|
| 186 |
+
const overlay = document.getElementById("memory-overlay");
|
| 187 |
+
if (overlay) {
|
| 188 |
+
overlay.style.display = "none";
|
| 189 |
+
// Ensure background video resumes after closing memory modal
|
| 190 |
+
const kv = window.kimiVideo;
|
| 191 |
+
if (kv && kv.activeVideo) {
|
| 192 |
+
try {
|
| 193 |
+
const v = kv.activeVideo;
|
| 194 |
+
if (v.ended) {
|
| 195 |
+
if (typeof kv.returnToNeutral === "function") kv.returnToNeutral();
|
| 196 |
+
} else if (v.paused) {
|
| 197 |
+
// Use centralized video utility for play
|
| 198 |
+
window.KimiVideoManager.getVideoElement(v)
|
| 199 |
+
.play()
|
| 200 |
+
.catch(() => {
|
| 201 |
+
if (typeof kv.returnToNeutral === "function") kv.returnToNeutral();
|
| 202 |
+
});
|
| 203 |
+
}
|
| 204 |
+
} catch {}
|
| 205 |
+
}
|
| 206 |
+
}
|
| 207 |
+
}
|
| 208 |
+
|
| 209 |
+
async loadMemories() {
|
| 210 |
+
if (!this.memorySystem) return;
|
| 211 |
+
|
| 212 |
+
try {
|
| 213 |
+
// Use debounce to avoid multiple rapid DB reads
|
| 214 |
+
this.debounce("loadMemories", async () => {
|
| 215 |
+
const memories = await this.memorySystem.getAllMemories();
|
| 216 |
+
console.log("Loading memories into UI:", memories.length);
|
| 217 |
+
this.renderMemories(memories);
|
| 218 |
+
});
|
| 219 |
+
} catch (error) {
|
| 220 |
+
console.error("Error loading memories:", error);
|
| 221 |
+
}
|
| 222 |
+
}
|
| 223 |
+
|
| 224 |
+
async filterMemories() {
|
| 225 |
+
const filterSelect = document.getElementById("memory-filter-category");
|
| 226 |
+
const searchInput = document.getElementById("memory-search");
|
| 227 |
+
if (!this.memorySystem) return;
|
| 228 |
+
|
| 229 |
+
try {
|
| 230 |
+
const category = filterSelect?.value;
|
| 231 |
+
const searchTerm = searchInput?.value.toLowerCase().trim();
|
| 232 |
+
let memories;
|
| 233 |
+
|
| 234 |
+
if (category) {
|
| 235 |
+
memories = await this.memorySystem.getMemoriesByCategory(category);
|
| 236 |
+
} else {
|
| 237 |
+
memories = await this.memorySystem.getAllMemories();
|
| 238 |
+
}
|
| 239 |
+
|
| 240 |
+
// Apply search filter if search term exists
|
| 241 |
+
if (searchTerm) {
|
| 242 |
+
memories = memories.filter(
|
| 243 |
+
memory =>
|
| 244 |
+
memory.content.toLowerCase().includes(searchTerm) ||
|
| 245 |
+
memory.category.toLowerCase().includes(searchTerm) ||
|
| 246 |
+
(memory.sourceText && memory.sourceText.toLowerCase().includes(searchTerm))
|
| 247 |
+
);
|
| 248 |
+
}
|
| 249 |
+
|
| 250 |
+
this.renderMemories(memories);
|
| 251 |
+
} catch (error) {
|
| 252 |
+
console.error("Error filtering memories:", error);
|
| 253 |
+
}
|
| 254 |
+
}
|
| 255 |
+
|
| 256 |
+
renderMemories(memories) {
|
| 257 |
+
const memoryList = document.getElementById("memory-list");
|
| 258 |
+
if (!memoryList) return;
|
| 259 |
+
|
| 260 |
+
console.log("Rendering memories:", memories); // Debug logging
|
| 261 |
+
|
| 262 |
+
if (memories.length === 0) {
|
| 263 |
+
memoryList.innerHTML = `
|
| 264 |
+
<div class="memory-empty">
|
| 265 |
+
<i class="fas fa-brain"></i>
|
| 266 |
+
<p>No memories found. Start chatting to build memories automatically, or add them manually.</p>
|
| 267 |
+
</div>
|
| 268 |
+
`;
|
| 269 |
+
return;
|
| 270 |
+
}
|
| 271 |
+
|
| 272 |
+
// Group memories by category for better organization
|
| 273 |
+
const groupedMemories = memories.reduce((groups, memory) => {
|
| 274 |
+
const category = memory.category || "other";
|
| 275 |
+
if (!groups[category]) groups[category] = [];
|
| 276 |
+
groups[category].push(memory);
|
| 277 |
+
return groups;
|
| 278 |
+
}, {});
|
| 279 |
+
|
| 280 |
+
let html = "";
|
| 281 |
+
|
| 282 |
+
// Toolbar with summarize action
|
| 283 |
+
html += `
|
| 284 |
+
<div class="memory-toolbar" style="display:flex; gap:8px; align-items:center; margin-bottom:12px;">
|
| 285 |
+
<button id="memory-summarize-btn" class="kimi-button" title="Summarize recent memories">📝 Summarize last 7 days</button>
|
| 286 |
+
</div>
|
| 287 |
+
`;
|
| 288 |
+
Object.entries(groupedMemories).forEach(([category, categoryMemories]) => {
|
| 289 |
+
html += `
|
| 290 |
+
<div class="memory-category-group">
|
| 291 |
+
<h4 class="memory-category-header">
|
| 292 |
+
${this.getCategoryIcon(category)} ${this.formatCategoryName(category)}
|
| 293 |
+
<span class="memory-category-count">(${categoryMemories.length})</span>
|
| 294 |
+
</h4>
|
| 295 |
+
<div class="memory-category-items">
|
| 296 |
+
`;
|
| 297 |
+
|
| 298 |
+
categoryMemories.forEach(memory => {
|
| 299 |
+
const confidence = Math.round(memory.confidence * 100);
|
| 300 |
+
const isAutomatic = memory.type === "auto_extracted";
|
| 301 |
+
const previewLength = 120;
|
| 302 |
+
const isLongContent = memory.content.length > previewLength;
|
| 303 |
+
const previewText = isLongContent ? memory.content.substring(0, previewLength) + "..." : memory.content;
|
| 304 |
+
const wordCount = memory.content.split(/\s+/).length;
|
| 305 |
+
const importance = typeof memory.importance === "number" ? memory.importance : 0.5;
|
| 306 |
+
const importanceLevel = this.getImportanceLevelFromValue(importance);
|
| 307 |
+
const importancePct = Math.round(importance * 100);
|
| 308 |
+
const tagsHtml = this.renderTags(memory.tags || []);
|
| 309 |
+
|
| 310 |
+
html += `
|
| 311 |
+
<div class="memory-item ${isAutomatic ? "memory-auto" : "memory-manual"}" data-memory-id="${memory.id}">
|
| 312 |
+
<div class="memory-header">
|
| 313 |
+
<div class="memory-item-title">${window.KimiValidationUtils && window.KimiValidationUtils.escapeHtml ? window.KimiValidationUtils.escapeHtml(memory.title || "") : memory.title || ""}${!memory.title ? "" : ""}</div>
|
| 314 |
+
<div class="memory-badges">
|
| 315 |
+
<span class="memory-type ${memory.type}">${memory.type === "auto_extracted" ? "🤖 Auto" : "✋ Manual"}</span>
|
| 316 |
+
<span class="memory-confidence confidence-${this.getConfidenceLevel(confidence)}">${confidence}%</span>
|
| 317 |
+
${memory.type === "summary" || (memory.tags && memory.tags.includes("summary")) ? `<span class="memory-summary-badge" style="background:var(--accent-color);color:white;padding:2px 6px;border-radius:12px;margin-left:8px;font-size:0.8em;">Summary</span>` : ""}
|
| 318 |
+
${isLongContent ? `<span class="memory-length">${wordCount} mots</span>` : ""}
|
| 319 |
+
<span class="memory-importance importance-${importanceLevel}" title="Importance: ${importancePct}% (${importanceLevel})">${importanceLevel.charAt(0).toUpperCase() + importanceLevel.slice(1)}</span>
|
| 320 |
+
</div>
|
| 321 |
+
</div>
|
| 322 |
+
${
|
| 323 |
+
!memory.title
|
| 324 |
+
? `
|
| 325 |
+
<div class="memory-preview">
|
| 326 |
+
<div class="memory-preview-text ${isLongContent ? "memory-preview-short" : ""}" id="preview-${memory.id}">
|
| 327 |
+
${this.highlightMemoryContent(previewText)}
|
| 328 |
+
</div>
|
| 329 |
+
${
|
| 330 |
+
isLongContent
|
| 331 |
+
? `
|
| 332 |
+
<div class="memory-preview-full" id="full-${memory.id}" style="display: none;">
|
| 333 |
+
${this.highlightMemoryContent(memory.content)}
|
| 334 |
+
</div>
|
| 335 |
+
<button class="memory-expand-btn" onclick="kimiMemoryUI.toggleMemoryContent('${memory.id}')">
|
| 336 |
+
<i class="fas fa-chevron-down" id="icon-${memory.id}"></i> <span data-i18n="view_more"></span>
|
| 337 |
+
</button>
|
| 338 |
+
`
|
| 339 |
+
: ""
|
| 340 |
+
}
|
| 341 |
+
</div>
|
| 342 |
+
`
|
| 343 |
+
: ""
|
| 344 |
+
}
|
| 345 |
+
${tagsHtml}
|
| 346 |
+
<div class="memory-meta">
|
| 347 |
+
<span class="memory-date">${this.formatDate(
|
| 348 |
+
// Robust fallback chain: createdAt -> timestamp -> lastAccess -> lastModified -> Date.now()
|
| 349 |
+
memory.createdAt || memory.timestamp || memory.lastAccess || memory.lastModified || Date.now()
|
| 350 |
+
)}</span>
|
| 351 |
+
${
|
| 352 |
+
memory.sourceText
|
| 353 |
+
? `<span class="memory-source" data-i18n="memory_source_label" title="${
|
| 354 |
+
window.KimiValidationUtils && window.KimiValidationUtils.escapeHtml
|
| 355 |
+
? window.KimiValidationUtils.escapeHtml(memory.sourceText)
|
| 356 |
+
: memory.sourceText
|
| 357 |
+
}"></span>`
|
| 358 |
+
: `<span data-i18n="memory_manually_added"></span>`
|
| 359 |
+
}
|
| 360 |
+
</div>
|
| 361 |
+
${
|
| 362 |
+
memory.sourceText
|
| 363 |
+
? `<div class="memory-source-content" id="source-content-${memory.id}" style="display:none;">${
|
| 364 |
+
window.KimiValidationUtils && window.KimiValidationUtils.escapeHtml
|
| 365 |
+
? window.KimiValidationUtils.escapeHtml(memory.sourceText)
|
| 366 |
+
: memory.sourceText
|
| 367 |
+
}</div>`
|
| 368 |
+
: ""
|
| 369 |
+
}
|
| 370 |
+
<div class="memory-actions">
|
| 371 |
+
<button class="memory-edit-btn" onclick="kimiMemoryUI.editMemory('${memory.id}')" data-i18n-title="edit_memory_button_title">
|
| 372 |
+
<i class="fas fa-edit"></i>
|
| 373 |
+
</button>
|
| 374 |
+
<button class="memory-delete-btn" onclick="kimiMemoryUI.deleteMemory('${memory.id}')" data-i18n-title="delete_memory_button_title">
|
| 375 |
+
<i class="fas fa-trash"></i>
|
| 376 |
+
</button>
|
| 377 |
+
</div>
|
| 378 |
+
</div>
|
| 379 |
+
`;
|
| 380 |
+
});
|
| 381 |
+
|
| 382 |
+
html += `
|
| 383 |
+
</div>
|
| 384 |
+
</div>
|
| 385 |
+
`;
|
| 386 |
+
});
|
| 387 |
+
|
| 388 |
+
// Minimal runtime guard: block accidental <script> tags in generated HTML
|
| 389 |
+
// This is a non-intrusive safety check that prevents XSS when the
|
| 390 |
+
// assembled `html` somehow contains script tags. In normal operation
|
| 391 |
+
// the content is escaped via KimiValidationUtils and highlightMemoryContent(),
|
| 392 |
+
// so this will not run; it only activates on suspicious input.
|
| 393 |
+
try {
|
| 394 |
+
if (/\<\s*script\b/i.test(html)) {
|
| 395 |
+
console.warn("Blocked suspicious <script> tag in memory HTML rendering");
|
| 396 |
+
// Fallback: render as safe text to avoid executing injected scripts
|
| 397 |
+
memoryList.textContent = html;
|
| 398 |
+
} else {
|
| 399 |
+
memoryList.innerHTML = html;
|
| 400 |
+
}
|
| 401 |
+
} catch (e) {
|
| 402 |
+
// On any unexpected error, fallback to safe text rendering
|
| 403 |
+
console.error("Error while rendering memories, falling back to safe text:", e);
|
| 404 |
+
memoryList.textContent = html;
|
| 405 |
+
}
|
| 406 |
+
|
| 407 |
+
// Apply translations to dynamic content
|
| 408 |
+
if (window.applyTranslations && typeof window.applyTranslations === "function") {
|
| 409 |
+
window.applyTranslations();
|
| 410 |
+
}
|
| 411 |
+
}
|
| 412 |
+
|
| 413 |
+
// Map importance value [0..1] to level string
|
| 414 |
+
getImportanceLevelFromValue(value) {
|
| 415 |
+
if (value >= 0.8) return "high";
|
| 416 |
+
if (value >= 0.6) return "medium";
|
| 417 |
+
return "low";
|
| 418 |
+
}
|
| 419 |
+
|
| 420 |
+
// Render tags as compact chips; show up to 4 then "+N"
|
| 421 |
+
renderTags(tags) {
|
| 422 |
+
if (!Array.isArray(tags) || tags.length === 0) return "";
|
| 423 |
+
const maxVisible = 4;
|
| 424 |
+
const visible = tags.slice(0, maxVisible);
|
| 425 |
+
const moreCount = tags.length - visible.length;
|
| 426 |
+
|
| 427 |
+
const escape = txt =>
|
| 428 |
+
window.KimiValidationUtils && window.KimiValidationUtils.escapeHtml ? window.KimiValidationUtils.escapeHtml(String(txt)) : String(txt);
|
| 429 |
+
|
| 430 |
+
const classify = tag => {
|
| 431 |
+
if (tag.startsWith("relationship:")) return "tag-relationship";
|
| 432 |
+
if (tag.startsWith("boundary:")) return "tag-boundary";
|
| 433 |
+
if (tag.startsWith("time:")) return "tag-time";
|
| 434 |
+
if (tag.startsWith("type:")) return "tag-type";
|
| 435 |
+
return "tag-generic";
|
| 436 |
+
};
|
| 437 |
+
|
| 438 |
+
const chips = visible.map(tag => `<span class="memory-tag ${classify(tag)}" title="${escape(tag)}">${escape(tag)}</span>`).join("");
|
| 439 |
+
|
| 440 |
+
const moreChip = moreCount > 0 ? `<span class="memory-tag tag-more" title="${moreCount} more">+${moreCount}</span>` : "";
|
| 441 |
+
|
| 442 |
+
return `<div class="memory-tags">${chips}${moreChip}</div>`;
|
| 443 |
+
}
|
| 444 |
+
|
| 445 |
+
formatCategoryName(category) {
|
| 446 |
+
// Try to resolve via i18n keys first, fallback to hardcoded English
|
| 447 |
+
const i18nKey = `memory_category_${category}`;
|
| 448 |
+
if (window.kimiI18nManager && typeof window.kimiI18nManager.t === "function") {
|
| 449 |
+
const val = window.kimiI18nManager.t(i18nKey);
|
| 450 |
+
if (val && val !== i18nKey) return val;
|
| 451 |
+
}
|
| 452 |
+
|
| 453 |
+
const names = {
|
| 454 |
+
personal: "Personal Information",
|
| 455 |
+
preferences: "Likes & Dislikes",
|
| 456 |
+
relationships: "Relationships & People",
|
| 457 |
+
activities: "Activities & Hobbies",
|
| 458 |
+
goals: "Goals & Aspirations",
|
| 459 |
+
experiences: "Shared Experiences",
|
| 460 |
+
important: "Important Events"
|
| 461 |
+
};
|
| 462 |
+
|
| 463 |
+
return names[category] || category.charAt(0).toUpperCase() + category.slice(1);
|
| 464 |
+
}
|
| 465 |
+
|
| 466 |
+
getConfidenceLevel(confidence) {
|
| 467 |
+
if (confidence >= 80) return "high";
|
| 468 |
+
if (confidence >= 60) return "medium";
|
| 469 |
+
return "low";
|
| 470 |
+
}
|
| 471 |
+
|
| 472 |
+
formatDate(raw) {
|
| 473 |
+
if (!raw) return "";
|
| 474 |
+
let date = null;
|
| 475 |
+
// Accept Date object, number, string
|
| 476 |
+
if (raw instanceof Date) {
|
| 477 |
+
date = raw;
|
| 478 |
+
} else {
|
| 479 |
+
// Some legacy records may store ISO string or Date object stringified
|
| 480 |
+
try {
|
| 481 |
+
date = new Date(raw);
|
| 482 |
+
} catch {
|
| 483 |
+
date = null;
|
| 484 |
+
}
|
| 485 |
+
}
|
| 486 |
+
if (!date || isNaN(date.getTime())) {
|
| 487 |
+
return ""; // Hide instead of showing 'Invalid Date'
|
| 488 |
+
}
|
| 489 |
+
const now = new Date();
|
| 490 |
+
const diffTime = now - date;
|
| 491 |
+
const diffDays = Math.floor(diffTime / (1000 * 60 * 60 * 24));
|
| 492 |
+
if (diffDays === 0) return "Today";
|
| 493 |
+
if (diffDays === 1) return "Yesterday";
|
| 494 |
+
if (diffDays < 7) return `${diffDays} days ago`;
|
| 495 |
+
return date.toLocaleDateString();
|
| 496 |
+
}
|
| 497 |
+
|
| 498 |
+
highlightMemoryContent(content) {
|
| 499 |
+
// Escape HTML first using centralized util
|
| 500 |
+
const escapedContent = window.KimiValidationUtils && window.KimiValidationUtils.escapeHtml ? window.KimiValidationUtils.escapeHtml(content) : content;
|
| 501 |
+
|
| 502 |
+
// Simple highlighting for search terms if there's a search active
|
| 503 |
+
const searchInput = document.getElementById("memory-search");
|
| 504 |
+
if (searchInput && searchInput.value.trim()) {
|
| 505 |
+
const searchTerm = searchInput.value.trim();
|
| 506 |
+
const regex = new RegExp(`(${searchTerm})`, "gi");
|
| 507 |
+
return escapedContent.replace(
|
| 508 |
+
regex,
|
| 509 |
+
'<mark style="background: var(--primary-color); color: white; padding: 1px 3px; border-radius: 2px;">$1</mark>'
|
| 510 |
+
);
|
| 511 |
+
}
|
| 512 |
+
|
| 513 |
+
return escapedContent;
|
| 514 |
+
}
|
| 515 |
+
|
| 516 |
+
getCategoryIcon(category) {
|
| 517 |
+
const icons = {
|
| 518 |
+
personal: "👤",
|
| 519 |
+
preferences: "❤️",
|
| 520 |
+
relationships: "👨👩👧👦",
|
| 521 |
+
activities: "🎯",
|
| 522 |
+
goals: "🎯",
|
| 523 |
+
experiences: "⭐",
|
| 524 |
+
important: "📌"
|
| 525 |
+
};
|
| 526 |
+
return icons[category] || "📝";
|
| 527 |
+
}
|
| 528 |
+
|
| 529 |
+
toggleMemoryContent(memoryId) {
|
| 530 |
+
const previewShort = document.getElementById(`preview-${memoryId}`);
|
| 531 |
+
const previewFull = document.getElementById(`full-${memoryId}`);
|
| 532 |
+
const icon = document.getElementById(`icon-${memoryId}`);
|
| 533 |
+
const expandBtn = icon?.closest(".memory-expand-btn");
|
| 534 |
+
|
| 535 |
+
if (!previewShort || !previewFull || !icon || !expandBtn) return;
|
| 536 |
+
|
| 537 |
+
const isExpanded = previewFull.style.display !== "none";
|
| 538 |
+
|
| 539 |
+
if (isExpanded) {
|
| 540 |
+
previewShort.style.display = "block";
|
| 541 |
+
previewFull.style.display = "none";
|
| 542 |
+
icon.className = "fas fa-chevron-down";
|
| 543 |
+
expandBtn.innerHTML = '<i class="fas fa-chevron-down"></i> <span data-i18n="view_more"></span>';
|
| 544 |
+
} else {
|
| 545 |
+
previewShort.style.display = "none";
|
| 546 |
+
previewFull.style.display = "block";
|
| 547 |
+
icon.className = "fas fa-chevron-up";
|
| 548 |
+
expandBtn.innerHTML = '<i class="fas fa-chevron-up"></i> <span data-i18n="view_less"></span>';
|
| 549 |
+
}
|
| 550 |
+
|
| 551 |
+
// Re-apply translations for the new button label
|
| 552 |
+
if (window.applyTranslations && typeof window.applyTranslations === "function") {
|
| 553 |
+
window.applyTranslations();
|
| 554 |
+
}
|
| 555 |
+
}
|
| 556 |
+
|
| 557 |
+
// Handle delegated click/touch events for .memory-source
|
| 558 |
+
handleMemorySourceToggle(e) {
|
| 559 |
+
try {
|
| 560 |
+
const el = e.target.closest && e.target.closest(".memory-source");
|
| 561 |
+
if (!el) return;
|
| 562 |
+
// Prevent triggering parent handlers
|
| 563 |
+
e.stopPropagation();
|
| 564 |
+
e.preventDefault();
|
| 565 |
+
this.toggleSourceContentForElement(el);
|
| 566 |
+
} catch (err) {
|
| 567 |
+
console.error("Error handling memory-source toggle", err);
|
| 568 |
+
}
|
| 569 |
+
}
|
| 570 |
+
|
| 571 |
+
// Toggle the adjacent .memory-source-content for a given .memory-source element
|
| 572 |
+
toggleSourceContentForElement(sourceEl) {
|
| 573 |
+
if (!sourceEl) return;
|
| 574 |
+
// The memory id is present on the nearest .memory-item
|
| 575 |
+
const item = sourceEl.closest(".memory-item");
|
| 576 |
+
if (!item) return;
|
| 577 |
+
const id = item.getAttribute("data-memory-id");
|
| 578 |
+
if (!id) return;
|
| 579 |
+
|
| 580 |
+
const contentEl = document.getElementById(`source-content-${id}`);
|
| 581 |
+
if (!contentEl) return;
|
| 582 |
+
|
| 583 |
+
const isVisible = contentEl.style.display !== "none" && contentEl.style.display !== "";
|
| 584 |
+
|
| 585 |
+
// Close any other open source contents
|
| 586 |
+
document.querySelectorAll(".memory-source-content").forEach(el => {
|
| 587 |
+
if (el !== contentEl) el.style.display = "none";
|
| 588 |
+
});
|
| 589 |
+
|
| 590 |
+
if (isVisible) {
|
| 591 |
+
contentEl.style.display = "none";
|
| 592 |
+
sourceEl.setAttribute("aria-expanded", "false");
|
| 593 |
+
} else {
|
| 594 |
+
// simple sliding animation via max-height for smoother appearance
|
| 595 |
+
contentEl.style.display = "block";
|
| 596 |
+
sourceEl.setAttribute("aria-expanded", "true");
|
| 597 |
+
}
|
| 598 |
+
}
|
| 599 |
+
|
| 600 |
+
async editMemory(memoryId) {
|
| 601 |
+
if (!this.memorySystem) return;
|
| 602 |
+
|
| 603 |
+
try {
|
| 604 |
+
// Get the memory to edit
|
| 605 |
+
const memories = await this.memorySystem.getAllMemories();
|
| 606 |
+
const memory = memories.find(m => m.id == memoryId);
|
| 607 |
+
if (!memory) {
|
| 608 |
+
this.showFeedback("Memory not found", "error");
|
| 609 |
+
return;
|
| 610 |
+
}
|
| 611 |
+
|
| 612 |
+
// Create edit dialog
|
| 613 |
+
const overlay = document.createElement("div");
|
| 614 |
+
overlay.className = "memory-edit-overlay";
|
| 615 |
+
overlay.style.cssText = `
|
| 616 |
+
position: fixed;
|
| 617 |
+
top: 0;
|
| 618 |
+
left: 0;
|
| 619 |
+
width: 100%;
|
| 620 |
+
height: 100%;
|
| 621 |
+
background: rgba(0, 0, 0, 0.8);
|
| 622 |
+
display: flex;
|
| 623 |
+
justify-content: center;
|
| 624 |
+
align-items: center;
|
| 625 |
+
z-index: 10001;
|
| 626 |
+
`;
|
| 627 |
+
|
| 628 |
+
const dialog = document.createElement("div");
|
| 629 |
+
dialog.className = "memory-edit-dialog";
|
| 630 |
+
dialog.style.cssText = `
|
| 631 |
+
background: var(--background-secondary);
|
| 632 |
+
border-radius: 12px;
|
| 633 |
+
padding: 24px;
|
| 634 |
+
width: 90%;
|
| 635 |
+
max-width: 500px;
|
| 636 |
+
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
|
| 637 |
+
`;
|
| 638 |
+
|
| 639 |
+
dialog.innerHTML = `
|
| 640 |
+
<h3 style="margin: 0 0 20px 0; color: var(--text-primary);">
|
| 641 |
+
<i class="fas fa-edit"></i> <span data-i18n="edit_memory_title"></span>
|
| 642 |
+
</h3>
|
| 643 |
+
<div style="margin-bottom: 12px;">
|
| 644 |
+
<label style="display: block; margin-bottom: 8px; font-weight: 500;" data-i18n="label_title"></label>
|
| 645 |
+
<input id="edit-memory-title" class="kimi-input" style="width:100%;" value="${
|
| 646 |
+
window.KimiValidationUtils && window.KimiValidationUtils.escapeHtml
|
| 647 |
+
? window.KimiValidationUtils.escapeHtml(memory.title || "")
|
| 648 |
+
: memory.title || ""
|
| 649 |
+
}" placeholder="Optional title for this memory" />
|
| 650 |
+
</div>
|
| 651 |
+
<div style="margin-bottom: 16px;">
|
| 652 |
+
<label style="display: block; margin-bottom: 8px; font-weight: 500;" data-i18n="label_category"></label>
|
| 653 |
+
<select id="edit-memory-category" class="kimi-select" style="width: 100%;">
|
| 654 |
+
<option value="personal" ${memory.category === "personal" ? "selected" : ""}>${
|
| 655 |
+
window.kimiI18nManager && window.kimiI18nManager.t ? window.kimiI18nManager.t("memory_category_personal") : "Personal Info"
|
| 656 |
+
}</option>
|
| 657 |
+
<option value="preferences" ${memory.category === "preferences" ? "selected" : ""}>${
|
| 658 |
+
window.kimiI18nManager && window.kimiI18nManager.t ? window.kimiI18nManager.t("memory_category_preferences") : "Likes & Dislikes"
|
| 659 |
+
}</option>
|
| 660 |
+
<option value="relationships" ${memory.category === "relationships" ? "selected" : ""}>${
|
| 661 |
+
window.kimiI18nManager && window.kimiI18nManager.t ? window.kimiI18nManager.t("memory_category_relationships") : "Relationships"
|
| 662 |
+
}</option>
|
| 663 |
+
<option value="activities" ${memory.category === "activities" ? "selected" : ""}>${
|
| 664 |
+
window.kimiI18nManager && window.kimiI18nManager.t ? window.kimiI18nManager.t("memory_category_activities") : "Activities & Hobbies"
|
| 665 |
+
}</option>
|
| 666 |
+
<option value="goals" ${memory.category === "goals" ? "selected" : ""}>${
|
| 667 |
+
window.kimiI18nManager && window.kimiI18nManager.t ? window.kimiI18nManager.t("memory_category_goals") : "Goals & Plans"
|
| 668 |
+
}</option>
|
| 669 |
+
<option value="experiences" ${memory.category === "experiences" ? "selected" : ""}>${
|
| 670 |
+
window.kimiI18nManager && window.kimiI18nManager.t ? window.kimiI18nManager.t("memory_category_experiences") : "Experiences"
|
| 671 |
+
}</option>
|
| 672 |
+
<option value="important" ${memory.category === "important" ? "selected" : ""}>${
|
| 673 |
+
window.kimiI18nManager && window.kimiI18nManager.t ? window.kimiI18nManager.t("memory_category_important") : "Important Events"
|
| 674 |
+
}</option>
|
| 675 |
+
</select>
|
| 676 |
+
</div>
|
| 677 |
+
<div style="margin-bottom: 20px;">
|
| 678 |
+
<label style="display: block; margin-bottom: 8px; font-weight: 500;" data-i18n="label_content"></label>
|
| 679 |
+
<textarea id="edit-memory-content" class="kimi-input" style="width: 100%; height: 100px; resize: vertical;" placeholder="Memory content...">${
|
| 680 |
+
window.KimiValidationUtils && window.KimiValidationUtils.escapeHtml
|
| 681 |
+
? window.KimiValidationUtils.escapeHtml(memory.sourceText || memory.content || "")
|
| 682 |
+
: memory.sourceText || memory.content || ""
|
| 683 |
+
}</textarea>
|
| 684 |
+
</div>
|
| 685 |
+
<div style="display: flex; gap: 12px; justify-content: flex-end;">
|
| 686 |
+
<button id="cancel-edit" class="kimi-button" style="background: #6c757d;" data-i18n="cancel">
|
| 687 |
+
<i class="fas fa-times"></i> <span data-i18n="cancel"></span>
|
| 688 |
+
</button>
|
| 689 |
+
<button id="save-edit" class="kimi-button">
|
| 690 |
+
<i class="fas fa-save"></i> <span data-i18n="save"></span>
|
| 691 |
+
</button>
|
| 692 |
+
</div>
|
| 693 |
+
`;
|
| 694 |
+
|
| 695 |
+
overlay.appendChild(dialog);
|
| 696 |
+
document.body.appendChild(overlay);
|
| 697 |
+
|
| 698 |
+
// Handle buttons
|
| 699 |
+
dialog.querySelector("#cancel-edit").addEventListener("click", () => {
|
| 700 |
+
document.body.removeChild(overlay);
|
| 701 |
+
});
|
| 702 |
+
|
| 703 |
+
dialog.querySelector("#save-edit").addEventListener("click", async () => {
|
| 704 |
+
const newTitle = dialog.querySelector("#edit-memory-title").value.trim();
|
| 705 |
+
const newCategory = dialog.querySelector("#edit-memory-category").value;
|
| 706 |
+
const newContent = dialog.querySelector("#edit-memory-content").value.trim();
|
| 707 |
+
|
| 708 |
+
if (!newContent) {
|
| 709 |
+
this.showFeedback(window.kimiI18nManager ? window.kimiI18nManager.t("validation_empty_message") : "Content cannot be empty", "error");
|
| 710 |
+
return;
|
| 711 |
+
}
|
| 712 |
+
|
| 713 |
+
console.log(`🔄 Attempting to update memory ID: ${memoryId}`);
|
| 714 |
+
console.log("New data:", { category: newCategory, content: newContent });
|
| 715 |
+
|
| 716 |
+
try {
|
| 717 |
+
// Also update sourceText so the "📝 Memory of the conversation" shows edited content
|
| 718 |
+
const result = await this.memorySystem.updateMemory(memoryId, {
|
| 719 |
+
title: newTitle,
|
| 720 |
+
category: newCategory,
|
| 721 |
+
content: newContent,
|
| 722 |
+
sourceText: newContent
|
| 723 |
+
});
|
| 724 |
+
|
| 725 |
+
console.log("Update result:", result);
|
| 726 |
+
|
| 727 |
+
if (result === true) {
|
| 728 |
+
// Close the modal
|
| 729 |
+
document.body.removeChild(overlay);
|
| 730 |
+
|
| 731 |
+
// Force full reload
|
| 732 |
+
await this.loadMemories();
|
| 733 |
+
await this.updateMemoryStats();
|
| 734 |
+
|
| 735 |
+
this.showFeedback(window.kimiI18nManager ? window.kimiI18nManager.t("saved") : "Saved");
|
| 736 |
+
console.log("✅ UI updated");
|
| 737 |
+
} else {
|
| 738 |
+
this.showFeedback(
|
| 739 |
+
window.kimiI18nManager ? window.kimiI18nManager.t("fallback_general_error") : "Error: unable to update memory",
|
| 740 |
+
"error"
|
| 741 |
+
);
|
| 742 |
+
console.error("❌ Update failed, result:", result);
|
| 743 |
+
}
|
| 744 |
+
} catch (error) {
|
| 745 |
+
console.error("Error updating memory:", error);
|
| 746 |
+
this.showFeedback(window.kimiI18nManager ? window.kimiI18nManager.t("fallback_general_error") : "Error updating memory", "error");
|
| 747 |
+
}
|
| 748 |
+
});
|
| 749 |
+
|
| 750 |
+
// Close on overlay click
|
| 751 |
+
overlay.addEventListener("click", e => {
|
| 752 |
+
if (e.target === overlay) {
|
| 753 |
+
document.body.removeChild(overlay);
|
| 754 |
+
}
|
| 755 |
+
});
|
| 756 |
+
} catch (error) {
|
| 757 |
+
console.error("Error editing memory:", error);
|
| 758 |
+
this.showFeedback("Error loading memory for editing", "error");
|
| 759 |
+
}
|
| 760 |
+
}
|
| 761 |
+
|
| 762 |
+
async deleteMemory(memoryId) {
|
| 763 |
+
if (!confirm("Are you sure you want to delete this memory?")) return;
|
| 764 |
+
|
| 765 |
+
try {
|
| 766 |
+
await this.memorySystem.deleteMemory(memoryId);
|
| 767 |
+
await this.loadMemories();
|
| 768 |
+
// Debounced stats update
|
| 769 |
+
this.debounce("updateStats", async () => await this.updateMemoryStats());
|
| 770 |
+
this.showFeedback("Memory deleted");
|
| 771 |
+
} catch (error) {
|
| 772 |
+
console.error("Error deleting memory:", error);
|
| 773 |
+
this.showFeedback("Error deleting memory", "error");
|
| 774 |
+
}
|
| 775 |
+
}
|
| 776 |
+
|
| 777 |
+
async exportMemories() {
|
| 778 |
+
if (!this.memorySystem) return;
|
| 779 |
+
|
| 780 |
+
try {
|
| 781 |
+
const exportData = await this.memorySystem.exportMemories();
|
| 782 |
+
if (exportData) {
|
| 783 |
+
const blob = new Blob([JSON.stringify(exportData, null, 2)], {
|
| 784 |
+
type: "application/json"
|
| 785 |
+
});
|
| 786 |
+
const url = URL.createObjectURL(blob);
|
| 787 |
+
const a = document.createElement("a");
|
| 788 |
+
a.href = url;
|
| 789 |
+
a.download = `kimi-memories-${new Date().toISOString().split("T")[0]}.json`;
|
| 790 |
+
a.click();
|
| 791 |
+
URL.revokeObjectURL(url);
|
| 792 |
+
this.showFeedback("Memories exported successfully");
|
| 793 |
+
}
|
| 794 |
+
} catch (error) {
|
| 795 |
+
console.error("Error exporting memories:", error);
|
| 796 |
+
this.showFeedback("Error exporting memories", "error");
|
| 797 |
+
}
|
| 798 |
+
}
|
| 799 |
+
|
| 800 |
+
async handleSummarizeAction() {
|
| 801 |
+
if (!this.memorySystem) return;
|
| 802 |
+
try {
|
| 803 |
+
// Destructive confirmation modal
|
| 804 |
+
const confirmMsg = `This action will create a single summary memory for the last 7 days and permanently DELETE the source memories. This is irreversible. Do you want to continue?`;
|
| 805 |
+
if (!confirm(confirmMsg)) {
|
| 806 |
+
this.showFeedback("Summary canceled");
|
| 807 |
+
return;
|
| 808 |
+
}
|
| 809 |
+
|
| 810 |
+
this.showFeedback("Creating summary and replacing sources...");
|
| 811 |
+
const result = await this.memorySystem.summarizeAndReplace(7, {});
|
| 812 |
+
if (result) {
|
| 813 |
+
this.showFeedback("Summary created and sources deleted");
|
| 814 |
+
await this.loadMemories();
|
| 815 |
+
await this.updateMemoryStats();
|
| 816 |
+
} else {
|
| 817 |
+
this.showFeedback("No recent memories to summarize");
|
| 818 |
+
}
|
| 819 |
+
} catch (e) {
|
| 820 |
+
console.error("Error creating destructive summary", e);
|
| 821 |
+
this.showFeedback("Error creating summary", "error");
|
| 822 |
+
}
|
| 823 |
+
}
|
| 824 |
+
|
| 825 |
+
async updateMemoryStats() {
|
| 826 |
+
if (!this.memorySystem) return;
|
| 827 |
+
|
| 828 |
+
try {
|
| 829 |
+
const stats = await this.memorySystem.getMemoryStats();
|
| 830 |
+
const memoryCount = document.getElementById("memory-count");
|
| 831 |
+
const memoryToggle = document.getElementById("memory-toggle");
|
| 832 |
+
|
| 833 |
+
if (memoryCount) {
|
| 834 |
+
memoryCount.textContent = `${stats.total} memories`;
|
| 835 |
+
}
|
| 836 |
+
|
| 837 |
+
// Update toggle state
|
| 838 |
+
if (memoryToggle) {
|
| 839 |
+
const enabled = this.memorySystem.memoryEnabled;
|
| 840 |
+
memoryToggle.setAttribute("aria-checked", enabled.toString());
|
| 841 |
+
memoryToggle.classList.toggle("active", enabled);
|
| 842 |
+
|
| 843 |
+
// Add visual indicator for memory status
|
| 844 |
+
const indicator = memoryToggle.querySelector(".memory-indicator") || document.createElement("div");
|
| 845 |
+
if (!memoryToggle.querySelector(".memory-indicator")) {
|
| 846 |
+
indicator.className = "memory-indicator";
|
| 847 |
+
memoryToggle.appendChild(indicator);
|
| 848 |
+
}
|
| 849 |
+
indicator.style.cssText = `
|
| 850 |
+
position: absolute;
|
| 851 |
+
top: -2px;
|
| 852 |
+
right: -2px;
|
| 853 |
+
width: 8px;
|
| 854 |
+
height: 8px;
|
| 855 |
+
border-radius: 50%;
|
| 856 |
+
background: ${enabled ? "#27ae60" : "#e74c3c"};
|
| 857 |
+
border: 2px solid white;
|
| 858 |
+
`;
|
| 859 |
+
}
|
| 860 |
+
} catch (error) {
|
| 861 |
+
console.error("Error updating memory stats:", error);
|
| 862 |
+
}
|
| 863 |
+
}
|
| 864 |
+
|
| 865 |
+
// Force refresh of the memory UI (useful for debugging)
|
| 866 |
+
async forceRefresh() {
|
| 867 |
+
console.log("🔄 Force refresh of memory UI...");
|
| 868 |
+
try {
|
| 869 |
+
if (this.memorySystem) {
|
| 870 |
+
// Migrate IDs if necessary
|
| 871 |
+
await this.memorySystem.migrateIncompatibleIDs();
|
| 872 |
+
|
| 873 |
+
// Reload memories
|
| 874 |
+
await this.loadMemories();
|
| 875 |
+
await this.updateMemoryStats();
|
| 876 |
+
|
| 877 |
+
console.log("✅ Forced refresh completed");
|
| 878 |
+
}
|
| 879 |
+
} catch (error) {
|
| 880 |
+
console.error("❌ Error during forced refresh:", error);
|
| 881 |
+
}
|
| 882 |
+
}
|
| 883 |
+
|
| 884 |
+
showFeedback(message, type = "success") {
|
| 885 |
+
// Create feedback element
|
| 886 |
+
const feedback = document.createElement("div");
|
| 887 |
+
feedback.className = `memory-feedback memory-feedback-${type}`;
|
| 888 |
+
feedback.textContent = message;
|
| 889 |
+
|
| 890 |
+
// Style the feedback based on type
|
| 891 |
+
let backgroundColor;
|
| 892 |
+
switch (type) {
|
| 893 |
+
case "error":
|
| 894 |
+
backgroundColor = "#e74c3c";
|
| 895 |
+
break;
|
| 896 |
+
case "info":
|
| 897 |
+
backgroundColor = "#3498db";
|
| 898 |
+
break;
|
| 899 |
+
default:
|
| 900 |
+
backgroundColor = "#27ae60";
|
| 901 |
+
}
|
| 902 |
+
|
| 903 |
+
// Style the feedback
|
| 904 |
+
Object.assign(feedback.style, {
|
| 905 |
+
position: "fixed",
|
| 906 |
+
top: "20px",
|
| 907 |
+
right: "20px",
|
| 908 |
+
padding: "12px 20px",
|
| 909 |
+
borderRadius: "6px",
|
| 910 |
+
color: "white",
|
| 911 |
+
backgroundColor: backgroundColor,
|
| 912 |
+
boxShadow: "0 4px 12px rgba(0,0,0,0.2)",
|
| 913 |
+
zIndex: "10000",
|
| 914 |
+
fontSize: "14px",
|
| 915 |
+
fontWeight: "500",
|
| 916 |
+
opacity: "0",
|
| 917 |
+
transform: "translateX(100%)",
|
| 918 |
+
transition: "all 0.3s ease"
|
| 919 |
+
});
|
| 920 |
+
|
| 921 |
+
document.body.appendChild(feedback);
|
| 922 |
+
|
| 923 |
+
// Animate in
|
| 924 |
+
setTimeout(() => {
|
| 925 |
+
feedback.style.opacity = "1";
|
| 926 |
+
feedback.style.transform = "translateX(0)";
|
| 927 |
+
}, 10);
|
| 928 |
+
|
| 929 |
+
// Remove after delay (longer for info messages, shorter for others)
|
| 930 |
+
const delay = type === "info" ? 2000 : 3000;
|
| 931 |
+
setTimeout(() => {
|
| 932 |
+
feedback.style.opacity = "0";
|
| 933 |
+
feedback.style.transform = "translateX(100%)";
|
| 934 |
+
setTimeout(() => {
|
| 935 |
+
if (feedback.parentNode) {
|
| 936 |
+
feedback.parentNode.removeChild(feedback);
|
| 937 |
+
}
|
| 938 |
+
}, 300);
|
| 939 |
+
}, delay);
|
| 940 |
+
}
|
| 941 |
+
}
|
| 942 |
+
|
| 943 |
+
// Initialize memory UI when DOM is ready
|
| 944 |
+
document.addEventListener("DOMContentLoaded", async () => {
|
| 945 |
+
window.kimiMemoryUI = new KimiMemoryUI();
|
| 946 |
+
|
| 947 |
+
// Wait for memory system to be ready
|
| 948 |
+
const waitForMemorySystem = () => {
|
| 949 |
+
if (window.kimiMemorySystem) {
|
| 950 |
+
window.kimiMemoryUI.init();
|
| 951 |
+
} else {
|
| 952 |
+
setTimeout(waitForMemorySystem, 100);
|
| 953 |
+
}
|
| 954 |
+
};
|
| 955 |
+
|
| 956 |
+
setTimeout(waitForMemorySystem, 1000); // Give time for initialization
|
| 957 |
+
});
|
| 958 |
+
|
| 959 |
+
window.KimiMemoryUI = KimiMemoryUI;
|
| 960 |
+
|
| 961 |
+
function ensureVideoNeutralOnUIChange() {
|
| 962 |
+
if (window.kimiVideo && window.kimiVideo.getCurrentVideoInfo) {
|
| 963 |
+
const info = window.kimiVideo.getCurrentVideoInfo();
|
| 964 |
+
if (info && info.ended) window.kimiVideo.returnToNeutral();
|
| 965 |
+
}
|
| 966 |
+
}
|
kimi-js/kimi-memory.js
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// ===== KIMI MEMORY MANAGER =====
|
| 2 |
+
class KimiMemory {
|
| 3 |
+
constructor(database) {
|
| 4 |
+
this.db = database;
|
| 5 |
+
this.preferences = {
|
| 6 |
+
voiceRate: 1.1,
|
| 7 |
+
voicePitch: 1.1,
|
| 8 |
+
voiceVolume: 0.8,
|
| 9 |
+
lastInteraction: null,
|
| 10 |
+
totalInteractions: 0,
|
| 11 |
+
emotionalState: "neutral"
|
| 12 |
+
};
|
| 13 |
+
this.isReady = false;
|
| 14 |
+
// affectionTrait will be loaded from database during init()
|
| 15 |
+
this.affectionTrait = 50; // Temporary default until loaded from DB
|
| 16 |
+
}
|
| 17 |
+
|
| 18 |
+
async init() {
|
| 19 |
+
if (!this.db) {
|
| 20 |
+
console.warn("Database not available, using local mode");
|
| 21 |
+
return;
|
| 22 |
+
}
|
| 23 |
+
try {
|
| 24 |
+
this.selectedCharacter = await this.db.getSelectedCharacter();
|
| 25 |
+
|
| 26 |
+
// Load affection trait from personality database with unified defaults
|
| 27 |
+
const charDefAff = (window.KIMI_CHARACTERS && window.KIMI_CHARACTERS[this.selectedCharacter]?.traits?.affection) || null;
|
| 28 |
+
const unifiedDefaults = window.kimiEmotionSystem?.TRAIT_DEFAULTS || { affection: 55 };
|
| 29 |
+
const defaultAff = typeof charDefAff === "number" ? charDefAff : unifiedDefaults.affection;
|
| 30 |
+
this.affectionTrait = await this.db.getPersonalityTrait("affection", defaultAff, this.selectedCharacter);
|
| 31 |
+
|
| 32 |
+
this.preferences = {
|
| 33 |
+
voiceRate: await this.db.getPreference("voiceRate", 1.1),
|
| 34 |
+
voicePitch: await this.db.getPreference("voicePitch", 1.1),
|
| 35 |
+
voiceVolume: await this.db.getPreference("voiceVolume", 0.8),
|
| 36 |
+
lastInteraction: await this.db.getPreference(`lastInteraction_${this.selectedCharacter}`, null),
|
| 37 |
+
totalInteractions: await this.db.getPreference(`totalInteractions_${this.selectedCharacter}`, 0),
|
| 38 |
+
emotionalState: await this.db.getPreference(`emotionalState_${this.selectedCharacter}`, "neutral")
|
| 39 |
+
};
|
| 40 |
+
// affectionTrait already loaded above with coherent default
|
| 41 |
+
this.isReady = true;
|
| 42 |
+
this.updateFavorabilityBar();
|
| 43 |
+
} catch (error) {
|
| 44 |
+
console.error("KimiMemory initialization error:", error);
|
| 45 |
+
}
|
| 46 |
+
}
|
| 47 |
+
|
| 48 |
+
async saveConversation(userText, kimiResponse, tokenInfo = null) {
|
| 49 |
+
if (!this.db) return;
|
| 50 |
+
|
| 51 |
+
try {
|
| 52 |
+
const character = await this.db.getSelectedCharacter();
|
| 53 |
+
|
| 54 |
+
// Use global personality average for conversation favorability score
|
| 55 |
+
let relationshipLevel = 50; // fallback
|
| 56 |
+
try {
|
| 57 |
+
const traits = window.getCharacterTraits ? await window.getCharacterTraits(character) : await this.db.getAllPersonalityTraits(character);
|
| 58 |
+
relationshipLevel = window.getPersonalityAverage ? window.getPersonalityAverage(traits) : 50;
|
| 59 |
+
} catch (error) {
|
| 60 |
+
console.warn("Error calculating relationship level for conversation:", error);
|
| 61 |
+
}
|
| 62 |
+
|
| 63 |
+
await this.db.saveConversation(userText, kimiResponse, relationshipLevel, new Date(), character);
|
| 64 |
+
|
| 65 |
+
// Legacy interactions counter kept for backward compatibility (not shown in UI now)
|
| 66 |
+
let total = await this.db.getPreference(`totalInteractions_${character}`, 0);
|
| 67 |
+
total = Number(total) + 1;
|
| 68 |
+
await this.db.setPreference(`totalInteractions_${character}`, total);
|
| 69 |
+
this.preferences.totalInteractions = total;
|
| 70 |
+
|
| 71 |
+
// Update tokens usage if provided (in/out)
|
| 72 |
+
if (tokenInfo && typeof tokenInfo.tokensIn === "number" && typeof tokenInfo.tokensOut === "number") {
|
| 73 |
+
const prevIn = Number(await this.db.getPreference(`totalTokensIn_${character}`, 0)) || 0;
|
| 74 |
+
const prevOut = Number(await this.db.getPreference(`totalTokensOut_${character}`, 0)) || 0;
|
| 75 |
+
await this.db.setPreference(`totalTokensIn_${character}`, prevIn + tokenInfo.tokensIn);
|
| 76 |
+
await this.db.setPreference(`totalTokensOut_${character}`, prevOut + tokenInfo.tokensOut);
|
| 77 |
+
}
|
| 78 |
+
|
| 79 |
+
let first = await this.db.getPreference(`firstInteraction_${character}`, null);
|
| 80 |
+
if (!first) {
|
| 81 |
+
first = new Date().toISOString();
|
| 82 |
+
await this.db.setPreference(`firstInteraction_${character}`, first);
|
| 83 |
+
}
|
| 84 |
+
|
| 85 |
+
this.preferences.lastInteraction = new Date().toISOString();
|
| 86 |
+
await this.db.setPreference(`lastInteraction_${character}`, this.preferences.lastInteraction);
|
| 87 |
+
} catch (error) {
|
| 88 |
+
console.error("Error saving conversation:", error);
|
| 89 |
+
}
|
| 90 |
+
}
|
| 91 |
+
|
| 92 |
+
async updateFavorability(change) {
|
| 93 |
+
try {
|
| 94 |
+
this.affectionTrait = Math.max(0, Math.min(100, this.affectionTrait + change));
|
| 95 |
+
if (this.db) {
|
| 96 |
+
await this.db.setPersonalityTrait("affection", this.affectionTrait, this.selectedCharacter);
|
| 97 |
+
}
|
| 98 |
+
this.updateFavorabilityBar();
|
| 99 |
+
} catch (error) {
|
| 100 |
+
console.error("Error updating favorability:", error);
|
| 101 |
+
}
|
| 102 |
+
}
|
| 103 |
+
|
| 104 |
+
async updateAffectionTrait() {
|
| 105 |
+
if (!this.db) return;
|
| 106 |
+
|
| 107 |
+
try {
|
| 108 |
+
this.selectedCharacter = await this.db.getSelectedCharacter();
|
| 109 |
+
// Use unified default that matches KimiEmotionSystem
|
| 110 |
+
const unifiedDefaults = window.kimiEmotionSystem?.TRAIT_DEFAULTS || { affection: 55 };
|
| 111 |
+
this.affectionTrait = await this.db.getPersonalityTrait("affection", unifiedDefaults.affection, this.selectedCharacter);
|
| 112 |
+
this.updateFavorabilityBar();
|
| 113 |
+
} catch (error) {
|
| 114 |
+
console.error("Error updating affection trait:", error);
|
| 115 |
+
}
|
| 116 |
+
}
|
| 117 |
+
|
| 118 |
+
/**
|
| 119 |
+
* @deprecated Use updateGlobalPersonalityUI().
|
| 120 |
+
* Thin wrapper retained for backward compatibility only.
|
| 121 |
+
*/
|
| 122 |
+
updateFavorabilityBar() {
|
| 123 |
+
if (window.updateGlobalPersonalityUI) {
|
| 124 |
+
window.updateGlobalPersonalityUI();
|
| 125 |
+
}
|
| 126 |
+
}
|
| 127 |
+
|
| 128 |
+
async getGreeting() {
|
| 129 |
+
const i18n = window.kimiI18nManager;
|
| 130 |
+
|
| 131 |
+
// Use global personality average instead of just affection trait
|
| 132 |
+
let relationshipLevel = 50; // fallback
|
| 133 |
+
try {
|
| 134 |
+
if (this.db) {
|
| 135 |
+
const traits = window.getCharacterTraits
|
| 136 |
+
? await window.getCharacterTraits(this.selectedCharacter)
|
| 137 |
+
: await this.db.getAllPersonalityTraits(this.selectedCharacter);
|
| 138 |
+
relationshipLevel = window.getPersonalityAverage ? window.getPersonalityAverage(traits) : 50;
|
| 139 |
+
}
|
| 140 |
+
} catch (error) {
|
| 141 |
+
console.warn("Error calculating greeting level:", error);
|
| 142 |
+
}
|
| 143 |
+
|
| 144 |
+
if (relationshipLevel <= 10) {
|
| 145 |
+
return i18n?.t("greeting_low") || "Hello.";
|
| 146 |
+
}
|
| 147 |
+
if (relationshipLevel < 40) {
|
| 148 |
+
return i18n?.t("greeting_mid") || "Hi. How can I help you?";
|
| 149 |
+
}
|
| 150 |
+
return i18n?.t("greeting_high") || "Hello my love! 💕";
|
| 151 |
+
}
|
| 152 |
+
}
|
| 153 |
+
|
| 154 |
+
// Export to global scope
|
| 155 |
+
window.KimiMemory = KimiMemory;
|
| 156 |
+
export default KimiMemory;
|
kimi-js/kimi-module.js
ADDED
|
@@ -0,0 +1,1949 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// KIMI MODULE SYSTEM
|
| 2 |
+
// KimiDataManager has been extracted to kimi-data-manager.js
|
| 3 |
+
// (kept global via window.KimiDataManager)
|
| 4 |
+
|
| 5 |
+
// Fonctions utilitaires et logique (référencent window.*)
|
| 6 |
+
|
| 7 |
+
// Helper function to avoid video logic duplication
|
| 8 |
+
async function handleVideoResponseWithFallback(sanitizedText, response, updatedTraits, selectedCharacter) {
|
| 9 |
+
if (window.kimiVideoController) {
|
| 10 |
+
// SIMPLE API
|
| 11 |
+
window.kimiVideoController.playVideo("user", sanitizedText);
|
| 12 |
+
if (response) {
|
| 13 |
+
window.kimiVideoController.playVideo("llm", response);
|
| 14 |
+
}
|
| 15 |
+
} else {
|
| 16 |
+
// Fallback to legacy logic
|
| 17 |
+
const lang = (await window.kimiDB?.getPreference("selectedLanguage", "en")) || "en";
|
| 18 |
+
const keywords = (window.KIMI_CONTEXT_KEYWORDS && (window.KIMI_CONTEXT_KEYWORDS[lang] || window.KIMI_CONTEXT_KEYWORDS.en)) || {};
|
| 19 |
+
// Centralized dancing detection via hasKeywordCategory
|
| 20 |
+
const userAskedDance = (window.hasKeywordCategory && window.hasKeywordCategory("dancing", sanitizedText, lang)) || false;
|
| 21 |
+
|
| 22 |
+
if (userAskedDance && window.kimiVideo) {
|
| 23 |
+
window.kimiVideo.switchToContext("dancing", "dancing", null, updatedTraits, updatedTraits.affection, true);
|
| 24 |
+
} else if (window.kimiVideo) {
|
| 25 |
+
const userEmotion = window.kimiEmotionSystem?.analyzeEmotionValidated(sanitizedText) || "neutral";
|
| 26 |
+
if (userEmotion === "negative") {
|
| 27 |
+
window.kimiVideo.respondWithEmotion("negative", updatedTraits, updatedTraits.affection);
|
| 28 |
+
} else if (response) {
|
| 29 |
+
const responseEmotion = window.kimiEmotionSystem?.analyzeEmotionValidated(response) || "positive";
|
| 30 |
+
// User negative override already handled; else use response emotion
|
| 31 |
+
window.kimiVideo.respondWithEmotion(responseEmotion, updatedTraits, updatedTraits.affection);
|
| 32 |
+
} else {
|
| 33 |
+
window.kimiVideo.respondWithEmotion("neutral", updatedTraits, updatedTraits.affection);
|
| 34 |
+
}
|
| 35 |
+
}
|
| 36 |
+
}
|
| 37 |
+
}
|
| 38 |
+
|
| 39 |
+
function updateFavorabilityLabel(characterKey) {
|
| 40 |
+
const favorabilityLabel = document.getElementById("favorability-label");
|
| 41 |
+
if (favorabilityLabel && window.KIMI_CHARACTERS && window.KIMI_CHARACTERS[characterKey]) {
|
| 42 |
+
// New semantics: show overall personality average (independent display)
|
| 43 |
+
favorabilityLabel.removeAttribute("for"); // decouple from any specific slider
|
| 44 |
+
if (window.setI18n) window.setI18n(favorabilityLabel, "personality_average_of", { name: window.KIMI_CHARACTERS[characterKey].name });
|
| 45 |
+
favorabilityLabel.textContent = `💖 Personality average of ${window.KIMI_CHARACTERS[characterKey].name}`;
|
| 46 |
+
if (!favorabilityLabel.getAttribute("title")) {
|
| 47 |
+
favorabilityLabel.setAttribute("title", "Average of (Affection + Playfulness + Intelligence + Empathy + Humor + Romance) / 6");
|
| 48 |
+
}
|
| 49 |
+
applyTranslations();
|
| 50 |
+
}
|
| 51 |
+
}
|
| 52 |
+
|
| 53 |
+
// Simplified personality average computation using centralized system
|
| 54 |
+
function computePersonalityAverage(traits) {
|
| 55 |
+
return window.getPersonalityAverage ? window.getPersonalityAverage(traits) : 50;
|
| 56 |
+
}
|
| 57 |
+
|
| 58 |
+
// Update UI elements (bar + percentage text + label) based on overall personality average
|
| 59 |
+
async function updateGlobalPersonalityUI(characterKey = null) {
|
| 60 |
+
try {
|
| 61 |
+
const db = window.kimiDB;
|
| 62 |
+
if (!db) return;
|
| 63 |
+
const character = characterKey || (await db.getSelectedCharacter());
|
| 64 |
+
const traits = window.getCharacterTraits ? await window.getCharacterTraits(character) : await db.getAllPersonalityTraits(character);
|
| 65 |
+
const avg = computePersonalityAverage(traits);
|
| 66 |
+
// Reuse existing favorability bar elements for global average
|
| 67 |
+
const bar = document.getElementById("favorability-bar");
|
| 68 |
+
const text = document.getElementById("favorability-text");
|
| 69 |
+
if (bar) bar.style.width = `${avg}%`;
|
| 70 |
+
if (text) text.textContent = `${avg.toFixed(2)}%`;
|
| 71 |
+
// Update label content if character provided
|
| 72 |
+
updateFavorabilityLabel(character);
|
| 73 |
+
} catch (e) {
|
| 74 |
+
console.warn("Failed to update global personality UI", e);
|
| 75 |
+
}
|
| 76 |
+
}
|
| 77 |
+
window.updateGlobalPersonalityUI = updateGlobalPersonalityUI;
|
| 78 |
+
|
| 79 |
+
async function loadCharacterSection() {
|
| 80 |
+
const kimiDB = window.kimiDB;
|
| 81 |
+
if (!kimiDB) return;
|
| 82 |
+
const characterGrid = document.getElementById("character-grid");
|
| 83 |
+
if (!characterGrid) return;
|
| 84 |
+
while (characterGrid.firstChild) {
|
| 85 |
+
characterGrid.removeChild(characterGrid.firstChild);
|
| 86 |
+
}
|
| 87 |
+
const selectedCharacter = await kimiDB.getSelectedCharacter();
|
| 88 |
+
for (const [key, info] of Object.entries(window.KIMI_CHARACTERS)) {
|
| 89 |
+
const card = document.createElement("div");
|
| 90 |
+
card.className = `character-card${key === selectedCharacter ? " selected" : ""}`;
|
| 91 |
+
card.dataset.character = key;
|
| 92 |
+
|
| 93 |
+
// Create character card elements safely
|
| 94 |
+
const img = document.createElement("img");
|
| 95 |
+
img.src = info.image;
|
| 96 |
+
img.alt = info.name;
|
| 97 |
+
|
| 98 |
+
const infoDiv = document.createElement("div");
|
| 99 |
+
infoDiv.className = "character-info";
|
| 100 |
+
|
| 101 |
+
const nameDiv = document.createElement("div");
|
| 102 |
+
nameDiv.className = "character-name";
|
| 103 |
+
nameDiv.textContent = info.name;
|
| 104 |
+
|
| 105 |
+
const detailsDiv = document.createElement("div");
|
| 106 |
+
detailsDiv.className = "character-details";
|
| 107 |
+
|
| 108 |
+
const ageDiv = document.createElement("div");
|
| 109 |
+
ageDiv.className = "character-age";
|
| 110 |
+
if (window.setI18n) window.setI18n(ageDiv, "character_age", { age: info.age });
|
| 111 |
+
|
| 112 |
+
const birthplaceDiv = document.createElement("div");
|
| 113 |
+
birthplaceDiv.className = "character-birthplace";
|
| 114 |
+
if (window.setI18n) window.setI18n(birthplaceDiv, "character_birthplace", { birthplace: info.birthplace });
|
| 115 |
+
|
| 116 |
+
const summaryDiv = document.createElement("div");
|
| 117 |
+
summaryDiv.className = "character-summary";
|
| 118 |
+
if (window.setI18n) window.setI18n(summaryDiv, `character_summary_${key}`);
|
| 119 |
+
|
| 120 |
+
detailsDiv.appendChild(ageDiv);
|
| 121 |
+
detailsDiv.appendChild(birthplaceDiv);
|
| 122 |
+
detailsDiv.appendChild(summaryDiv);
|
| 123 |
+
|
| 124 |
+
infoDiv.appendChild(nameDiv);
|
| 125 |
+
infoDiv.appendChild(detailsDiv);
|
| 126 |
+
|
| 127 |
+
const promptLabel = document.createElement("div");
|
| 128 |
+
promptLabel.className = "character-prompt-label";
|
| 129 |
+
if (window.setI18n) window.setI18n(promptLabel, "system_prompt");
|
| 130 |
+
promptLabel.textContent = "System Prompt";
|
| 131 |
+
|
| 132 |
+
const promptInput = document.createElement("textarea");
|
| 133 |
+
promptInput.className = "character-prompt-input";
|
| 134 |
+
promptInput.id = `prompt-${key}`;
|
| 135 |
+
promptInput.rows = 6;
|
| 136 |
+
|
| 137 |
+
// Create buttons container
|
| 138 |
+
const buttonsContainer = document.createElement("div");
|
| 139 |
+
buttonsContainer.className = "character-prompt-buttons";
|
| 140 |
+
|
| 141 |
+
// Save button
|
| 142 |
+
const saveButton = document.createElement("button");
|
| 143 |
+
saveButton.className = "kimi-button character-save-btn";
|
| 144 |
+
saveButton.id = `save-${key}`;
|
| 145 |
+
if (window.setI18n) window.setI18n(saveButton, "save");
|
| 146 |
+
saveButton.textContent = "Save";
|
| 147 |
+
|
| 148 |
+
// Reset button
|
| 149 |
+
const resetButton = document.createElement("button");
|
| 150 |
+
resetButton.className = "kimi-button character-reset-btn";
|
| 151 |
+
resetButton.id = `reset-${key}`;
|
| 152 |
+
if (window.setI18n) window.setI18n(resetButton, "reset_to_default");
|
| 153 |
+
resetButton.textContent = "Reset to Default";
|
| 154 |
+
|
| 155 |
+
buttonsContainer.appendChild(saveButton);
|
| 156 |
+
buttonsContainer.appendChild(resetButton);
|
| 157 |
+
|
| 158 |
+
card.appendChild(img);
|
| 159 |
+
card.appendChild(infoDiv);
|
| 160 |
+
card.appendChild(promptLabel);
|
| 161 |
+
card.appendChild(promptInput);
|
| 162 |
+
card.appendChild(buttonsContainer);
|
| 163 |
+
characterGrid.appendChild(card);
|
| 164 |
+
}
|
| 165 |
+
applyTranslations();
|
| 166 |
+
|
| 167 |
+
// Initialize prompt values and button event listeners
|
| 168 |
+
for (const key of Object.keys(window.KIMI_CHARACTERS)) {
|
| 169 |
+
const promptInput = document.getElementById(`prompt-${key}`);
|
| 170 |
+
const saveButton = document.getElementById(`save-${key}`);
|
| 171 |
+
const resetButton = document.getElementById(`reset-${key}`);
|
| 172 |
+
|
| 173 |
+
if (promptInput) {
|
| 174 |
+
const prompt = await kimiDB.getSystemPromptForCharacter(key);
|
| 175 |
+
promptInput.value = prompt;
|
| 176 |
+
promptInput.disabled = key !== selectedCharacter;
|
| 177 |
+
}
|
| 178 |
+
|
| 179 |
+
// Save button event listener
|
| 180 |
+
if (saveButton) {
|
| 181 |
+
saveButton.addEventListener("click", async () => {
|
| 182 |
+
if (promptInput) {
|
| 183 |
+
await kimiDB.setSystemPromptForCharacter(key, promptInput.value);
|
| 184 |
+
|
| 185 |
+
// Visual feedback
|
| 186 |
+
const originalText = saveButton.textContent;
|
| 187 |
+
saveButton.textContent = "Saved!";
|
| 188 |
+
saveButton.classList.add("success");
|
| 189 |
+
saveButton.disabled = true;
|
| 190 |
+
|
| 191 |
+
setTimeout(() => {
|
| 192 |
+
if (window.setI18n) window.setI18n(saveButton, "save");
|
| 193 |
+
applyTranslations();
|
| 194 |
+
saveButton.classList.remove("success");
|
| 195 |
+
saveButton.disabled = false;
|
| 196 |
+
}, 1500);
|
| 197 |
+
|
| 198 |
+
// Refresh personality if this is the selected character
|
| 199 |
+
if (key === selectedCharacter && window.kimiLLM && window.kimiLLM.refreshMemoryContext) {
|
| 200 |
+
await window.kimiLLM.refreshMemoryContext();
|
| 201 |
+
}
|
| 202 |
+
}
|
| 203 |
+
});
|
| 204 |
+
}
|
| 205 |
+
|
| 206 |
+
// Reset button event listener
|
| 207 |
+
if (resetButton) {
|
| 208 |
+
resetButton.addEventListener("click", async () => {
|
| 209 |
+
const defaultPrompt = window.KIMI_CHARACTERS[key]?.defaultPrompt || "";
|
| 210 |
+
if (promptInput) {
|
| 211 |
+
promptInput.value = defaultPrompt;
|
| 212 |
+
await kimiDB.setSystemPromptForCharacter(key, defaultPrompt);
|
| 213 |
+
|
| 214 |
+
// Visual feedback
|
| 215 |
+
const originalText = resetButton.textContent;
|
| 216 |
+
resetButton.textContent = "Reset!";
|
| 217 |
+
resetButton.classList.add("animated");
|
| 218 |
+
if (window.setI18n) window.setI18n(resetButton, "reset_done");
|
| 219 |
+
applyTranslations();
|
| 220 |
+
|
| 221 |
+
setTimeout(() => {
|
| 222 |
+
if (window.setI18n) window.setI18n(resetButton, "reset_to_default");
|
| 223 |
+
applyTranslations();
|
| 224 |
+
resetButton.classList.remove("animated");
|
| 225 |
+
}, 1500);
|
| 226 |
+
|
| 227 |
+
// Refresh personality if this is the selected character
|
| 228 |
+
if (key === selectedCharacter && window.kimiLLM && window.kimiLLM.refreshMemoryContext) {
|
| 229 |
+
await window.kimiLLM.refreshMemoryContext();
|
| 230 |
+
}
|
| 231 |
+
}
|
| 232 |
+
});
|
| 233 |
+
}
|
| 234 |
+
}
|
| 235 |
+
characterGrid.querySelectorAll(".character-card").forEach(card => {
|
| 236 |
+
card.addEventListener("click", async () => {
|
| 237 |
+
characterGrid.querySelectorAll(".character-card").forEach(c => c.classList.remove("selected"));
|
| 238 |
+
card.classList.add("selected");
|
| 239 |
+
const charKey = card.dataset.character;
|
| 240 |
+
for (const key of Object.keys(window.KIMI_CHARACTERS)) {
|
| 241 |
+
const promptInput = document.getElementById(`prompt-${key}`);
|
| 242 |
+
const saveButton = document.getElementById(`save-${key}`);
|
| 243 |
+
const resetButton = document.getElementById(`reset-${key}`);
|
| 244 |
+
|
| 245 |
+
if (promptInput) promptInput.disabled = key !== charKey;
|
| 246 |
+
if (saveButton) saveButton.disabled = key !== charKey;
|
| 247 |
+
if (resetButton) resetButton.disabled = key !== charKey;
|
| 248 |
+
}
|
| 249 |
+
updateFavorabilityLabel(charKey);
|
| 250 |
+
const chatHeaderName = document.querySelector(".chat-header span[data-i18n]");
|
| 251 |
+
if (chatHeaderName) {
|
| 252 |
+
const info = window.KIMI_CHARACTERS[charKey] || window.KIMI_CHARACTERS.kimi;
|
| 253 |
+
if (window.setI18n) window.setI18n(chatHeaderName, `chat_with_${charKey}`);
|
| 254 |
+
applyTranslations();
|
| 255 |
+
}
|
| 256 |
+
|
| 257 |
+
// Update personality trait sliders with selected character's traits
|
| 258 |
+
await updatePersonalitySliders(charKey);
|
| 259 |
+
});
|
| 260 |
+
});
|
| 261 |
+
|
| 262 |
+
// Initialize personality sliders with current selected character's traits
|
| 263 |
+
await updatePersonalitySliders(selectedCharacter);
|
| 264 |
+
}
|
| 265 |
+
|
| 266 |
+
async function getBasicResponse(reaction) {
|
| 267 |
+
// Use centralized fallback manager instead of duplicated logic
|
| 268 |
+
if (window.KimiFallbackManager) {
|
| 269 |
+
return await window.KimiFallbackManager.getEmotionalResponse(reaction);
|
| 270 |
+
}
|
| 271 |
+
|
| 272 |
+
// Fallback to legacy system if KimiFallbackManager not available
|
| 273 |
+
const i18n = window.kimiI18nManager;
|
| 274 |
+
return i18n ? i18n.t("fallback_technical_error") : "Sorry, I'm having technical difficulties! 💕";
|
| 275 |
+
}
|
| 276 |
+
|
| 277 |
+
// Déporté vers KimiEmotionSystem: utiliser window.updatePersonalityTraitsFromEmotion
|
| 278 |
+
|
| 279 |
+
async function analyzeAndReact(text, useAdvancedLLM = true, onStreamToken = null) {
|
| 280 |
+
const kimiDB = window.kimiDB;
|
| 281 |
+
const kimiLLM = window.kimiLLM;
|
| 282 |
+
const kimiVideo = window.kimiVideo;
|
| 283 |
+
const kimiMemory = window.kimiMemory;
|
| 284 |
+
const isSystemReady = window.isSystemReady;
|
| 285 |
+
|
| 286 |
+
try {
|
| 287 |
+
// Validate and sanitize input
|
| 288 |
+
if (!text || typeof text !== "string") {
|
| 289 |
+
throw new Error("Invalid input text");
|
| 290 |
+
}
|
| 291 |
+
|
| 292 |
+
const sanitizedText = window.KimiSecurityUtils?.sanitizeInput(text) || text.trim();
|
| 293 |
+
if (!sanitizedText) {
|
| 294 |
+
throw new Error("Empty input after sanitization");
|
| 295 |
+
}
|
| 296 |
+
|
| 297 |
+
const lowerText = sanitizedText.toLowerCase();
|
| 298 |
+
let reaction = window.kimiAnalyzeEmotion(sanitizedText, "auto");
|
| 299 |
+
let emotionIntensity = 0;
|
| 300 |
+
let response;
|
| 301 |
+
|
| 302 |
+
const selectedCharacter = await kimiDB.getSelectedCharacter();
|
| 303 |
+
const traits = window.getCharacterTraits ? await window.getCharacterTraits(selectedCharacter) : await kimiDB.getAllPersonalityTraits(selectedCharacter);
|
| 304 |
+
const avg = window.getPersonalityAverage ? window.getPersonalityAverage(traits) : 50;
|
| 305 |
+
const affection = typeof traits.affection === "number" ? traits.affection : 55;
|
| 306 |
+
const characterTraits = window.KIMI_CHARACTERS[selectedCharacter]?.traits || "";
|
| 307 |
+
|
| 308 |
+
// Only trigger listening videos for voice input, NOT for text chat
|
| 309 |
+
// Text chat should keep neutral videos until LLM response processing begins
|
| 310 |
+
|
| 311 |
+
if (typeof window.updatePersonalityTraitsFromEmotion === "function") {
|
| 312 |
+
await window.updatePersonalityTraitsFromEmotion(reaction, sanitizedText);
|
| 313 |
+
}
|
| 314 |
+
|
| 315 |
+
if (useAdvancedLLM && isSystemReady && kimiLLM) {
|
| 316 |
+
try {
|
| 317 |
+
const providerPref = kimiDB ? await kimiDB.getPreference("llmProvider", "openrouter") : "openrouter";
|
| 318 |
+
const apiKey = kimiDB && window.KimiProviderUtils ? await window.KimiProviderUtils.getApiKey(kimiDB, providerPref) : null;
|
| 319 |
+
|
| 320 |
+
if (apiKey && apiKey.trim() !== "") {
|
| 321 |
+
try {
|
| 322 |
+
if (window.dispatchEvent) {
|
| 323 |
+
window.emitAppEvent && window.emitAppEvent("chat:typing:start");
|
| 324 |
+
}
|
| 325 |
+
} catch (e) {}
|
| 326 |
+
|
| 327 |
+
// Use streaming if onStreamToken callback is provided
|
| 328 |
+
if (onStreamToken && typeof kimiLLM.chatStreaming === "function") {
|
| 329 |
+
response = await kimiLLM.chatStreaming(sanitizedText, onStreamToken);
|
| 330 |
+
} else {
|
| 331 |
+
response = await kimiLLM.chat(sanitizedText);
|
| 332 |
+
}
|
| 333 |
+
|
| 334 |
+
try {
|
| 335 |
+
if (window.dispatchEvent) {
|
| 336 |
+
window.emitAppEvent && window.emitAppEvent("chat:typing:stop");
|
| 337 |
+
}
|
| 338 |
+
} catch (e) {}
|
| 339 |
+
|
| 340 |
+
const updatedTraits = window.getCharacterTraits
|
| 341 |
+
? await window.getCharacterTraits(selectedCharacter)
|
| 342 |
+
: await kimiDB.getAllPersonalityTraits(selectedCharacter);
|
| 343 |
+
|
| 344 |
+
// Use centralized video handling to avoid duplication
|
| 345 |
+
await handleVideoResponseWithFallback(sanitizedText, response, updatedTraits, selectedCharacter);
|
| 346 |
+
|
| 347 |
+
if (kimiLLM.updatePersonalityFromResponse) {
|
| 348 |
+
await kimiLLM.updatePersonalityFromResponse(sanitizedText, response);
|
| 349 |
+
const selectedCharacter2 = await kimiDB.getSelectedCharacter();
|
| 350 |
+
const traits2 = window.getCharacterTraits
|
| 351 |
+
? await window.getCharacterTraits(selectedCharacter2)
|
| 352 |
+
: await kimiDB.getAllPersonalityTraits(selectedCharacter2);
|
| 353 |
+
if (kimiVideo && kimiVideo.setMoodByPersonality) {
|
| 354 |
+
kimiVideo.setMoodByPersonality(traits2);
|
| 355 |
+
}
|
| 356 |
+
}
|
| 357 |
+
} else {
|
| 358 |
+
// No API key configured - use centralized fallback
|
| 359 |
+
response = window.KimiFallbackManager
|
| 360 |
+
? window.KimiFallbackManager.getFallbackMessage("api_missing")
|
| 361 |
+
: "To chat with me, add your API key in settings! 💕";
|
| 362 |
+
const updatedTraits = window.getCharacterTraits
|
| 363 |
+
? await window.getCharacterTraits(selectedCharacter)
|
| 364 |
+
: await kimiDB.getAllPersonalityTraits(selectedCharacter);
|
| 365 |
+
kimiVideo.respondWithEmotion("neutral", updatedTraits, updatedTraits.affection);
|
| 366 |
+
}
|
| 367 |
+
} catch (error) {
|
| 368 |
+
console.warn("LLM not available:", error.message);
|
| 369 |
+
try {
|
| 370 |
+
if (window.dispatchEvent) {
|
| 371 |
+
window.emitAppEvent && window.emitAppEvent("chat:typing:stop");
|
| 372 |
+
}
|
| 373 |
+
} catch (e) {}
|
| 374 |
+
// Still show API key message if no key is configured
|
| 375 |
+
const providerPref2 = kimiDB ? await kimiDB.getPreference("llmProvider", "openrouter") : "openrouter";
|
| 376 |
+
const apiKey = kimiDB && window.KimiProviderUtils ? await window.KimiProviderUtils.getApiKey(kimiDB, providerPref2) : null;
|
| 377 |
+
if (!apiKey || apiKey.trim() === "") {
|
| 378 |
+
response = window.KimiFallbackManager
|
| 379 |
+
? window.KimiFallbackManager.getFallbackMessage("api_missing")
|
| 380 |
+
: "To chat with me, add your API key in settings! 💕";
|
| 381 |
+
} else {
|
| 382 |
+
response = await getBasicResponse(reaction);
|
| 383 |
+
}
|
| 384 |
+
const updatedTraits = window.getCharacterTraits
|
| 385 |
+
? await window.getCharacterTraits(selectedCharacter)
|
| 386 |
+
: await kimiDB.getAllPersonalityTraits(selectedCharacter);
|
| 387 |
+
|
| 388 |
+
// Use centralized video handling to avoid duplication
|
| 389 |
+
await handleVideoResponseWithFallback(sanitizedText, response, updatedTraits, selectedCharacter);
|
| 390 |
+
}
|
| 391 |
+
} else {
|
| 392 |
+
// System not ready - check if it's because of missing API key
|
| 393 |
+
const providerPref3 = kimiDB ? await kimiDB.getPreference("llmProvider", "openrouter") : "openrouter";
|
| 394 |
+
const apiKey = kimiDB && window.KimiProviderUtils ? await window.KimiProviderUtils.getApiKey(kimiDB, providerPref3) : null;
|
| 395 |
+
if (!apiKey || apiKey.trim() === "") {
|
| 396 |
+
response = window.KimiFallbackManager
|
| 397 |
+
? window.KimiFallbackManager.getFallbackMessage("api_missing")
|
| 398 |
+
: "To chat with me, add your API key in settings! 💕";
|
| 399 |
+
} else {
|
| 400 |
+
response = await getBasicResponse(reaction);
|
| 401 |
+
}
|
| 402 |
+
const updatedTraits = window.getCharacterTraits
|
| 403 |
+
? await window.getCharacterTraits(selectedCharacter)
|
| 404 |
+
: await kimiDB.getAllPersonalityTraits(selectedCharacter);
|
| 405 |
+
|
| 406 |
+
// Use centralized video handling to avoid duplication
|
| 407 |
+
await handleVideoResponseWithFallback(sanitizedText, null, updatedTraits, selectedCharacter);
|
| 408 |
+
}
|
| 409 |
+
|
| 410 |
+
// Use token usage collected by LLM manager if available
|
| 411 |
+
let tokenInfo = null;
|
| 412 |
+
if (window._lastKimiTokenUsage) {
|
| 413 |
+
tokenInfo = window._lastKimiTokenUsage;
|
| 414 |
+
window._lastKimiTokenUsage = null; // consume once
|
| 415 |
+
} else if (window.KimiTokenUtils) {
|
| 416 |
+
// Fallback approximate (no system prompt included)
|
| 417 |
+
try {
|
| 418 |
+
const est = window.KimiTokenUtils.estimate;
|
| 419 |
+
tokenInfo = { tokensIn: est(sanitizedText), tokensOut: est(response) };
|
| 420 |
+
} catch {}
|
| 421 |
+
}
|
| 422 |
+
await kimiMemory.saveConversation(sanitizedText, response, tokenInfo);
|
| 423 |
+
if (typeof updateStats === "function") {
|
| 424 |
+
updateStats();
|
| 425 |
+
}
|
| 426 |
+
|
| 427 |
+
// Extract memories automatically from conversation if system is enabled
|
| 428 |
+
if (window.kimiMemorySystem && window.kimiMemorySystem.memoryEnabled) {
|
| 429 |
+
try {
|
| 430 |
+
const extractedMemories = await window.kimiMemorySystem.extractMemoryFromText(sanitizedText, response);
|
| 431 |
+
if (extractedMemories && extractedMemories.length > 0) {
|
| 432 |
+
// Update memory stats in UI
|
| 433 |
+
if (window.kimiMemoryUI && window.kimiMemoryUI.isInitialized) {
|
| 434 |
+
await window.kimiMemoryUI.updateMemoryStats();
|
| 435 |
+
// Show subtle notification for extracted memories
|
| 436 |
+
window.kimiMemoryUI.showFeedback(
|
| 437 |
+
`💭 ${extractedMemories.length} new ${extractedMemories.length === 1 ? "memory" : "memories"} learned`,
|
| 438 |
+
"info"
|
| 439 |
+
);
|
| 440 |
+
}
|
| 441 |
+
}
|
| 442 |
+
} catch (error) {
|
| 443 |
+
console.warn("Memory extraction error:", error);
|
| 444 |
+
}
|
| 445 |
+
}
|
| 446 |
+
|
| 447 |
+
return response;
|
| 448 |
+
} catch (error) {
|
| 449 |
+
console.error("Error in analyzeAndReact:", error);
|
| 450 |
+
|
| 451 |
+
// Use centralized fallback response
|
| 452 |
+
const fallbackResponse = window.KimiFallbackManager
|
| 453 |
+
? window.KimiFallbackManager.getFallbackMessage("technical_error")
|
| 454 |
+
: "I'm sorry, I encountered an issue processing your message. Please try again.";
|
| 455 |
+
|
| 456 |
+
try {
|
| 457 |
+
// Attempt to save the error for debugging while still providing user feedback
|
| 458 |
+
if (kimiMemory && kimiMemory.saveConversation) {
|
| 459 |
+
await kimiMemory.saveConversation(text || "Error", fallbackResponse);
|
| 460 |
+
}
|
| 461 |
+
} catch (saveError) {
|
| 462 |
+
console.error("Failed to save error conversation:", saveError);
|
| 463 |
+
}
|
| 464 |
+
|
| 465 |
+
return fallbackResponse;
|
| 466 |
+
}
|
| 467 |
+
}
|
| 468 |
+
|
| 469 |
+
function addMessageToChat(sender, text, conversationId = null) {
|
| 470 |
+
const chatMessages = document.getElementById("chat-messages");
|
| 471 |
+
// Allow empty text for streaming (we'll update it progressively)
|
| 472 |
+
if (text === undefined || text === null) return;
|
| 473 |
+
|
| 474 |
+
const messageDiv = document.createElement("div");
|
| 475 |
+
messageDiv.className = `message ${sender}`;
|
| 476 |
+
|
| 477 |
+
const time = new Date().toLocaleTimeString("en-US", {
|
| 478 |
+
hour: "2-digit",
|
| 479 |
+
minute: "2-digit"
|
| 480 |
+
});
|
| 481 |
+
|
| 482 |
+
const messageTimeDiv = document.createElement("div");
|
| 483 |
+
messageTimeDiv.className = "message-time";
|
| 484 |
+
messageTimeDiv.style.display = "flex";
|
| 485 |
+
messageTimeDiv.style.justifyContent = "space-between";
|
| 486 |
+
messageTimeDiv.style.alignItems = "center";
|
| 487 |
+
|
| 488 |
+
const timeSpan = document.createElement("span");
|
| 489 |
+
timeSpan.textContent = time;
|
| 490 |
+
timeSpan.style.flex = "1";
|
| 491 |
+
|
| 492 |
+
const deleteBtn = document.createElement("button");
|
| 493 |
+
deleteBtn.className = "delete-message-btn";
|
| 494 |
+
const icon = document.createElement("i");
|
| 495 |
+
icon.className = "fas fa-trash";
|
| 496 |
+
deleteBtn.appendChild(icon);
|
| 497 |
+
deleteBtn.style.background = "none";
|
| 498 |
+
deleteBtn.style.border = "none";
|
| 499 |
+
deleteBtn.style.cursor = "pointer";
|
| 500 |
+
deleteBtn.style.color = "#aaa";
|
| 501 |
+
deleteBtn.style.fontSize = "1em";
|
| 502 |
+
deleteBtn.style.marginLeft = "8px";
|
| 503 |
+
deleteBtn.setAttribute("aria-label", "Delete message");
|
| 504 |
+
deleteBtn.addEventListener("click", async function (e) {
|
| 505 |
+
e.stopPropagation();
|
| 506 |
+
messageDiv.remove();
|
| 507 |
+
if (conversationId && window.kimiDB && window.kimiDB.deleteSingleMessage) {
|
| 508 |
+
await window.kimiDB.deleteSingleMessage(conversationId, sender);
|
| 509 |
+
}
|
| 510 |
+
});
|
| 511 |
+
|
| 512 |
+
messageTimeDiv.appendChild(timeSpan);
|
| 513 |
+
messageTimeDiv.appendChild(deleteBtn);
|
| 514 |
+
|
| 515 |
+
const textDiv = document.createElement("div");
|
| 516 |
+
// Use formatted text with HTML support (secure formatting)
|
| 517 |
+
if (text && window.KimiValidationUtils && window.KimiValidationUtils.formatChatText) {
|
| 518 |
+
textDiv.innerHTML = window.KimiValidationUtils.formatChatText(text);
|
| 519 |
+
} else {
|
| 520 |
+
textDiv.textContent = text || ""; // Fallback to plain text
|
| 521 |
+
}
|
| 522 |
+
|
| 523 |
+
messageDiv.appendChild(textDiv);
|
| 524 |
+
messageDiv.appendChild(messageTimeDiv);
|
| 525 |
+
|
| 526 |
+
chatMessages.appendChild(messageDiv);
|
| 527 |
+
chatMessages.scrollTop = chatMessages.scrollHeight;
|
| 528 |
+
|
| 529 |
+
// Return an object that allows updating the message content for streaming
|
| 530 |
+
return {
|
| 531 |
+
updateText: newText => {
|
| 532 |
+
// Use formatted text for streaming updates too
|
| 533 |
+
if (newText && window.KimiValidationUtils && window.KimiValidationUtils.formatChatText) {
|
| 534 |
+
textDiv.innerHTML = window.KimiValidationUtils.formatChatText(newText);
|
| 535 |
+
} else {
|
| 536 |
+
textDiv.textContent = newText;
|
| 537 |
+
}
|
| 538 |
+
// Throttle scrolling to prevent visual stuttering during streaming
|
| 539 |
+
if (!textDiv._scrollTimeout) {
|
| 540 |
+
textDiv._scrollTimeout = setTimeout(() => {
|
| 541 |
+
chatMessages.scrollTop = chatMessages.scrollHeight;
|
| 542 |
+
textDiv._scrollTimeout = null;
|
| 543 |
+
}, 50); // Throttle to 20 FPS max
|
| 544 |
+
}
|
| 545 |
+
},
|
| 546 |
+
element: messageDiv,
|
| 547 |
+
textElement: textDiv
|
| 548 |
+
};
|
| 549 |
+
}
|
| 550 |
+
|
| 551 |
+
async function loadChatHistory() {
|
| 552 |
+
const kimiDB = window.kimiDB;
|
| 553 |
+
const kimiMemory = window.kimiMemory;
|
| 554 |
+
const chatMessages = document.getElementById("chat-messages");
|
| 555 |
+
|
| 556 |
+
while (chatMessages.firstChild) {
|
| 557 |
+
chatMessages.removeChild(chatMessages.firstChild);
|
| 558 |
+
}
|
| 559 |
+
|
| 560 |
+
if (kimiDB) {
|
| 561 |
+
try {
|
| 562 |
+
const recent = await kimiDB.getRecentConversations(10);
|
| 563 |
+
|
| 564 |
+
if (recent.length === 0) {
|
| 565 |
+
const greeting = await kimiMemory.getGreeting();
|
| 566 |
+
addMessageToChat("kimi", greeting);
|
| 567 |
+
} else {
|
| 568 |
+
recent.forEach(conv => {
|
| 569 |
+
addMessageToChat("user", conv.user, conv.id);
|
| 570 |
+
addMessageToChat("kimi", conv.kimi, conv.id);
|
| 571 |
+
});
|
| 572 |
+
}
|
| 573 |
+
} catch (error) {
|
| 574 |
+
console.error("Error while loading history:", error);
|
| 575 |
+
const greeting = await kimiMemory.getGreeting();
|
| 576 |
+
addMessageToChat("kimi", greeting);
|
| 577 |
+
}
|
| 578 |
+
} else {
|
| 579 |
+
const greeting = await kimiMemory.getGreeting();
|
| 580 |
+
addMessageToChat("kimi", greeting);
|
| 581 |
+
}
|
| 582 |
+
}
|
| 583 |
+
|
| 584 |
+
async function loadSettingsData() {
|
| 585 |
+
const kimiDB = window.kimiDB;
|
| 586 |
+
const kimiLLM = window.kimiLLM;
|
| 587 |
+
if (!kimiDB) return;
|
| 588 |
+
try {
|
| 589 |
+
// Batch load preferences for better performance
|
| 590 |
+
const preferenceKeys = [
|
| 591 |
+
"voiceRate",
|
| 592 |
+
"voicePitch",
|
| 593 |
+
"voiceVolume",
|
| 594 |
+
"selectedLanguage",
|
| 595 |
+
"providerApiKey",
|
| 596 |
+
"llmProvider",
|
| 597 |
+
"llmModelId",
|
| 598 |
+
"selectedCharacter",
|
| 599 |
+
"llmTemperature",
|
| 600 |
+
"llmMaxTokens",
|
| 601 |
+
"llmTopP",
|
| 602 |
+
"llmFrequencyPenalty",
|
| 603 |
+
"llmPresencePenalty",
|
| 604 |
+
"enableStreaming"
|
| 605 |
+
];
|
| 606 |
+
const preferences = await kimiDB.getPreferencesBatch(preferenceKeys);
|
| 607 |
+
|
| 608 |
+
// Set default values for missing preferences
|
| 609 |
+
const voiceRate = preferences.voiceRate !== undefined ? preferences.voiceRate : 1.1;
|
| 610 |
+
const voicePitch = preferences.voicePitch !== undefined ? preferences.voicePitch : 1.1;
|
| 611 |
+
const voiceVolume = preferences.voiceVolume !== undefined ? preferences.voiceVolume : 0.8;
|
| 612 |
+
const selectedLanguage = preferences.selectedLanguage || "en";
|
| 613 |
+
// Normalize legacy formats to primary subtag (e.g., 'en-US' -> 'en')
|
| 614 |
+
const normSelectedLanguage = (function (raw) {
|
| 615 |
+
if (!raw) return "en";
|
| 616 |
+
let r = String(raw).toLowerCase();
|
| 617 |
+
if (r.includes(":")) r = r.split(":").pop();
|
| 618 |
+
r = r.replace("_", "-");
|
| 619 |
+
return r.includes("-") ? r.split("-")[0] : r;
|
| 620 |
+
})(selectedLanguage);
|
| 621 |
+
const apiKey = preferences.providerApiKey || "";
|
| 622 |
+
const provider = preferences.llmProvider || "openrouter";
|
| 623 |
+
// Resolve baseUrl based on provider-specific stored preferences to avoid cross-provider leaks
|
| 624 |
+
const placeholders = window.KimiProviderPlaceholders || {};
|
| 625 |
+
let baseUrl;
|
| 626 |
+
if (provider === "openai-compatible" || provider === "ollama") {
|
| 627 |
+
const key = `llmBaseUrl_${provider}`;
|
| 628 |
+
try {
|
| 629 |
+
baseUrl = await kimiDB.getPreference(key, provider === "openai-compatible" ? "" : placeholders[provider]);
|
| 630 |
+
} catch (e) {
|
| 631 |
+
baseUrl = provider === "openai-compatible" ? "" : placeholders[provider];
|
| 632 |
+
}
|
| 633 |
+
} else {
|
| 634 |
+
baseUrl = placeholders[provider] || placeholders.openai;
|
| 635 |
+
}
|
| 636 |
+
const modelId = preferences.llmModelId || (window.kimiLLM ? window.kimiLLM.currentModel : "");
|
| 637 |
+
const selectedCharacter = preferences.selectedCharacter || "kimi";
|
| 638 |
+
const llmTemperature = preferences.llmTemperature !== undefined ? preferences.llmTemperature : 0.9;
|
| 639 |
+
const llmMaxTokens = preferences.llmMaxTokens !== undefined ? preferences.llmMaxTokens : 400;
|
| 640 |
+
const llmTopP = preferences.llmTopP !== undefined ? preferences.llmTopP : 0.9;
|
| 641 |
+
const llmFrequencyPenalty = preferences.llmFrequencyPenalty !== undefined ? preferences.llmFrequencyPenalty : 0.9;
|
| 642 |
+
const llmPresencePenalty = preferences.llmPresencePenalty !== undefined ? preferences.llmPresencePenalty : 0.8;
|
| 643 |
+
const enableStreaming = preferences.enableStreaming !== undefined ? preferences.enableStreaming : true;
|
| 644 |
+
|
| 645 |
+
// Update UI with voice settings
|
| 646 |
+
const languageSelect = document.getElementById("language-selection");
|
| 647 |
+
if (languageSelect) languageSelect.value = normSelectedLanguage;
|
| 648 |
+
updateSlider("voice-rate", voiceRate);
|
| 649 |
+
updateSlider("voice-pitch", voicePitch);
|
| 650 |
+
updateSlider("voice-volume", voiceVolume);
|
| 651 |
+
|
| 652 |
+
// Update LLM settings
|
| 653 |
+
updateSlider("llm-temperature", llmTemperature);
|
| 654 |
+
updateSlider("llm-max-tokens", llmMaxTokens);
|
| 655 |
+
updateSlider("llm-top-p", llmTopP);
|
| 656 |
+
updateSlider("llm-frequency-penalty", llmFrequencyPenalty);
|
| 657 |
+
updateSlider("llm-presence-penalty", llmPresencePenalty);
|
| 658 |
+
|
| 659 |
+
// Update streaming toggle
|
| 660 |
+
const streamingToggle = document.getElementById("enable-streaming");
|
| 661 |
+
if (streamingToggle) {
|
| 662 |
+
if (enableStreaming) {
|
| 663 |
+
streamingToggle.classList.add("active");
|
| 664 |
+
} else {
|
| 665 |
+
streamingToggle.classList.remove("active");
|
| 666 |
+
}
|
| 667 |
+
streamingToggle.setAttribute("aria-checked", String(enableStreaming));
|
| 668 |
+
}
|
| 669 |
+
|
| 670 |
+
// Batch load personality traits
|
| 671 |
+
const traitNames = ["affection", "playfulness", "intelligence", "empathy", "humor", "romance"];
|
| 672 |
+
const personality = await kimiDB.getPersonalityTraitsBatch(traitNames, selectedCharacter);
|
| 673 |
+
const defaults = [65, 55, 70, 75, 60, 50];
|
| 674 |
+
|
| 675 |
+
traitNames.forEach((trait, index) => {
|
| 676 |
+
const value = typeof personality[trait] === "number" ? personality[trait] : defaults[index];
|
| 677 |
+
updateSlider(`trait-${trait}`, value);
|
| 678 |
+
|
| 679 |
+
// Update memory cache for affection
|
| 680 |
+
if (trait === "affection" && window.kimiMemory) {
|
| 681 |
+
window.kimiMemory.affectionTrait = value;
|
| 682 |
+
}
|
| 683 |
+
});
|
| 684 |
+
|
| 685 |
+
// Sync personality traits to ensure consistency
|
| 686 |
+
await syncPersonalityTraits(selectedCharacter);
|
| 687 |
+
|
| 688 |
+
await updateStats();
|
| 689 |
+
|
| 690 |
+
// Update API key input
|
| 691 |
+
const apiKeyInput = document.getElementById("provider-api-key");
|
| 692 |
+
if (apiKeyInput) {
|
| 693 |
+
// Get the API key for the current provider
|
| 694 |
+
let providerKey = "";
|
| 695 |
+
if (window.KimiProviderUtils) {
|
| 696 |
+
providerKey = await window.KimiProviderUtils.getApiKey(kimiDB, provider);
|
| 697 |
+
} else {
|
| 698 |
+
providerKey = apiKey; // fallback to old method
|
| 699 |
+
}
|
| 700 |
+
apiKeyInput.value = providerKey || "";
|
| 701 |
+
}
|
| 702 |
+
const providerSelect = document.getElementById("llm-provider");
|
| 703 |
+
if (providerSelect) providerSelect.value = provider;
|
| 704 |
+
const baseUrlInput = document.getElementById("llm-base-url");
|
| 705 |
+
if (baseUrlInput) {
|
| 706 |
+
// Determine whether base URL should be editable for this provider
|
| 707 |
+
const isModifiable = provider === "openai-compatible" || provider === "ollama";
|
| 708 |
+
// Provider-specific defaults/placeholders
|
| 709 |
+
const placeholders = {
|
| 710 |
+
openrouter: "https://openrouter.ai/api/v1/chat/completions",
|
| 711 |
+
openai: "https://api.openai.com/v1/chat/completions",
|
| 712 |
+
groq: "https://api.groq.com/openai/v1/chat/completions",
|
| 713 |
+
together: "https://api.together.xyz/v1/chat/completions",
|
| 714 |
+
deepseek: "https://api.deepseek.com/chat/completions",
|
| 715 |
+
"openai-compatible": "",
|
| 716 |
+
ollama: "http://localhost:11434/api/chat"
|
| 717 |
+
};
|
| 718 |
+
const placeholder = placeholders[provider] || placeholders.openai;
|
| 719 |
+
baseUrlInput.placeholder = provider === "openai-compatible" ? "" : placeholder;
|
| 720 |
+
|
| 721 |
+
if (isModifiable) {
|
| 722 |
+
// Show stored baseUrl for modifiable providers (could be empty)
|
| 723 |
+
baseUrlInput.value = baseUrl || "";
|
| 724 |
+
baseUrlInput.disabled = false;
|
| 725 |
+
baseUrlInput.style.opacity = "1";
|
| 726 |
+
} else {
|
| 727 |
+
// For fixed providers show the provider URL as value and make input readonly
|
| 728 |
+
baseUrlInput.value = placeholder;
|
| 729 |
+
baseUrlInput.disabled = true;
|
| 730 |
+
baseUrlInput.style.opacity = "0.6";
|
| 731 |
+
}
|
| 732 |
+
}
|
| 733 |
+
const modelIdInput = document.getElementById("llm-model-id");
|
| 734 |
+
if (modelIdInput) {
|
| 735 |
+
if (provider === "openrouter") {
|
| 736 |
+
modelIdInput.value = modelId;
|
| 737 |
+
} else {
|
| 738 |
+
modelIdInput.value = ""; // only placeholder for non-OpenRouter providers
|
| 739 |
+
}
|
| 740 |
+
}
|
| 741 |
+
// For non-OpenRouter providers we keep placeholder per provider; the value is already set above.
|
| 742 |
+
const apiKeyLabel = document.getElementById("api-key-label");
|
| 743 |
+
if (apiKeyLabel) {
|
| 744 |
+
apiKeyLabel.textContent = window.KimiProviderUtils ? window.KimiProviderUtils.getLabelForProvider(provider) : "API Key";
|
| 745 |
+
}
|
| 746 |
+
|
| 747 |
+
loadAvailableModels();
|
| 748 |
+
} catch (error) {
|
| 749 |
+
console.error("Error while loading settings:", error);
|
| 750 |
+
}
|
| 751 |
+
}
|
| 752 |
+
|
| 753 |
+
function updateSlider(id, value) {
|
| 754 |
+
const slider = document.getElementById(id);
|
| 755 |
+
const valueSpan = document.getElementById(`${id}-value`);
|
| 756 |
+
if (slider && valueSpan) {
|
| 757 |
+
slider.value = value;
|
| 758 |
+
valueSpan.textContent = value;
|
| 759 |
+
}
|
| 760 |
+
}
|
| 761 |
+
|
| 762 |
+
async function updatePersonalitySliders(characterKey) {
|
| 763 |
+
const kimiDB = window.kimiDB;
|
| 764 |
+
if (!kimiDB) return;
|
| 765 |
+
|
| 766 |
+
try {
|
| 767 |
+
// Get current traits from database for this character
|
| 768 |
+
const savedTraits = window.getCharacterTraits ? await window.getCharacterTraits(characterKey) : await kimiDB.getAllPersonalityTraits(characterKey);
|
| 769 |
+
|
| 770 |
+
// Get default traits from KIMI_CHARACTERS constants
|
| 771 |
+
const characterDefaults = window.KIMI_CHARACTERS[characterKey]?.traits || {};
|
| 772 |
+
|
| 773 |
+
// Get unified defaults
|
| 774 |
+
const unifiedDefaults = window.kimiEmotionSystem?.TRAIT_DEFAULTS || {
|
| 775 |
+
affection: 55,
|
| 776 |
+
playfulness: 55,
|
| 777 |
+
intelligence: 70,
|
| 778 |
+
empathy: 75,
|
| 779 |
+
humor: 60,
|
| 780 |
+
romance: 50
|
| 781 |
+
};
|
| 782 |
+
|
| 783 |
+
// Use saved traits if they exist, otherwise fall back to character defaults, then unified defaults
|
| 784 |
+
const traits = {
|
| 785 |
+
affection: savedTraits.affection ?? characterDefaults.affection ?? unifiedDefaults.affection,
|
| 786 |
+
playfulness: savedTraits.playfulness ?? characterDefaults.playfulness ?? unifiedDefaults.playfulness,
|
| 787 |
+
intelligence: savedTraits.intelligence ?? characterDefaults.intelligence ?? unifiedDefaults.intelligence,
|
| 788 |
+
empathy: savedTraits.empathy ?? characterDefaults.empathy ?? unifiedDefaults.empathy,
|
| 789 |
+
humor: savedTraits.humor ?? characterDefaults.humor ?? unifiedDefaults.humor,
|
| 790 |
+
romance: savedTraits.romance ?? characterDefaults.romance ?? unifiedDefaults.romance
|
| 791 |
+
};
|
| 792 |
+
|
| 793 |
+
// Check if sliders exist before updating them
|
| 794 |
+
const sliderUpdates = [
|
| 795 |
+
{ id: "trait-affection", value: traits.affection },
|
| 796 |
+
{ id: "trait-playfulness", value: traits.playfulness },
|
| 797 |
+
{ id: "trait-intelligence", value: traits.intelligence },
|
| 798 |
+
{ id: "trait-empathy", value: traits.empathy },
|
| 799 |
+
{ id: "trait-humor", value: traits.humor },
|
| 800 |
+
{ id: "trait-romance", value: traits.romance }
|
| 801 |
+
];
|
| 802 |
+
|
| 803 |
+
for (const update of sliderUpdates) {
|
| 804 |
+
const slider = document.getElementById(update.id);
|
| 805 |
+
if (slider) {
|
| 806 |
+
updateSlider(update.id, update.value);
|
| 807 |
+
}
|
| 808 |
+
}
|
| 809 |
+
} catch (error) {
|
| 810 |
+
console.error("Error updating personality sliders:", error);
|
| 811 |
+
}
|
| 812 |
+
}
|
| 813 |
+
|
| 814 |
+
async function updateStats() {
|
| 815 |
+
const kimiDB = window.kimiDB;
|
| 816 |
+
if (!kimiDB) return;
|
| 817 |
+
const character = await kimiDB.getSelectedCharacter();
|
| 818 |
+
// Retrieve token usage (fallback to 0)
|
| 819 |
+
const tokensIn = await kimiDB.getPreference(`totalTokensIn_${character}`, 0);
|
| 820 |
+
const tokensOut = await kimiDB.getPreference(`totalTokensOut_${character}`, 0);
|
| 821 |
+
const charDefAff = (window.KIMI_CHARACTERS && window.KIMI_CHARACTERS[character]?.traits?.affection) || null;
|
| 822 |
+
const genericAff = (window.getTraitDefaults && window.getTraitDefaults().affection) || 55;
|
| 823 |
+
const defaultAff = typeof charDefAff === "number" ? charDefAff : genericAff;
|
| 824 |
+
const affectionTrait = await kimiDB.getPersonalityTrait("affection", defaultAff, character);
|
| 825 |
+
const conversations = await kimiDB.getAllConversations(character);
|
| 826 |
+
let firstInteraction = await kimiDB.getPreference(`firstInteraction_${character}`);
|
| 827 |
+
if (!firstInteraction && conversations.length > 0) {
|
| 828 |
+
firstInteraction = conversations[0].timestamp;
|
| 829 |
+
await kimiDB.setPreference(`firstInteraction_${character}`, firstInteraction);
|
| 830 |
+
}
|
| 831 |
+
const tokensEl = document.getElementById("tokens-usage");
|
| 832 |
+
const favorabilityEl = document.getElementById("current-favorability");
|
| 833 |
+
const conversationsEl = document.getElementById("conversations-count");
|
| 834 |
+
const daysEl = document.getElementById("days-together");
|
| 835 |
+
if (tokensEl) tokensEl.textContent = `${tokensIn} / ${tokensOut}`;
|
| 836 |
+
if (favorabilityEl) {
|
| 837 |
+
const v = Number(affectionTrait) || 0;
|
| 838 |
+
favorabilityEl.textContent = `${Math.max(0, Math.min(100, v)).toFixed(2)}%`;
|
| 839 |
+
}
|
| 840 |
+
if (conversationsEl) conversationsEl.textContent = conversations.length;
|
| 841 |
+
if (firstInteraction && daysEl) {
|
| 842 |
+
const days = Math.floor((new Date() - new Date(firstInteraction)) / (1000 * 60 * 60 * 24));
|
| 843 |
+
daysEl.textContent = days;
|
| 844 |
+
}
|
| 845 |
+
}
|
| 846 |
+
|
| 847 |
+
function initializeAllSliders() {
|
| 848 |
+
const sliders = [
|
| 849 |
+
"voice-rate",
|
| 850 |
+
"voice-pitch",
|
| 851 |
+
"voice-volume",
|
| 852 |
+
"trait-affection",
|
| 853 |
+
"trait-playfulness",
|
| 854 |
+
"trait-intelligence",
|
| 855 |
+
"trait-empathy",
|
| 856 |
+
"trait-humor",
|
| 857 |
+
"trait-romance",
|
| 858 |
+
"llm-temperature",
|
| 859 |
+
"llm-max-tokens",
|
| 860 |
+
"llm-top-p",
|
| 861 |
+
"llm-frequency-penalty",
|
| 862 |
+
"llm-presence-penalty",
|
| 863 |
+
"interface-opacity"
|
| 864 |
+
];
|
| 865 |
+
|
| 866 |
+
sliders.forEach(sliderId => {
|
| 867 |
+
const slider = document.getElementById(sliderId);
|
| 868 |
+
const valueSpan = document.getElementById(`${sliderId}-value`);
|
| 869 |
+
if (slider && valueSpan) {
|
| 870 |
+
valueSpan.textContent = slider.value;
|
| 871 |
+
}
|
| 872 |
+
});
|
| 873 |
+
}
|
| 874 |
+
|
| 875 |
+
async function syncLLMMaxTokensSlider() {
|
| 876 |
+
const kimiDB = window.kimiDB;
|
| 877 |
+
const llmMaxTokensSlider = document.getElementById("llm-max-tokens");
|
| 878 |
+
const llmMaxTokensValue = document.getElementById("llm-max-tokens-value");
|
| 879 |
+
if (llmMaxTokensSlider && llmMaxTokensValue && kimiDB) {
|
| 880 |
+
const saved = await kimiDB.getPreference("llmMaxTokens", 400);
|
| 881 |
+
llmMaxTokensSlider.value = saved;
|
| 882 |
+
llmMaxTokensValue.textContent = saved;
|
| 883 |
+
}
|
| 884 |
+
}
|
| 885 |
+
|
| 886 |
+
async function syncLLMTemperatureSlider() {
|
| 887 |
+
const kimiDB = window.kimiDB;
|
| 888 |
+
const llmTemperatureSlider = document.getElementById("llm-temperature");
|
| 889 |
+
const llmTemperatureValue = document.getElementById("llm-temperature-value");
|
| 890 |
+
if (llmTemperatureSlider && llmTemperatureValue && kimiDB) {
|
| 891 |
+
const saved = await kimiDB.getPreference("llmTemperature", 0.8);
|
| 892 |
+
llmTemperatureSlider.value = saved;
|
| 893 |
+
llmTemperatureValue.textContent = saved;
|
| 894 |
+
}
|
| 895 |
+
}
|
| 896 |
+
|
| 897 |
+
function updateTabsScrollIndicator() {
|
| 898 |
+
const tabsContainer = document.querySelector(".settings-tabs");
|
| 899 |
+
if (!tabsContainer) return;
|
| 900 |
+
|
| 901 |
+
const isOverflowing = tabsContainer.scrollWidth > tabsContainer.clientWidth;
|
| 902 |
+
|
| 903 |
+
if (isOverflowing) {
|
| 904 |
+
tabsContainer.classList.remove("no-overflow");
|
| 905 |
+
} else {
|
| 906 |
+
tabsContainer.classList.add("no-overflow");
|
| 907 |
+
}
|
| 908 |
+
}
|
| 909 |
+
|
| 910 |
+
async function loadAvailableModels() {
|
| 911 |
+
// Prevent multiple simultaneous calls
|
| 912 |
+
if (loadAvailableModels._loading) {
|
| 913 |
+
return;
|
| 914 |
+
}
|
| 915 |
+
loadAvailableModels._loading = true;
|
| 916 |
+
|
| 917 |
+
const kimiLLM = window.kimiLLM;
|
| 918 |
+
if (!kimiLLM) {
|
| 919 |
+
console.warn("❌ KimiLLM not yet initialized for loadAvailableModels");
|
| 920 |
+
loadAvailableModels._loading = false;
|
| 921 |
+
return;
|
| 922 |
+
}
|
| 923 |
+
|
| 924 |
+
const modelsContainer = document.getElementById("models-container");
|
| 925 |
+
if (!modelsContainer) {
|
| 926 |
+
console.warn("❌ Models container not found");
|
| 927 |
+
loadAvailableModels._loading = false;
|
| 928 |
+
return;
|
| 929 |
+
}
|
| 930 |
+
|
| 931 |
+
try {
|
| 932 |
+
const stats = await kimiLLM.getModelStats();
|
| 933 |
+
|
| 934 |
+
const signature = JSON.stringify(Object.keys(stats.available || {}).sort());
|
| 935 |
+
|
| 936 |
+
if (loadAvailableModels._rendered && loadAvailableModels._signature === signature) {
|
| 937 |
+
const currentId = stats.current && stats.current.id;
|
| 938 |
+
const cards = modelsContainer.querySelectorAll(".model-card");
|
| 939 |
+
cards.forEach(card => {
|
| 940 |
+
if (card.dataset.modelId === currentId) {
|
| 941 |
+
card.classList.add("selected");
|
| 942 |
+
} else {
|
| 943 |
+
card.classList.remove("selected");
|
| 944 |
+
}
|
| 945 |
+
});
|
| 946 |
+
loadAvailableModels._loading = false;
|
| 947 |
+
return;
|
| 948 |
+
}
|
| 949 |
+
|
| 950 |
+
while (modelsContainer.firstChild) {
|
| 951 |
+
modelsContainer.removeChild(modelsContainer.firstChild);
|
| 952 |
+
}
|
| 953 |
+
|
| 954 |
+
// Check if we have available models
|
| 955 |
+
if (!stats.available || Object.keys(stats.available).length === 0) {
|
| 956 |
+
console.warn("⚠️ No models available in stats");
|
| 957 |
+
const noModelsDiv = document.createElement("div");
|
| 958 |
+
noModelsDiv.className = "no-models-message";
|
| 959 |
+
noModelsDiv.innerHTML = `
|
| 960 |
+
<p>⚠️ No models available. Please check your API key.</p>
|
| 961 |
+
`;
|
| 962 |
+
modelsContainer.appendChild(noModelsDiv);
|
| 963 |
+
loadAvailableModels._loading = false;
|
| 964 |
+
return;
|
| 965 |
+
}
|
| 966 |
+
|
| 967 |
+
// Only log once when models are loaded, not repeated calls
|
| 968 |
+
if (!loadAvailableModels._lastLoadTime || Date.now() - loadAvailableModels._lastLoadTime > 5000) {
|
| 969 |
+
if (window.KIMI_CONFIG?.DEBUG?.ENABLED) {
|
| 970 |
+
console.log(`✅ Loaded ${Object.keys(stats.available).length} LLM models`);
|
| 971 |
+
}
|
| 972 |
+
loadAvailableModels._lastLoadTime = Date.now();
|
| 973 |
+
}
|
| 974 |
+
const createCard = (id, model) => {
|
| 975 |
+
const modelDiv = document.createElement("div");
|
| 976 |
+
modelDiv.className = `model-card ${id === stats.current.id ? "selected" : ""}`;
|
| 977 |
+
modelDiv.dataset.modelId = id;
|
| 978 |
+
const searchable = [model.name || "", model.provider || "", id, (model.strengths || []).join(" ")].join(" ").toLowerCase();
|
| 979 |
+
modelDiv.dataset.search = searchable;
|
| 980 |
+
|
| 981 |
+
// Create model card elements safely
|
| 982 |
+
const modelHeader = document.createElement("div");
|
| 983 |
+
modelHeader.className = "model-header";
|
| 984 |
+
|
| 985 |
+
const modelName = document.createElement("div");
|
| 986 |
+
modelName.className = "model-name";
|
| 987 |
+
modelName.textContent = model.name;
|
| 988 |
+
|
| 989 |
+
const modelProvider = document.createElement("div");
|
| 990 |
+
modelProvider.className = "model-provider";
|
| 991 |
+
modelProvider.textContent = model.provider;
|
| 992 |
+
|
| 993 |
+
modelHeader.appendChild(modelName);
|
| 994 |
+
modelHeader.appendChild(modelProvider);
|
| 995 |
+
|
| 996 |
+
const modelDescription = document.createElement("div");
|
| 997 |
+
modelDescription.className = "model-description";
|
| 998 |
+
const rawIn = model.pricing && typeof model.pricing.input !== "undefined" ? model.pricing.input : "N/A";
|
| 999 |
+
const rawOut = model.pricing && typeof model.pricing.output !== "undefined" ? model.pricing.output : "N/A";
|
| 1000 |
+
const inNum = typeof rawIn === "number" ? rawIn : typeof rawIn === "string" ? Number(rawIn) : NaN;
|
| 1001 |
+
const outNum = typeof rawOut === "number" ? rawOut : typeof rawOut === "string" ? Number(rawOut) : NaN;
|
| 1002 |
+
const inIsNum = Number.isFinite(inNum);
|
| 1003 |
+
const outIsNum = Number.isFinite(outNum);
|
| 1004 |
+
const bothNA = !inIsNum && !outIsNum;
|
| 1005 |
+
const bothZero = inIsNum && outIsNum && inNum === 0 && outNum === 0;
|
| 1006 |
+
const isFreeName =
|
| 1007 |
+
/free/i.test(model.name || "") || /free/i.test(id || "") || (Array.isArray(model.strengths) && model.strengths.some(s => /free/i.test(s)));
|
| 1008 |
+
const fmt = n => {
|
| 1009 |
+
if (!Number.isFinite(n)) return "N/A";
|
| 1010 |
+
const roundedInt = Math.round(n);
|
| 1011 |
+
if (Math.abs(n - roundedInt) < 1e-6) return `${roundedInt}$`;
|
| 1012 |
+
return `${n.toFixed(2)}$`;
|
| 1013 |
+
};
|
| 1014 |
+
let inStr = inIsNum ? (inNum === 0 ? "Free" : fmt(inNum)) : "N/A";
|
| 1015 |
+
let outStr = outIsNum ? (outNum === 0 ? "Free" : fmt(outNum)) : "N/A";
|
| 1016 |
+
let priceText;
|
| 1017 |
+
if (bothZero || isFreeName) {
|
| 1018 |
+
priceText = "Price: Free";
|
| 1019 |
+
} else if (bothNA) {
|
| 1020 |
+
priceText = "Price: N/A";
|
| 1021 |
+
} else {
|
| 1022 |
+
priceText = `Price: ${inStr} per 1M input tokens, ${outStr} per 1M output tokens`;
|
| 1023 |
+
}
|
| 1024 |
+
modelDescription.textContent = `Context: ${model.contextWindow.toLocaleString()} tokens | ${priceText}`;
|
| 1025 |
+
|
| 1026 |
+
const modelStrengths = document.createElement("div");
|
| 1027 |
+
modelStrengths.className = "model-strengths";
|
| 1028 |
+
if (priceText === "Price: Free") {
|
| 1029 |
+
const badge = document.createElement("span");
|
| 1030 |
+
badge.className = "strength-tag";
|
| 1031 |
+
badge.textContent = "Free";
|
| 1032 |
+
modelStrengths.appendChild(badge);
|
| 1033 |
+
}
|
| 1034 |
+
model.strengths.forEach(strength => {
|
| 1035 |
+
const strengthTag = document.createElement("span");
|
| 1036 |
+
strengthTag.className = "strength-tag";
|
| 1037 |
+
strengthTag.textContent = strength;
|
| 1038 |
+
modelStrengths.appendChild(strengthTag);
|
| 1039 |
+
});
|
| 1040 |
+
|
| 1041 |
+
modelDiv.appendChild(modelHeader);
|
| 1042 |
+
modelDiv.appendChild(modelDescription);
|
| 1043 |
+
modelDiv.appendChild(modelStrengths);
|
| 1044 |
+
|
| 1045 |
+
modelDiv.addEventListener("click", async () => {
|
| 1046 |
+
try {
|
| 1047 |
+
await kimiLLM.setCurrentModel(id);
|
| 1048 |
+
document.querySelectorAll(".model-card").forEach(card => card.classList.remove("selected"));
|
| 1049 |
+
modelDiv.classList.add("selected");
|
| 1050 |
+
console.log(`🤖 Model switched to: ${model.name}`);
|
| 1051 |
+
|
| 1052 |
+
// Show brief feedback to user
|
| 1053 |
+
const feedback = document.createElement("div");
|
| 1054 |
+
feedback.textContent = `Model changed to ${model.name}`;
|
| 1055 |
+
feedback.style.cssText = `
|
| 1056 |
+
position: fixed; top: 20px; right: 20px; z-index: 10000;
|
| 1057 |
+
background: #27ae60; color: white; padding: 12px 20px;
|
| 1058 |
+
border-radius: 6px; font-size: 14px; font-weight: 500;
|
| 1059 |
+
box-shadow: 0 4px 12px rgba(0,0,0,0.2);
|
| 1060 |
+
`;
|
| 1061 |
+
document.body.appendChild(feedback);
|
| 1062 |
+
setTimeout(() => feedback.remove(), 3000);
|
| 1063 |
+
} catch (error) {
|
| 1064 |
+
console.error("Error while changing model:", error);
|
| 1065 |
+
// Show error feedback
|
| 1066 |
+
const errorFeedback = document.createElement("div");
|
| 1067 |
+
errorFeedback.textContent = `Error changing model: ${error.message}`;
|
| 1068 |
+
errorFeedback.style.cssText = `
|
| 1069 |
+
position: fixed; top: 20px; right: 20px; z-index: 10000;
|
| 1070 |
+
background: #e74c3c; color: white; padding: 12px 20px;
|
| 1071 |
+
border-radius: 6px; font-size: 14px; font-weight: 500;
|
| 1072 |
+
box-shadow: 0 4px 12px rgba(0,0,0,0.2);
|
| 1073 |
+
`;
|
| 1074 |
+
document.body.appendChild(errorFeedback);
|
| 1075 |
+
setTimeout(() => errorFeedback.remove(), 5000);
|
| 1076 |
+
}
|
| 1077 |
+
});
|
| 1078 |
+
|
| 1079 |
+
return modelDiv;
|
| 1080 |
+
};
|
| 1081 |
+
|
| 1082 |
+
const recommendedIds = window.kimiLLM && Array.isArray(window.kimiLLM.recommendedModelIds) ? window.kimiLLM.recommendedModelIds : [];
|
| 1083 |
+
|
| 1084 |
+
const recommendedEntries = recommendedIds.map(id => [id, stats.available[id]]).filter(([, model]) => !!model);
|
| 1085 |
+
|
| 1086 |
+
const otherEntries = Object.entries(stats.available)
|
| 1087 |
+
.filter(([id]) => !recommendedIds.includes(id))
|
| 1088 |
+
.sort((a, b) => (a[1].name || a[0]).localeCompare(b[1].name || b[0]));
|
| 1089 |
+
|
| 1090 |
+
const searchWrap = document.createElement("div");
|
| 1091 |
+
searchWrap.className = "models-search-container";
|
| 1092 |
+
const searchInput = document.createElement("input");
|
| 1093 |
+
searchInput.type = "text";
|
| 1094 |
+
searchInput.className = "kimi-input";
|
| 1095 |
+
searchInput.id = "models-search";
|
| 1096 |
+
// use i18n placeholder key
|
| 1097 |
+
if (window.kimiI18nManager && typeof window.kimiI18nManager.t === "function") {
|
| 1098 |
+
searchInput.setAttribute("data-i18n-placeholder", "models_search_placeholder");
|
| 1099 |
+
} else {
|
| 1100 |
+
searchInput.placeholder = "Filter models...";
|
| 1101 |
+
}
|
| 1102 |
+
searchWrap.appendChild(searchInput);
|
| 1103 |
+
modelsContainer.appendChild(searchWrap);
|
| 1104 |
+
if (typeof loadAvailableModels._searchValue === "string") {
|
| 1105 |
+
searchInput.value = loadAvailableModels._searchValue;
|
| 1106 |
+
}
|
| 1107 |
+
|
| 1108 |
+
if (recommendedEntries.length > 0) {
|
| 1109 |
+
const recSection = document.createElement("div");
|
| 1110 |
+
recSection.className = "models-section recommended-models";
|
| 1111 |
+
const title = document.createElement("div");
|
| 1112 |
+
title.className = "models-section-title";
|
| 1113 |
+
// i18n aware title
|
| 1114 |
+
if (window.setI18n) window.setI18n(title, "models_recommended_title");
|
| 1115 |
+
recSection.appendChild(title);
|
| 1116 |
+
const list = document.createElement("div");
|
| 1117 |
+
list.className = "models-list";
|
| 1118 |
+
recommendedEntries.forEach(([id, model]) => {
|
| 1119 |
+
list.appendChild(createCard(id, model));
|
| 1120 |
+
});
|
| 1121 |
+
recSection.appendChild(list);
|
| 1122 |
+
modelsContainer.appendChild(recSection);
|
| 1123 |
+
}
|
| 1124 |
+
|
| 1125 |
+
if (otherEntries.length > 0) {
|
| 1126 |
+
const allSection = document.createElement("div");
|
| 1127 |
+
allSection.className = "models-section all-models";
|
| 1128 |
+
const header = document.createElement("div");
|
| 1129 |
+
header.className = "models-section-title";
|
| 1130 |
+
const toggleBtn = document.createElement("button");
|
| 1131 |
+
toggleBtn.type = "button";
|
| 1132 |
+
toggleBtn.className = "kimi-button";
|
| 1133 |
+
toggleBtn.style.marginLeft = "8px";
|
| 1134 |
+
// toggle show/hide label via i18n when available
|
| 1135 |
+
if (window.kimiI18nManager && typeof window.kimiI18nManager.t === "function") {
|
| 1136 |
+
const currentKey = loadAvailableModels._allCollapsed === false ? "button_hide" : "button_show";
|
| 1137 |
+
if (window.setI18n) window.setI18n(toggleBtn, currentKey);
|
| 1138 |
+
toggleBtn.textContent = window.kimiI18nManager.t(currentKey);
|
| 1139 |
+
} else {
|
| 1140 |
+
toggleBtn.textContent = loadAvailableModels._allCollapsed === false ? "Hide" : "Show";
|
| 1141 |
+
}
|
| 1142 |
+
const label = document.createElement("span");
|
| 1143 |
+
if (window.setI18n) window.setI18n(label, "models_all_title");
|
| 1144 |
+
header.appendChild(label);
|
| 1145 |
+
header.appendChild(toggleBtn);
|
| 1146 |
+
const refreshBtn = document.createElement("button");
|
| 1147 |
+
refreshBtn.type = "button";
|
| 1148 |
+
refreshBtn.className = "kimi-button";
|
| 1149 |
+
refreshBtn.style.marginLeft = "8px";
|
| 1150 |
+
if (window.kimiI18nManager && typeof window.kimiI18nManager.t === "function") {
|
| 1151 |
+
if (window.setI18n) window.setI18n(refreshBtn, "button_refresh");
|
| 1152 |
+
} else {
|
| 1153 |
+
refreshBtn.textContent = "Refresh";
|
| 1154 |
+
}
|
| 1155 |
+
refreshBtn.addEventListener("click", async () => {
|
| 1156 |
+
try {
|
| 1157 |
+
refreshBtn.disabled = true;
|
| 1158 |
+
const oldText = refreshBtn.textContent;
|
| 1159 |
+
refreshBtn.textContent = "Refreshing...";
|
| 1160 |
+
if (window.kimiLLM && window.kimiLLM.refreshRemoteModels) {
|
| 1161 |
+
await window.kimiLLM.refreshRemoteModels();
|
| 1162 |
+
}
|
| 1163 |
+
loadAvailableModels._signature = null;
|
| 1164 |
+
loadAvailableModels._rendered = false;
|
| 1165 |
+
const savedSearch = searchInput.value;
|
| 1166 |
+
loadAvailableModels._searchValue = savedSearch;
|
| 1167 |
+
await loadAvailableModels();
|
| 1168 |
+
} catch (e) {
|
| 1169 |
+
console.error("Error refreshing models:", e);
|
| 1170 |
+
} finally {
|
| 1171 |
+
refreshBtn.disabled = false;
|
| 1172 |
+
refreshBtn.textContent = "Refresh";
|
| 1173 |
+
}
|
| 1174 |
+
});
|
| 1175 |
+
header.appendChild(refreshBtn);
|
| 1176 |
+
const list = document.createElement("div");
|
| 1177 |
+
list.className = "models-list";
|
| 1178 |
+
otherEntries.forEach(([id, model]) => {
|
| 1179 |
+
list.appendChild(createCard(id, model));
|
| 1180 |
+
});
|
| 1181 |
+
const collapsed = loadAvailableModels._allCollapsed !== false;
|
| 1182 |
+
list.style.display = collapsed ? "none" : "block";
|
| 1183 |
+
toggleBtn.addEventListener("click", () => {
|
| 1184 |
+
const nowCollapsed = list.style.display !== "none";
|
| 1185 |
+
list.style.display = nowCollapsed ? "none" : "block";
|
| 1186 |
+
loadAvailableModels._allCollapsed = nowCollapsed;
|
| 1187 |
+
if (window.kimiI18nManager && typeof window.kimiI18nManager.t === "function") {
|
| 1188 |
+
const key = nowCollapsed ? "button_show" : "button_hide";
|
| 1189 |
+
if (window.setI18n) window.setI18n(toggleBtn, key);
|
| 1190 |
+
toggleBtn.textContent = window.kimiI18nManager.t(key);
|
| 1191 |
+
} else {
|
| 1192 |
+
toggleBtn.textContent = nowCollapsed ? "Show" : "Hide";
|
| 1193 |
+
}
|
| 1194 |
+
});
|
| 1195 |
+
allSection.appendChild(header);
|
| 1196 |
+
allSection.appendChild(list);
|
| 1197 |
+
modelsContainer.appendChild(allSection);
|
| 1198 |
+
}
|
| 1199 |
+
|
| 1200 |
+
const applyFilter = term => {
|
| 1201 |
+
const q = (term || "").toLowerCase().trim();
|
| 1202 |
+
const cards = modelsContainer.querySelectorAll(".model-card");
|
| 1203 |
+
cards.forEach(card => {
|
| 1204 |
+
const hay = card.dataset.search || "";
|
| 1205 |
+
card.style.display = q && !hay.includes(q) ? "none" : "";
|
| 1206 |
+
});
|
| 1207 |
+
};
|
| 1208 |
+
searchInput.addEventListener("input", e => {
|
| 1209 |
+
loadAvailableModels._searchValue = e.target.value;
|
| 1210 |
+
applyFilter(e.target.value);
|
| 1211 |
+
});
|
| 1212 |
+
if (searchInput.value) {
|
| 1213 |
+
applyFilter(searchInput.value);
|
| 1214 |
+
}
|
| 1215 |
+
|
| 1216 |
+
loadAvailableModels._rendered = true;
|
| 1217 |
+
loadAvailableModels._signature = signature;
|
| 1218 |
+
} catch (error) {
|
| 1219 |
+
console.error("Error loading available models:", error);
|
| 1220 |
+
const errorDiv = document.createElement("div");
|
| 1221 |
+
errorDiv.className = "models-error-message";
|
| 1222 |
+
// Escape any content from error.message to prevent XSS when inserted into innerHTML
|
| 1223 |
+
const safeMsg =
|
| 1224 |
+
window.KimiValidationUtils && window.KimiValidationUtils.escapeHtml
|
| 1225 |
+
? window.KimiValidationUtils.escapeHtml(error.message || String(error))
|
| 1226 |
+
: String(error.message || error);
|
| 1227 |
+
errorDiv.innerHTML = `
|
| 1228 |
+
<p>❌ Error loading models: ${safeMsg}</p>
|
| 1229 |
+
`;
|
| 1230 |
+
modelsContainer.appendChild(errorDiv);
|
| 1231 |
+
} finally {
|
| 1232 |
+
loadAvailableModels._loading = false;
|
| 1233 |
+
}
|
| 1234 |
+
}
|
| 1235 |
+
|
| 1236 |
+
// Debug utilities removed for production optimization
|
| 1237 |
+
|
| 1238 |
+
async function sendMessage() {
|
| 1239 |
+
const chatInput = document.getElementById("chat-input");
|
| 1240 |
+
const waitingIndicator = document.getElementById("waiting-indicator");
|
| 1241 |
+
let message = chatInput.value;
|
| 1242 |
+
|
| 1243 |
+
// Enhanced input validation using our new validation utils
|
| 1244 |
+
const validation = window.KimiValidationUtils?.validateMessage(message);
|
| 1245 |
+
if (!validation || !validation.valid) {
|
| 1246 |
+
// Show error to user
|
| 1247 |
+
if (validation?.error) {
|
| 1248 |
+
addMessageToChat("system", `❌ ${validation.error}`);
|
| 1249 |
+
}
|
| 1250 |
+
// Use sanitized version if available
|
| 1251 |
+
if (validation?.sanitized) {
|
| 1252 |
+
chatInput.value = validation.sanitized;
|
| 1253 |
+
}
|
| 1254 |
+
return;
|
| 1255 |
+
}
|
| 1256 |
+
|
| 1257 |
+
message = validation.sanitized || message.trim();
|
| 1258 |
+
if (!message) return;
|
| 1259 |
+
|
| 1260 |
+
addMessageToChat("user", message);
|
| 1261 |
+
chatInput.value = "";
|
| 1262 |
+
if (waitingIndicator) waitingIndicator.style.display = "inline-block";
|
| 1263 |
+
|
| 1264 |
+
try {
|
| 1265 |
+
// Check if streaming is enabled (you can add a preference for this)
|
| 1266 |
+
const streamingEnabled = await window.kimiDB?.getPreference("enableStreaming", window.KIMI_CONFIG?.DEFAULTS?.ENABLE_STREAMING ?? true);
|
| 1267 |
+
|
| 1268 |
+
if (streamingEnabled && window.kimiLLM && typeof window.kimiLLM.chatStreaming === "function") {
|
| 1269 |
+
// Use streaming through analyzeAndReact
|
| 1270 |
+
let streamingResponse = "";
|
| 1271 |
+
const messageObj = addMessageToChat("kimi", ""); // Start with empty message
|
| 1272 |
+
|
| 1273 |
+
// Safety check: ensure messageObj is valid
|
| 1274 |
+
if (!messageObj || typeof messageObj.updateText !== "function") {
|
| 1275 |
+
console.error("Failed to create streaming message object, falling back to non-streaming");
|
| 1276 |
+
const response = await analyzeAndReact(message);
|
| 1277 |
+
let finalResponse = response;
|
| 1278 |
+
if (!finalResponse || typeof finalResponse !== "string" || finalResponse.trim().length < 2) {
|
| 1279 |
+
finalResponse = window.getLocalizedEmotionalResponse ? window.getLocalizedEmotionalResponse("neutral") : "I'm here for you!";
|
| 1280 |
+
}
|
| 1281 |
+
addMessageToChat("kimi", finalResponse);
|
| 1282 |
+
if (window.voiceManager && !message.startsWith("Vous:")) {
|
| 1283 |
+
window.voiceManager.speak(finalResponse);
|
| 1284 |
+
}
|
| 1285 |
+
if (waitingIndicator) waitingIndicator.style.display = "none";
|
| 1286 |
+
return;
|
| 1287 |
+
}
|
| 1288 |
+
|
| 1289 |
+
try {
|
| 1290 |
+
// Start streaming response processing
|
| 1291 |
+
if (window.KIMI_CONFIG?.DEBUG?.ENABLED) {
|
| 1292 |
+
console.log("🔄 Starting streaming response...");
|
| 1293 |
+
}
|
| 1294 |
+
let emotionDetected = false;
|
| 1295 |
+
|
| 1296 |
+
const response = await analyzeAndReact(message, true, token => {
|
| 1297 |
+
streamingResponse += token;
|
| 1298 |
+
if (messageObj && messageObj.updateText) {
|
| 1299 |
+
messageObj.updateText(streamingResponse);
|
| 1300 |
+
}
|
| 1301 |
+
// Progressive analysis disabled to prevent UI flickering during streaming
|
| 1302 |
+
// All analysis will be done after streaming completes
|
| 1303 |
+
});
|
| 1304 |
+
// Streaming completed
|
| 1305 |
+
if (window.KIMI_CONFIG?.DEBUG?.ENABLED) {
|
| 1306 |
+
console.log("✅ Streaming completed, final response length:", streamingResponse.length);
|
| 1307 |
+
}
|
| 1308 |
+
|
| 1309 |
+
// Final processing after streaming completes
|
| 1310 |
+
let finalResponse = streamingResponse || response;
|
| 1311 |
+
if (!finalResponse || finalResponse.trim().length < 2) {
|
| 1312 |
+
finalResponse = window.getLocalizedEmotionalResponse ? window.getLocalizedEmotionalResponse("neutral") : "I'm here for you!";
|
| 1313 |
+
if (messageObj && messageObj.updateText) {
|
| 1314 |
+
messageObj.updateText(finalResponse);
|
| 1315 |
+
}
|
| 1316 |
+
} else {
|
| 1317 |
+
if (messageObj && messageObj.updateText) {
|
| 1318 |
+
messageObj.updateText(finalResponse);
|
| 1319 |
+
}
|
| 1320 |
+
}
|
| 1321 |
+
|
| 1322 |
+
// Voice synthesis after streaming completes (if not started during streaming)
|
| 1323 |
+
if (window.voiceManager && !message.startsWith("Vous:") && finalResponse.length > 20) {
|
| 1324 |
+
// Check if voice synthesis should happen
|
| 1325 |
+
const shouldSpeak = await window.kimiDB?.getPreference("voiceEnabled", window.KIMI_CONFIG?.DEFAULTS?.VOICE_ENABLED ?? true);
|
| 1326 |
+
if (shouldSpeak) {
|
| 1327 |
+
window.voiceManager.speak(finalResponse);
|
| 1328 |
+
}
|
| 1329 |
+
}
|
| 1330 |
+
|
| 1331 |
+
// Final comprehensive system updates
|
| 1332 |
+
try {
|
| 1333 |
+
// Final emotion analysis if not done during streaming
|
| 1334 |
+
if (!emotionDetected && window.kimiAnalyzeEmotion) {
|
| 1335 |
+
const finalEmotion = window.kimiAnalyzeEmotion(finalResponse);
|
| 1336 |
+
if (finalEmotion && finalEmotion !== "neutral") {
|
| 1337 |
+
emotionDetected = true;
|
| 1338 |
+
}
|
| 1339 |
+
}
|
| 1340 |
+
|
| 1341 |
+
// Final personality update
|
| 1342 |
+
if (window.updatePersonalityTraitsFromEmotion && finalResponse.length > 50) {
|
| 1343 |
+
const finalEmotion = window.kimiAnalyzeEmotion ? window.kimiAnalyzeEmotion(finalResponse) : "neutral";
|
| 1344 |
+
await window.updatePersonalityTraitsFromEmotion(finalEmotion, finalResponse);
|
| 1345 |
+
}
|
| 1346 |
+
|
| 1347 |
+
// Final memory extraction
|
| 1348 |
+
if (window.kimiMemory && typeof window.kimiMemory.extractMemoriesFromConversation === "function") {
|
| 1349 |
+
await window.kimiMemory.extractMemoriesFromConversation(message, finalResponse);
|
| 1350 |
+
}
|
| 1351 |
+
|
| 1352 |
+
// Final video state adjustment
|
| 1353 |
+
if (window.kimiVideo && window.kimiDB) {
|
| 1354 |
+
const selectedCharacter = await window.kimiDB.getSelectedCharacter();
|
| 1355 |
+
const traits = window.getCharacterTraits
|
| 1356 |
+
? await window.getCharacterTraits(selectedCharacter)
|
| 1357 |
+
: await window.kimiDB.getAllPersonalityTraits(selectedCharacter);
|
| 1358 |
+
if (traits && emotionDetected) {
|
| 1359 |
+
window.kimiVideo.setMoodByPersonality(traits);
|
| 1360 |
+
}
|
| 1361 |
+
}
|
| 1362 |
+
} catch (finalError) {
|
| 1363 |
+
console.warn("Final system updates failed:", finalError);
|
| 1364 |
+
}
|
| 1365 |
+
|
| 1366 |
+
if (waitingIndicator) waitingIndicator.style.display = "none";
|
| 1367 |
+
} catch (streamingError) {
|
| 1368 |
+
console.warn("Streaming failed, falling back to non-streaming:", streamingError);
|
| 1369 |
+
// Fallback to non-streaming
|
| 1370 |
+
const response = await analyzeAndReact(message);
|
| 1371 |
+
let finalResponse = response;
|
| 1372 |
+
if (!finalResponse || typeof finalResponse !== "string" || finalResponse.trim().length < 2) {
|
| 1373 |
+
finalResponse = window.getLocalizedEmotionalResponse ? window.getLocalizedEmotionalResponse("neutral") : "I'm here for you!";
|
| 1374 |
+
}
|
| 1375 |
+
if (messageObj && messageObj.updateText) {
|
| 1376 |
+
messageObj.updateText(finalResponse);
|
| 1377 |
+
}
|
| 1378 |
+
|
| 1379 |
+
if (window.voiceManager && !message.startsWith("Vous:")) {
|
| 1380 |
+
window.voiceManager.speak(finalResponse);
|
| 1381 |
+
}
|
| 1382 |
+
if (waitingIndicator) waitingIndicator.style.display = "none";
|
| 1383 |
+
}
|
| 1384 |
+
} else {
|
| 1385 |
+
// Use non-streaming (original behavior)
|
| 1386 |
+
const response = await analyzeAndReact(message);
|
| 1387 |
+
let finalResponse = response;
|
| 1388 |
+
// If the LLM's response is empty, null, or too short, use the emotional fallback.
|
| 1389 |
+
if (!finalResponse || typeof finalResponse !== "string" || finalResponse.trim().length < 2) {
|
| 1390 |
+
finalResponse = window.getLocalizedEmotionalResponse ? window.getLocalizedEmotionalResponse("neutral") : "I'm here for you!";
|
| 1391 |
+
}
|
| 1392 |
+
setTimeout(() => {
|
| 1393 |
+
addMessageToChat("kimi", finalResponse);
|
| 1394 |
+
if (window.voiceManager && !message.startsWith("Vous:")) {
|
| 1395 |
+
window.voiceManager.speak(finalResponse);
|
| 1396 |
+
}
|
| 1397 |
+
if (waitingIndicator) waitingIndicator.style.display = "none";
|
| 1398 |
+
}, 1000);
|
| 1399 |
+
}
|
| 1400 |
+
} catch (error) {
|
| 1401 |
+
console.error("Error while generating response:", error);
|
| 1402 |
+
const i18n = window.kimiI18nManager;
|
| 1403 |
+
const fallbackResponse = i18n ? i18n.t("fallback_general_error") : "Sorry my love, I am having a little technical issue! 💕";
|
| 1404 |
+
addMessageToChat("kimi", fallbackResponse);
|
| 1405 |
+
if (window.voiceManager) {
|
| 1406 |
+
window.voiceManager.speak(fallbackResponse);
|
| 1407 |
+
}
|
| 1408 |
+
if (waitingIndicator) waitingIndicator.style.display = "none";
|
| 1409 |
+
}
|
| 1410 |
+
}
|
| 1411 |
+
|
| 1412 |
+
function setupSettingsListeners(kimiDB, kimiMemory) {
|
| 1413 |
+
const voiceRateSlider = document.getElementById("voice-rate");
|
| 1414 |
+
const voicePitchSlider = document.getElementById("voice-pitch");
|
| 1415 |
+
const voiceVolumeSlider = document.getElementById("voice-volume");
|
| 1416 |
+
const languageSelect = document.getElementById("language-selection");
|
| 1417 |
+
const voiceSelect = document.getElementById("voice-selection");
|
| 1418 |
+
// Affection restored as editable trait.
|
| 1419 |
+
const traitSliders = ["trait-affection", "trait-playfulness", "trait-intelligence", "trait-empathy", "trait-humor", "trait-romance"];
|
| 1420 |
+
const llmTemperatureSlider = document.getElementById("llm-temperature");
|
| 1421 |
+
const llmMaxTokensSlider = document.getElementById("llm-max-tokens");
|
| 1422 |
+
const llmTopPSlider = document.getElementById("llm-top-p");
|
| 1423 |
+
const llmFrequencyPenaltySlider = document.getElementById("llm-frequency-penalty");
|
| 1424 |
+
const llmPresencePenaltySlider = document.getElementById("llm-presence-penalty");
|
| 1425 |
+
const enableStreamingToggle = document.getElementById("enable-streaming");
|
| 1426 |
+
const colorThemeSelect = document.getElementById("color-theme");
|
| 1427 |
+
const interfaceOpacitySlider = document.getElementById("interface-opacity");
|
| 1428 |
+
|
| 1429 |
+
// SIMPLE FIX: Initialize _kimiListenerCleanup to prevent undefined error
|
| 1430 |
+
if (!window._kimiListenerCleanup) {
|
| 1431 |
+
window._kimiListenerCleanup = [];
|
| 1432 |
+
}
|
| 1433 |
+
|
| 1434 |
+
// Create debounced functions for better performance
|
| 1435 |
+
const debouncedVoiceRateUpdate = window.KimiPerformanceUtils?.debounce(async value => {
|
| 1436 |
+
if (kimiDB) await kimiDB.setPreference("voiceRate", parseFloat(value));
|
| 1437 |
+
if (kimiMemory && kimiMemory.preferences) {
|
| 1438 |
+
kimiMemory.preferences.voiceRate = parseFloat(value);
|
| 1439 |
+
}
|
| 1440 |
+
}, 300);
|
| 1441 |
+
|
| 1442 |
+
const debouncedVoicePitchUpdate = window.KimiPerformanceUtils?.debounce(async value => {
|
| 1443 |
+
if (kimiDB) await kimiDB.setPreference("voicePitch", parseFloat(value));
|
| 1444 |
+
if (kimiMemory && kimiMemory.preferences) {
|
| 1445 |
+
kimiMemory.preferences.voicePitch = parseFloat(value);
|
| 1446 |
+
}
|
| 1447 |
+
}, 300);
|
| 1448 |
+
|
| 1449 |
+
const debouncedVoiceVolumeUpdate = window.KimiPerformanceUtils?.debounce(async value => {
|
| 1450 |
+
if (kimiDB) await kimiDB.setPreference("voiceVolume", parseFloat(value));
|
| 1451 |
+
if (kimiMemory && kimiMemory.preferences) {
|
| 1452 |
+
kimiMemory.preferences.voiceVolume = parseFloat(value);
|
| 1453 |
+
}
|
| 1454 |
+
}, 300);
|
| 1455 |
+
|
| 1456 |
+
const debouncedLLMTempUpdate = window.KimiPerformanceUtils?.debounce(async value => {
|
| 1457 |
+
if (kimiDB) await kimiDB.setPreference("llmTemperature", parseFloat(value));
|
| 1458 |
+
if (window.kimiLLMManager) window.kimiLLMManager.temperature = parseFloat(value);
|
| 1459 |
+
}, 300);
|
| 1460 |
+
|
| 1461 |
+
const debouncedLLMTokensUpdate = window.KimiPerformanceUtils?.debounce(async value => {
|
| 1462 |
+
if (kimiDB) await kimiDB.setPreference("llmMaxTokens", parseInt(value));
|
| 1463 |
+
if (window.kimiLLMManager) window.kimiLLMManager.maxTokens = parseInt(value);
|
| 1464 |
+
}, 300);
|
| 1465 |
+
|
| 1466 |
+
const debouncedLLMTopPUpdate = window.KimiPerformanceUtils?.debounce(async value => {
|
| 1467 |
+
if (kimiDB) await kimiDB.setPreference("llmTopP", parseFloat(value));
|
| 1468 |
+
if (window.kimiLLMManager) window.kimiLLMManager.topP = parseFloat(value);
|
| 1469 |
+
}, 300);
|
| 1470 |
+
|
| 1471 |
+
const debouncedLLMFrequencyPenaltyUpdate = window.KimiPerformanceUtils?.debounce(async value => {
|
| 1472 |
+
if (kimiDB) await kimiDB.setPreference("llmFrequencyPenalty", parseFloat(value));
|
| 1473 |
+
if (window.kimiLLMManager) window.kimiLLMManager.frequencyPenalty = parseFloat(value);
|
| 1474 |
+
}, 300);
|
| 1475 |
+
|
| 1476 |
+
const debouncedLLMPresencePenaltyUpdate = window.KimiPerformanceUtils?.debounce(async value => {
|
| 1477 |
+
if (kimiDB) await kimiDB.setPreference("llmPresencePenalty", parseFloat(value));
|
| 1478 |
+
if (window.kimiLLMManager) window.kimiLLMManager.presencePenalty = parseFloat(value);
|
| 1479 |
+
}, 300);
|
| 1480 |
+
|
| 1481 |
+
const debouncedOpacityUpdate = window.KimiPerformanceUtils?.debounce(async value => {
|
| 1482 |
+
if (kimiDB) await kimiDB.setPreference("interfaceOpacity", parseFloat(value));
|
| 1483 |
+
if (window.kimiAppearanceManager && window.kimiAppearanceManager.changeInterfaceOpacity)
|
| 1484 |
+
await window.kimiAppearanceManager.changeInterfaceOpacity(parseFloat(value));
|
| 1485 |
+
}, 300);
|
| 1486 |
+
|
| 1487 |
+
if (voiceRateSlider) {
|
| 1488 |
+
const listener = e => {
|
| 1489 |
+
const validation = window.KimiValidationUtils?.validateRange(e.target.value, "voiceRate");
|
| 1490 |
+
// Preserve legitimate zero values (avoid using || which treats 0 as falsy)
|
| 1491 |
+
let value = validation && !isNaN(validation.value) ? validation.value : parseFloat(e.target.value);
|
| 1492 |
+
if (isNaN(value)) value = 1.1;
|
| 1493 |
+
|
| 1494 |
+
document.getElementById("voice-rate-value").textContent = value;
|
| 1495 |
+
e.target.value = value; // Ensure slider shows validated value
|
| 1496 |
+
debouncedVoiceRateUpdate(value);
|
| 1497 |
+
};
|
| 1498 |
+
voiceRateSlider.addEventListener("input", listener);
|
| 1499 |
+
window._kimiListenerCleanup.push(() => voiceRateSlider.removeEventListener("input", listener));
|
| 1500 |
+
}
|
| 1501 |
+
if (voicePitchSlider) {
|
| 1502 |
+
const listener = e => {
|
| 1503 |
+
const validation = window.KimiValidationUtils?.validateRange(e.target.value, "voicePitch");
|
| 1504 |
+
let value = validation && !isNaN(validation.value) ? validation.value : parseFloat(e.target.value);
|
| 1505 |
+
if (isNaN(value)) value = 1.1;
|
| 1506 |
+
|
| 1507 |
+
document.getElementById("voice-pitch-value").textContent = value;
|
| 1508 |
+
e.target.value = value;
|
| 1509 |
+
debouncedVoicePitchUpdate(value);
|
| 1510 |
+
};
|
| 1511 |
+
voicePitchSlider.addEventListener("input", listener);
|
| 1512 |
+
window._kimiListenerCleanup.push(() => voicePitchSlider.removeEventListener("input", listener));
|
| 1513 |
+
}
|
| 1514 |
+
if (voiceVolumeSlider) {
|
| 1515 |
+
const listener = e => {
|
| 1516 |
+
const validation = window.KimiValidationUtils?.validateRange(e.target.value, "voiceVolume");
|
| 1517 |
+
let value = validation && !isNaN(validation.value) ? validation.value : parseFloat(e.target.value);
|
| 1518 |
+
if (isNaN(value)) value = 0.8;
|
| 1519 |
+
|
| 1520 |
+
document.getElementById("voice-volume-value").textContent = value;
|
| 1521 |
+
e.target.value = value;
|
| 1522 |
+
debouncedVoiceVolumeUpdate(value);
|
| 1523 |
+
};
|
| 1524 |
+
voiceVolumeSlider.addEventListener("input", listener);
|
| 1525 |
+
window._kimiListenerCleanup.push(() => voiceVolumeSlider.removeEventListener("input", listener));
|
| 1526 |
+
}
|
| 1527 |
+
// Note: Language selector event listener is now handled by VoiceManager.setupLanguageSelector()
|
| 1528 |
+
// This prevents duplicate event listeners and ensures proper voice/language coordination
|
| 1529 |
+
|
| 1530 |
+
// Note: Voice selector event listener is now handled by VoiceManager.updateVoiceSelector()
|
| 1531 |
+
// This prevents duplicate event listeners and ensures proper voice preference coordination
|
| 1532 |
+
|
| 1533 |
+
// Batch personality traits optimization
|
| 1534 |
+
let personalityBatchTimeout = null;
|
| 1535 |
+
const pendingTraitChanges = {};
|
| 1536 |
+
|
| 1537 |
+
traitSliders.forEach(traitId => {
|
| 1538 |
+
const traitSlider = document.getElementById(traitId);
|
| 1539 |
+
if (traitSlider) {
|
| 1540 |
+
traitSlider.removeEventListener("input", window["_kimiTraitListener_" + traitId]);
|
| 1541 |
+
window["_kimiTraitListener_" + traitId] = async e => {
|
| 1542 |
+
const trait = traitId.replace("trait-", "");
|
| 1543 |
+
const value = parseInt(e.target.value, 10);
|
| 1544 |
+
|
| 1545 |
+
// Update UI immediately for responsive feel
|
| 1546 |
+
const valueSpan = document.getElementById(traitId + "-value");
|
| 1547 |
+
if (valueSpan) {
|
| 1548 |
+
valueSpan.textContent = value;
|
| 1549 |
+
}
|
| 1550 |
+
|
| 1551 |
+
// Store pending change for batch processing
|
| 1552 |
+
pendingTraitChanges[trait] = value;
|
| 1553 |
+
|
| 1554 |
+
// Clear existing timeout and set new one for batch save
|
| 1555 |
+
if (personalityBatchTimeout) {
|
| 1556 |
+
clearTimeout(personalityBatchTimeout);
|
| 1557 |
+
}
|
| 1558 |
+
|
| 1559 |
+
personalityBatchTimeout = setTimeout(async () => {
|
| 1560 |
+
if (kimiDB && Object.keys(pendingTraitChanges).length > 0) {
|
| 1561 |
+
try {
|
| 1562 |
+
// Use batch operation for all pending changes (affection included)
|
| 1563 |
+
await kimiDB.setPersonalityBatch(pendingTraitChanges);
|
| 1564 |
+
|
| 1565 |
+
// Side-effects handled by central 'personality:updated' listener.
|
| 1566 |
+
} catch (error) {
|
| 1567 |
+
console.error("Error batch saving personality traits:", error);
|
| 1568 |
+
}
|
| 1569 |
+
|
| 1570 |
+
// Clear pending changes
|
| 1571 |
+
Object.keys(pendingTraitChanges).forEach(key => delete pendingTraitChanges[key]);
|
| 1572 |
+
}
|
| 1573 |
+
}, 500); // Debounce for 500ms to batch multiple rapid changes
|
| 1574 |
+
};
|
| 1575 |
+
traitSlider.addEventListener("input", window["_kimiTraitListener_" + traitId]);
|
| 1576 |
+
}
|
| 1577 |
+
});
|
| 1578 |
+
if (llmTemperatureSlider) {
|
| 1579 |
+
const listener = e => {
|
| 1580 |
+
const validation = window.KimiValidationUtils?.validateRange(e.target.value, "llmTemperature");
|
| 1581 |
+
let value = validation && !isNaN(validation.value) ? validation.value : parseFloat(e.target.value);
|
| 1582 |
+
if (isNaN(value)) value = 0.9;
|
| 1583 |
+
|
| 1584 |
+
document.getElementById("llm-temperature-value").textContent = value;
|
| 1585 |
+
e.target.value = value;
|
| 1586 |
+
debouncedLLMTempUpdate(value);
|
| 1587 |
+
};
|
| 1588 |
+
llmTemperatureSlider.addEventListener("input", listener);
|
| 1589 |
+
window._kimiListenerCleanup.push(() => llmTemperatureSlider.removeEventListener("input", listener));
|
| 1590 |
+
}
|
| 1591 |
+
if (llmMaxTokensSlider) {
|
| 1592 |
+
const listener = e => {
|
| 1593 |
+
const validation = window.KimiValidationUtils?.validateRange(e.target.value, "llmMaxTokens");
|
| 1594 |
+
const value = validation?.value || parseInt(e.target.value) || 400;
|
| 1595 |
+
|
| 1596 |
+
document.getElementById("llm-max-tokens-value").textContent = value;
|
| 1597 |
+
e.target.value = value;
|
| 1598 |
+
debouncedLLMTokensUpdate(value);
|
| 1599 |
+
};
|
| 1600 |
+
llmMaxTokensSlider.addEventListener("input", listener);
|
| 1601 |
+
window._kimiListenerCleanup.push(() => llmMaxTokensSlider.removeEventListener("input", listener));
|
| 1602 |
+
}
|
| 1603 |
+
if (llmTopPSlider) {
|
| 1604 |
+
const listener = e => {
|
| 1605 |
+
const validation = window.KimiValidationUtils?.validateRange(e.target.value, "llmTopP");
|
| 1606 |
+
let value = validation && !isNaN(validation.value) ? validation.value : parseFloat(e.target.value);
|
| 1607 |
+
if (isNaN(value)) value = 0.9;
|
| 1608 |
+
|
| 1609 |
+
document.getElementById("llm-top-p-value").textContent = value;
|
| 1610 |
+
e.target.value = value;
|
| 1611 |
+
debouncedLLMTopPUpdate(value);
|
| 1612 |
+
};
|
| 1613 |
+
llmTopPSlider.addEventListener("input", listener);
|
| 1614 |
+
window._kimiListenerCleanup.push(() => llmTopPSlider.removeEventListener("input", listener));
|
| 1615 |
+
}
|
| 1616 |
+
if (llmFrequencyPenaltySlider) {
|
| 1617 |
+
const listener = e => {
|
| 1618 |
+
const validation = window.KimiValidationUtils?.validateRange(e.target.value, "llmFrequencyPenalty");
|
| 1619 |
+
let value = validation && !isNaN(validation.value) ? validation.value : parseFloat(e.target.value);
|
| 1620 |
+
if (isNaN(value)) value = 0.9;
|
| 1621 |
+
|
| 1622 |
+
document.getElementById("llm-frequency-penalty-value").textContent = value;
|
| 1623 |
+
e.target.value = value;
|
| 1624 |
+
debouncedLLMFrequencyPenaltyUpdate(value);
|
| 1625 |
+
};
|
| 1626 |
+
llmFrequencyPenaltySlider.addEventListener("input", listener);
|
| 1627 |
+
window._kimiListenerCleanup.push(() => llmFrequencyPenaltySlider.removeEventListener("input", listener));
|
| 1628 |
+
}
|
| 1629 |
+
if (llmPresencePenaltySlider) {
|
| 1630 |
+
const listener = e => {
|
| 1631 |
+
const validation = window.KimiValidationUtils?.validateRange(e.target.value, "llmPresencePenalty");
|
| 1632 |
+
let value = validation && !isNaN(validation.value) ? validation.value : parseFloat(e.target.value);
|
| 1633 |
+
if (isNaN(value)) value = 0.8;
|
| 1634 |
+
|
| 1635 |
+
document.getElementById("llm-presence-penalty-value").textContent = value;
|
| 1636 |
+
e.target.value = value;
|
| 1637 |
+
debouncedLLMPresencePenaltyUpdate(value);
|
| 1638 |
+
};
|
| 1639 |
+
llmPresencePenaltySlider.addEventListener("input", listener);
|
| 1640 |
+
window._kimiListenerCleanup.push(() => llmPresencePenaltySlider.removeEventListener("input", listener));
|
| 1641 |
+
}
|
| 1642 |
+
if (enableStreamingToggle) {
|
| 1643 |
+
const listener = async () => {
|
| 1644 |
+
try {
|
| 1645 |
+
const isEnabled = enableStreamingToggle.classList.contains("active");
|
| 1646 |
+
const newState = !isEnabled;
|
| 1647 |
+
enableStreamingToggle.classList.toggle("active", newState);
|
| 1648 |
+
enableStreamingToggle.setAttribute("aria-checked", newState ? "true" : "false");
|
| 1649 |
+
if (kimiDB) await kimiDB.setPreference("enableStreaming", newState);
|
| 1650 |
+
} catch (error) {
|
| 1651 |
+
console.error("Error toggling streaming:", error);
|
| 1652 |
+
}
|
| 1653 |
+
};
|
| 1654 |
+
enableStreamingToggle.addEventListener("click", listener);
|
| 1655 |
+
window._kimiListenerCleanup.push(() => enableStreamingToggle.removeEventListener("click", listener));
|
| 1656 |
+
}
|
| 1657 |
+
if (colorThemeSelect) {
|
| 1658 |
+
colorThemeSelect.removeEventListener("change", window._kimiColorThemeListener);
|
| 1659 |
+
window._kimiColorThemeListener = async e => {
|
| 1660 |
+
if (kimiDB) await kimiDB.setPreference("colorTheme", e.target.value);
|
| 1661 |
+
if (window.kimiAppearanceManager && window.kimiAppearanceManager.changeTheme) await window.kimiAppearanceManager.changeTheme(e.target.value);
|
| 1662 |
+
};
|
| 1663 |
+
colorThemeSelect.addEventListener("change", window._kimiColorThemeListener);
|
| 1664 |
+
}
|
| 1665 |
+
if (interfaceOpacitySlider) {
|
| 1666 |
+
const listener = e => {
|
| 1667 |
+
const validation = window.KimiValidationUtils?.validateRange(e.target.value, "interfaceOpacity");
|
| 1668 |
+
let value = validation && !isNaN(validation.value) ? validation.value : parseFloat(e.target.value);
|
| 1669 |
+
if (isNaN(value)) value = 0.8;
|
| 1670 |
+
|
| 1671 |
+
document.getElementById("interface-opacity-value").textContent = value;
|
| 1672 |
+
e.target.value = value;
|
| 1673 |
+
debouncedOpacityUpdate(value);
|
| 1674 |
+
};
|
| 1675 |
+
interfaceOpacitySlider.addEventListener("input", listener);
|
| 1676 |
+
window._kimiListenerCleanup.push(() => interfaceOpacitySlider.removeEventListener("input", listener));
|
| 1677 |
+
}
|
| 1678 |
+
// Animation toggle is handled by KimiAppearanceManager
|
| 1679 |
+
// Remove the duplicate handler to prevent conflicts
|
| 1680 |
+
// Real-time transcript toggle (shows live speech transcription and AI responses)
|
| 1681 |
+
const transcriptToggle = document.getElementById("transcript-toggle");
|
| 1682 |
+
if (transcriptToggle) {
|
| 1683 |
+
if (kimiDB && kimiDB.getPreference) {
|
| 1684 |
+
kimiDB.getPreference("showTranscript", window.KIMI_CONFIG?.DEFAULTS?.SHOW_TRANSCRIPT ?? true).then(showTranscript => {
|
| 1685 |
+
transcriptToggle.classList.toggle("active", showTranscript);
|
| 1686 |
+
transcriptToggle.setAttribute("aria-checked", showTranscript ? "true" : "false");
|
| 1687 |
+
});
|
| 1688 |
+
}
|
| 1689 |
+
const onToggle = async () => {
|
| 1690 |
+
const enabled = !transcriptToggle.classList.contains("active");
|
| 1691 |
+
transcriptToggle.classList.toggle("active", enabled);
|
| 1692 |
+
transcriptToggle.setAttribute("aria-checked", enabled ? "true" : "false");
|
| 1693 |
+
// Save transcript display preference
|
| 1694 |
+
if (kimiDB && kimiDB.setPreference) {
|
| 1695 |
+
await kimiDB.setPreference("showTranscript", enabled);
|
| 1696 |
+
}
|
| 1697 |
+
// Apply change immediately if transcript is currently visible
|
| 1698 |
+
if (window.kimiVoiceManager && window.kimiVoiceManager.updateTranscriptVisibility) {
|
| 1699 |
+
if (!enabled) {
|
| 1700 |
+
// Hide transcript immediately if disabled (uses centralized logic)
|
| 1701 |
+
await window.kimiVoiceManager.updateTranscriptVisibility(false);
|
| 1702 |
+
}
|
| 1703 |
+
// If enabled, transcript will show naturally during next voice interaction
|
| 1704 |
+
}
|
| 1705 |
+
};
|
| 1706 |
+
transcriptToggle.onclick = onToggle;
|
| 1707 |
+
transcriptToggle.onkeydown = async e => {
|
| 1708 |
+
if (e.key === " " || e.key === "Enter") {
|
| 1709 |
+
e.preventDefault();
|
| 1710 |
+
await onToggle();
|
| 1711 |
+
}
|
| 1712 |
+
};
|
| 1713 |
+
}
|
| 1714 |
+
}
|
| 1715 |
+
|
| 1716 |
+
// Exposer globalement (KimiDataManager already exposed in kimi-data-manager.js)
|
| 1717 |
+
window.updateFavorabilityLabel = updateFavorabilityLabel;
|
| 1718 |
+
window.loadCharacterSection = loadCharacterSection;
|
| 1719 |
+
window.getBasicResponse = getBasicResponse;
|
| 1720 |
+
window.analyzeAndReact = analyzeAndReact;
|
| 1721 |
+
window.addMessageToChat = addMessageToChat;
|
| 1722 |
+
window.loadChatHistory = loadChatHistory;
|
| 1723 |
+
window.loadSettingsData = loadSettingsData;
|
| 1724 |
+
window.updateSlider = updateSlider;
|
| 1725 |
+
|
| 1726 |
+
// DYNAMIC SLIDER SYNC
|
| 1727 |
+
async function refreshAllSliders() {
|
| 1728 |
+
if (!window.kimiDB) return;
|
| 1729 |
+
const prefMap = [
|
| 1730 |
+
["voice-rate", "voiceRate", "VOICE_RATE"],
|
| 1731 |
+
["voice-pitch", "voicePitch", "VOICE_PITCH"],
|
| 1732 |
+
["voice-volume", "voiceVolume", "VOICE_VOLUME"],
|
| 1733 |
+
["llm-temperature", "llmTemperature", "LLM_TEMPERATURE"],
|
| 1734 |
+
["llm-max-tokens", "llmMaxTokens", "LLM_MAX_TOKENS"],
|
| 1735 |
+
["llm-top-p", "llmTopP", "LLM_TOP_P"],
|
| 1736 |
+
["llm-frequency-penalty", "llmFrequencyPenalty", "LLM_FREQUENCY_PENALTY"],
|
| 1737 |
+
["llm-presence-penalty", "llmPresencePenalty", "LLM_PRESENCE_PENALTY"],
|
| 1738 |
+
["interface-opacity", "interfaceOpacity", "INTERFACE_OPACITY"]
|
| 1739 |
+
];
|
| 1740 |
+
for (const [sliderId, prefKey, defaultKey] of prefMap) {
|
| 1741 |
+
try {
|
| 1742 |
+
const el = document.getElementById(sliderId);
|
| 1743 |
+
if (!el) continue;
|
| 1744 |
+
const stored = await window.kimiDB.getPreference(prefKey, window.KIMI_CONFIG?.DEFAULTS?.[defaultKey]);
|
| 1745 |
+
if (typeof stored === "number" || (typeof stored === "string" && stored !== null)) {
|
| 1746 |
+
updateSlider(sliderId, stored);
|
| 1747 |
+
}
|
| 1748 |
+
} catch {}
|
| 1749 |
+
}
|
| 1750 |
+
|
| 1751 |
+
// Load streaming preference
|
| 1752 |
+
try {
|
| 1753 |
+
const enableStreamingToggle = document.getElementById("enable-streaming");
|
| 1754 |
+
if (enableStreamingToggle) {
|
| 1755 |
+
const streamingEnabled = await window.kimiDB.getPreference("enableStreaming", window.KIMI_CONFIG?.DEFAULTS?.ENABLE_STREAMING ?? true);
|
| 1756 |
+
enableStreamingToggle.classList.toggle("active", streamingEnabled);
|
| 1757 |
+
enableStreamingToggle.setAttribute("aria-checked", streamingEnabled ? "true" : "false");
|
| 1758 |
+
}
|
| 1759 |
+
} catch {}
|
| 1760 |
+
}
|
| 1761 |
+
window.refreshAllSliders = refreshAllSliders;
|
| 1762 |
+
|
| 1763 |
+
const _debouncedPrefUpdate = window.KimiPerformanceUtils
|
| 1764 |
+
? window.KimiPerformanceUtils.debounce(evt => {
|
| 1765 |
+
const key = evt.detail?.key;
|
| 1766 |
+
if (!key) return;
|
| 1767 |
+
const keyToSlider = {
|
| 1768 |
+
voiceRate: "voice-rate",
|
| 1769 |
+
voicePitch: "voice-pitch",
|
| 1770 |
+
voiceVolume: "voice-volume",
|
| 1771 |
+
llmTemperature: "llm-temperature",
|
| 1772 |
+
llmMaxTokens: "llm-max-tokens",
|
| 1773 |
+
llmTopP: "llm-top-p",
|
| 1774 |
+
llmFrequencyPenalty: "llm-frequency-penalty",
|
| 1775 |
+
llmPresencePenalty: "llm-presence-penalty",
|
| 1776 |
+
interfaceOpacity: "interface-opacity"
|
| 1777 |
+
};
|
| 1778 |
+
const sliderId = keyToSlider[key];
|
| 1779 |
+
if (sliderId && typeof evt.detail.value !== "undefined") {
|
| 1780 |
+
updateSlider(sliderId, evt.detail.value);
|
| 1781 |
+
}
|
| 1782 |
+
}, 120)
|
| 1783 |
+
: null;
|
| 1784 |
+
window.addEventListener("preferenceUpdated", evt => {
|
| 1785 |
+
if (_debouncedPrefUpdate) _debouncedPrefUpdate(evt);
|
| 1786 |
+
});
|
| 1787 |
+
window.updatePersonalitySliders = updatePersonalitySliders;
|
| 1788 |
+
window.updateStats = updateStats;
|
| 1789 |
+
window.initializeAllSliders = initializeAllSliders;
|
| 1790 |
+
window.syncLLMMaxTokensSlider = syncLLMMaxTokensSlider;
|
| 1791 |
+
window.syncLLMTemperatureSlider = syncLLMTemperatureSlider;
|
| 1792 |
+
window.updateTabsScrollIndicator = updateTabsScrollIndicator;
|
| 1793 |
+
window.loadAvailableModels = loadAvailableModels;
|
| 1794 |
+
window.sendMessage = sendMessage;
|
| 1795 |
+
window.setupSettingsListeners = setupSettingsListeners;
|
| 1796 |
+
window.syncPersonalityTraits = syncPersonalityTraits;
|
| 1797 |
+
window.validateEmotionContext = validateEmotionContext;
|
| 1798 |
+
window.ensureVideoContextConsistency = ensureVideoContextConsistency;
|
| 1799 |
+
|
| 1800 |
+
document.addEventListener("DOMContentLoaded", function () {
|
| 1801 |
+
const toggleBtn = document.getElementById("toggle-personality-traits");
|
| 1802 |
+
const cheatPanel = document.getElementById("personality-traits-panel");
|
| 1803 |
+
if (toggleBtn && cheatPanel) {
|
| 1804 |
+
toggleBtn.addEventListener("click", function () {
|
| 1805 |
+
const expanded = toggleBtn.getAttribute("aria-expanded") === "true";
|
| 1806 |
+
toggleBtn.setAttribute("aria-expanded", !expanded);
|
| 1807 |
+
cheatPanel.classList.toggle("open", !expanded);
|
| 1808 |
+
});
|
| 1809 |
+
}
|
| 1810 |
+
|
| 1811 |
+
// Refresh UI models list when the LLM model changes programmatically
|
| 1812 |
+
try {
|
| 1813 |
+
window.addEventListener("llmModelChanged", () => {
|
| 1814 |
+
if (typeof window.loadAvailableModels === "function") {
|
| 1815 |
+
window.loadAvailableModels();
|
| 1816 |
+
}
|
| 1817 |
+
});
|
| 1818 |
+
} catch (e) {}
|
| 1819 |
+
|
| 1820 |
+
// Typing indicator wiring
|
| 1821 |
+
try {
|
| 1822 |
+
// Soft tweak of API key input attributes shortly after load to reduce password manager prompts
|
| 1823 |
+
setTimeout(() => {
|
| 1824 |
+
const apiInput = document.getElementById("openrouter-api-key");
|
| 1825 |
+
if (apiInput) {
|
| 1826 |
+
apiInput.setAttribute("autocomplete", "new-password");
|
| 1827 |
+
apiInput.setAttribute("name", "openrouter_api_key");
|
| 1828 |
+
apiInput.setAttribute("data-lpignore", "true");
|
| 1829 |
+
apiInput.setAttribute("data-1p-ignore", "true");
|
| 1830 |
+
apiInput.setAttribute("data-bwignore", "true");
|
| 1831 |
+
apiInput.setAttribute("data-form-type", "other");
|
| 1832 |
+
apiInput.setAttribute("autocapitalize", "none");
|
| 1833 |
+
apiInput.setAttribute("autocorrect", "off");
|
| 1834 |
+
apiInput.setAttribute("spellcheck", "false");
|
| 1835 |
+
}
|
| 1836 |
+
}, 300);
|
| 1837 |
+
|
| 1838 |
+
window.addEventListener("chat:typing:start", () => {
|
| 1839 |
+
const waitingIndicator = document.getElementById("waiting-indicator");
|
| 1840 |
+
const globalTyping = document.getElementById("global-typing-indicator");
|
| 1841 |
+
clearTimeout(window._kimiTypingDelayTimer);
|
| 1842 |
+
window._kimiTypingDelayTimer = setTimeout(() => {
|
| 1843 |
+
if (waitingIndicator) waitingIndicator.classList.add("visible");
|
| 1844 |
+
if (globalTyping) globalTyping.classList.add("visible");
|
| 1845 |
+
}, 150);
|
| 1846 |
+
// Safety auto-hide after 10s in case stop event is blocked
|
| 1847 |
+
clearTimeout(window._kimiTypingSafetyTimer);
|
| 1848 |
+
window._kimiTypingSafetyTimer = setTimeout(() => {
|
| 1849 |
+
if (waitingIndicator) waitingIndicator.classList.remove("visible");
|
| 1850 |
+
if (globalTyping) globalTyping.classList.remove("visible");
|
| 1851 |
+
}, 10000);
|
| 1852 |
+
});
|
| 1853 |
+
window.addEventListener("chat:typing:stop", () => {
|
| 1854 |
+
const waitingIndicator = document.getElementById("waiting-indicator");
|
| 1855 |
+
const globalTyping = document.getElementById("global-typing-indicator");
|
| 1856 |
+
if (waitingIndicator) waitingIndicator.classList.remove("visible");
|
| 1857 |
+
if (globalTyping) globalTyping.classList.remove("visible");
|
| 1858 |
+
clearTimeout(window._kimiTypingSafetyTimer);
|
| 1859 |
+
clearTimeout(window._kimiTypingDelayTimer);
|
| 1860 |
+
});
|
| 1861 |
+
} catch (e) {}
|
| 1862 |
+
});
|
| 1863 |
+
|
| 1864 |
+
// Function to sync all personality traits with database and UI
|
| 1865 |
+
async function syncPersonalityTraits(characterName = null) {
|
| 1866 |
+
const kimiDB = window.kimiDB;
|
| 1867 |
+
if (!kimiDB) return;
|
| 1868 |
+
|
| 1869 |
+
const selectedCharacter = characterName || (await kimiDB.getSelectedCharacter());
|
| 1870 |
+
const traits = window.getCharacterTraits ? await window.getCharacterTraits(selectedCharacter) : await kimiDB.getAllPersonalityTraits(selectedCharacter);
|
| 1871 |
+
|
| 1872 |
+
// Build required traits prioritizing character-specific defaults (fallback to generic)
|
| 1873 |
+
const getRequiredTraits = () => {
|
| 1874 |
+
const charDefaults = (window.KIMI_CHARACTERS && window.KIMI_CHARACTERS[selectedCharacter]?.traits) || {};
|
| 1875 |
+
let generic = {};
|
| 1876 |
+
if (window.KimiEmotionSystem) {
|
| 1877 |
+
const emotionSystem = new window.KimiEmotionSystem(kimiDB);
|
| 1878 |
+
generic = emotionSystem.TRAIT_DEFAULTS;
|
| 1879 |
+
} else if (window.getTraitDefaults) {
|
| 1880 |
+
generic = window.getTraitDefaults();
|
| 1881 |
+
} else {
|
| 1882 |
+
generic = { affection: 55, playfulness: 55, intelligence: 70, empathy: 75, humor: 60, romance: 50 };
|
| 1883 |
+
}
|
| 1884 |
+
// Character defaults take precedence over generic defaults
|
| 1885 |
+
return { ...generic, ...charDefaults };
|
| 1886 |
+
};
|
| 1887 |
+
|
| 1888 |
+
const requiredTraits = getRequiredTraits();
|
| 1889 |
+
let needsUpdate = false;
|
| 1890 |
+
const updatedTraits = {};
|
| 1891 |
+
|
| 1892 |
+
for (const [trait, defaultValue] of Object.entries(requiredTraits)) {
|
| 1893 |
+
const currentValue = traits[trait];
|
| 1894 |
+
if (typeof currentValue !== "number" || currentValue < 0 || currentValue > 100) {
|
| 1895 |
+
updatedTraits[trait] = defaultValue;
|
| 1896 |
+
needsUpdate = true;
|
| 1897 |
+
} else {
|
| 1898 |
+
updatedTraits[trait] = currentValue;
|
| 1899 |
+
}
|
| 1900 |
+
}
|
| 1901 |
+
|
| 1902 |
+
// Update database if needed
|
| 1903 |
+
if (needsUpdate) {
|
| 1904 |
+
await kimiDB.setPersonalityBatch(updatedTraits, selectedCharacter);
|
| 1905 |
+
}
|
| 1906 |
+
|
| 1907 |
+
// Update UI sliders
|
| 1908 |
+
for (const [trait, value] of Object.entries(updatedTraits)) {
|
| 1909 |
+
updateSlider(`trait-${trait}`, value);
|
| 1910 |
+
}
|
| 1911 |
+
|
| 1912 |
+
// Update memory cache
|
| 1913 |
+
if (window.kimiMemory && updatedTraits.affection) {
|
| 1914 |
+
window.kimiMemory.affectionTrait = updatedTraits.affection;
|
| 1915 |
+
if (window.updateGlobalPersonalityUI) {
|
| 1916 |
+
window.updateGlobalPersonalityUI();
|
| 1917 |
+
} else if (window.kimiMemory.updateFavorabilityBar) {
|
| 1918 |
+
// Fallback (will internally compute average now)
|
| 1919 |
+
window.kimiMemory.updateFavorabilityBar();
|
| 1920 |
+
}
|
| 1921 |
+
}
|
| 1922 |
+
|
| 1923 |
+
// Video/voice updates are centralized in the 'personality:updated' listener.
|
| 1924 |
+
|
| 1925 |
+
return updatedTraits;
|
| 1926 |
+
}
|
| 1927 |
+
|
| 1928 |
+
// Simplified validation using centralized emotion system
|
| 1929 |
+
function validateEmotionContext(emotion) {
|
| 1930 |
+
return window.kimiEmotionSystem?.validateEmotion(emotion) || "neutral";
|
| 1931 |
+
}
|
| 1932 |
+
|
| 1933 |
+
// Simplified video context consistency check using centralized system
|
| 1934 |
+
async function ensureVideoContextConsistency() {
|
| 1935 |
+
if (!window.kimiVideo || !window.kimiDB) return;
|
| 1936 |
+
|
| 1937 |
+
const selectedCharacter = await window.kimiDB.getSelectedCharacter();
|
| 1938 |
+
const traits = window.getCharacterTraits
|
| 1939 |
+
? await window.getCharacterTraits(selectedCharacter)
|
| 1940 |
+
: await window.kimiDB.getAllPersonalityTraits(selectedCharacter);
|
| 1941 |
+
|
| 1942 |
+
// Validate current video context using centralized validation
|
| 1943 |
+
const currentInfo = window.kimiVideo.getCurrentVideoInfo();
|
| 1944 |
+
const validatedEmotion = validateEmotionContext(currentInfo.emotion);
|
| 1945 |
+
|
| 1946 |
+
if (validatedEmotion !== currentInfo.emotion) {
|
| 1947 |
+
window.kimiVideo.switchToContext("neutral", "neutral", null, traits, traits.affection);
|
| 1948 |
+
}
|
| 1949 |
+
}
|
kimi-js/kimi-plugin-manager.js
ADDED
|
@@ -0,0 +1,260 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
class KimiPluginManager {
|
| 2 |
+
constructor() {
|
| 3 |
+
this.plugins = [];
|
| 4 |
+
this.pluginsRoot = "kimi-plugins/";
|
| 5 |
+
}
|
| 6 |
+
|
| 7 |
+
// Common security validation for plugin file paths
|
| 8 |
+
isValidPluginPath(path) {
|
| 9 |
+
return (
|
| 10 |
+
typeof path === "string" &&
|
| 11 |
+
/^[-a-zA-Z0-9_\/.]+$/.test(path) &&
|
| 12 |
+
!path.startsWith("/") &&
|
| 13 |
+
!path.includes("..") &&
|
| 14 |
+
!/^https?:\/\//i.test(path) &&
|
| 15 |
+
path.startsWith("kimi-plugins/")
|
| 16 |
+
);
|
| 17 |
+
}
|
| 18 |
+
async loadPlugins() {
|
| 19 |
+
const pluginDirs = await this.getPluginDirs();
|
| 20 |
+
this.plugins = [];
|
| 21 |
+
let pluginThemeActive = false;
|
| 22 |
+
for (const dir of pluginDirs) {
|
| 23 |
+
try {
|
| 24 |
+
const manifest = await fetch(this.pluginsRoot + dir + "/manifest.json").then(r => r.json());
|
| 25 |
+
manifest._dir = dir;
|
| 26 |
+
manifest.enabled = this.isPluginEnabled(dir, manifest.enabled);
|
| 27 |
+
|
| 28 |
+
// Basic manifest validation and path sanitization (deny external or absolute URLs)
|
| 29 |
+
const validTypes = new Set(["theme", "voice", "behavior"]);
|
| 30 |
+
const isSafePath = p =>
|
| 31 |
+
typeof p === "string" &&
|
| 32 |
+
/^[-a-zA-Z0-9_\/.]+$/.test(p) &&
|
| 33 |
+
!p.startsWith("/") &&
|
| 34 |
+
!p.includes("..") &&
|
| 35 |
+
!/^https?:\/\//i.test(p);
|
| 36 |
+
|
| 37 |
+
if (!manifest.name || !manifest.type || !validTypes.has(manifest.type)) {
|
| 38 |
+
console.warn(`Invalid plugin manifest in ${dir}: missing name or invalid type`);
|
| 39 |
+
continue;
|
| 40 |
+
}
|
| 41 |
+
if (manifest.style && !isSafePath(manifest.style)) {
|
| 42 |
+
console.warn(`Blocked unsafe style path in ${dir}: ${manifest.style}`);
|
| 43 |
+
delete manifest.style;
|
| 44 |
+
}
|
| 45 |
+
if (manifest.main && !isSafePath(manifest.main)) {
|
| 46 |
+
console.warn(`Blocked unsafe main path in ${dir}: ${manifest.main}`);
|
| 47 |
+
delete manifest.main;
|
| 48 |
+
}
|
| 49 |
+
|
| 50 |
+
this.plugins.push(manifest);
|
| 51 |
+
|
| 52 |
+
if (manifest.enabled && manifest.style) {
|
| 53 |
+
this.loadCSS(this.pluginsRoot + dir + "/" + manifest.style);
|
| 54 |
+
}
|
| 55 |
+
if (manifest.enabled && manifest.main) {
|
| 56 |
+
this.loadJS(this.pluginsRoot + dir + "/" + manifest.main);
|
| 57 |
+
}
|
| 58 |
+
if (manifest.enabled && manifest.type === "theme" && dir === "sample-theme") {
|
| 59 |
+
pluginThemeActive = true;
|
| 60 |
+
}
|
| 61 |
+
} catch (e) {
|
| 62 |
+
console.warn("Failed loading plugin:", dir, e);
|
| 63 |
+
}
|
| 64 |
+
}
|
| 65 |
+
if (pluginThemeActive) {
|
| 66 |
+
document.documentElement.setAttribute("data-theme", "plugin-sample-theme");
|
| 67 |
+
} else {
|
| 68 |
+
// Restore previous or default theme depuis Dexie
|
| 69 |
+
if (window.kimiDB && window.kimiDB.getPreference) {
|
| 70 |
+
const userTheme = await window.kimiDB.getPreference("colorTheme", "dark");
|
| 71 |
+
document.documentElement.setAttribute("data-theme", userTheme);
|
| 72 |
+
} else {
|
| 73 |
+
document.documentElement.setAttribute("data-theme", "dark");
|
| 74 |
+
}
|
| 75 |
+
}
|
| 76 |
+
this.renderPluginList();
|
| 77 |
+
}
|
| 78 |
+
async getPluginDirs() {
|
| 79 |
+
return ["sample-theme", "sample-voice", "sample-behavior"];
|
| 80 |
+
}
|
| 81 |
+
loadCSS(href) {
|
| 82 |
+
if (!window.KimiDOMUtils) {
|
| 83 |
+
console.error("KimiDOMUtils not available for loadCSS");
|
| 84 |
+
return;
|
| 85 |
+
}
|
| 86 |
+
if (!window.KimiDOMUtils.get('link[href="' + href + '"]')) {
|
| 87 |
+
if (!this.isValidPluginPath(href)) {
|
| 88 |
+
console.error(`Blocked unsafe CSS path: ${href}`);
|
| 89 |
+
return;
|
| 90 |
+
}
|
| 91 |
+
|
| 92 |
+
const link = document.createElement("link");
|
| 93 |
+
link.rel = "stylesheet";
|
| 94 |
+
link.type = "text/css";
|
| 95 |
+
link.href = href;
|
| 96 |
+
|
| 97 |
+
link.onerror = function () {
|
| 98 |
+
console.error(`Failed to load plugin CSS: ${href}`);
|
| 99 |
+
};
|
| 100 |
+
|
| 101 |
+
document.head.appendChild(link);
|
| 102 |
+
}
|
| 103 |
+
}
|
| 104 |
+
loadJS(src) {
|
| 105 |
+
if (!window.KimiDOMUtils) {
|
| 106 |
+
console.error("KimiDOMUtils not available for loadJS");
|
| 107 |
+
return;
|
| 108 |
+
}
|
| 109 |
+
if (!window.KimiDOMUtils.get('script[src="' + src + '"]')) {
|
| 110 |
+
if (!this.isValidPluginPath(src)) {
|
| 111 |
+
console.error(`Blocked unsafe script path: ${src}`);
|
| 112 |
+
return;
|
| 113 |
+
}
|
| 114 |
+
|
| 115 |
+
const script = document.createElement("script");
|
| 116 |
+
script.src = src;
|
| 117 |
+
script.type = "text/javascript";
|
| 118 |
+
|
| 119 |
+
script.onerror = function () {
|
| 120 |
+
console.error(`Failed to load plugin script: ${src}`);
|
| 121 |
+
};
|
| 122 |
+
|
| 123 |
+
if (window.CSP_NONCE) {
|
| 124 |
+
script.nonce = window.CSP_NONCE;
|
| 125 |
+
}
|
| 126 |
+
|
| 127 |
+
document.body.appendChild(script);
|
| 128 |
+
}
|
| 129 |
+
}
|
| 130 |
+
renderPluginList() {
|
| 131 |
+
if (!window.KimiDOMUtils) {
|
| 132 |
+
console.error("KimiDOMUtils not available");
|
| 133 |
+
return;
|
| 134 |
+
}
|
| 135 |
+
const container = window.KimiDOMUtils.get("#plugin-list");
|
| 136 |
+
if (!container) return;
|
| 137 |
+
while (container.firstChild) {
|
| 138 |
+
container.removeChild(container.firstChild);
|
| 139 |
+
}
|
| 140 |
+
for (const plugin of this.plugins) {
|
| 141 |
+
const div = document.createElement("div");
|
| 142 |
+
div.className = "plugin-card";
|
| 143 |
+
// Left: info
|
| 144 |
+
const info = document.createElement("div");
|
| 145 |
+
info.className = "plugin-info";
|
| 146 |
+
const title = document.createElement("div");
|
| 147 |
+
title.className = "plugin-title";
|
| 148 |
+
title.textContent = plugin.name;
|
| 149 |
+
const type = document.createElement("span");
|
| 150 |
+
type.className = "plugin-type";
|
| 151 |
+
type.textContent = plugin.type;
|
| 152 |
+
title.appendChild(type);
|
| 153 |
+
const desc = document.createElement("div");
|
| 154 |
+
desc.className = "plugin-desc";
|
| 155 |
+
desc.textContent = plugin.description;
|
| 156 |
+
const author = document.createElement("div");
|
| 157 |
+
author.className = "plugin-author";
|
| 158 |
+
author.textContent = plugin.author;
|
| 159 |
+
info.appendChild(title);
|
| 160 |
+
info.appendChild(desc);
|
| 161 |
+
info.appendChild(author);
|
| 162 |
+
div.appendChild(info);
|
| 163 |
+
// Center: badges/swatch
|
| 164 |
+
const centerCol = document.createElement("div");
|
| 165 |
+
centerCol.className = "plugin-card-center";
|
| 166 |
+
const typeBadge = document.createElement("span");
|
| 167 |
+
typeBadge.className = "plugin-type-badge";
|
| 168 |
+
typeBadge.textContent =
|
| 169 |
+
plugin.type === "theme" ? "Theme" : plugin.type.charAt(0).toUpperCase() + plugin.type.slice(1);
|
| 170 |
+
centerCol.appendChild(typeBadge);
|
| 171 |
+
if (plugin.type === "theme") {
|
| 172 |
+
const swatch = document.createElement("div");
|
| 173 |
+
swatch.className = "plugin-theme-swatch";
|
| 174 |
+
|
| 175 |
+
// Create color spans safely
|
| 176 |
+
const colors = ["#3b82f6", "#a5b4fc", "#6366f1"];
|
| 177 |
+
colors.forEach(color => {
|
| 178 |
+
const span = document.createElement("span");
|
| 179 |
+
span.style.background = color;
|
| 180 |
+
swatch.appendChild(span);
|
| 181 |
+
});
|
| 182 |
+
centerCol.appendChild(swatch);
|
| 183 |
+
if (plugin.enabled) {
|
| 184 |
+
const activeBadge = document.createElement("span");
|
| 185 |
+
activeBadge.className = "plugin-active-badge";
|
| 186 |
+
activeBadge.textContent = "Active Theme";
|
| 187 |
+
centerCol.appendChild(activeBadge);
|
| 188 |
+
}
|
| 189 |
+
}
|
| 190 |
+
div.appendChild(centerCol);
|
| 191 |
+
// Right: switch
|
| 192 |
+
const rightCol = document.createElement("div");
|
| 193 |
+
rightCol.className = "plugin-card-switch";
|
| 194 |
+
const switchLabel = document.createElement("label");
|
| 195 |
+
switchLabel.className = "toggle-switch";
|
| 196 |
+
const input = document.createElement("input");
|
| 197 |
+
input.type = "checkbox";
|
| 198 |
+
input.checked = !!plugin.enabled;
|
| 199 |
+
input.style.display = "none";
|
| 200 |
+
input.addEventListener("change", () => {
|
| 201 |
+
plugin.enabled = input.checked;
|
| 202 |
+
this.savePluginState(plugin._dir, plugin.enabled);
|
| 203 |
+
this.loadPlugins();
|
| 204 |
+
if (input.checked) {
|
| 205 |
+
switchLabel.classList.add("active");
|
| 206 |
+
} else {
|
| 207 |
+
switchLabel.classList.remove("active");
|
| 208 |
+
}
|
| 209 |
+
});
|
| 210 |
+
const slider = document.createElement("span");
|
| 211 |
+
slider.className = "slider";
|
| 212 |
+
switchLabel.appendChild(input);
|
| 213 |
+
switchLabel.appendChild(slider);
|
| 214 |
+
if (input.checked) switchLabel.classList.add("active");
|
| 215 |
+
rightCol.appendChild(switchLabel);
|
| 216 |
+
div.appendChild(rightCol);
|
| 217 |
+
container.appendChild(div);
|
| 218 |
+
}
|
| 219 |
+
}
|
| 220 |
+
savePluginState(dir, enabled) {
|
| 221 |
+
const key = "kimi-plugin-enabled-" + dir;
|
| 222 |
+
localStorage.setItem(key, enabled ? "1" : "0");
|
| 223 |
+
}
|
| 224 |
+
isPluginEnabled(dir, defaultValue) {
|
| 225 |
+
const key = "kimi-plugin-enabled-" + dir;
|
| 226 |
+
const val = localStorage.getItem(key);
|
| 227 |
+
if (val === null) return defaultValue;
|
| 228 |
+
return val === "1";
|
| 229 |
+
}
|
| 230 |
+
}
|
| 231 |
+
|
| 232 |
+
window.KimiPluginManager = new KimiPluginManager();
|
| 233 |
+
|
| 234 |
+
document.addEventListener("DOMContentLoaded", () => {
|
| 235 |
+
if (window.KimiPluginManager) window.KimiPluginManager.loadPlugins();
|
| 236 |
+
const refreshBtn = document.getElementById("refresh-plugins");
|
| 237 |
+
if (refreshBtn) {
|
| 238 |
+
refreshBtn.onclick = async () => {
|
| 239 |
+
const originalText = refreshBtn.innerHTML;
|
| 240 |
+
refreshBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Refreshing...';
|
| 241 |
+
refreshBtn.disabled = true;
|
| 242 |
+
|
| 243 |
+
try {
|
| 244 |
+
await window.KimiPluginManager.loadPlugins();
|
| 245 |
+
refreshBtn.innerHTML = '<i class="fas fa-check"></i> Refreshed!';
|
| 246 |
+
setTimeout(() => {
|
| 247 |
+
refreshBtn.innerHTML = originalText;
|
| 248 |
+
refreshBtn.disabled = false;
|
| 249 |
+
}, 1500);
|
| 250 |
+
} catch (error) {
|
| 251 |
+
console.error("Error refreshing plugins:", error);
|
| 252 |
+
refreshBtn.innerHTML = '<i class="fas fa-exclamation-triangle"></i> Error';
|
| 253 |
+
setTimeout(() => {
|
| 254 |
+
refreshBtn.innerHTML = originalText;
|
| 255 |
+
refreshBtn.disabled = false;
|
| 256 |
+
}, 2000);
|
| 257 |
+
}
|
| 258 |
+
};
|
| 259 |
+
}
|
| 260 |
+
});
|
kimi-js/kimi-script.js
ADDED
|
@@ -0,0 +1,1250 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import KimiDatabase from "./kimi-database.js";
|
| 2 |
+
import KimiLLMManager from "./kimi-llm-manager.js";
|
| 3 |
+
import KimiEmotionSystem from "./kimi-emotion-system.js";
|
| 4 |
+
import KimiMemorySystem from "./kimi-memory-system.js";
|
| 5 |
+
import KimiMemory from "./kimi-memory.js";
|
| 6 |
+
import { KimiDataManager } from "./kimi-data-manager.js"; // Explicit import (phasing out window.KimiDataManager)
|
| 7 |
+
import { initializeVideoController } from "./kimi-video-controller.js"; // Unified video control
|
| 8 |
+
|
| 9 |
+
document.addEventListener("DOMContentLoaded", async function () {
|
| 10 |
+
const DEFAULT_SYSTEM_PROMPT = window.DEFAULT_SYSTEM_PROMPT;
|
| 11 |
+
|
| 12 |
+
let kimiDB = null;
|
| 13 |
+
let kimiLLM = null;
|
| 14 |
+
let isSystemReady = false;
|
| 15 |
+
|
| 16 |
+
// Global debug flag for sync/log verbosity (default: false)
|
| 17 |
+
if (typeof window.KIMI_DEBUG_SYNC === "undefined") {
|
| 18 |
+
window.KIMI_DEBUG_SYNC = false;
|
| 19 |
+
}
|
| 20 |
+
|
| 21 |
+
const kimiInit = new KimiInitManager();
|
| 22 |
+
let kimiVideo = null;
|
| 23 |
+
|
| 24 |
+
// Error manager is already initialized in kimi-error-manager.js
|
| 25 |
+
|
| 26 |
+
try {
|
| 27 |
+
kimiDB = new KimiDatabase();
|
| 28 |
+
await kimiDB.init();
|
| 29 |
+
|
| 30 |
+
// Expose globally as soon as available
|
| 31 |
+
window.kimiDB = kimiDB;
|
| 32 |
+
|
| 33 |
+
const selectedCharacter = await kimiDB.getPreference("selectedCharacter", "kimi");
|
| 34 |
+
const favorabilityLabel = window.KimiDOMUtils.get("#favorability-label");
|
| 35 |
+
if (favorabilityLabel && window.KIMI_CHARACTERS && window.KIMI_CHARACTERS[selectedCharacter]) {
|
| 36 |
+
favorabilityLabel.removeAttribute("for");
|
| 37 |
+
if (window.setI18n) window.setI18n(favorabilityLabel, "personality_average_of");
|
| 38 |
+
favorabilityLabel.setAttribute("data-i18n-params", JSON.stringify({ name: window.KIMI_CHARACTERS[selectedCharacter].name }));
|
| 39 |
+
favorabilityLabel.textContent = `💖 Personality average of ${window.KIMI_CHARACTERS[selectedCharacter].name}`;
|
| 40 |
+
}
|
| 41 |
+
const chatHeaderName = window.KimiDOMUtils.get(".chat-header span[data-i18n]");
|
| 42 |
+
if (chatHeaderName && window.KIMI_CHARACTERS && window.KIMI_CHARACTERS[selectedCharacter]) {
|
| 43 |
+
if (window.setI18n) window.setI18n(chatHeaderName, `chat_with_${selectedCharacter}`);
|
| 44 |
+
}
|
| 45 |
+
kimiLLM = new KimiLLMManager(kimiDB);
|
| 46 |
+
window.kimiLLM = kimiLLM;
|
| 47 |
+
await kimiLLM.init();
|
| 48 |
+
|
| 49 |
+
// Initialize unified emotion system
|
| 50 |
+
window.kimiEmotionSystem = new KimiEmotionSystem(kimiDB);
|
| 51 |
+
|
| 52 |
+
// Initialize the new memory system
|
| 53 |
+
window.kimiMemorySystem = new KimiMemorySystem(kimiDB);
|
| 54 |
+
await window.kimiMemorySystem.init();
|
| 55 |
+
|
| 56 |
+
// Initialize legacy memory for favorability
|
| 57 |
+
const kimiMemory = new KimiMemory(kimiDB);
|
| 58 |
+
await kimiMemory.init();
|
| 59 |
+
window.kimiMemory = kimiMemory;
|
| 60 |
+
|
| 61 |
+
// Expose globally (already set before init)
|
| 62 |
+
|
| 63 |
+
// Load available models now that LLM is ready
|
| 64 |
+
if (window.loadAvailableModels) {
|
| 65 |
+
setTimeout(() => window.loadAvailableModels(), 500);
|
| 66 |
+
}
|
| 67 |
+
|
| 68 |
+
isSystemReady = true;
|
| 69 |
+
window.isSystemReady = true;
|
| 70 |
+
// API config UI will be initialized after ApiUi is defined
|
| 71 |
+
// Update global personality average UI once initial traits are loaded
|
| 72 |
+
if (window.updateGlobalPersonalityUI) {
|
| 73 |
+
try {
|
| 74 |
+
await window.updateGlobalPersonalityUI(selectedCharacter);
|
| 75 |
+
} catch {}
|
| 76 |
+
}
|
| 77 |
+
if (window.refreshAllSliders) {
|
| 78 |
+
try {
|
| 79 |
+
await window.refreshAllSliders();
|
| 80 |
+
} catch {}
|
| 81 |
+
}
|
| 82 |
+
} catch (error) {
|
| 83 |
+
console.error("Initialization error:", error);
|
| 84 |
+
// Log initialization error to error manager
|
| 85 |
+
if (window.kimiErrorManager) {
|
| 86 |
+
window.kimiErrorManager.logInitError("KimiApp", error, {
|
| 87 |
+
selectedCharacter: selectedCharacter,
|
| 88 |
+
stage: "main_initialization"
|
| 89 |
+
});
|
| 90 |
+
}
|
| 91 |
+
}
|
| 92 |
+
// Centralized helpers for API config UI
|
| 93 |
+
const ApiUi = {
|
| 94 |
+
presenceDot: () => document.getElementById("api-key-presence"),
|
| 95 |
+
presenceDotTest: () => document.getElementById("api-key-presence-test"),
|
| 96 |
+
apiKeyInput: () => document.getElementById("provider-api-key"),
|
| 97 |
+
toggleBtn: () => document.getElementById("toggle-api-key"),
|
| 98 |
+
providerSelect: () => document.getElementById("llm-provider"),
|
| 99 |
+
baseUrlInput: () => document.getElementById("llm-base-url"),
|
| 100 |
+
modelIdInput: () => document.getElementById("llm-model-id"),
|
| 101 |
+
savedBadge: () => document.getElementById("api-key-saved"),
|
| 102 |
+
statusSpan: () => document.getElementById("api-status"),
|
| 103 |
+
testBtn: () => document.getElementById("test-api"),
|
| 104 |
+
// Saved key indicator (left dot)
|
| 105 |
+
setPresence(color) {
|
| 106 |
+
const dot = this.presenceDot();
|
| 107 |
+
if (dot) dot.style.backgroundColor = color;
|
| 108 |
+
},
|
| 109 |
+
// Test result indicator (right dot)
|
| 110 |
+
setTestPresence(color) {
|
| 111 |
+
const dot2 = this.presenceDotTest();
|
| 112 |
+
if (dot2) dot2.style.backgroundColor = color;
|
| 113 |
+
},
|
| 114 |
+
clearStatus() {
|
| 115 |
+
const s = this.statusSpan();
|
| 116 |
+
if (s) {
|
| 117 |
+
s.textContent = "";
|
| 118 |
+
s.style.color = "";
|
| 119 |
+
}
|
| 120 |
+
},
|
| 121 |
+
setTestEnabled(enabled) {
|
| 122 |
+
const b = this.testBtn();
|
| 123 |
+
if (b) b.disabled = !enabled;
|
| 124 |
+
}
|
| 125 |
+
};
|
| 126 |
+
|
| 127 |
+
// Initial presence state based on current input value
|
| 128 |
+
{
|
| 129 |
+
const currentVal = (ApiUi.apiKeyInput() || {}).value || "";
|
| 130 |
+
const colorInit = currentVal && currentVal.length > 0 ? "#4caf50" : "#9e9e9e";
|
| 131 |
+
ApiUi.setPresence(colorInit);
|
| 132 |
+
// On load, test status is unknown
|
| 133 |
+
ApiUi.setTestPresence("#9e9e9e");
|
| 134 |
+
}
|
| 135 |
+
|
| 136 |
+
// Initialize API config UI from saved preferences
|
| 137 |
+
async function initializeApiConfigUI() {
|
| 138 |
+
try {
|
| 139 |
+
if (!window.kimiDB) return;
|
| 140 |
+
const provider = await window.kimiDB.getPreference("llmProvider", "openrouter");
|
| 141 |
+
// Resolve base URL preference: prefer provider-specific stored key for modifiable
|
| 142 |
+
let baseUrl;
|
| 143 |
+
const shared = window.KimiProviderPlaceholders || {};
|
| 144 |
+
if (provider === "openai-compatible" || provider === "ollama") {
|
| 145 |
+
const key = `llmBaseUrl_${provider}`;
|
| 146 |
+
const defaultForProvider = provider === "openai-compatible" ? "" : shared[provider];
|
| 147 |
+
baseUrl = await window.kimiDB.getPreference(key, defaultForProvider);
|
| 148 |
+
} else {
|
| 149 |
+
baseUrl = shared[provider] || shared.openai;
|
| 150 |
+
}
|
| 151 |
+
const modelId = await window.kimiDB.getPreference("llmModelId", window.kimiLLM ? window.kimiLLM.currentModel : "model-id");
|
| 152 |
+
const providerSelect = ApiUi.providerSelect();
|
| 153 |
+
if (providerSelect) providerSelect.value = provider;
|
| 154 |
+
const baseUrlInput = ApiUi.baseUrlInput();
|
| 155 |
+
const modelIdInput = ApiUi.modelIdInput();
|
| 156 |
+
const apiKeyInput = ApiUi.apiKeyInput();
|
| 157 |
+
|
| 158 |
+
// Set base URL based on modifiability
|
| 159 |
+
if (baseUrlInput) {
|
| 160 |
+
const isModifiable = isUrlModifiable(provider);
|
| 161 |
+
baseUrlInput.value = baseUrl || "";
|
| 162 |
+
baseUrlInput.disabled = !isModifiable;
|
| 163 |
+
baseUrlInput.style.opacity = isModifiable ? "1" : "0.6";
|
| 164 |
+
}
|
| 165 |
+
// Only prefill model for OpenRouter, others should show placeholder only
|
| 166 |
+
if (modelIdInput) {
|
| 167 |
+
if (provider === "openrouter") {
|
| 168 |
+
if (!modelIdInput.value) modelIdInput.value = modelId;
|
| 169 |
+
} else {
|
| 170 |
+
modelIdInput.value = "";
|
| 171 |
+
}
|
| 172 |
+
}
|
| 173 |
+
// Load the provider-specific key
|
| 174 |
+
const keyPref = window.KimiProviderUtils ? window.KimiProviderUtils.getKeyPrefForProvider(provider) : "providerApiKey";
|
| 175 |
+
const storedKey = await window.kimiDB.getPreference(keyPref, "");
|
| 176 |
+
if (apiKeyInput) apiKeyInput.value = storedKey || "";
|
| 177 |
+
ApiUi.setPresence(storedKey ? "#4caf50" : "#9e9e9e");
|
| 178 |
+
ApiUi.setTestPresence("#9e9e9e");
|
| 179 |
+
const savedBadge = ApiUi.savedBadge();
|
| 180 |
+
if (savedBadge) {
|
| 181 |
+
// Show only if provider requires a key and key exists
|
| 182 |
+
if (provider !== "ollama" && storedKey) {
|
| 183 |
+
savedBadge.style.display = "inline";
|
| 184 |
+
} else {
|
| 185 |
+
savedBadge.style.display = "none";
|
| 186 |
+
}
|
| 187 |
+
}
|
| 188 |
+
ApiUi.clearStatus();
|
| 189 |
+
// Enable/disable Test button according to validation (Ollama does not require API key)
|
| 190 |
+
const valid = !!(window.KIMI_VALIDATORS && window.KIMI_VALIDATORS.validateApiKey(storedKey || ""));
|
| 191 |
+
ApiUi.setTestEnabled(provider === "ollama" ? true : valid);
|
| 192 |
+
// Update dynamic label and placeholders using change handler logic
|
| 193 |
+
if (providerSelect && typeof providerSelect.dispatchEvent === "function") {
|
| 194 |
+
const ev = new Event("change");
|
| 195 |
+
providerSelect.dispatchEvent(ev);
|
| 196 |
+
}
|
| 197 |
+
} catch (e) {
|
| 198 |
+
console.warn("Failed to initialize API config UI:", e);
|
| 199 |
+
// Log UI initialization error
|
| 200 |
+
if (window.kimiErrorManager) {
|
| 201 |
+
window.kimiErrorManager.logUIError("ApiConfigUI", e, {
|
| 202 |
+
stage: "api_config_initialization"
|
| 203 |
+
});
|
| 204 |
+
}
|
| 205 |
+
}
|
| 206 |
+
}
|
| 207 |
+
// Hydrate API config UI from DB after ApiUi is defined and function declared
|
| 208 |
+
initializeApiConfigUI();
|
| 209 |
+
|
| 210 |
+
// Listen for model changes and update the UI only for OpenRouter
|
| 211 |
+
window.addEventListener("llmModelChanged", function (event) {
|
| 212 |
+
const modelIdInput = ApiUi.modelIdInput();
|
| 213 |
+
const providerSelect = ApiUi.providerSelect();
|
| 214 |
+
|
| 215 |
+
// Only update the field if current provider is OpenRouter
|
| 216 |
+
if (modelIdInput && event.detail && event.detail.id && providerSelect && providerSelect.value === "openrouter") {
|
| 217 |
+
modelIdInput.value = event.detail.id;
|
| 218 |
+
}
|
| 219 |
+
});
|
| 220 |
+
|
| 221 |
+
// Helper function to check if URL is modifiable for current provider
|
| 222 |
+
function isUrlModifiable(provider) {
|
| 223 |
+
return provider === "openai-compatible" || provider === "ollama";
|
| 224 |
+
}
|
| 225 |
+
|
| 226 |
+
const providerSelectEl = document.getElementById("llm-provider");
|
| 227 |
+
if (providerSelectEl) {
|
| 228 |
+
providerSelectEl.addEventListener("change", async function (e) {
|
| 229 |
+
const provider = e.target.value;
|
| 230 |
+
const baseUrlInput = ApiUi.baseUrlInput();
|
| 231 |
+
const modelIdInput = ApiUi.modelIdInput();
|
| 232 |
+
const apiKeyInput = ApiUi.apiKeyInput();
|
| 233 |
+
|
| 234 |
+
const shared = window.KimiProviderPlaceholders || {};
|
| 235 |
+
const p = {
|
| 236 |
+
url: shared[provider] || "",
|
| 237 |
+
keyPh: provider === "ollama" ? "" : "your-key",
|
| 238 |
+
model: provider === "openrouter" && window.kimiLLM ? window.kimiLLM.currentModel : "model-id"
|
| 239 |
+
};
|
| 240 |
+
if (baseUrlInput) {
|
| 241 |
+
// Set placeholder: for openai-compatible we want an empty placeholder
|
| 242 |
+
baseUrlInput.placeholder = provider === "openai-compatible" ? "" : p.url;
|
| 243 |
+
// Only allow URL modification for custom and ollama providers
|
| 244 |
+
const isModifiable = isUrlModifiable(provider);
|
| 245 |
+
|
| 246 |
+
if (isModifiable) {
|
| 247 |
+
// For custom and ollama: load saved URL or use sensible default per provider
|
| 248 |
+
const defaultForProvider = provider === "openai-compatible" ? "" : p.url;
|
| 249 |
+
const key = `llmBaseUrl_${provider}`;
|
| 250 |
+
const savedUrl = await window.kimiDB.getPreference(key, defaultForProvider);
|
| 251 |
+
baseUrlInput.value = savedUrl || "";
|
| 252 |
+
baseUrlInput.disabled = false;
|
| 253 |
+
baseUrlInput.style.opacity = "1";
|
| 254 |
+
} else {
|
| 255 |
+
// For other providers: fixed URL, not modifiable
|
| 256 |
+
baseUrlInput.value = p.url;
|
| 257 |
+
baseUrlInput.disabled = true;
|
| 258 |
+
baseUrlInput.style.opacity = "0.6";
|
| 259 |
+
}
|
| 260 |
+
}
|
| 261 |
+
if (apiKeyInput) {
|
| 262 |
+
apiKeyInput.placeholder = p.keyPh;
|
| 263 |
+
// Masquer/désactiver le champ pour Ollama/local
|
| 264 |
+
if (provider === "ollama") {
|
| 265 |
+
apiKeyInput.value = "";
|
| 266 |
+
apiKeyInput.disabled = true;
|
| 267 |
+
apiKeyInput.style.display = "none";
|
| 268 |
+
} else {
|
| 269 |
+
apiKeyInput.disabled = false;
|
| 270 |
+
apiKeyInput.style.display = "";
|
| 271 |
+
}
|
| 272 |
+
}
|
| 273 |
+
if (modelIdInput) {
|
| 274 |
+
modelIdInput.placeholder = p.model;
|
| 275 |
+
// Only populate the field for OpenRouter since those are the models we have in the list
|
| 276 |
+
// For other providers, user must manually enter the provider-specific model ID
|
| 277 |
+
modelIdInput.value = provider === "openrouter" && window.kimiLLM ? window.kimiLLM.currentModel : "";
|
| 278 |
+
}
|
| 279 |
+
if (window.kimiDB) {
|
| 280 |
+
await window.kimiDB.setPreference("llmProvider", provider);
|
| 281 |
+
|
| 282 |
+
const apiKeyLabel = document.getElementById("api-key-label");
|
| 283 |
+
// Load provider-specific key into the input for clarity
|
| 284 |
+
const keyPref = window.KimiProviderUtils ? window.KimiProviderUtils.getKeyPrefForProvider(provider) : "providerApiKey";
|
| 285 |
+
const storedKey = await window.kimiDB.getPreference(keyPref, "");
|
| 286 |
+
if (apiKeyInput && provider !== "ollama") apiKeyInput.value = storedKey || "";
|
| 287 |
+
const color = provider === "ollama" ? "#9e9e9e" : storedKey && storedKey.length > 0 ? "#4caf50" : "#9e9e9e";
|
| 288 |
+
ApiUi.setPresence(color);
|
| 289 |
+
// Changing provider invalidates previous test state
|
| 290 |
+
ApiUi.setTestPresence("#9e9e9e");
|
| 291 |
+
ApiUi.setTestEnabled(provider === "ollama" ? true : !!(window.KIMI_VALIDATORS && window.KIMI_VALIDATORS.validateApiKey(storedKey || "")));
|
| 292 |
+
|
| 293 |
+
// Dynamic label per provider
|
| 294 |
+
if (apiKeyLabel) {
|
| 295 |
+
apiKeyLabel.textContent = window.KimiProviderUtils ? window.KimiProviderUtils.getLabelForProvider(provider) : "API Key";
|
| 296 |
+
}
|
| 297 |
+
const savedBadge = ApiUi.savedBadge();
|
| 298 |
+
if (savedBadge) {
|
| 299 |
+
if (provider !== "ollama" && storedKey) {
|
| 300 |
+
savedBadge.style.display = "inline";
|
| 301 |
+
} else {
|
| 302 |
+
savedBadge.style.display = "none";
|
| 303 |
+
}
|
| 304 |
+
}
|
| 305 |
+
ApiUi.clearStatus();
|
| 306 |
+
|
| 307 |
+
// Save URL after all UI updates are complete
|
| 308 |
+
const isModifiableFinal = isUrlModifiable(provider);
|
| 309 |
+
// Only persist provider-specific llmBaseUrl when the provider allows modification.
|
| 310 |
+
if (isModifiableFinal && baseUrlInput) {
|
| 311 |
+
const key = `llmBaseUrl_${provider}`;
|
| 312 |
+
await window.kimiDB.setPreference(key, baseUrlInput.value || "");
|
| 313 |
+
}
|
| 314 |
+
}
|
| 315 |
+
});
|
| 316 |
+
|
| 317 |
+
// Listen for model ID changes and update the current model
|
| 318 |
+
const modelIdInput = ApiUi.modelIdInput();
|
| 319 |
+
if (modelIdInput) {
|
| 320 |
+
modelIdInput.addEventListener("blur", async function (e) {
|
| 321 |
+
const newModelId = e.target.value.trim();
|
| 322 |
+
if (newModelId && window.kimiLLM && newModelId !== window.kimiLLM.currentModel) {
|
| 323 |
+
try {
|
| 324 |
+
await window.kimiLLM.setCurrentModel(newModelId);
|
| 325 |
+
} catch (error) {
|
| 326 |
+
console.warn("Failed to set model:", error.message);
|
| 327 |
+
// Reset to current model if setting failed
|
| 328 |
+
e.target.value = window.kimiLLM.currentModel || "";
|
| 329 |
+
}
|
| 330 |
+
}
|
| 331 |
+
});
|
| 332 |
+
}
|
| 333 |
+
|
| 334 |
+
// Listen for Base URL changes and save for modifiable providers
|
| 335 |
+
const baseUrlInput = ApiUi.baseUrlInput();
|
| 336 |
+
if (baseUrlInput) {
|
| 337 |
+
baseUrlInput.addEventListener("blur", async function (e) {
|
| 338 |
+
const providerSelect = ApiUi.providerSelect();
|
| 339 |
+
const provider = providerSelect ? providerSelect.value : "openrouter";
|
| 340 |
+
const isModifiable = isUrlModifiable(provider);
|
| 341 |
+
|
| 342 |
+
if (isModifiable && window.kimiDB) {
|
| 343 |
+
const newUrl = e.target.value.trim();
|
| 344 |
+
try {
|
| 345 |
+
const key = `llmBaseUrl_${provider}`;
|
| 346 |
+
// Allow empty string to be saved for openai-compatible (user may clear it)
|
| 347 |
+
await window.kimiDB.setPreference(key, newUrl || "");
|
| 348 |
+
} catch (error) {
|
| 349 |
+
console.warn("Failed to save base URL:", error.message);
|
| 350 |
+
}
|
| 351 |
+
}
|
| 352 |
+
});
|
| 353 |
+
}
|
| 354 |
+
}
|
| 355 |
+
|
| 356 |
+
// Loading screen management
|
| 357 |
+
const hideLoadingScreen = () => {
|
| 358 |
+
const loadingScreen = document.getElementById("loading-screen");
|
| 359 |
+
if (loadingScreen) {
|
| 360 |
+
loadingScreen.style.opacity = "0";
|
| 361 |
+
setTimeout(() => {
|
| 362 |
+
loadingScreen.style.display = "none";
|
| 363 |
+
}, 500);
|
| 364 |
+
}
|
| 365 |
+
};
|
| 366 |
+
|
| 367 |
+
// Hide loading screen when resources are loaded
|
| 368 |
+
if (document.readyState === "complete") {
|
| 369 |
+
setTimeout(hideLoadingScreen, 1000);
|
| 370 |
+
} else {
|
| 371 |
+
window.addEventListener("load", () => {
|
| 372 |
+
setTimeout(hideLoadingScreen, 1000);
|
| 373 |
+
});
|
| 374 |
+
}
|
| 375 |
+
|
| 376 |
+
// Use centralized video utilities
|
| 377 |
+
let video1 = window.KimiVideoManager.getVideoElement("#video1");
|
| 378 |
+
let video2 = window.KimiVideoManager.getVideoElement("#video2");
|
| 379 |
+
if (!video1 || !video2) {
|
| 380 |
+
const videoContainer = document.querySelector(".video-container");
|
| 381 |
+
if (videoContainer) {
|
| 382 |
+
video1 = window.KimiVideoManager.createVideoElement("video1", "bg-video active");
|
| 383 |
+
video2 = window.KimiVideoManager.createVideoElement("video2", "bg-video");
|
| 384 |
+
videoContainer.appendChild(video1);
|
| 385 |
+
videoContainer.appendChild(video2);
|
| 386 |
+
}
|
| 387 |
+
}
|
| 388 |
+
let activeVideo = video1;
|
| 389 |
+
let inactiveVideo = video2;
|
| 390 |
+
kimiVideo = new window.KimiVideoManager(video1, video2);
|
| 391 |
+
await kimiVideo.init(kimiDB);
|
| 392 |
+
window.kimiVideo = kimiVideo;
|
| 393 |
+
|
| 394 |
+
// Initialize unified video controller
|
| 395 |
+
window.kimiVideoController = initializeVideoController(kimiVideo, window.kimiEmotionSystem, kimiDB);
|
| 396 |
+
|
| 397 |
+
if (video1 && video2 && kimiDB && kimiDB.getSelectedCharacter) {
|
| 398 |
+
try {
|
| 399 |
+
const selectedCharacter = await kimiDB.getSelectedCharacter();
|
| 400 |
+
if (selectedCharacter && window.KIMI_CHARACTERS) {
|
| 401 |
+
kimiVideo.setCharacter(selectedCharacter);
|
| 402 |
+
const folder = window.KIMI_CHARACTERS[selectedCharacter].videoFolder;
|
| 403 |
+
const neutralVideo = `${folder}neutral/neutral-gentle-breathing.mp4`;
|
| 404 |
+
const video1Source = video1.querySelector("source");
|
| 405 |
+
if (video1Source) {
|
| 406 |
+
video1Source.setAttribute("src", neutralVideo);
|
| 407 |
+
video1.load();
|
| 408 |
+
}
|
| 409 |
+
}
|
| 410 |
+
if (kimiVideo && kimiVideo.switchToContext) {
|
| 411 |
+
kimiVideo.switchToContext("neutral");
|
| 412 |
+
}
|
| 413 |
+
} catch (e) {
|
| 414 |
+
console.warn("Error loading initial video:", e);
|
| 415 |
+
}
|
| 416 |
+
}
|
| 417 |
+
|
| 418 |
+
async function attachCharacterSection() {
|
| 419 |
+
let saveCharacterBtn = window.KimiDOMUtils.get("#save-character-btn");
|
| 420 |
+
if (saveCharacterBtn) {
|
| 421 |
+
saveCharacterBtn.addEventListener("click", async e => {
|
| 422 |
+
const settingsPanel = window.KimiDOMUtils.get(".settings-panel");
|
| 423 |
+
let scrollTop = settingsPanel ? settingsPanel.scrollTop : null;
|
| 424 |
+
const characterGrid = window.KimiDOMUtils.get("#character-grid");
|
| 425 |
+
const selectedCard = characterGrid ? characterGrid.querySelector(".character-card.selected") : null;
|
| 426 |
+
if (!selectedCard) return;
|
| 427 |
+
const charKey = selectedCard.dataset.character;
|
| 428 |
+
// Character save should not toggle the API key saved indicator.
|
| 429 |
+
const promptInput = window.KimiDOMUtils.get(`#prompt-${charKey}`);
|
| 430 |
+
const prompt = promptInput ? promptInput.value : "";
|
| 431 |
+
|
| 432 |
+
await window.kimiDB.setSelectedCharacter(charKey);
|
| 433 |
+
await window.kimiDB.setSystemPromptForCharacter(charKey, prompt);
|
| 434 |
+
// Ensure memory system uses the correct character
|
| 435 |
+
if (window.kimiMemorySystem) {
|
| 436 |
+
window.kimiMemorySystem.selectedCharacter = charKey;
|
| 437 |
+
}
|
| 438 |
+
if (window.kimiVideo && window.kimiVideo.setCharacter) {
|
| 439 |
+
window.kimiVideo.setCharacter(charKey);
|
| 440 |
+
if (window.kimiVideo.switchToContext) {
|
| 441 |
+
window.kimiVideo.switchToContext("neutral");
|
| 442 |
+
}
|
| 443 |
+
}
|
| 444 |
+
if (window.voiceManager && window.voiceManager.updateSelectedCharacter) {
|
| 445 |
+
await window.voiceManager.updateSelectedCharacter();
|
| 446 |
+
}
|
| 447 |
+
|
| 448 |
+
await window.loadCharacterSection();
|
| 449 |
+
if (settingsPanel && scrollTop !== null) {
|
| 450 |
+
requestAnimationFrame(() => {
|
| 451 |
+
settingsPanel.scrollTop = scrollTop;
|
| 452 |
+
});
|
| 453 |
+
}
|
| 454 |
+
// Refresh memory tab after character selection
|
| 455 |
+
if (window.kimiMemoryUI && typeof window.kimiMemoryUI.updateMemoryStats === "function") {
|
| 456 |
+
await window.kimiMemoryUI.updateMemoryStats();
|
| 457 |
+
}
|
| 458 |
+
if (window.setI18n) window.setI18n(saveCharacterBtn, "saved");
|
| 459 |
+
saveCharacterBtn.classList.add("success");
|
| 460 |
+
saveCharacterBtn.disabled = true;
|
| 461 |
+
|
| 462 |
+
setTimeout(() => {
|
| 463 |
+
if (window.setI18n) window.setI18n(saveCharacterBtn, "save");
|
| 464 |
+
saveCharacterBtn.classList.remove("success");
|
| 465 |
+
saveCharacterBtn.disabled = false;
|
| 466 |
+
}, 1000);
|
| 467 |
+
|
| 468 |
+
// Force full UI refresh to ensure all character-specific modules reinitialize.
|
| 469 |
+
// Full page refresh to reinitialize all character-dependent modules.
|
| 470 |
+
setTimeout(() => {
|
| 471 |
+
try {
|
| 472 |
+
window.location.reload();
|
| 473 |
+
} catch (e) {
|
| 474 |
+
console.warn("Page reload failed", e);
|
| 475 |
+
}
|
| 476 |
+
}, 1200); // slightly after button reset to allow visual feedback
|
| 477 |
+
});
|
| 478 |
+
}
|
| 479 |
+
let settingsButton2 = window.KimiDOMUtils.get("#settings-button");
|
| 480 |
+
if (settingsButton2) {
|
| 481 |
+
settingsButton2.addEventListener("click", window.loadCharacterSection);
|
| 482 |
+
}
|
| 483 |
+
}
|
| 484 |
+
await attachCharacterSection();
|
| 485 |
+
|
| 486 |
+
const chatContainer = document.getElementById("chat-container");
|
| 487 |
+
const chatButton = document.getElementById("chat-button");
|
| 488 |
+
const chatToggle = document.getElementById("chat-toggle");
|
| 489 |
+
const chatMessages = document.getElementById("chat-messages");
|
| 490 |
+
const chatInput = document.getElementById("chat-input");
|
| 491 |
+
const sendButton = document.getElementById("send-button");
|
| 492 |
+
const chatDelete = document.getElementById("chat-delete");
|
| 493 |
+
const waitingIndicator = document.getElementById("waiting-indicator");
|
| 494 |
+
|
| 495 |
+
if (!chatContainer || !chatButton || !chatMessages) {
|
| 496 |
+
console.error("Critical chat elements missing from DOM");
|
| 497 |
+
return;
|
| 498 |
+
}
|
| 499 |
+
|
| 500 |
+
window.kimiOverlayManager = new window.KimiOverlayManager();
|
| 501 |
+
|
| 502 |
+
chatButton.addEventListener("click", () => {
|
| 503 |
+
window.kimiOverlayManager.toggle("chat-container");
|
| 504 |
+
if (window.kimiOverlayManager.isOpen("chat-container")) {
|
| 505 |
+
window.loadChatHistory();
|
| 506 |
+
}
|
| 507 |
+
});
|
| 508 |
+
|
| 509 |
+
if (chatToggle) {
|
| 510 |
+
chatToggle.addEventListener("click", () => {
|
| 511 |
+
window.kimiOverlayManager.close("chat-container");
|
| 512 |
+
});
|
| 513 |
+
}
|
| 514 |
+
|
| 515 |
+
// Setup chat input and send button event listeners
|
| 516 |
+
if (sendButton) {
|
| 517 |
+
sendButton.addEventListener("click", () => {
|
| 518 |
+
if (typeof window.sendMessage === "function") {
|
| 519 |
+
window.sendMessage();
|
| 520 |
+
} else {
|
| 521 |
+
console.error("sendMessage function not available");
|
| 522 |
+
}
|
| 523 |
+
});
|
| 524 |
+
console.log("✅ Send button event listener attached");
|
| 525 |
+
} else {
|
| 526 |
+
console.error("Send button not found");
|
| 527 |
+
}
|
| 528 |
+
|
| 529 |
+
if (chatInput) {
|
| 530 |
+
chatInput.addEventListener("keydown", e => {
|
| 531 |
+
if (e.key === "Enter" && !e.shiftKey) {
|
| 532 |
+
e.preventDefault();
|
| 533 |
+
if (typeof window.sendMessage === "function") {
|
| 534 |
+
window.sendMessage();
|
| 535 |
+
} else {
|
| 536 |
+
console.error("sendMessage function not available");
|
| 537 |
+
}
|
| 538 |
+
}
|
| 539 |
+
});
|
| 540 |
+
(function (el) {
|
| 541 |
+
if (!el) return;
|
| 542 |
+
const pad = (p => (p ? parseFloat(p) : 0))(getComputedStyle(el).paddingTop) + (p => (p ? parseFloat(p) : 0))(getComputedStyle(el).paddingBottom);
|
| 543 |
+
const lh = parseFloat(getComputedStyle(el).lineHeight) || 18,
|
| 544 |
+
max = lh * 4 + pad;
|
| 545 |
+
const a = () => {
|
| 546 |
+
el.style.height = "auto";
|
| 547 |
+
el.style.height = Math.min(el.scrollHeight, max) + "px";
|
| 548 |
+
};
|
| 549 |
+
el.addEventListener("input", a);
|
| 550 |
+
el.addEventListener("focus", a);
|
| 551 |
+
setTimeout(a, 0);
|
| 552 |
+
})(chatInput);
|
| 553 |
+
console.log("✅ Chat input event listener attached");
|
| 554 |
+
} else {
|
| 555 |
+
console.error("Chat input not found");
|
| 556 |
+
}
|
| 557 |
+
|
| 558 |
+
const settingsOverlay = document.getElementById("settings-overlay");
|
| 559 |
+
const settingsButton = document.getElementById("settings-button");
|
| 560 |
+
const settingsClose = document.getElementById("settings-close");
|
| 561 |
+
|
| 562 |
+
const helpOverlay = document.getElementById("help-overlay");
|
| 563 |
+
const helpButton = document.getElementById("help-button");
|
| 564 |
+
const helpClose = document.getElementById("help-close");
|
| 565 |
+
const globalHelpButton = document.getElementById("global-help-button");
|
| 566 |
+
|
| 567 |
+
if (!settingsButton || !helpButton) {
|
| 568 |
+
console.error("Critical UI buttons missing from DOM");
|
| 569 |
+
return;
|
| 570 |
+
}
|
| 571 |
+
|
| 572 |
+
const openHelp = () => window.kimiOverlayManager.open("help-overlay");
|
| 573 |
+
|
| 574 |
+
helpButton.addEventListener("click", openHelp);
|
| 575 |
+
if (globalHelpButton) {
|
| 576 |
+
globalHelpButton.addEventListener("click", openHelp);
|
| 577 |
+
}
|
| 578 |
+
|
| 579 |
+
if (helpClose) {
|
| 580 |
+
helpClose.addEventListener("click", () => {
|
| 581 |
+
window.kimiOverlayManager.close("help-overlay");
|
| 582 |
+
});
|
| 583 |
+
}
|
| 584 |
+
|
| 585 |
+
settingsButton.addEventListener("click", () => {
|
| 586 |
+
window.kimiOverlayManager.open("settings-overlay");
|
| 587 |
+
|
| 588 |
+
// Prevent multiple settings loading
|
| 589 |
+
if (!window._settingsLoading) {
|
| 590 |
+
window._settingsLoading = true;
|
| 591 |
+
window.loadSettingsData();
|
| 592 |
+
|
| 593 |
+
setTimeout(() => {
|
| 594 |
+
window.updateTabsScrollIndicator();
|
| 595 |
+
if (window.initializeAllSliders) window.initializeAllSliders();
|
| 596 |
+
if (window.syncLLMMaxTokensSlider) window.syncLLMMaxTokensSlider();
|
| 597 |
+
if (window.syncLLMTemperatureSlider) window.syncLLMTemperatureSlider();
|
| 598 |
+
if (window.setupSettingsListeners) window.setupSettingsListeners(window.kimiDB, window.kimiMemory);
|
| 599 |
+
if (window.syncPersonalityTraits) window.syncPersonalityTraits();
|
| 600 |
+
if (window.ensureVideoContextConsistency) window.ensureVideoContextConsistency();
|
| 601 |
+
|
| 602 |
+
// Only retry loading models if not already done
|
| 603 |
+
if (window.loadAvailableModels && !loadAvailableModels._loading) {
|
| 604 |
+
setTimeout(() => window.loadAvailableModels(), 100);
|
| 605 |
+
}
|
| 606 |
+
|
| 607 |
+
window._settingsLoading = false;
|
| 608 |
+
}, 200);
|
| 609 |
+
}
|
| 610 |
+
});
|
| 611 |
+
|
| 612 |
+
if (settingsClose) {
|
| 613 |
+
settingsClose.addEventListener("click", () => {
|
| 614 |
+
window.kimiOverlayManager.close("settings-overlay");
|
| 615 |
+
});
|
| 616 |
+
}
|
| 617 |
+
|
| 618 |
+
// Initialisation unifiée de la gestion des tabs
|
| 619 |
+
window.kimiTabManager = new window.KimiTabManager({
|
| 620 |
+
onTabChange: async tabName => {
|
| 621 |
+
if (tabName === "personality") {
|
| 622 |
+
await window.loadCharacterSection();
|
| 623 |
+
}
|
| 624 |
+
}
|
| 625 |
+
});
|
| 626 |
+
|
| 627 |
+
window.kimiUIEventManager = new window.KimiUIEventManager();
|
| 628 |
+
window.kimiUIEventManager.addEvent(window, "resize", window.updateTabsScrollIndicator);
|
| 629 |
+
|
| 630 |
+
window.kimiFormManager = new window.KimiFormManager({ db: window.kimiDB, memory: window.kimiMemory });
|
| 631 |
+
|
| 632 |
+
const testVoiceButton = document.getElementById("test-voice");
|
| 633 |
+
if (testVoiceButton) {
|
| 634 |
+
testVoiceButton.addEventListener("click", () => {
|
| 635 |
+
if (voiceManager) {
|
| 636 |
+
const rate = parseFloat(document.getElementById("voice-rate").value);
|
| 637 |
+
const pitch = parseFloat(document.getElementById("voice-pitch").value);
|
| 638 |
+
const volume = parseFloat(document.getElementById("voice-volume").value);
|
| 639 |
+
|
| 640 |
+
if (window.kimiMemory.preferences) {
|
| 641 |
+
window.kimiMemory.preferences.voiceRate = rate;
|
| 642 |
+
window.kimiMemory.preferences.voicePitch = pitch;
|
| 643 |
+
window.kimiMemory.preferences.voiceVolume = volume;
|
| 644 |
+
}
|
| 645 |
+
|
| 646 |
+
const testMessage =
|
| 647 |
+
window.kimiI18nManager?.t("voice_test_message") || "Hello my love! Here is my new voice configured with all the settings! Do you like it?";
|
| 648 |
+
voiceManager.speak(testMessage, {
|
| 649 |
+
rate,
|
| 650 |
+
pitch,
|
| 651 |
+
volume
|
| 652 |
+
});
|
| 653 |
+
} else {
|
| 654 |
+
console.warn("Voice manager not initialized");
|
| 655 |
+
}
|
| 656 |
+
});
|
| 657 |
+
}
|
| 658 |
+
|
| 659 |
+
const testApiButton = document.getElementById("test-api");
|
| 660 |
+
if (testApiButton) {
|
| 661 |
+
testApiButton.addEventListener("click", async () => {
|
| 662 |
+
const statusSpan = ApiUi.statusSpan();
|
| 663 |
+
const apiKeyInput = ApiUi.apiKeyInput();
|
| 664 |
+
const apiKey = apiKeyInput ? apiKeyInput.value.trim() : "";
|
| 665 |
+
const providerSelect = ApiUi.providerSelect();
|
| 666 |
+
const baseUrlInput = ApiUi.baseUrlInput();
|
| 667 |
+
const modelIdInput = ApiUi.modelIdInput();
|
| 668 |
+
const provider = providerSelect ? providerSelect.value : "openrouter";
|
| 669 |
+
const baseUrl = baseUrlInput ? baseUrlInput.value.trim() : "";
|
| 670 |
+
const modelId = modelIdInput ? modelIdInput.value.trim() : "";
|
| 671 |
+
|
| 672 |
+
if (!statusSpan) return;
|
| 673 |
+
|
| 674 |
+
if (provider !== "ollama" && !apiKey) {
|
| 675 |
+
statusSpan.textContent = window.kimiI18nManager?.t("api_key_missing") || "API key missing";
|
| 676 |
+
statusSpan.style.color = "#ff6b6b";
|
| 677 |
+
return;
|
| 678 |
+
}
|
| 679 |
+
|
| 680 |
+
// Validate API key format before saving/testing
|
| 681 |
+
if (provider !== "ollama") {
|
| 682 |
+
const isValid = (window.KIMI_VALIDATORS && window.KIMI_VALIDATORS.validateApiKey(apiKey)) || false;
|
| 683 |
+
if (!isValid) {
|
| 684 |
+
statusSpan.textContent = window.kimiI18nManager?.t("api_key_invalid_format") || "Invalid API key format (must start with sk-or-v1-)";
|
| 685 |
+
statusSpan.style.color = "#ff6b6b";
|
| 686 |
+
return;
|
| 687 |
+
}
|
| 688 |
+
}
|
| 689 |
+
|
| 690 |
+
if (window.kimiDB) {
|
| 691 |
+
// Save API key under provider-specific preference key (skip for Ollama)
|
| 692 |
+
if (provider !== "ollama") {
|
| 693 |
+
const keyPref = window.KimiProviderUtils ? window.KimiProviderUtils.getKeyPrefForProvider(provider) : "providerApiKey";
|
| 694 |
+
await window.kimiDB.setPreference(keyPref, apiKey);
|
| 695 |
+
}
|
| 696 |
+
await window.kimiDB.setPreference("llmProvider", provider);
|
| 697 |
+
if (baseUrl) {
|
| 698 |
+
// Save under provider-specific key to avoid cross-provider contamination
|
| 699 |
+
const key = `llmBaseUrl_${provider}`;
|
| 700 |
+
await window.kimiDB.setPreference(key, baseUrl);
|
| 701 |
+
}
|
| 702 |
+
if (modelId) await window.kimiDB.setPreference("llmModelId", modelId);
|
| 703 |
+
}
|
| 704 |
+
statusSpan.textContent = "Testing in progress...";
|
| 705 |
+
statusSpan.style.color = "#ffa726";
|
| 706 |
+
|
| 707 |
+
try {
|
| 708 |
+
if (window.kimiLLM) {
|
| 709 |
+
// Test API minimal et centralisé pour tous les providers
|
| 710 |
+
const result = await window.kimiLLM.testApiKeyMinimal(modelId);
|
| 711 |
+
if (result.success) {
|
| 712 |
+
statusSpan.textContent = "Connection successful!";
|
| 713 |
+
statusSpan.style.color = "#4caf50";
|
| 714 |
+
// Only show saved badge if an actual non-empty API key is stored and provider requires one
|
| 715 |
+
const savedBadge = ApiUi.savedBadge();
|
| 716 |
+
if (savedBadge) {
|
| 717 |
+
const apiKeyInputEl = ApiUi.apiKeyInput();
|
| 718 |
+
const hasKey = apiKeyInputEl && apiKeyInputEl.value.trim().length > 0;
|
| 719 |
+
if (provider !== "ollama" && hasKey) {
|
| 720 |
+
savedBadge.textContent = (window.kimiI18nManager && window.kimiI18nManager.t("saved_short")) || "Saved";
|
| 721 |
+
savedBadge.style.display = "inline";
|
| 722 |
+
} else {
|
| 723 |
+
savedBadge.style.display = "none";
|
| 724 |
+
}
|
| 725 |
+
}
|
| 726 |
+
|
| 727 |
+
if (result.response) {
|
| 728 |
+
setTimeout(() => {
|
| 729 |
+
statusSpan.textContent = `Test response: \"${result.response.substring(0, 50)}...\"`;
|
| 730 |
+
}, 1000);
|
| 731 |
+
}
|
| 732 |
+
// Mark test success explicitly
|
| 733 |
+
ApiUi.setTestPresence("#4caf50");
|
| 734 |
+
} else {
|
| 735 |
+
statusSpan.textContent = `${result.error}`;
|
| 736 |
+
statusSpan.style.color = "#ff6b6b";
|
| 737 |
+
ApiUi.setTestPresence("#9e9e9e");
|
| 738 |
+
if (result.error.includes("similaires disponibles")) {
|
| 739 |
+
setTimeout(() => {}, 1000);
|
| 740 |
+
}
|
| 741 |
+
}
|
| 742 |
+
} else {
|
| 743 |
+
statusSpan.textContent = "LLM manager not initialized";
|
| 744 |
+
statusSpan.style.color = "#ff6b6b";
|
| 745 |
+
ApiUi.setTestPresence("#9e9e9e");
|
| 746 |
+
}
|
| 747 |
+
} catch (error) {
|
| 748 |
+
console.error("Error while testing API:", error);
|
| 749 |
+
statusSpan.textContent = `Error: ${error.message}`;
|
| 750 |
+
statusSpan.style.color = "#ff6b6b";
|
| 751 |
+
ApiUi.setTestPresence("#9e9e9e");
|
| 752 |
+
|
| 753 |
+
if (error.message.includes("non disponible")) {
|
| 754 |
+
setTimeout(() => {}, 1000);
|
| 755 |
+
}
|
| 756 |
+
}
|
| 757 |
+
});
|
| 758 |
+
}
|
| 759 |
+
|
| 760 |
+
// Global, single handler for API key input to save and update presence in real-time
|
| 761 |
+
(function setupApiKeyInputHandler() {
|
| 762 |
+
const input = ApiUi.apiKeyInput();
|
| 763 |
+
if (!input) return;
|
| 764 |
+
let t;
|
| 765 |
+
input.addEventListener("input", () => {
|
| 766 |
+
clearTimeout(t);
|
| 767 |
+
t = setTimeout(async () => {
|
| 768 |
+
const providerEl = ApiUi.providerSelect();
|
| 769 |
+
const provider = providerEl ? providerEl.value : "openrouter";
|
| 770 |
+
const keyPref = window.KimiProviderUtils ? window.KimiProviderUtils.getKeyPrefForProvider(provider) : "providerApiKey";
|
| 771 |
+
const value = input.value.trim();
|
| 772 |
+
// Update Test button state immediately
|
| 773 |
+
const validNow = !!(window.KIMI_VALIDATORS && window.KIMI_VALIDATORS.validateApiKey(value));
|
| 774 |
+
ApiUi.setTestEnabled(provider === "ollama" ? true : validNow);
|
| 775 |
+
if (window.kimiDB) {
|
| 776 |
+
try {
|
| 777 |
+
await window.kimiDB.setPreference(keyPref, value);
|
| 778 |
+
const savedBadge = ApiUi.savedBadge();
|
| 779 |
+
if (savedBadge) {
|
| 780 |
+
if (value) {
|
| 781 |
+
savedBadge.textContent = (window.kimiI18nManager && window.kimiI18nManager.t("saved_short")) || "Saved";
|
| 782 |
+
savedBadge.style.display = "inline";
|
| 783 |
+
} else {
|
| 784 |
+
savedBadge.style.display = "none";
|
| 785 |
+
}
|
| 786 |
+
}
|
| 787 |
+
ApiUi.setPresence(value ? "#4caf50" : "#9e9e9e");
|
| 788 |
+
// Any key change invalidates previous test state
|
| 789 |
+
ApiUi.setTestPresence("#9e9e9e");
|
| 790 |
+
ApiUi.clearStatus();
|
| 791 |
+
} catch (e) {
|
| 792 |
+
// Validation error from DB
|
| 793 |
+
const s = ApiUi.statusSpan();
|
| 794 |
+
if (s) {
|
| 795 |
+
s.textContent = e?.message || "Invalid API key";
|
| 796 |
+
s.style.color = "#ff6b6b";
|
| 797 |
+
}
|
| 798 |
+
ApiUi.setTestEnabled(false);
|
| 799 |
+
ApiUi.setTestPresence("#9e9e9e");
|
| 800 |
+
}
|
| 801 |
+
}
|
| 802 |
+
}, window.KIMI_SECURITY_CONFIG?.DEBOUNCE_DELAY || 300);
|
| 803 |
+
});
|
| 804 |
+
})();
|
| 805 |
+
|
| 806 |
+
// Toggle show/hide for API key
|
| 807 |
+
(function setupToggleEye() {
|
| 808 |
+
const btn = ApiUi.toggleBtn();
|
| 809 |
+
const input = ApiUi.apiKeyInput();
|
| 810 |
+
if (!btn || !input) return;
|
| 811 |
+
btn.addEventListener("click", () => {
|
| 812 |
+
const showing = input.type === "text";
|
| 813 |
+
input.type = showing ? "password" : "text";
|
| 814 |
+
btn.setAttribute("aria-pressed", String(!showing));
|
| 815 |
+
const icon = btn.querySelector("i");
|
| 816 |
+
if (icon) {
|
| 817 |
+
icon.classList.toggle("fa-eye");
|
| 818 |
+
icon.classList.toggle("fa-eye-slash");
|
| 819 |
+
}
|
| 820 |
+
btn.setAttribute("aria-label", showing ? "Show API key" : "Hide API key");
|
| 821 |
+
});
|
| 822 |
+
})();
|
| 823 |
+
|
| 824 |
+
kimiInit.register(
|
| 825 |
+
"appearanceManager",
|
| 826 |
+
async () => {
|
| 827 |
+
const manager = new KimiAppearanceManager(window.kimiDB);
|
| 828 |
+
await manager.init();
|
| 829 |
+
window.kimiAppearanceManager = manager;
|
| 830 |
+
return manager;
|
| 831 |
+
},
|
| 832 |
+
[],
|
| 833 |
+
500
|
| 834 |
+
);
|
| 835 |
+
kimiInit.register(
|
| 836 |
+
"dataManager",
|
| 837 |
+
async () => {
|
| 838 |
+
const manager = new KimiDataManager(window.kimiDB);
|
| 839 |
+
await manager.init();
|
| 840 |
+
window.kimiDataManager = manager;
|
| 841 |
+
return manager;
|
| 842 |
+
},
|
| 843 |
+
[],
|
| 844 |
+
600
|
| 845 |
+
);
|
| 846 |
+
|
| 847 |
+
kimiInit.register(
|
| 848 |
+
"voiceManager",
|
| 849 |
+
async () => {
|
| 850 |
+
if (window.KimiVoiceManager) {
|
| 851 |
+
const manager = new KimiVoiceManager(window.kimiDB, window.kimiMemory);
|
| 852 |
+
const success = await manager.init();
|
| 853 |
+
if (success) {
|
| 854 |
+
manager.setOnSpeechAnalysis(window.analyzeAndReact);
|
| 855 |
+
return manager;
|
| 856 |
+
}
|
| 857 |
+
}
|
| 858 |
+
return null;
|
| 859 |
+
},
|
| 860 |
+
[],
|
| 861 |
+
1000
|
| 862 |
+
);
|
| 863 |
+
|
| 864 |
+
try {
|
| 865 |
+
await kimiInit.initializeAll();
|
| 866 |
+
window.voiceManager = kimiInit.getInstance("voiceManager");
|
| 867 |
+
window.kimiMemory.updateFavorabilityBar();
|
| 868 |
+
} catch (error) {
|
| 869 |
+
console.error("Initialization error:", error);
|
| 870 |
+
}
|
| 871 |
+
|
| 872 |
+
// Setup unified event handlers to prevent duplicates
|
| 873 |
+
setupUnifiedEventHandlers();
|
| 874 |
+
|
| 875 |
+
// Initialize language and UI
|
| 876 |
+
await initializeLanguageAndUI();
|
| 877 |
+
|
| 878 |
+
// Setup message handling
|
| 879 |
+
setupMessageHandling();
|
| 880 |
+
|
| 881 |
+
// Function definitions
|
| 882 |
+
function setupUnifiedEventHandlers() {
|
| 883 |
+
// SIMPLE FIX: Initialize _kimiEventCleanup to prevent undefined error
|
| 884 |
+
if (!window._kimiEventCleanup) {
|
| 885 |
+
window._kimiEventCleanup = [];
|
| 886 |
+
}
|
| 887 |
+
|
| 888 |
+
// Helper function to safely add event listeners
|
| 889 |
+
function safeAddEventListener(element, event, handler, identifier) {
|
| 890 |
+
if (element && !element[identifier]) {
|
| 891 |
+
element.addEventListener(event, handler);
|
| 892 |
+
element[identifier] = true;
|
| 893 |
+
|
| 894 |
+
// Simple cleanup system
|
| 895 |
+
const cleanupFn = () => {
|
| 896 |
+
element.removeEventListener(event, handler);
|
| 897 |
+
element[identifier] = false;
|
| 898 |
+
};
|
| 899 |
+
|
| 900 |
+
// Store cleanup function in the simple array
|
| 901 |
+
window._kimiEventCleanup.push(cleanupFn);
|
| 902 |
+
}
|
| 903 |
+
}
|
| 904 |
+
|
| 905 |
+
// Chat event handlers
|
| 906 |
+
const chatDelete = document.getElementById("chat-delete");
|
| 907 |
+
if (chatDelete) {
|
| 908 |
+
const handler = async () => {
|
| 909 |
+
if (confirm("Do you really want to delete all chat messages? This cannot be undone.")) {
|
| 910 |
+
const chatMessages = document.getElementById("chat-messages");
|
| 911 |
+
if (chatMessages) {
|
| 912 |
+
chatMessages.textContent = "";
|
| 913 |
+
}
|
| 914 |
+
if (window.kimiDB && window.kimiDB.db) {
|
| 915 |
+
try {
|
| 916 |
+
await window.kimiDB.db.conversations.clear();
|
| 917 |
+
} catch (error) {
|
| 918 |
+
console.error("Error deleting conversations:", error);
|
| 919 |
+
}
|
| 920 |
+
}
|
| 921 |
+
}
|
| 922 |
+
};
|
| 923 |
+
safeAddEventListener(chatDelete, "click", handler, "_kimiChatDeleteHandlerAttached");
|
| 924 |
+
}
|
| 925 |
+
}
|
| 926 |
+
|
| 927 |
+
async function initializeLanguageAndUI() {
|
| 928 |
+
// Language initialization
|
| 929 |
+
window.kimiI18nManager = new window.KimiI18nManager();
|
| 930 |
+
const lang = await kimiDB.getPreference("selectedLanguage", "en");
|
| 931 |
+
await window.kimiI18nManager.setLanguage(lang);
|
| 932 |
+
// Note: Language selector event listener is now handled by VoiceManager.setupLanguageSelector()
|
| 933 |
+
// This prevents duplicate event listeners and ensures proper coordination between voice and i18n systems
|
| 934 |
+
|
| 935 |
+
window.kimiUIStateManager = new window.KimiUIStateManager();
|
| 936 |
+
}
|
| 937 |
+
|
| 938 |
+
function setupMessageHandling() {
|
| 939 |
+
// Chat event handlers are already attached in the main script
|
| 940 |
+
// No need to reattach them here to avoid duplicates
|
| 941 |
+
}
|
| 942 |
+
|
| 943 |
+
// ==== BATCHED EVENT AGGREGATOR (personality + preferences) ====
|
| 944 |
+
const batchedUpdates = {
|
| 945 |
+
personality: null,
|
| 946 |
+
preferences: new Set()
|
| 947 |
+
};
|
| 948 |
+
let batchTimer = null;
|
| 949 |
+
|
| 950 |
+
function scheduleFlush() {
|
| 951 |
+
if (batchTimer) return;
|
| 952 |
+
batchTimer = setTimeout(flushBatchedUpdates, 100); // 100ms coalescing window
|
| 953 |
+
}
|
| 954 |
+
|
| 955 |
+
async function flushBatchedUpdates() {
|
| 956 |
+
const personalityPayload = batchedUpdates.personality;
|
| 957 |
+
const prefKeys = Array.from(batchedUpdates.preferences);
|
| 958 |
+
batchedUpdates.personality = null;
|
| 959 |
+
batchedUpdates.preferences.clear();
|
| 960 |
+
batchTimer = null;
|
| 961 |
+
|
| 962 |
+
// Apply personality update once (last-wins)
|
| 963 |
+
if (personalityPayload) {
|
| 964 |
+
const { character, traits } = personalityPayload;
|
| 965 |
+
const defaults = (window.getTraitDefaults && window.getTraitDefaults()) || {
|
| 966 |
+
affection: 55, // Baseline affection default
|
| 967 |
+
romance: 50,
|
| 968 |
+
empathy: 75,
|
| 969 |
+
playfulness: 55,
|
| 970 |
+
humor: 60,
|
| 971 |
+
intelligence: 70
|
| 972 |
+
};
|
| 973 |
+
|
| 974 |
+
// Prefer persisted DB traits over defaults to avoid temporary inconsistencies.
|
| 975 |
+
let dbTraits = null;
|
| 976 |
+
try {
|
| 977 |
+
if (window.kimiDB && typeof window.kimiDB.getAllPersonalityTraits === "function") {
|
| 978 |
+
dbTraits = window.getCharacterTraits
|
| 979 |
+
? await window.getCharacterTraits(character || null)
|
| 980 |
+
: await window.kimiDB.getAllPersonalityTraits(character || null);
|
| 981 |
+
}
|
| 982 |
+
} catch (e) {
|
| 983 |
+
dbTraits = null;
|
| 984 |
+
}
|
| 985 |
+
|
| 986 |
+
const baseline = { ...defaults, ...(dbTraits || {}) };
|
| 987 |
+
const safeTraits = {};
|
| 988 |
+
for (const key of Object.keys(defaults)) {
|
| 989 |
+
// If incoming payload provides the key, use it; otherwise use baseline (DB -> defaults)
|
| 990 |
+
let raw = Object.prototype.hasOwnProperty.call(traits || {}, key) ? traits[key] : baseline[key];
|
| 991 |
+
let v = Number(raw);
|
| 992 |
+
if (!isFinite(v) || isNaN(v)) v = Number(baseline[key]);
|
| 993 |
+
v = Math.max(0, Math.min(100, v));
|
| 994 |
+
safeTraits[key] = v;
|
| 995 |
+
}
|
| 996 |
+
if (window.KIMI_DEBUG_SYNC) {
|
| 997 |
+
window.KIMI_CONFIG?.debugLog("SYNC", `Personality updated for ${character}:`, safeTraits);
|
| 998 |
+
}
|
| 999 |
+
// Centralize side-effects elsewhere; aggregator remains a coalesced logger only.
|
| 1000 |
+
}
|
| 1001 |
+
|
| 1002 |
+
// Preference keys batch (currently UI refresh for sliders already handled elsewhere)
|
| 1003 |
+
if (prefKeys.length > 0) {
|
| 1004 |
+
// Potential future hook: log or perform aggregated operations
|
| 1005 |
+
// console.log("⚙️ Batched preference keys:", prefKeys);
|
| 1006 |
+
}
|
| 1007 |
+
}
|
| 1008 |
+
|
| 1009 |
+
// Also listen to the DB-wrapped event name to preserve batched logging
|
| 1010 |
+
window.addEventListener("personality:updated", event => {
|
| 1011 |
+
batchedUpdates.personality = event.detail; // last event wins
|
| 1012 |
+
scheduleFlush();
|
| 1013 |
+
});
|
| 1014 |
+
window.addEventListener("preferenceUpdated", event => {
|
| 1015 |
+
if (event.detail?.key) batchedUpdates.preferences.add(event.detail.key);
|
| 1016 |
+
scheduleFlush();
|
| 1017 |
+
});
|
| 1018 |
+
|
| 1019 |
+
// Add global keyboard event listener for microphone toggle (F8)
|
| 1020 |
+
let f8KeyPressed = false;
|
| 1021 |
+
|
| 1022 |
+
document.addEventListener("keydown", function (event) {
|
| 1023 |
+
// Check if F8 key is pressed and no input field is focused
|
| 1024 |
+
if (event.key === "F8" && !f8KeyPressed) {
|
| 1025 |
+
f8KeyPressed = true;
|
| 1026 |
+
const activeElement = document.activeElement;
|
| 1027 |
+
const isInputFocused =
|
| 1028 |
+
activeElement && (activeElement.tagName === "INPUT" || activeElement.tagName === "TEXTAREA" || activeElement.isContentEditable);
|
| 1029 |
+
|
| 1030 |
+
// Only trigger if no input field is focused
|
| 1031 |
+
if (!isInputFocused && window.voiceManager && window.voiceManager.toggleMicrophone) {
|
| 1032 |
+
event.preventDefault();
|
| 1033 |
+
window.voiceManager.toggleMicrophone();
|
| 1034 |
+
}
|
| 1035 |
+
}
|
| 1036 |
+
});
|
| 1037 |
+
|
| 1038 |
+
// Refresh sliders when character or language preference changes
|
| 1039 |
+
window.addEventListener("preferenceUpdated", evt => {
|
| 1040 |
+
const k = evt.detail?.key;
|
| 1041 |
+
if (!k) return;
|
| 1042 |
+
if (k === "selectedCharacter" || k === "selectedLanguage") {
|
| 1043 |
+
if (window.refreshAllSliders) {
|
| 1044 |
+
setTimeout(() => window.refreshAllSliders(), 50);
|
| 1045 |
+
}
|
| 1046 |
+
}
|
| 1047 |
+
});
|
| 1048 |
+
|
| 1049 |
+
document.addEventListener("keyup", function (event) {
|
| 1050 |
+
if (event.key === "F8") {
|
| 1051 |
+
f8KeyPressed = false;
|
| 1052 |
+
}
|
| 1053 |
+
});
|
| 1054 |
+
|
| 1055 |
+
// Monitor for consistency and errors
|
| 1056 |
+
setInterval(async () => {
|
| 1057 |
+
if (window.ensureVideoContextConsistency) {
|
| 1058 |
+
await window.ensureVideoContextConsistency();
|
| 1059 |
+
}
|
| 1060 |
+
}, 30000); // Check every 30 seconds
|
| 1061 |
+
|
| 1062 |
+
// Personality sync: global event and wrappers
|
| 1063 |
+
(function setupPersonalitySync() {
|
| 1064 |
+
// Guard to avoid multiple initializations
|
| 1065 |
+
if (window._kimiPersonalitySyncReady) return;
|
| 1066 |
+
window._kimiPersonalitySyncReady = true;
|
| 1067 |
+
|
| 1068 |
+
const dispatchUpdated = async (partialTraits, characterHint = null) => {
|
| 1069 |
+
try {
|
| 1070 |
+
const character = characterHint || (window.kimiDB && (await window.kimiDB.getSelectedCharacter())) || null;
|
| 1071 |
+
window.dispatchEvent(
|
| 1072 |
+
new CustomEvent("personality:updated", {
|
| 1073 |
+
detail: { character, traits: { ...partialTraits } }
|
| 1074 |
+
})
|
| 1075 |
+
);
|
| 1076 |
+
} catch (e) {}
|
| 1077 |
+
};
|
| 1078 |
+
|
| 1079 |
+
const tryWrapDB = () => {
|
| 1080 |
+
const db = window.kimiDB;
|
| 1081 |
+
if (!db) return false;
|
| 1082 |
+
|
| 1083 |
+
const wrapOnce = (obj, methodName, buildTraitsFromArgs) => {
|
| 1084 |
+
if (!obj || typeof obj[methodName] !== "function") return;
|
| 1085 |
+
if (obj[methodName]._kimiWrapped) return;
|
| 1086 |
+
const original = obj[methodName].bind(obj);
|
| 1087 |
+
obj[methodName] = async function (...args) {
|
| 1088 |
+
const res = await original(...args);
|
| 1089 |
+
try {
|
| 1090 |
+
const { traits, character } = await buildTraitsFromArgs(args, res);
|
| 1091 |
+
if (traits && Object.keys(traits).length > 0) {
|
| 1092 |
+
await dispatchUpdated(traits, character);
|
| 1093 |
+
}
|
| 1094 |
+
} catch (e) {}
|
| 1095 |
+
return res;
|
| 1096 |
+
};
|
| 1097 |
+
obj[methodName]._kimiWrapped = true;
|
| 1098 |
+
};
|
| 1099 |
+
|
| 1100 |
+
// setPersonalityTrait(trait, value, character?)
|
| 1101 |
+
wrapOnce(db, "setPersonalityTrait", async args => {
|
| 1102 |
+
const [trait, value, character] = args;
|
| 1103 |
+
return { traits: { [String(trait)]: Number(value) }, character: character || null };
|
| 1104 |
+
});
|
| 1105 |
+
|
| 1106 |
+
// setPersonalityBatch(traitsObj, character?)
|
| 1107 |
+
wrapOnce(db, "setPersonalityBatch", async args => {
|
| 1108 |
+
const [traitsObj, character] = args;
|
| 1109 |
+
const traits = {};
|
| 1110 |
+
if (traitsObj && typeof traitsObj === "object") {
|
| 1111 |
+
for (const [k, v] of Object.entries(traitsObj)) {
|
| 1112 |
+
traits[String(k)] = Number(v);
|
| 1113 |
+
}
|
| 1114 |
+
}
|
| 1115 |
+
return { traits, character: character || null };
|
| 1116 |
+
});
|
| 1117 |
+
|
| 1118 |
+
// savePersonality(personalityObj, character?)
|
| 1119 |
+
wrapOnce(db, "savePersonality", async args => {
|
| 1120 |
+
const [personalityObj, character] = args;
|
| 1121 |
+
const traits = {};
|
| 1122 |
+
if (personalityObj && typeof personalityObj === "object") {
|
| 1123 |
+
for (const [k, v] of Object.entries(personalityObj)) {
|
| 1124 |
+
traits[String(k)] = Number(v);
|
| 1125 |
+
}
|
| 1126 |
+
}
|
| 1127 |
+
return { traits, character: character || null };
|
| 1128 |
+
});
|
| 1129 |
+
|
| 1130 |
+
return true;
|
| 1131 |
+
};
|
| 1132 |
+
|
| 1133 |
+
// Try immediately and then retry a few times if DB not yet ready
|
| 1134 |
+
if (!tryWrapDB()) {
|
| 1135 |
+
let attempts = 0;
|
| 1136 |
+
const maxAttempts = 20;
|
| 1137 |
+
const interval = setInterval(() => {
|
| 1138 |
+
attempts++;
|
| 1139 |
+
if (tryWrapDB() || attempts >= maxAttempts) {
|
| 1140 |
+
clearInterval(interval);
|
| 1141 |
+
}
|
| 1142 |
+
}, 250);
|
| 1143 |
+
}
|
| 1144 |
+
|
| 1145 |
+
// Central listener: debounce UI/video sync to avoid thrashing
|
| 1146 |
+
let syncTimer = null;
|
| 1147 |
+
let lastTraits = {};
|
| 1148 |
+
window.addEventListener("personality:updated", async e => {
|
| 1149 |
+
try {
|
| 1150 |
+
if (e && e.detail && e.detail.traits) {
|
| 1151 |
+
// Merge incremental updates
|
| 1152 |
+
lastTraits = { ...lastTraits, ...e.detail.traits };
|
| 1153 |
+
}
|
| 1154 |
+
} catch {}
|
| 1155 |
+
|
| 1156 |
+
if (syncTimer) clearTimeout(syncTimer);
|
| 1157 |
+
syncTimer = setTimeout(async () => {
|
| 1158 |
+
try {
|
| 1159 |
+
const db = window.kimiDB;
|
| 1160 |
+
const character = (e && e.detail && e.detail.character) || (db && (await db.getSelectedCharacter())) || null;
|
| 1161 |
+
let traits = lastTraits;
|
| 1162 |
+
if (!traits || Object.keys(traits).length === 0) {
|
| 1163 |
+
// Fallback: fetch all traits if partial not provided
|
| 1164 |
+
traits = db && (window.getCharacterTraits ? await window.getCharacterTraits(character) : await db.getAllPersonalityTraits(character));
|
| 1165 |
+
}
|
| 1166 |
+
|
| 1167 |
+
// 1) Update UI sliders if available
|
| 1168 |
+
if (typeof window.updateSlider === "function" && traits) {
|
| 1169 |
+
for (const [trait, value] of Object.entries(traits)) {
|
| 1170 |
+
const id = `trait-${trait}`;
|
| 1171 |
+
if (document.getElementById(id)) {
|
| 1172 |
+
try {
|
| 1173 |
+
window.updateSlider(id, value);
|
| 1174 |
+
} catch {}
|
| 1175 |
+
}
|
| 1176 |
+
}
|
| 1177 |
+
}
|
| 1178 |
+
if (typeof window.syncPersonalityTraits === "function") {
|
| 1179 |
+
try {
|
| 1180 |
+
await window.syncPersonalityTraits(character);
|
| 1181 |
+
} catch {}
|
| 1182 |
+
}
|
| 1183 |
+
|
| 1184 |
+
// 2) Update memory cache affection bar if available
|
| 1185 |
+
if (window.kimiMemory && typeof window.kimiMemory.updateAffectionTrait === "function") {
|
| 1186 |
+
try {
|
| 1187 |
+
await window.kimiMemory.updateAffectionTrait();
|
| 1188 |
+
} catch {}
|
| 1189 |
+
}
|
| 1190 |
+
|
| 1191 |
+
// 3) Update video mood by personality
|
| 1192 |
+
if (window.kimiVideo && typeof window.kimiVideo.setMoodByPersonality === "function") {
|
| 1193 |
+
const allTraits =
|
| 1194 |
+
traits && Object.keys(traits).length > 0
|
| 1195 |
+
? { ...traits }
|
| 1196 |
+
: (db &&
|
| 1197 |
+
(window.getCharacterTraits ? await window.getCharacterTraits(character) : await db.getAllPersonalityTraits(character))) ||
|
| 1198 |
+
{};
|
| 1199 |
+
try {
|
| 1200 |
+
window.kimiVideo.setMoodByPersonality(allTraits);
|
| 1201 |
+
} catch {}
|
| 1202 |
+
// 3b) Update voice modulation based on personality
|
| 1203 |
+
try {
|
| 1204 |
+
if (window.voiceManager && typeof window.voiceManager.updatePersonalityModulation === "function") {
|
| 1205 |
+
window.voiceManager.updatePersonalityModulation(allTraits);
|
| 1206 |
+
}
|
| 1207 |
+
} catch {}
|
| 1208 |
+
}
|
| 1209 |
+
|
| 1210 |
+
// 4) Ensure current video context is valid (lightweight guard)
|
| 1211 |
+
let beforeInfo = null;
|
| 1212 |
+
try {
|
| 1213 |
+
if (window.kimiVideo && typeof window.kimiVideo.getCurrentVideoInfo === "function") {
|
| 1214 |
+
beforeInfo = window.kimiVideo.getCurrentVideoInfo();
|
| 1215 |
+
}
|
| 1216 |
+
} catch {}
|
| 1217 |
+
|
| 1218 |
+
if (typeof window.ensureVideoContextConsistency === "function") {
|
| 1219 |
+
try {
|
| 1220 |
+
await window.ensureVideoContextConsistency();
|
| 1221 |
+
} catch {}
|
| 1222 |
+
}
|
| 1223 |
+
|
| 1224 |
+
try {
|
| 1225 |
+
if (window.KIMI_DEBUG_SYNC && window.kimiVideo && typeof window.kimiVideo.getCurrentVideoInfo === "function") {
|
| 1226 |
+
const afterInfo = window.kimiVideo.getCurrentVideoInfo();
|
| 1227 |
+
if (
|
| 1228 |
+
beforeInfo &&
|
| 1229 |
+
afterInfo &&
|
| 1230 |
+
(beforeInfo.context !== afterInfo.context ||
|
| 1231 |
+
beforeInfo.emotion !== afterInfo.emotion ||
|
| 1232 |
+
beforeInfo.category !== afterInfo.category)
|
| 1233 |
+
) {
|
| 1234 |
+
console.log("🔧 SyncGuard: corrected video context", { from: beforeInfo, to: afterInfo });
|
| 1235 |
+
}
|
| 1236 |
+
}
|
| 1237 |
+
} catch {}
|
| 1238 |
+
} catch {
|
| 1239 |
+
} finally {
|
| 1240 |
+
lastTraits = {};
|
| 1241 |
+
if (window.updateGlobalPersonalityUI) {
|
| 1242 |
+
try {
|
| 1243 |
+
await window.updateGlobalPersonalityUI();
|
| 1244 |
+
} catch {}
|
| 1245 |
+
}
|
| 1246 |
+
}
|
| 1247 |
+
}, 120); // small debounce
|
| 1248 |
+
});
|
| 1249 |
+
})();
|
| 1250 |
+
});
|
kimi-js/kimi-security.js
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// ===== KIMI SECURITY & VALIDATION CONFIGURATION =====
|
| 2 |
+
|
| 3 |
+
window.KIMI_SECURITY_CONFIG = {
|
| 4 |
+
// Input validation limits
|
| 5 |
+
MAX_MESSAGE_LENGTH: 5000,
|
| 6 |
+
MAX_API_KEY_LENGTH: 200,
|
| 7 |
+
MIN_API_KEY_LENGTH: 10,
|
| 8 |
+
|
| 9 |
+
// Security settings
|
| 10 |
+
API_KEY_PATTERNS: [
|
| 11 |
+
/^sk-or-v1-[a-zA-Z0-9]{16,}$/, // OpenRouter pattern (relaxed length)
|
| 12 |
+
/^sk-[a-zA-Z0-9_\-]{16,}$/, // OpenAI and similar (relaxed)
|
| 13 |
+
/^[a-zA-Z0-9_\-]{16,}$/ // Generic API key fallback
|
| 14 |
+
],
|
| 15 |
+
|
| 16 |
+
// Cache settings
|
| 17 |
+
CACHE_MAX_AGE: 300000, // 5 minutes
|
| 18 |
+
CACHE_MAX_SIZE: 100,
|
| 19 |
+
|
| 20 |
+
// Performance settings
|
| 21 |
+
DEBOUNCE_DELAY: 300,
|
| 22 |
+
BATCH_DELAY: 800,
|
| 23 |
+
THROTTLE_LIMIT: 1000,
|
| 24 |
+
|
| 25 |
+
// Error messages
|
| 26 |
+
ERRORS: {
|
| 27 |
+
INVALID_INPUT: "Invalid input provided",
|
| 28 |
+
MESSAGE_TOO_LONG: "Message too long. Please keep it under {max} characters.",
|
| 29 |
+
INVALID_API_KEY: "Invalid API key format",
|
| 30 |
+
NETWORK_ERROR: "Network error. Please check your connection.",
|
| 31 |
+
SYSTEM_ERROR: "System error occurred. Please try again."
|
| 32 |
+
}
|
| 33 |
+
};
|
| 34 |
+
|
| 35 |
+
// Validation utilities using the configuration
|
| 36 |
+
window.KIMI_VALIDATORS = {
|
| 37 |
+
validateMessage: message => {
|
| 38 |
+
if (!message || typeof message !== "string") return { valid: false, error: "INVALID_INPUT" };
|
| 39 |
+
if (message.length > window.KIMI_SECURITY_CONFIG.MAX_MESSAGE_LENGTH) {
|
| 40 |
+
return {
|
| 41 |
+
valid: false,
|
| 42 |
+
error: "MESSAGE_TOO_LONG",
|
| 43 |
+
params: { max: window.KIMI_SECURITY_CONFIG.MAX_MESSAGE_LENGTH }
|
| 44 |
+
};
|
| 45 |
+
}
|
| 46 |
+
return { valid: true };
|
| 47 |
+
},
|
| 48 |
+
|
| 49 |
+
validateApiKey: key => {
|
| 50 |
+
if (!key || typeof key !== "string") return false;
|
| 51 |
+
if (key.length < window.KIMI_SECURITY_CONFIG.MIN_API_KEY_LENGTH) return false;
|
| 52 |
+
if (key.length > window.KIMI_SECURITY_CONFIG.MAX_API_KEY_LENGTH) return false;
|
| 53 |
+
|
| 54 |
+
return window.KIMI_SECURITY_CONFIG.API_KEY_PATTERNS.some(pattern => pattern.test(key));
|
| 55 |
+
},
|
| 56 |
+
|
| 57 |
+
validateSliderValue: (value, type) => {
|
| 58 |
+
// Use centralized config from KIMI_CONFIG
|
| 59 |
+
if (window.KIMI_CONFIG && window.KIMI_CONFIG.validate) {
|
| 60 |
+
return window.KIMI_CONFIG.validate(value, type);
|
| 61 |
+
}
|
| 62 |
+
|
| 63 |
+
// Fallback if config not available
|
| 64 |
+
const num = parseFloat(value);
|
| 65 |
+
if (isNaN(num)) return { valid: false, value: 0 };
|
| 66 |
+
|
| 67 |
+
return { valid: true, value: num };
|
| 68 |
+
}
|
| 69 |
+
};
|
| 70 |
+
|
| 71 |
+
window.KIMI_SECURITY_INITIALIZED = true;
|
| 72 |
+
|
| 73 |
+
// ===== Global Input Hardening (anti-autofill and password manager suppression) =====
|
| 74 |
+
(function setupGlobalInputHardening() {
|
| 75 |
+
try {
|
| 76 |
+
const ATTRS = {
|
| 77 |
+
autocomplete: "off",
|
| 78 |
+
autocapitalize: "none",
|
| 79 |
+
autocorrect: "off",
|
| 80 |
+
spellcheck: "false",
|
| 81 |
+
inputmode: "text",
|
| 82 |
+
"aria-autocomplete": "none",
|
| 83 |
+
"data-lpignore": "true",
|
| 84 |
+
"data-1p-ignore": "true",
|
| 85 |
+
"data-bwignore": "true",
|
| 86 |
+
"data-form-type": "other"
|
| 87 |
+
};
|
| 88 |
+
|
| 89 |
+
const API_INPUT_ID = "openrouter-api-key";
|
| 90 |
+
|
| 91 |
+
function hardenElement(el) {
|
| 92 |
+
if (!el || !(el instanceof HTMLElement)) return;
|
| 93 |
+
const tag = el.tagName;
|
| 94 |
+
if (tag !== "INPUT" && tag !== "TEXTAREA") return;
|
| 95 |
+
|
| 96 |
+
// Do not convert other inputs to password; only enforce anti-autofill attributes
|
| 97 |
+
for (const [k, v] of Object.entries(ATTRS)) {
|
| 98 |
+
try {
|
| 99 |
+
if (el.getAttribute(k) !== v) el.setAttribute(k, v);
|
| 100 |
+
} catch {}
|
| 101 |
+
}
|
| 102 |
+
|
| 103 |
+
// Special handling for the API key field: ensure it's treated as non-credential by managers
|
| 104 |
+
if (el.id === API_INPUT_ID) {
|
| 105 |
+
try {
|
| 106 |
+
// Keep password type by default for masking; JS toggler can switch to text on demand
|
| 107 |
+
if (!el.hasAttribute("type")) el.setAttribute("type", "password");
|
| 108 |
+
// Explicitly set a non-credential-ish name/value context
|
| 109 |
+
if (el.getAttribute("name") !== "openrouter_api_key") el.setAttribute("name", "openrouter_api_key");
|
| 110 |
+
if (el.getAttribute("autocomplete") !== "new-password") el.setAttribute("autocomplete", "new-password");
|
| 111 |
+
} catch {}
|
| 112 |
+
} else {
|
| 113 |
+
// For non-API inputs, if browser set type=password by heuristics, revert to text
|
| 114 |
+
try {
|
| 115 |
+
if (el.getAttribute("type") === "password" && el.id !== API_INPUT_ID) {
|
| 116 |
+
el.setAttribute("type", "text");
|
| 117 |
+
}
|
| 118 |
+
} catch {}
|
| 119 |
+
}
|
| 120 |
+
}
|
| 121 |
+
|
| 122 |
+
function hardenAll(scope = document) {
|
| 123 |
+
const nodes = scope.querySelectorAll("input, textarea");
|
| 124 |
+
nodes.forEach(hardenElement);
|
| 125 |
+
}
|
| 126 |
+
|
| 127 |
+
// Initial pass
|
| 128 |
+
if (document.readyState === "loading") {
|
| 129 |
+
document.addEventListener("DOMContentLoaded", () => hardenAll());
|
| 130 |
+
} else {
|
| 131 |
+
hardenAll();
|
| 132 |
+
}
|
| 133 |
+
|
| 134 |
+
// Observe dynamic DOM changes
|
| 135 |
+
const mo = new MutationObserver(mutations => {
|
| 136 |
+
for (const m of mutations) {
|
| 137 |
+
if (m.type === "childList") {
|
| 138 |
+
m.addedNodes.forEach(node => {
|
| 139 |
+
if (node.nodeType === 1) {
|
| 140 |
+
if (node.matches && (node.matches("input") || node.matches("textarea"))) {
|
| 141 |
+
hardenElement(node);
|
| 142 |
+
}
|
| 143 |
+
const descendants = node.querySelectorAll ? node.querySelectorAll("input, textarea") : [];
|
| 144 |
+
descendants.forEach(hardenElement);
|
| 145 |
+
}
|
| 146 |
+
});
|
| 147 |
+
}
|
| 148 |
+
}
|
| 149 |
+
});
|
| 150 |
+
try {
|
| 151 |
+
mo.observe(document.documentElement || document.body, {
|
| 152 |
+
subtree: true,
|
| 153 |
+
childList: true
|
| 154 |
+
});
|
| 155 |
+
} catch {}
|
| 156 |
+
|
| 157 |
+
// Expose for debugging if needed
|
| 158 |
+
window._kimiInputHardener = { hardenAll };
|
| 159 |
+
} catch (e) {
|
| 160 |
+
// Fail-safe: never block the app
|
| 161 |
+
console.warn("Input hardening setup error:", e);
|
| 162 |
+
}
|
| 163 |
+
})();
|
kimi-js/kimi-utils.js
ADDED
|
@@ -0,0 +1,1118 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// CENTRALIZED KIMI UTILITIES
|
| 2 |
+
// Lightweight additional helpers (Phase 1 consolidation):
|
| 3 |
+
// 1. emitAppEvent(name, detail) -> standard CustomEvent emitter with safe try/catch.
|
| 4 |
+
// 2. getCharacterTraits(char?) -> single place to fetch all personality traits (reduces repetition).
|
| 5 |
+
// 3. setI18n(element, key, params?) -> uniform attribute assignment for i18n keys.
|
| 6 |
+
// These are intentionally minimal & backward-compatible.
|
| 7 |
+
|
| 8 |
+
if (!window.emitAppEvent) {
|
| 9 |
+
window.emitAppEvent = function emitAppEvent(name, detail = {}) {
|
| 10 |
+
if (!name) return false;
|
| 11 |
+
try {
|
| 12 |
+
window.dispatchEvent(new CustomEvent(name, { detail }));
|
| 13 |
+
return true;
|
| 14 |
+
} catch {
|
| 15 |
+
return false;
|
| 16 |
+
}
|
| 17 |
+
};
|
| 18 |
+
}
|
| 19 |
+
|
| 20 |
+
if (!window.getCharacterTraits) {
|
| 21 |
+
window.getCharacterTraits = async function getCharacterTraits(character = null) {
|
| 22 |
+
try {
|
| 23 |
+
const db = window.kimiDB;
|
| 24 |
+
if (!db || typeof db.getAllPersonalityTraits !== "function") return {};
|
| 25 |
+
// Init cache structure
|
| 26 |
+
if (!window.__KIMI_TRAITS_CACHE__) {
|
| 27 |
+
const cfgTtl = (window.KIMI_TRAITS_CACHE_CONFIG && window.KIMI_TRAITS_CACHE_CONFIG.ttlMs) || window.KIMI_CONFIG?.traitsCacheTtlMs;
|
| 28 |
+
const ttl = typeof cfgTtl === "number" && cfgTtl > 0 ? cfgTtl : 120000;
|
| 29 |
+
window.__KIMI_TRAITS_CACHE__ = {
|
| 30 |
+
data: new Map(),
|
| 31 |
+
ts: new Map(),
|
| 32 |
+
ttl,
|
| 33 |
+
metrics: { hits: 0, misses: 0, invalidations: 0 }
|
| 34 |
+
}; // TTL configurable (default 2 min)
|
| 35 |
+
// Centralized trait keys constant
|
| 36 |
+
window.__KIMI_TRAIT_KEYS__ = ["affection", "playfulness", "intelligence", "empathy", "humor", "romance"];
|
| 37 |
+
}
|
| 38 |
+
const cache = window.__KIMI_TRAITS_CACHE__;
|
| 39 |
+
let char = character;
|
| 40 |
+
if (!char && typeof db.getSelectedCharacter === "function") {
|
| 41 |
+
char = await db.getSelectedCharacter();
|
| 42 |
+
}
|
| 43 |
+
if (!char) return {};
|
| 44 |
+
const now = Date.now();
|
| 45 |
+
const cached = cache.data.get(char);
|
| 46 |
+
const ts = cache.ts.get(char) || 0;
|
| 47 |
+
if (cached && now - ts < cache.ttl) {
|
| 48 |
+
cache.metrics.hits++;
|
| 49 |
+
return cached;
|
| 50 |
+
}
|
| 51 |
+
cache.metrics.misses++;
|
| 52 |
+
const traits = (await db.getAllPersonalityTraits(char)) || {};
|
| 53 |
+
cache.data.set(char, traits);
|
| 54 |
+
cache.ts.set(char, now);
|
| 55 |
+
return traits;
|
| 56 |
+
} catch {
|
| 57 |
+
return {};
|
| 58 |
+
}
|
| 59 |
+
};
|
| 60 |
+
}
|
| 61 |
+
|
| 62 |
+
if (!window.setI18n) {
|
| 63 |
+
window.setI18n = function setI18n(el, key, params = null) {
|
| 64 |
+
if (!el || !key) return;
|
| 65 |
+
try {
|
| 66 |
+
el.setAttribute("data-i18n", key);
|
| 67 |
+
if (params && typeof params === "object") {
|
| 68 |
+
el.setAttribute("data-i18n-params", JSON.stringify(params));
|
| 69 |
+
}
|
| 70 |
+
} catch {}
|
| 71 |
+
};
|
| 72 |
+
}
|
| 73 |
+
|
| 74 |
+
// Invalidate trait cache on relevant preference updates (e.g., personality changes)
|
| 75 |
+
if (window.addEventListener && !window.__KIMI_TRAITS_CACHE_LISTENER__) {
|
| 76 |
+
window.__KIMI_TRAITS_CACHE_LISTENER__ = true;
|
| 77 |
+
window.addEventListener("preferenceUpdated", ev => {
|
| 78 |
+
try {
|
| 79 |
+
if (!window.__KIMI_TRAITS_CACHE__) return;
|
| 80 |
+
const key = ev?.detail?.key;
|
| 81 |
+
const traitKeys = window.__KIMI_TRAIT_KEYS__ || ["affection", "playfulness", "intelligence", "empathy", "humor", "romance"];
|
| 82 |
+
const shouldInvalidate = !key || traitKeys.includes(String(key).toLowerCase()) || /trait|personality/i.test(key);
|
| 83 |
+
if (shouldInvalidate) {
|
| 84 |
+
window.__KIMI_TRAITS_CACHE__.data.clear();
|
| 85 |
+
window.__KIMI_TRAITS_CACHE__.ts.clear();
|
| 86 |
+
if (window.__KIMI_TRAITS_CACHE__.metrics) window.__KIMI_TRAITS_CACHE__.metrics.invalidations++;
|
| 87 |
+
}
|
| 88 |
+
} catch {}
|
| 89 |
+
});
|
| 90 |
+
}
|
| 91 |
+
|
| 92 |
+
// Debug helper
|
| 93 |
+
if (!window.kimiTraitCacheInfo) {
|
| 94 |
+
window.kimiTraitCacheInfo = function () {
|
| 95 |
+
const c = window.__KIMI_TRAITS_CACHE__;
|
| 96 |
+
if (!c) return { enabled: false };
|
| 97 |
+
return {
|
| 98 |
+
enabled: true,
|
| 99 |
+
entries: c.data.size,
|
| 100 |
+
ttlMs: c.ttl,
|
| 101 |
+
keys: [...c.data.keys()],
|
| 102 |
+
metrics: c.metrics ? { ...c.metrics } : null,
|
| 103 |
+
traitKeys: window.__KIMI_TRAIT_KEYS__ || []
|
| 104 |
+
};
|
| 105 |
+
};
|
| 106 |
+
}
|
| 107 |
+
|
| 108 |
+
// Simple runtime micro-test for cache invalidation
|
| 109 |
+
if (!window.testTraitCacheInvalidation) {
|
| 110 |
+
window.testTraitCacheInvalidation = async function () {
|
| 111 |
+
const report = { initialFetch: null, secondFetchCached: null, afterInvalidation: null, passed: false };
|
| 112 |
+
try {
|
| 113 |
+
const traits1 = await window.getCharacterTraits();
|
| 114 |
+
report.initialFetch = { keys: Object.keys(traits1).length };
|
| 115 |
+
const before = performance.now();
|
| 116 |
+
const traits2 = await window.getCharacterTraits();
|
| 117 |
+
const dur2 = performance.now() - before;
|
| 118 |
+
report.secondFetchCached = { ms: Math.round(dur2), keys: Object.keys(traits2).length };
|
| 119 |
+
// Emit fake preferenceUpdated affecting traits
|
| 120 |
+
window.emitAppEvent && window.emitAppEvent("preferenceUpdated", { key: "affection" });
|
| 121 |
+
const traits3 = await window.getCharacterTraits();
|
| 122 |
+
report.afterInvalidation = { keys: Object.keys(traits3).length };
|
| 123 |
+
report.passed = report.initialFetch.keys === report.afterInvalidation.keys;
|
| 124 |
+
} catch (e) {
|
| 125 |
+
report.error = String(e);
|
| 126 |
+
}
|
| 127 |
+
return report;
|
| 128 |
+
};
|
| 129 |
+
}
|
| 130 |
+
|
| 131 |
+
// Input validation and sanitization utilities
|
| 132 |
+
window.KimiValidationUtils = {
|
| 133 |
+
validateMessage(message) {
|
| 134 |
+
if (!message || typeof message !== "string") {
|
| 135 |
+
return { valid: false, error: "Message must be a non-empty string" };
|
| 136 |
+
}
|
| 137 |
+
const trimmed = message.trim();
|
| 138 |
+
if (!trimmed) return { valid: false, error: "Message cannot be empty" };
|
| 139 |
+
const MAX = (window.KIMI_SECURITY_CONFIG && window.KIMI_SECURITY_CONFIG.MAX_MESSAGE_LENGTH) || 5000;
|
| 140 |
+
if (trimmed.length > MAX) {
|
| 141 |
+
return { valid: false, error: `Message too long (max ${MAX} characters)` };
|
| 142 |
+
}
|
| 143 |
+
return { valid: true, sanitized: this.escapeHtml(trimmed) };
|
| 144 |
+
},
|
| 145 |
+
escapeHtml(text) {
|
| 146 |
+
const div = document.createElement("div");
|
| 147 |
+
div.textContent = text;
|
| 148 |
+
return div.innerHTML;
|
| 149 |
+
},
|
| 150 |
+
/**
|
| 151 |
+
* Format chat text with simple markdown-like syntax (secure)
|
| 152 |
+
* Supports: **bold**, *italic*, and preserves line breaks
|
| 153 |
+
* Security: All text is escaped first, then selective formatting is applied
|
| 154 |
+
*/
|
| 155 |
+
formatChatText(text) {
|
| 156 |
+
if (!text || typeof text !== "string") return "";
|
| 157 |
+
|
| 158 |
+
// First: Escape all HTML to prevent XSS
|
| 159 |
+
let escaped = this.escapeHtml(text);
|
| 160 |
+
|
| 161 |
+
// Optional: Replace em-dash with regular dash if preferred
|
| 162 |
+
escaped = escaped.replace(/—/g, "-");
|
| 163 |
+
|
| 164 |
+
// Second: Apply simple formatting (only on escaped text)
|
| 165 |
+
// **bold** -> <strong>bold</strong>
|
| 166 |
+
escaped = escaped.replace(/\*\*(.*?)\*\*/g, "<strong>$1</strong>");
|
| 167 |
+
|
| 168 |
+
// *italic* -> <em>italic</em> (but not if already inside **)
|
| 169 |
+
escaped = escaped.replace(/(?<!\*)\*([^*]+?)\*(?!\*)/g, "<em>$1</em>");
|
| 170 |
+
|
| 171 |
+
// Smart paragraph handling: only create <p> for double line breaks or real paragraphs
|
| 172 |
+
// Split by double line breaks (\n\n) to identify real paragraphs
|
| 173 |
+
const realParagraphs = escaped.split(/\n\s*\n/).filter(para => para.trim().length > 0);
|
| 174 |
+
|
| 175 |
+
if (realParagraphs.length > 1) {
|
| 176 |
+
// Multiple paragraphs found - wrap each in <p>
|
| 177 |
+
escaped = realParagraphs.map(p => `<p>${p.trim().replace(/\n/g, " ")}</p>`).join("");
|
| 178 |
+
} else {
|
| 179 |
+
// Single paragraph - just convert single \n to spaces (natural text flow)
|
| 180 |
+
escaped = escaped.replace(/\n/g, " ");
|
| 181 |
+
}
|
| 182 |
+
|
| 183 |
+
return escaped;
|
| 184 |
+
},
|
| 185 |
+
validateRange(value, key) {
|
| 186 |
+
const bounds = {
|
| 187 |
+
voiceRate: { min: 0.5, max: 2, def: 1.1 },
|
| 188 |
+
voicePitch: { min: 0.5, max: 2, def: 1.1 },
|
| 189 |
+
voiceVolume: { min: 0, max: 1, def: 0.8 },
|
| 190 |
+
llmTemperature: { min: 0, max: 1, def: 0.9 },
|
| 191 |
+
llmMaxTokens: { min: 1, max: 8192, def: 400 },
|
| 192 |
+
llmTopP: { min: 0, max: 1, def: 0.9 },
|
| 193 |
+
llmFrequencyPenalty: { min: 0, max: 2, def: 0.9 },
|
| 194 |
+
llmPresencePenalty: { min: 0, max: 2, def: 0.8 },
|
| 195 |
+
interfaceOpacity: { min: 0.1, max: 1, def: 0.8 }
|
| 196 |
+
};
|
| 197 |
+
const b = bounds[key] || { min: 0, max: 100, def: 0 };
|
| 198 |
+
const v = window.KimiSecurityUtils
|
| 199 |
+
? window.KimiSecurityUtils.validateRange(value, b.min, b.max, b.def)
|
| 200 |
+
: isNaN(parseFloat(value))
|
| 201 |
+
? b.def
|
| 202 |
+
: Math.max(b.min, Math.min(b.max, parseFloat(value)));
|
| 203 |
+
return { value: v, clamped: v !== parseFloat(value) };
|
| 204 |
+
}
|
| 205 |
+
};
|
| 206 |
+
|
| 207 |
+
// Provider utilities used across the app
|
| 208 |
+
const KimiProviderUtils = {
|
| 209 |
+
getKeyPrefForProvider(provider) {
|
| 210 |
+
// Each provider should have its own separate API key storage
|
| 211 |
+
const providerKeys = {
|
| 212 |
+
openrouter: "openrouterApiKey",
|
| 213 |
+
openai: "openaiApiKey",
|
| 214 |
+
groq: "groqApiKey",
|
| 215 |
+
together: "togetherApiKey",
|
| 216 |
+
deepseek: "deepseekApiKey",
|
| 217 |
+
"openai-compatible": "customApiKey",
|
| 218 |
+
ollama: null
|
| 219 |
+
};
|
| 220 |
+
return providerKeys[provider] || "providerApiKey";
|
| 221 |
+
},
|
| 222 |
+
async getApiKey(db, provider) {
|
| 223 |
+
if (!db) return null;
|
| 224 |
+
if (provider === "ollama") return "__local__";
|
| 225 |
+
const keyPref = this.getKeyPrefForProvider(provider);
|
| 226 |
+
return await db.getPreference(keyPref);
|
| 227 |
+
},
|
| 228 |
+
getLabelForProvider(provider) {
|
| 229 |
+
const labels = {
|
| 230 |
+
openrouter: "OpenRouter API Key",
|
| 231 |
+
openai: "OpenAI API Key",
|
| 232 |
+
groq: "Groq API Key",
|
| 233 |
+
together: "Together API Key",
|
| 234 |
+
deepseek: "DeepSeek API Key",
|
| 235 |
+
custom: "Custom API Key",
|
| 236 |
+
"openai-compatible": "API Key",
|
| 237 |
+
ollama: "API Key"
|
| 238 |
+
};
|
| 239 |
+
return labels[provider] || "API Key";
|
| 240 |
+
}
|
| 241 |
+
};
|
| 242 |
+
window.KimiProviderUtils = KimiProviderUtils;
|
| 243 |
+
// Shared provider placeholders used by UI and LLM manager. Keep in window for backward compatibility.
|
| 244 |
+
const KimiProviderPlaceholders = {
|
| 245 |
+
openrouter: "https://openrouter.ai/api/v1/chat/completions",
|
| 246 |
+
openai: "https://api.openai.com/v1/chat/completions",
|
| 247 |
+
groq: "https://api.groq.com/openai/v1/chat/completions",
|
| 248 |
+
together: "https://api.together.xyz/v1/chat/completions",
|
| 249 |
+
deepseek: "https://api.deepseek.com/chat/completions",
|
| 250 |
+
"openai-compatible": "",
|
| 251 |
+
ollama: "http://localhost:11434/api/chat"
|
| 252 |
+
};
|
| 253 |
+
window.KimiProviderPlaceholders = KimiProviderPlaceholders;
|
| 254 |
+
export { KimiProviderUtils, KimiProviderPlaceholders };
|
| 255 |
+
|
| 256 |
+
// Performance utility functions for debouncing and throttling
|
| 257 |
+
window.KimiPerformanceUtils = {
|
| 258 |
+
debounce: function (func, wait, immediate = false, context = null) {
|
| 259 |
+
let timeout;
|
| 260 |
+
let result;
|
| 261 |
+
|
| 262 |
+
return function executedFunction(...args) {
|
| 263 |
+
const later = () => {
|
| 264 |
+
timeout = null;
|
| 265 |
+
if (!immediate) {
|
| 266 |
+
result = func.apply(context || this, args);
|
| 267 |
+
}
|
| 268 |
+
};
|
| 269 |
+
|
| 270 |
+
const callNow = immediate && !timeout;
|
| 271 |
+
clearTimeout(timeout);
|
| 272 |
+
timeout = setTimeout(later, wait);
|
| 273 |
+
|
| 274 |
+
if (callNow) {
|
| 275 |
+
result = func.apply(context || this, args);
|
| 276 |
+
}
|
| 277 |
+
|
| 278 |
+
return result;
|
| 279 |
+
};
|
| 280 |
+
},
|
| 281 |
+
|
| 282 |
+
throttle: function (func, limit, options = {}) {
|
| 283 |
+
const { leading = true, trailing = true } = options;
|
| 284 |
+
let inThrottle;
|
| 285 |
+
let lastFunc;
|
| 286 |
+
let lastRan;
|
| 287 |
+
|
| 288 |
+
return function (...args) {
|
| 289 |
+
if (!inThrottle) {
|
| 290 |
+
if (leading) {
|
| 291 |
+
func.apply(this, args);
|
| 292 |
+
}
|
| 293 |
+
lastRan = Date.now();
|
| 294 |
+
inThrottle = true;
|
| 295 |
+
} else {
|
| 296 |
+
clearTimeout(lastFunc);
|
| 297 |
+
lastFunc = setTimeout(
|
| 298 |
+
() => {
|
| 299 |
+
if (trailing && Date.now() - lastRan >= limit) {
|
| 300 |
+
func.apply(this, args);
|
| 301 |
+
lastRan = Date.now();
|
| 302 |
+
}
|
| 303 |
+
},
|
| 304 |
+
limit - (Date.now() - lastRan)
|
| 305 |
+
);
|
| 306 |
+
}
|
| 307 |
+
|
| 308 |
+
setTimeout(() => (inThrottle = false), limit);
|
| 309 |
+
};
|
| 310 |
+
}
|
| 311 |
+
};
|
| 312 |
+
|
| 313 |
+
// Language management utilities
|
| 314 |
+
window.KimiLanguageUtils = {
|
| 315 |
+
// Default language priority: auto -> user preference -> browser -> fr
|
| 316 |
+
async getLanguage() {
|
| 317 |
+
if (window.kimiDB && window.kimiDB.getPreference) {
|
| 318 |
+
const userLang = await window.kimiDB.getPreference("selectedLanguage", null);
|
| 319 |
+
if (userLang && userLang !== "auto") {
|
| 320 |
+
return userLang;
|
| 321 |
+
}
|
| 322 |
+
}
|
| 323 |
+
|
| 324 |
+
// Auto-detect from browser
|
| 325 |
+
const browserLang = navigator.language?.split("-")[0] || "en";
|
| 326 |
+
const supportedLangs = ["en", "fr", "es", "de", "it", "ja", "zh"];
|
| 327 |
+
return supportedLangs.includes(browserLang) ? browserLang : "en";
|
| 328 |
+
},
|
| 329 |
+
|
| 330 |
+
// Auto-detect language from text content
|
| 331 |
+
detectLanguage(text) {
|
| 332 |
+
if (!text) return "en";
|
| 333 |
+
|
| 334 |
+
if (/[àâäéèêëîïôöùûüÿç]/i.test(text)) return "fr";
|
| 335 |
+
if (/[äöüß]/i.test(text)) return "de";
|
| 336 |
+
if (/[ñáéíóúü]/i.test(text)) return "es";
|
| 337 |
+
if (/[àèìòù]/i.test(text)) return "it";
|
| 338 |
+
if (/[\u3040-\u309f\u30a0-\u30ff\u4e00-\u9faf]/i.test(text)) return "ja";
|
| 339 |
+
if (/[\u4e00-\u9fff]/i.test(text)) return "zh";
|
| 340 |
+
return "en";
|
| 341 |
+
},
|
| 342 |
+
// Normalize language codes to a primary subtag (e.g. 'en-US' -> 'en', 'us:en' -> 'en')
|
| 343 |
+
normalizeLanguageCode(raw) {
|
| 344 |
+
if (!raw) return "";
|
| 345 |
+
try {
|
| 346 |
+
let norm = String(raw).toLowerCase();
|
| 347 |
+
if (norm.includes(":")) {
|
| 348 |
+
const parts = norm.split(":");
|
| 349 |
+
norm = parts[parts.length - 1];
|
| 350 |
+
}
|
| 351 |
+
norm = norm.replace("_", "-");
|
| 352 |
+
if (norm.includes("-")) norm = norm.split("-")[0];
|
| 353 |
+
return norm;
|
| 354 |
+
} catch (e) {
|
| 355 |
+
return "";
|
| 356 |
+
}
|
| 357 |
+
}
|
| 358 |
+
};
|
| 359 |
+
|
| 360 |
+
// Security and validation utilities
|
| 361 |
+
class KimiSecurityUtils {
|
| 362 |
+
static sanitizeInput(input, type = "text") {
|
| 363 |
+
if (typeof input !== "string") return "";
|
| 364 |
+
|
| 365 |
+
switch (type) {
|
| 366 |
+
case "html":
|
| 367 |
+
return input.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
| 368 |
+
case "number":
|
| 369 |
+
const num = parseFloat(input);
|
| 370 |
+
return isNaN(num) ? 0 : num;
|
| 371 |
+
case "integer":
|
| 372 |
+
const int = parseInt(input, 10);
|
| 373 |
+
return isNaN(int) ? 0 : int;
|
| 374 |
+
case "url":
|
| 375 |
+
try {
|
| 376 |
+
new URL(input);
|
| 377 |
+
return input;
|
| 378 |
+
} catch {
|
| 379 |
+
return "";
|
| 380 |
+
}
|
| 381 |
+
default:
|
| 382 |
+
return input.trim();
|
| 383 |
+
}
|
| 384 |
+
}
|
| 385 |
+
|
| 386 |
+
static validateRange(value, min, max, defaultValue = 0) {
|
| 387 |
+
const num = parseFloat(value);
|
| 388 |
+
if (isNaN(num)) return defaultValue;
|
| 389 |
+
return Math.max(min, Math.min(max, num));
|
| 390 |
+
}
|
| 391 |
+
|
| 392 |
+
static validateApiKey(key) {
|
| 393 |
+
if (!key || typeof key !== "string") return false;
|
| 394 |
+
if (window.KIMI_VALIDATORS && typeof window.KIMI_VALIDATORS.validateApiKey === "function") {
|
| 395 |
+
return !!window.KIMI_VALIDATORS.validateApiKey(key.trim());
|
| 396 |
+
}
|
| 397 |
+
return key.trim().length > 10 && (key.startsWith("sk-") || key.startsWith("sk-or-"));
|
| 398 |
+
}
|
| 399 |
+
}
|
| 400 |
+
|
| 401 |
+
// Cache management for better performance
|
| 402 |
+
class KimiCacheManager {
|
| 403 |
+
constructor(maxAge = 300000) {
|
| 404 |
+
// 5 minutes default
|
| 405 |
+
this.cache = new Map();
|
| 406 |
+
this.maxAge = maxAge;
|
| 407 |
+
}
|
| 408 |
+
|
| 409 |
+
set(key, value, customMaxAge = null) {
|
| 410 |
+
const maxAge = customMaxAge || this.maxAge;
|
| 411 |
+
this.cache.set(key, {
|
| 412 |
+
value,
|
| 413 |
+
timestamp: Date.now(),
|
| 414 |
+
maxAge
|
| 415 |
+
});
|
| 416 |
+
|
| 417 |
+
// Clean old entries periodically
|
| 418 |
+
if (this.cache.size > 100) {
|
| 419 |
+
this.cleanup();
|
| 420 |
+
}
|
| 421 |
+
}
|
| 422 |
+
|
| 423 |
+
get(key) {
|
| 424 |
+
const entry = this.cache.get(key);
|
| 425 |
+
if (!entry) return null;
|
| 426 |
+
|
| 427 |
+
const now = Date.now();
|
| 428 |
+
if (now - entry.timestamp > entry.maxAge) {
|
| 429 |
+
this.cache.delete(key);
|
| 430 |
+
return null;
|
| 431 |
+
}
|
| 432 |
+
|
| 433 |
+
return entry.value;
|
| 434 |
+
}
|
| 435 |
+
|
| 436 |
+
has(key) {
|
| 437 |
+
return this.get(key) !== null;
|
| 438 |
+
}
|
| 439 |
+
|
| 440 |
+
delete(key) {
|
| 441 |
+
return this.cache.delete(key);
|
| 442 |
+
}
|
| 443 |
+
|
| 444 |
+
clear() {
|
| 445 |
+
this.cache.clear();
|
| 446 |
+
}
|
| 447 |
+
|
| 448 |
+
cleanup() {
|
| 449 |
+
const now = Date.now();
|
| 450 |
+
for (const [key, entry] of this.cache.entries()) {
|
| 451 |
+
if (now - entry.timestamp > entry.maxAge) {
|
| 452 |
+
this.cache.delete(key);
|
| 453 |
+
}
|
| 454 |
+
}
|
| 455 |
+
}
|
| 456 |
+
|
| 457 |
+
getStats() {
|
| 458 |
+
return {
|
| 459 |
+
size: this.cache.size,
|
| 460 |
+
keys: Array.from(this.cache.keys())
|
| 461 |
+
};
|
| 462 |
+
}
|
| 463 |
+
}
|
| 464 |
+
|
| 465 |
+
class KimiBaseManager {
|
| 466 |
+
constructor() {
|
| 467 |
+
// Common base for all managers
|
| 468 |
+
}
|
| 469 |
+
|
| 470 |
+
// Utility method to format file size
|
| 471 |
+
formatFileSize(bytes) {
|
| 472 |
+
if (bytes === 0) return "0 Bytes";
|
| 473 |
+
const k = 1024;
|
| 474 |
+
const sizes = ["Bytes", "KB", "MB", "GB", "TB"];
|
| 475 |
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
| 476 |
+
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + " " + sizes[i];
|
| 477 |
+
}
|
| 478 |
+
|
| 479 |
+
// Utility method for error handling
|
| 480 |
+
handleError(error, context = "Operation") {
|
| 481 |
+
console.error(`Error in ${context}:`, error);
|
| 482 |
+
}
|
| 483 |
+
|
| 484 |
+
// Utility method to wait
|
| 485 |
+
async delay(ms) {
|
| 486 |
+
return new Promise(resolve => setTimeout(resolve, ms));
|
| 487 |
+
}
|
| 488 |
+
}
|
| 489 |
+
|
| 490 |
+
// KimiVideoManager implementation moved to ./kimi-videos.js
|
| 491 |
+
// Ensure the video manager module is evaluated so it registers itself on window
|
| 492 |
+
import "./kimi-videos.js";
|
| 493 |
+
|
| 494 |
+
function getMoodCategoryFromPersonality(traits) {
|
| 495 |
+
// Use unified emotion system
|
| 496 |
+
if (window.kimiEmotionSystem) {
|
| 497 |
+
return window.kimiEmotionSystem.getMoodCategoryFromPersonality(traits);
|
| 498 |
+
}
|
| 499 |
+
|
| 500 |
+
// Fallback (should not be reached) - must match emotion system calculation
|
| 501 |
+
const keys = ["affection", "romance", "empathy", "playfulness", "humor", "intelligence"];
|
| 502 |
+
let sum = 0;
|
| 503 |
+
let count = 0;
|
| 504 |
+
keys.forEach(key => {
|
| 505 |
+
if (typeof traits[key] === "number") {
|
| 506 |
+
sum += traits[key];
|
| 507 |
+
count++;
|
| 508 |
+
}
|
| 509 |
+
});
|
| 510 |
+
const avg = count > 0 ? sum / count : 50;
|
| 511 |
+
|
| 512 |
+
if (avg >= 80) return "speakingPositive";
|
| 513 |
+
if (avg >= 60) return "neutral";
|
| 514 |
+
if (avg >= 40) return "neutral";
|
| 515 |
+
if (avg >= 20) return "speakingNegative";
|
| 516 |
+
return "speakingNegative";
|
| 517 |
+
}
|
| 518 |
+
|
| 519 |
+
// Expose personality → mood helper for video manager
|
| 520 |
+
window.getMoodCategoryFromPersonality = getMoodCategoryFromPersonality;
|
| 521 |
+
|
| 522 |
+
// Centralized initialization manager
|
| 523 |
+
class KimiInitManager {
|
| 524 |
+
constructor() {
|
| 525 |
+
this.managers = new Map();
|
| 526 |
+
this.initOrder = [];
|
| 527 |
+
this.isInitialized = false;
|
| 528 |
+
}
|
| 529 |
+
|
| 530 |
+
register(name, managerFactory, dependencies = [], delay = 0) {
|
| 531 |
+
this.managers.set(name, {
|
| 532 |
+
factory: managerFactory,
|
| 533 |
+
dependencies,
|
| 534 |
+
delay,
|
| 535 |
+
instance: null,
|
| 536 |
+
initialized: false
|
| 537 |
+
});
|
| 538 |
+
}
|
| 539 |
+
|
| 540 |
+
async initializeAll() {
|
| 541 |
+
if (this.isInitialized) return;
|
| 542 |
+
|
| 543 |
+
// Sort by dependencies and delays
|
| 544 |
+
const sortedManagers = this.topologicalSort();
|
| 545 |
+
|
| 546 |
+
for (const managerName of sortedManagers) {
|
| 547 |
+
await this.initializeManager(managerName);
|
| 548 |
+
}
|
| 549 |
+
|
| 550 |
+
this.isInitialized = true;
|
| 551 |
+
}
|
| 552 |
+
async initializeManager(name) {
|
| 553 |
+
const manager = this.managers.get(name);
|
| 554 |
+
if (!manager || manager.initialized) return;
|
| 555 |
+
|
| 556 |
+
// Wait for dependencies
|
| 557 |
+
for (const dep of manager.dependencies) {
|
| 558 |
+
await this.initializeManager(dep);
|
| 559 |
+
}
|
| 560 |
+
|
| 561 |
+
// Apply delay if necessary
|
| 562 |
+
if (manager.delay > 0) {
|
| 563 |
+
await new Promise(resolve => setTimeout(resolve, manager.delay));
|
| 564 |
+
}
|
| 565 |
+
|
| 566 |
+
try {
|
| 567 |
+
manager.instance = await manager.factory();
|
| 568 |
+
manager.initialized = true;
|
| 569 |
+
} catch (error) {
|
| 570 |
+
console.error(`Error during initialization of ${name}:`, error);
|
| 571 |
+
throw error;
|
| 572 |
+
}
|
| 573 |
+
}
|
| 574 |
+
|
| 575 |
+
topologicalSort() {
|
| 576 |
+
// Simple implementation of topological sort
|
| 577 |
+
const sorted = [];
|
| 578 |
+
const visited = new Set();
|
| 579 |
+
const temp = new Set();
|
| 580 |
+
|
| 581 |
+
const visit = name => {
|
| 582 |
+
if (temp.has(name)) {
|
| 583 |
+
throw new Error(`Circular dependency detected: ${name}`);
|
| 584 |
+
}
|
| 585 |
+
if (visited.has(name)) return;
|
| 586 |
+
|
| 587 |
+
temp.add(name);
|
| 588 |
+
const manager = this.managers.get(name);
|
| 589 |
+
|
| 590 |
+
for (const dep of manager.dependencies) {
|
| 591 |
+
visit(dep);
|
| 592 |
+
}
|
| 593 |
+
|
| 594 |
+
temp.delete(name);
|
| 595 |
+
visited.add(name);
|
| 596 |
+
sorted.push(name);
|
| 597 |
+
};
|
| 598 |
+
|
| 599 |
+
for (const name of this.managers.keys()) {
|
| 600 |
+
visit(name);
|
| 601 |
+
}
|
| 602 |
+
|
| 603 |
+
return sorted;
|
| 604 |
+
}
|
| 605 |
+
|
| 606 |
+
getInstance(name) {
|
| 607 |
+
const manager = this.managers.get(name);
|
| 608 |
+
return manager ? manager.instance : null;
|
| 609 |
+
}
|
| 610 |
+
}
|
| 611 |
+
|
| 612 |
+
// Utility class for DOM manipulations
|
| 613 |
+
class KimiDOMUtils {
|
| 614 |
+
static get(selector) {
|
| 615 |
+
return document.querySelector(selector);
|
| 616 |
+
}
|
| 617 |
+
static getAll(selector) {
|
| 618 |
+
return document.querySelectorAll(selector);
|
| 619 |
+
}
|
| 620 |
+
static setText(selector, text) {
|
| 621 |
+
const el = this.get(selector);
|
| 622 |
+
if (el) el.textContent = text;
|
| 623 |
+
}
|
| 624 |
+
static setValue(selector, value) {
|
| 625 |
+
const el = this.get(selector);
|
| 626 |
+
if (el) el.value = value;
|
| 627 |
+
}
|
| 628 |
+
static show(selector) {
|
| 629 |
+
const el = this.get(selector);
|
| 630 |
+
if (el) el.style.display = "";
|
| 631 |
+
}
|
| 632 |
+
static hide(selector) {
|
| 633 |
+
const el = this.get(selector);
|
| 634 |
+
if (el) el.style.display = "none";
|
| 635 |
+
}
|
| 636 |
+
static toggle(selector) {
|
| 637 |
+
const el = this.get(selector);
|
| 638 |
+
if (el) el.style.display = el.style.display === "none" ? "" : "none";
|
| 639 |
+
}
|
| 640 |
+
static addClass(selector, className) {
|
| 641 |
+
const el = this.get(selector);
|
| 642 |
+
if (el) el.classList.add(className);
|
| 643 |
+
}
|
| 644 |
+
static removeClass(selector, className) {
|
| 645 |
+
const el = this.get(selector);
|
| 646 |
+
if (el) el.classList.remove(className);
|
| 647 |
+
}
|
| 648 |
+
static transition(selector, property, value, duration = 300) {
|
| 649 |
+
const el = this.get(selector);
|
| 650 |
+
if (el) {
|
| 651 |
+
el.style.transition = property + " " + duration + "ms";
|
| 652 |
+
el.style[property] = value;
|
| 653 |
+
}
|
| 654 |
+
}
|
| 655 |
+
}
|
| 656 |
+
|
| 657 |
+
// Déclaration complète de la classe KimiOverlayManager
|
| 658 |
+
class KimiOverlayManager {
|
| 659 |
+
constructor() {
|
| 660 |
+
this.overlays = {};
|
| 661 |
+
this._initAll();
|
| 662 |
+
}
|
| 663 |
+
_initAll() {
|
| 664 |
+
const overlayIds = ["chat-container", "settings-overlay", "help-overlay"];
|
| 665 |
+
overlayIds.forEach(id => {
|
| 666 |
+
const el = document.getElementById(id);
|
| 667 |
+
if (el) {
|
| 668 |
+
this.overlays[id] = el;
|
| 669 |
+
if (id !== "chat-container") {
|
| 670 |
+
el.addEventListener("click", e => {
|
| 671 |
+
if (e.target === el) {
|
| 672 |
+
this.close(id);
|
| 673 |
+
}
|
| 674 |
+
});
|
| 675 |
+
}
|
| 676 |
+
}
|
| 677 |
+
});
|
| 678 |
+
}
|
| 679 |
+
open(name) {
|
| 680 |
+
const el = this.overlays[name];
|
| 681 |
+
if (el) el.classList.add("visible");
|
| 682 |
+
}
|
| 683 |
+
close(name) {
|
| 684 |
+
const el = this.overlays[name];
|
| 685 |
+
if (el) el.classList.remove("visible");
|
| 686 |
+
// Ensure background video resumes after closing any overlay
|
| 687 |
+
const kv = window.kimiVideo;
|
| 688 |
+
if (kv && kv.activeVideo) {
|
| 689 |
+
try {
|
| 690 |
+
const v = kv.activeVideo;
|
| 691 |
+
if (v.ended) {
|
| 692 |
+
if (typeof kv.returnToNeutral === "function") kv.returnToNeutral();
|
| 693 |
+
} else if (v.paused) {
|
| 694 |
+
v.play().catch(() => {
|
| 695 |
+
if (typeof kv.returnToNeutral === "function") kv.returnToNeutral();
|
| 696 |
+
});
|
| 697 |
+
}
|
| 698 |
+
} catch {}
|
| 699 |
+
}
|
| 700 |
+
}
|
| 701 |
+
toggle(name) {
|
| 702 |
+
const el = this.overlays[name];
|
| 703 |
+
if (el) el.classList.toggle("visible");
|
| 704 |
+
}
|
| 705 |
+
isOpen(name) {
|
| 706 |
+
const el = this.overlays[name];
|
| 707 |
+
return el ? el.classList.contains("visible") : false;
|
| 708 |
+
}
|
| 709 |
+
}
|
| 710 |
+
|
| 711 |
+
function getCharacterInfo(characterName) {
|
| 712 |
+
return window.KIMI_CHARACTERS[characterName] || window.KIMI_CHARACTERS.kimi;
|
| 713 |
+
}
|
| 714 |
+
|
| 715 |
+
// Restauration de la classe KimiTabManager
|
| 716 |
+
class KimiTabManager {
|
| 717 |
+
constructor(options = {}) {
|
| 718 |
+
this.settingsOverlay = document.getElementById("settings-overlay");
|
| 719 |
+
this.settingsTabs = document.querySelectorAll(".settings-tab");
|
| 720 |
+
this.tabContents = document.querySelectorAll(".tab-content");
|
| 721 |
+
this.settingsContent = document.querySelector(".settings-content");
|
| 722 |
+
this.onTabChange = options.onTabChange || null;
|
| 723 |
+
this.resizeObserver = null;
|
| 724 |
+
// Guard flag to batch ResizeObserver callbacks within a frame
|
| 725 |
+
this._resizeRafScheduled = false;
|
| 726 |
+
this.init();
|
| 727 |
+
}
|
| 728 |
+
|
| 729 |
+
init() {
|
| 730 |
+
this.settingsTabs.forEach(tab => {
|
| 731 |
+
tab.addEventListener("click", () => {
|
| 732 |
+
this.activateTab(tab.dataset.tab);
|
| 733 |
+
});
|
| 734 |
+
});
|
| 735 |
+
const activeTab = document.querySelector(".settings-tab.active");
|
| 736 |
+
if (activeTab) this.activateTab(activeTab.dataset.tab);
|
| 737 |
+
this.setupResizeObserver();
|
| 738 |
+
this.setupModalObserver();
|
| 739 |
+
}
|
| 740 |
+
|
| 741 |
+
activateTab(tabName) {
|
| 742 |
+
this.settingsTabs.forEach(tab => {
|
| 743 |
+
if (tab.dataset.tab === tabName) tab.classList.add("active");
|
| 744 |
+
else tab.classList.remove("active");
|
| 745 |
+
});
|
| 746 |
+
this.tabContents.forEach(content => {
|
| 747 |
+
if (content.dataset.tab === tabName) content.classList.add("active");
|
| 748 |
+
else content.classList.remove("active");
|
| 749 |
+
});
|
| 750 |
+
// Ensure the content scroll resets to the top when changing tabs
|
| 751 |
+
if (this.settingsContent) {
|
| 752 |
+
this.settingsContent.scrollTop = 0;
|
| 753 |
+
// Defer once to handle layout updates after class toggles
|
| 754 |
+
window.requestAnimationFrame(() => {
|
| 755 |
+
this.settingsContent.scrollTop = 0;
|
| 756 |
+
});
|
| 757 |
+
}
|
| 758 |
+
if (this.onTabChange) this.onTabChange(tabName);
|
| 759 |
+
setTimeout(() => this.adjustTabsForScrollbar(), 100);
|
| 760 |
+
if (window.innerWidth <= 768) {
|
| 761 |
+
const tab = Array.from(this.settingsTabs).find(t => t.dataset.tab === tabName);
|
| 762 |
+
if (tab) tab.scrollIntoView({ behavior: "smooth", block: "nearest", inline: "center" });
|
| 763 |
+
}
|
| 764 |
+
}
|
| 765 |
+
|
| 766 |
+
setupResizeObserver() {
|
| 767 |
+
if ("ResizeObserver" in window && this.settingsContent) {
|
| 768 |
+
this.resizeObserver = new ResizeObserver(() => {
|
| 769 |
+
// Defer to next animation frame to avoid ResizeObserver loop warnings
|
| 770 |
+
if (this._resizeRafScheduled) return;
|
| 771 |
+
this._resizeRafScheduled = true;
|
| 772 |
+
window.requestAnimationFrame(() => {
|
| 773 |
+
this._resizeRafScheduled = false;
|
| 774 |
+
this.adjustTabsForScrollbar();
|
| 775 |
+
});
|
| 776 |
+
});
|
| 777 |
+
this.resizeObserver.observe(this.settingsContent);
|
| 778 |
+
}
|
| 779 |
+
}
|
| 780 |
+
|
| 781 |
+
setupModalObserver() {
|
| 782 |
+
if (!this.settingsOverlay) return;
|
| 783 |
+
const observer = new MutationObserver(mutations => {
|
| 784 |
+
mutations.forEach(mutation => {
|
| 785 |
+
if (mutation.type === "attributes" && mutation.attributeName === "class") {
|
| 786 |
+
if (this.settingsOverlay.classList.contains("visible")) {
|
| 787 |
+
// Reset scroll to top when the settings modal opens
|
| 788 |
+
if (this.settingsContent) {
|
| 789 |
+
this.settingsContent.scrollTop = 0;
|
| 790 |
+
window.requestAnimationFrame(() => {
|
| 791 |
+
this.settingsContent.scrollTop = 0;
|
| 792 |
+
});
|
| 793 |
+
}
|
| 794 |
+
}
|
| 795 |
+
}
|
| 796 |
+
});
|
| 797 |
+
});
|
| 798 |
+
observer.observe(this.settingsOverlay, { attributes: true, attributeFilter: ["class"] });
|
| 799 |
+
}
|
| 800 |
+
|
| 801 |
+
adjustTabsForScrollbar() {
|
| 802 |
+
if (!this.settingsContent || !this.settingsTabs.length) return;
|
| 803 |
+
const tabsContainer = document.querySelector(".settings-tabs");
|
| 804 |
+
const hasVerticalScrollbar = this.settingsContent.scrollHeight > this.settingsContent.clientHeight;
|
| 805 |
+
if (hasVerticalScrollbar) {
|
| 806 |
+
const scrollbarWidth = this.settingsContent.offsetWidth - this.settingsContent.clientWidth;
|
| 807 |
+
tabsContainer.classList.add("compressed");
|
| 808 |
+
tabsContainer.style.paddingRight = `${Math.max(scrollbarWidth, 8)}px`;
|
| 809 |
+
tabsContainer.style.boxSizing = "border-box";
|
| 810 |
+
const tabs = tabsContainer.querySelectorAll(".settings-tab");
|
| 811 |
+
const availableWidth = tabsContainer.clientWidth - scrollbarWidth;
|
| 812 |
+
const tabCount = tabs.length;
|
| 813 |
+
const idealTabWidth = availableWidth / tabCount;
|
| 814 |
+
tabs.forEach(tab => {
|
| 815 |
+
if (idealTabWidth < 140) {
|
| 816 |
+
tab.style.fontSize = "0.85rem";
|
| 817 |
+
tab.style.padding = "14px 10px";
|
| 818 |
+
} else if (idealTabWidth < 160) {
|
| 819 |
+
tab.style.fontSize = "0.95rem";
|
| 820 |
+
tab.style.padding = "15px 12px";
|
| 821 |
+
} else {
|
| 822 |
+
tab.style.fontSize = "1rem";
|
| 823 |
+
tab.style.padding = "16px 16px";
|
| 824 |
+
}
|
| 825 |
+
});
|
| 826 |
+
} else {
|
| 827 |
+
tabsContainer.classList.remove("compressed");
|
| 828 |
+
tabsContainer.style.paddingRight = "";
|
| 829 |
+
tabsContainer.style.boxSizing = "";
|
| 830 |
+
const tabs = tabsContainer.querySelectorAll(".settings-tab");
|
| 831 |
+
tabs.forEach(tab => {
|
| 832 |
+
tab.style.fontSize = "";
|
| 833 |
+
tab.style.padding = "";
|
| 834 |
+
});
|
| 835 |
+
}
|
| 836 |
+
}
|
| 837 |
+
}
|
| 838 |
+
|
| 839 |
+
class KimiUIEventManager {
|
| 840 |
+
constructor() {
|
| 841 |
+
this.events = [];
|
| 842 |
+
}
|
| 843 |
+
addEvent(target, type, handler, options) {
|
| 844 |
+
target.addEventListener(type, handler, options);
|
| 845 |
+
this.events.push({ target, type, handler, options });
|
| 846 |
+
}
|
| 847 |
+
removeAll() {
|
| 848 |
+
for (const { target, type, handler, options } of this.events) {
|
| 849 |
+
target.removeEventListener(type, handler, options);
|
| 850 |
+
}
|
| 851 |
+
this.events = [];
|
| 852 |
+
}
|
| 853 |
+
}
|
| 854 |
+
|
| 855 |
+
class KimiFormManager {
|
| 856 |
+
constructor(options = {}) {
|
| 857 |
+
this.db = options.db || null;
|
| 858 |
+
this.memory = options.memory || null;
|
| 859 |
+
this._autoInit = options.autoInit === true;
|
| 860 |
+
if (this._autoInit) {
|
| 861 |
+
this._initSliders();
|
| 862 |
+
}
|
| 863 |
+
}
|
| 864 |
+
init() {
|
| 865 |
+
this._initSliders();
|
| 866 |
+
}
|
| 867 |
+
_initSliders() {
|
| 868 |
+
document.querySelectorAll(".kimi-slider").forEach(slider => {
|
| 869 |
+
const valueSpan = document.getElementById(slider.id + "-value");
|
| 870 |
+
if (valueSpan) valueSpan.textContent = slider.value;
|
| 871 |
+
// Only update visible value; side-effects handled by specialized listeners
|
| 872 |
+
slider.addEventListener("input", () => {
|
| 873 |
+
if (valueSpan) valueSpan.textContent = slider.value;
|
| 874 |
+
});
|
| 875 |
+
});
|
| 876 |
+
}
|
| 877 |
+
}
|
| 878 |
+
|
| 879 |
+
class KimiUIStateManager {
|
| 880 |
+
constructor() {
|
| 881 |
+
this.state = {
|
| 882 |
+
overlays: {},
|
| 883 |
+
activeTab: null,
|
| 884 |
+
favorability: 65,
|
| 885 |
+
transcript: "",
|
| 886 |
+
chatOpen: false,
|
| 887 |
+
settingsOpen: false,
|
| 888 |
+
micActive: false,
|
| 889 |
+
sliders: {}
|
| 890 |
+
};
|
| 891 |
+
this.overlayManager = window.kimiOverlayManager || null;
|
| 892 |
+
this.tabManager = window.kimiTabManager || null;
|
| 893 |
+
this.formManager = window.kimiFormManager || null;
|
| 894 |
+
}
|
| 895 |
+
setOverlay(name, visible) {
|
| 896 |
+
this.state.overlays[name] = visible;
|
| 897 |
+
if (this.overlayManager) {
|
| 898 |
+
if (visible) this.overlayManager.open(name);
|
| 899 |
+
else this.overlayManager.close(name);
|
| 900 |
+
}
|
| 901 |
+
}
|
| 902 |
+
setActiveTab(tabName) {
|
| 903 |
+
this.state.activeTab = tabName;
|
| 904 |
+
if (this.tabManager) this.tabManager.activateTab(tabName);
|
| 905 |
+
}
|
| 906 |
+
/**
|
| 907 |
+
* @deprecated Prefer calling updateGlobalPersonalityUI() after updating traits.
|
| 908 |
+
* This direct setter will be removed in a future cleanup.
|
| 909 |
+
*/
|
| 910 |
+
setPersonalityAverage(value) {
|
| 911 |
+
const v = Number(value) || 0;
|
| 912 |
+
const clamped = Math.max(0, Math.min(100, v));
|
| 913 |
+
this.state.favorability = clamped;
|
| 914 |
+
window.KimiDOMUtils.setText("#favorability-text", `${clamped.toFixed(2)}%`);
|
| 915 |
+
window.KimiDOMUtils.get("#favorability-bar").style.width = `${clamped}%`;
|
| 916 |
+
}
|
| 917 |
+
/**
|
| 918 |
+
* @deprecated Use setPersonalityAverage() (itself deprecated) or updateGlobalPersonalityUI().
|
| 919 |
+
*/
|
| 920 |
+
setFavorability(value) {
|
| 921 |
+
this.setPersonalityAverage(value);
|
| 922 |
+
}
|
| 923 |
+
async setTranscript(text) {
|
| 924 |
+
this.state.transcript = text;
|
| 925 |
+
// Always use the proper transcript management via VoiceManager
|
| 926 |
+
if (window.kimiVoiceManager && window.kimiVoiceManager.updateTranscriptVisibility) {
|
| 927 |
+
await window.kimiVoiceManager.updateTranscriptVisibility(!!text, text);
|
| 928 |
+
} else {
|
| 929 |
+
console.warn("VoiceManager not available - transcript display may not work properly");
|
| 930 |
+
}
|
| 931 |
+
}
|
| 932 |
+
setChatOpen(open) {
|
| 933 |
+
this.state.chatOpen = open;
|
| 934 |
+
this.setOverlay("chat-container", open);
|
| 935 |
+
}
|
| 936 |
+
setSettingsOpen(open) {
|
| 937 |
+
this.state.settingsOpen = open;
|
| 938 |
+
this.setOverlay("settings-overlay", open);
|
| 939 |
+
}
|
| 940 |
+
setMicActive(active) {
|
| 941 |
+
this.state.micActive = active;
|
| 942 |
+
window.KimiDOMUtils.get("#mic-button").classList.toggle("active", active);
|
| 943 |
+
}
|
| 944 |
+
setSlider(id, value) {
|
| 945 |
+
this.state.sliders[id] = value;
|
| 946 |
+
if (this.formManager) {
|
| 947 |
+
const slider = document.getElementById(id);
|
| 948 |
+
if (slider) slider.value = value;
|
| 949 |
+
const valueSpan = document.getElementById(id + "-value");
|
| 950 |
+
if (valueSpan) valueSpan.textContent = value;
|
| 951 |
+
}
|
| 952 |
+
}
|
| 953 |
+
getState() {
|
| 954 |
+
return { ...this.state };
|
| 955 |
+
}
|
| 956 |
+
}
|
| 957 |
+
|
| 958 |
+
// SIMPLE Fallback management - BASIC ONLY
|
| 959 |
+
window.KimiFallbackManager = {
|
| 960 |
+
getFallbackMessage: function (errorType, customMessage = null) {
|
| 961 |
+
const i18n = window.kimiI18nManager;
|
| 962 |
+
|
| 963 |
+
// If i18n is available, try to get translated message
|
| 964 |
+
if (i18n && typeof i18n.t === "function") {
|
| 965 |
+
if (customMessage) {
|
| 966 |
+
const translated = i18n.t(customMessage);
|
| 967 |
+
if (translated && translated !== customMessage) {
|
| 968 |
+
return translated;
|
| 969 |
+
}
|
| 970 |
+
}
|
| 971 |
+
|
| 972 |
+
const translationKey = `fallback_${errorType}`;
|
| 973 |
+
const translated = i18n.t(translationKey);
|
| 974 |
+
if (translated && translated !== translationKey) {
|
| 975 |
+
return translated;
|
| 976 |
+
}
|
| 977 |
+
}
|
| 978 |
+
|
| 979 |
+
// Fallback to hardcoded messages in multiple languages
|
| 980 |
+
const fallbacks = {
|
| 981 |
+
api_missing: {
|
| 982 |
+
fr: "Pour discuter avec moi, ajoute ta clé API du provider choisi dans les paramètres ! 💕",
|
| 983 |
+
en: "To chat with me, add your selected provider API key in settings! 💕",
|
| 984 |
+
es: "Para chatear conmigo, agrega la clave API de tu proveedor en configuración! 💕",
|
| 985 |
+
de: "Um mit mir zu chatten, füge deinen Anbieter-API-Schlüssel in den Einstellungen hinzu! 💕",
|
| 986 |
+
it: "Per chattare con me, aggiungi la chiave API del provider nelle impostazioni! 💕"
|
| 987 |
+
},
|
| 988 |
+
api_error: {
|
| 989 |
+
fr: "Désolée, le service IA est temporairement indisponible. Veuillez réessayer plus tard.",
|
| 990 |
+
en: "Sorry, the AI service is temporarily unavailable. Please try again later.",
|
| 991 |
+
es: "Lo siento, el servicio de IA no está disponible temporalmente. Inténtalo de nuevo más tarde.",
|
| 992 |
+
de: "Entschuldigung, der KI-Service ist vorübergehend nicht verfügbar. Bitte versuchen Sie es später erneut.",
|
| 993 |
+
it: "Spiacente, il servizio IA è temporaneamente non disponibile. Riprova più tardi."
|
| 994 |
+
},
|
| 995 |
+
model_error: {
|
| 996 |
+
fr: "Désolée, le modèle sélectionné n'est pas disponible. Veuillez choisir un autre modèle.",
|
| 997 |
+
en: "Sorry, the selected model is not available. Please choose another model.",
|
| 998 |
+
es: "Lo siento, el modelo seleccionado no está disponible. Elige otro modelo.",
|
| 999 |
+
de: "Entschuldigung, das ausgewählte Modell ist nicht verfügbar. Bitte wählen Sie ein anderes Modell.",
|
| 1000 |
+
it: "Spiacente, il modello selezionato non è disponibile. Scegli un altro modello."
|
| 1001 |
+
}
|
| 1002 |
+
};
|
| 1003 |
+
|
| 1004 |
+
// Detect current language (fallback detection)
|
| 1005 |
+
const currentLang = this.detectCurrentLanguage();
|
| 1006 |
+
|
| 1007 |
+
if (fallbacks[errorType] && fallbacks[errorType][currentLang]) {
|
| 1008 |
+
return fallbacks[errorType][currentLang];
|
| 1009 |
+
}
|
| 1010 |
+
|
| 1011 |
+
// Ultimate fallback to English
|
| 1012 |
+
if (fallbacks[errorType] && fallbacks[errorType].en) {
|
| 1013 |
+
return fallbacks[errorType].en;
|
| 1014 |
+
}
|
| 1015 |
+
|
| 1016 |
+
switch (errorType) {
|
| 1017 |
+
case "api_missing":
|
| 1018 |
+
return "To chat with me, add your API key in settings! 💕";
|
| 1019 |
+
case "api_error":
|
| 1020 |
+
case "api":
|
| 1021 |
+
return "Sorry, the AI service is temporarily unavailable. Please try again later.";
|
| 1022 |
+
case "model_error":
|
| 1023 |
+
case "model":
|
| 1024 |
+
return "Sorry, the selected model is not available. Please choose another model or check your configuration.";
|
| 1025 |
+
case "network_error":
|
| 1026 |
+
case "network":
|
| 1027 |
+
return "Sorry, I cannot respond because there is no internet connection.";
|
| 1028 |
+
case "technical_error":
|
| 1029 |
+
case "technical":
|
| 1030 |
+
return "Sorry, I am unable to answer due to a technical issue.";
|
| 1031 |
+
case "general_error":
|
| 1032 |
+
default:
|
| 1033 |
+
return "Sorry my love, I am having a little technical issue! 💕";
|
| 1034 |
+
}
|
| 1035 |
+
},
|
| 1036 |
+
|
| 1037 |
+
detectCurrentLanguage: function () {
|
| 1038 |
+
// Try to get language from various sources
|
| 1039 |
+
|
| 1040 |
+
// 1. Try from language selector if available
|
| 1041 |
+
const langSelect = document.getElementById("language-selection");
|
| 1042 |
+
if (langSelect && langSelect.value) {
|
| 1043 |
+
return langSelect.value;
|
| 1044 |
+
}
|
| 1045 |
+
|
| 1046 |
+
// 2. Try from HTML lang attribute
|
| 1047 |
+
const htmlLang = document.documentElement.lang;
|
| 1048 |
+
if (htmlLang) {
|
| 1049 |
+
return htmlLang.split("-")[0]; // Get just the language part
|
| 1050 |
+
}
|
| 1051 |
+
|
| 1052 |
+
// 3. Try from browser language
|
| 1053 |
+
const browserLang = navigator.language || navigator.userLanguage;
|
| 1054 |
+
if (browserLang) {
|
| 1055 |
+
return browserLang.split("-")[0];
|
| 1056 |
+
}
|
| 1057 |
+
|
| 1058 |
+
// 4. Default to English (as seems to be the default for this app)
|
| 1059 |
+
return "en";
|
| 1060 |
+
},
|
| 1061 |
+
|
| 1062 |
+
showFallbackResponse: async function (errorType, customMessage = null) {
|
| 1063 |
+
const message = this.getFallbackMessage(errorType, customMessage);
|
| 1064 |
+
|
| 1065 |
+
// Add to chat
|
| 1066 |
+
if (window.addMessageToChat) {
|
| 1067 |
+
window.addMessageToChat("kimi", message);
|
| 1068 |
+
}
|
| 1069 |
+
|
| 1070 |
+
// Speak if available
|
| 1071 |
+
if (window.voiceManager && window.voiceManager.speak) {
|
| 1072 |
+
window.voiceManager.speak(message);
|
| 1073 |
+
}
|
| 1074 |
+
|
| 1075 |
+
// SIMPLE: Always show neutral videos in fallback mode
|
| 1076 |
+
if (window.kimiVideo && window.kimiVideo.switchToContext) {
|
| 1077 |
+
window.kimiVideo.switchToContext("neutral", "neutral");
|
| 1078 |
+
}
|
| 1079 |
+
|
| 1080 |
+
return message;
|
| 1081 |
+
}
|
| 1082 |
+
};
|
| 1083 |
+
|
| 1084 |
+
window.KimiBaseManager = KimiBaseManager;
|
| 1085 |
+
// KimiVideoManager is provided by the separate module `kimi-videos.js` which sets
|
| 1086 |
+
// `window.KimiVideoManager` when executed. Do not reference the symbol here to
|
| 1087 |
+
// avoid ReferenceError during module evaluation.
|
| 1088 |
+
window.KimiSecurityUtils = KimiSecurityUtils;
|
| 1089 |
+
window.KimiCacheManager = new KimiCacheManager(); // Create global instance
|
| 1090 |
+
// Expose helper used by the video manager
|
| 1091 |
+
window.getCharacterInfo = getCharacterInfo;
|
| 1092 |
+
window.KimiInitManager = KimiInitManager;
|
| 1093 |
+
window.KimiDOMUtils = KimiDOMUtils;
|
| 1094 |
+
window.KimiOverlayManager = KimiOverlayManager;
|
| 1095 |
+
window.KimiTabManager = KimiTabManager;
|
| 1096 |
+
window.KimiUIEventManager = KimiUIEventManager;
|
| 1097 |
+
window.KimiFormManager = KimiFormManager;
|
| 1098 |
+
window.KimiUIStateManager = KimiUIStateManager;
|
| 1099 |
+
|
| 1100 |
+
window.KimiTokenUtils = {
|
| 1101 |
+
// Approximate token estimation (heuristic):
|
| 1102 |
+
// Base: 1 token ~ 4 chars (English average). We refine by word count and punctuation density.
|
| 1103 |
+
estimate(text) {
|
| 1104 |
+
if (!text || typeof text !== "string") return 0;
|
| 1105 |
+
const trimmed = text.trim();
|
| 1106 |
+
if (!trimmed) return 0;
|
| 1107 |
+
const charLen = trimmed.length;
|
| 1108 |
+
const words = trimmed.split(/\s+/).length;
|
| 1109 |
+
// Base estimates
|
| 1110 |
+
let estimateByChars = Math.ceil(charLen / 4);
|
| 1111 |
+
const estimateByWords = Math.ceil(words * 1.3); // average 1.3 tokens per word
|
| 1112 |
+
// Blend and adjust for punctuation heavy content
|
| 1113 |
+
const punctCount = (trimmed.match(/[.,!?;:]/g) || []).length;
|
| 1114 |
+
const punctFactor = 1 + Math.min(punctCount / Math.max(words, 1) / 5, 0.15); // cap at +15%
|
| 1115 |
+
const blended = Math.round((estimateByChars * 0.55 + estimateByWords * 0.45) * punctFactor);
|
| 1116 |
+
return Math.max(1, blended);
|
| 1117 |
+
}
|
| 1118 |
+
};
|