Spaces:
Running
Running
Upload 33 files
Browse files- CHANGELOG.md +10 -0
- index.html +47 -57
- kimi-js/kimi-data-manager.js +318 -0
- kimi-js/kimi-module.js +3 -312
- kimi-js/kimi-script.js +1 -0
CHANGELOG.md
CHANGED
@@ -1,5 +1,15 @@
|
|
1 |
# Virtual Kimi App Changelog
|
2 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
3 |
# [1.1.4.1] - 2025-09-03
|
4 |
|
5 |
### Bug Fixes
|
|
|
1 |
# Virtual Kimi App Changelog
|
2 |
|
3 |
+
# [1.1.5] - 2025-09-03
|
4 |
+
|
5 |
+
### Bug Fixes
|
6 |
+
|
7 |
+
- Fixed some issues.
|
8 |
+
|
9 |
+
### Changed
|
10 |
+
|
11 |
+
- Separated the KimiDataManager class and moved logic into the new file `kimi-data-manager.js`.
|
12 |
+
|
13 |
# [1.1.4.1] - 2025-09-03
|
14 |
|
15 |
### Bug Fixes
|
index.html
CHANGED
@@ -37,29 +37,44 @@
|
|
37 |
content="Virtual AI companion with evolving personality and advanced voice recognition.">
|
38 |
<meta property="twitter:image" content="kimi-icons/virtualkimi-logo.png">
|
39 |
|
40 |
-
<!-- Schema.org
|
41 |
<script type="application/ld+json">
|
42 |
-
|
43 |
-
|
44 |
-
|
45 |
-
|
46 |
-
|
47 |
-
|
48 |
-
|
49 |
-
|
50 |
-
|
51 |
-
|
52 |
-
|
53 |
-
|
54 |
-
|
55 |
-
|
56 |
-
|
57 |
-
|
58 |
-
|
59 |
-
|
60 |
-
|
61 |
-
|
62 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
63 |
|
64 |
<!-- Favicon -->
|
65 |
<link rel="icon" type="image/x-icon" href="favicon.ico">
|
@@ -1072,7 +1087,7 @@
|
|
1072 |
<h3><i class="fas fa-code"></i> Technical Information</h3>
|
1073 |
<div class="tech-info">
|
1074 |
<p><strong>Created date :</strong> July 16, 2025</p>
|
1075 |
-
<p><strong>Version :</strong> v1.1.
|
1076 |
<p><strong>Last update :</strong> September 03, 2025</p>
|
1077 |
<p><strong>Technologies :</strong> HTML5, CSS3, JavaScript ES6+, IndexedDB, Web Speech
|
1078 |
API</p>
|
@@ -1083,10 +1098,16 @@
|
|
1083 |
</div>
|
1084 |
</div>
|
1085 |
</div>
|
1086 |
-
</div>
|
1087 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1088 |
<script src="dexie.min.js"></script>
|
1089 |
-
<script src="kimi-locale/i18n.js"
|
1090 |
<script type="module" src="kimi-js/kimi-personality-utils.js"></script>
|
1091 |
<script type="module" src="kimi-js/kimi-utils.js"></script>
|
1092 |
<script type="module" src="kimi-js/kimi-main.js"></script>
|
@@ -1097,41 +1118,10 @@
|
|
1097 |
<script type="module" src="kimi-js/kimi-constants.js"></script>
|
1098 |
<script type="module" src="kimi-js/kimi-memory-ui.js"></script>
|
1099 |
<script type="module" src="kimi-js/kimi-appearance.js"></script>
|
|
|
1100 |
<script type="module" src="kimi-js/kimi-module.js"></script>
|
1101 |
<script type="module" src="kimi-js/kimi-script.js"></script>
|
1102 |
<script type="module" src="kimi-js/kimi-plugin-manager.js"></script>
|
1103 |
-
|
1104 |
-
<!-- Schema.org JSON-LD for better SEO -->
|
1105 |
-
<script type="application/ld+json">
|
1106 |
-
{
|
1107 |
-
"@context": "https://schema.org",
|
1108 |
-
"@type": "WebPage",
|
1109 |
-
"name": "Virtual Kimi - Virtual AI Companion",
|
1110 |
-
"description": "Virtual Kimi, your virtual AI girlfriend and companion with an evolving personality, multi-provider AI support, advanced voice recognition and immersive interface. The future of human-AI girlfriend relationships.",
|
1111 |
-
"url": "https://virtualkimi.com/virtual-kimi-app/index.html",
|
1112 |
-
"mainEntity": {
|
1113 |
-
"@type": "SoftwareApplication",
|
1114 |
-
"name": "Virtual Kimi",
|
1115 |
-
"applicationCategory": "AI Companion",
|
1116 |
-
"operatingSystem": "Web Browser",
|
1117 |
-
"description": "Virtual Kimi, a virtual AI girlfriend and companion with an evolving personality, multi-provider AI support, voice recognition and immersive interface",
|
1118 |
-
"features": [
|
1119 |
-
"Advanced voice recognition",
|
1120 |
-
"Evolving personality with 6 adjustable traits",
|
1121 |
-
"Premium LLM integration",
|
1122 |
-
"5 customizable visual themes",
|
1123 |
-
"Persistent memory",
|
1124 |
-
"Intelligent affection system"
|
1125 |
-
],
|
1126 |
-
"author": {
|
1127 |
-
"@type": "Person",
|
1128 |
-
"name": "Jean & Kimi"
|
1129 |
-
},
|
1130 |
-
"dateCreated": "2025-07-16",
|
1131 |
-
"version": "v1.1.4.1"
|
1132 |
-
}
|
1133 |
-
}
|
1134 |
-
</script>
|
1135 |
</body>
|
1136 |
|
1137 |
</html>
|
|
|
37 |
content="Virtual AI companion with evolving personality and advanced voice recognition.">
|
38 |
<meta property="twitter:image" content="kimi-icons/virtualkimi-logo.png">
|
39 |
|
40 |
+
<!-- Schema.org consolidated JSON-LD (WebPage + mainEntity SoftwareApplication) -->
|
41 |
<script type="application/ld+json">
|
42 |
+
{
|
43 |
+
"@context": "https://schema.org",
|
44 |
+
"@type": "WebPage",
|
45 |
+
"name": "Virtual Kimi - Virtual AI Companion",
|
46 |
+
"description": "Virtual Kimi, your virtual AI girlfriend and companion with an evolving personality, multi-provider AI support, advanced voice recognition and immersive interface.",
|
47 |
+
"url": "https://virtualkimi.com/virtual-kimi-app/index.html",
|
48 |
+
"mainEntity": {
|
49 |
+
"@type": "SoftwareApplication",
|
50 |
+
"@id": "https://virtualkimi.com/virtual-kimi-app/#app",
|
51 |
+
"name": "Virtual Kimi",
|
52 |
+
"description": "Virtual Kimi, your virtual AI girlfriend and companion with an evolving personality, multi-provider AI support, voice recognition and immersive interface",
|
53 |
+
"applicationCategory": "AI Companion",
|
54 |
+
"operatingSystem": "Web Browser",
|
55 |
+
"offers": {
|
56 |
+
"@type": "Offer",
|
57 |
+
"price": "0",
|
58 |
+
"priceCurrency": "USD"
|
59 |
+
},
|
60 |
+
"creator": {
|
61 |
+
"@type": "Person",
|
62 |
+
"name": "Jean & Kimi"
|
63 |
+
},
|
64 |
+
"dateCreated": "2025-07-16",
|
65 |
+
"dateModified": "2025-09-03",
|
66 |
+
"version": "v1.1.5",
|
67 |
+
"features": [
|
68 |
+
"Advanced voice recognition",
|
69 |
+
"Evolving personality with 6 adjustable traits",
|
70 |
+
"Premium LLM integration",
|
71 |
+
"5 customizable visual themes",
|
72 |
+
"Persistent memory",
|
73 |
+
"Intelligent affection system"
|
74 |
+
]
|
75 |
+
}
|
76 |
+
}
|
77 |
+
</script>
|
78 |
|
79 |
<!-- Favicon -->
|
80 |
<link rel="icon" type="image/x-icon" href="favicon.ico">
|
|
|
1087 |
<h3><i class="fas fa-code"></i> Technical Information</h3>
|
1088 |
<div class="tech-info">
|
1089 |
<p><strong>Created date :</strong> July 16, 2025</p>
|
1090 |
+
<p><strong>Version :</strong> v1.1.5</p>
|
1091 |
<p><strong>Last update :</strong> September 03, 2025</p>
|
1092 |
<p><strong>Technologies :</strong> HTML5, CSS3, JavaScript ES6+, IndexedDB, Web Speech
|
1093 |
API</p>
|
|
|
1098 |
</div>
|
1099 |
</div>
|
1100 |
</div>
|
|
|
1101 |
|
1102 |
+
<!-- Script load order (critical):
|
1103 |
+
1. Legacy globals (dexie, i18n) must load before modules needing them.
|
1104 |
+
2. Base utilities (kimi-utils.js) define KimiBaseManager and helpers.
|
1105 |
+
3. Managers that extend base (appearance, data) after utils.
|
1106 |
+
4. Core module wiring (kimi-module.js) after managers so window.* hooks exist.
|
1107 |
+
5. Initialization / orchestration (kimi-script.js) last.
|
1108 |
+
Keep this order when adding new managers. -->
|
1109 |
<script src="dexie.min.js"></script>
|
1110 |
+
<script src="kimi-locale/i18n.js"></script>
|
1111 |
<script type="module" src="kimi-js/kimi-personality-utils.js"></script>
|
1112 |
<script type="module" src="kimi-js/kimi-utils.js"></script>
|
1113 |
<script type="module" src="kimi-js/kimi-main.js"></script>
|
|
|
1118 |
<script type="module" src="kimi-js/kimi-constants.js"></script>
|
1119 |
<script type="module" src="kimi-js/kimi-memory-ui.js"></script>
|
1120 |
<script type="module" src="kimi-js/kimi-appearance.js"></script>
|
1121 |
+
<script type="module" src="kimi-js/kimi-data-manager.js"></script>
|
1122 |
<script type="module" src="kimi-js/kimi-module.js"></script>
|
1123 |
<script type="module" src="kimi-js/kimi-script.js"></script>
|
1124 |
<script type="module" src="kimi-js/kimi-plugin-manager.js"></script>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1125 |
</body>
|
1126 |
|
1127 |
</html>
|
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-module.js
CHANGED
@@ -1,314 +1,6 @@
|
|
1 |
// KIMI MODULE SYSTEM
|
2 |
-
|
3 |
-
|
4 |
-
constructor(database) {
|
5 |
-
super();
|
6 |
-
this.db = database;
|
7 |
-
}
|
8 |
-
|
9 |
-
async init() {
|
10 |
-
this.setupDataControls();
|
11 |
-
await this.updateStorageInfo();
|
12 |
-
}
|
13 |
-
|
14 |
-
setupDataControls() {
|
15 |
-
const exportButton = document.getElementById("export-data");
|
16 |
-
if (exportButton) {
|
17 |
-
exportButton.addEventListener("click", () => this.exportAllData());
|
18 |
-
}
|
19 |
-
|
20 |
-
const importButton = document.getElementById("import-data");
|
21 |
-
const importFile = document.getElementById("import-file");
|
22 |
-
if (importButton && importFile) {
|
23 |
-
importButton.addEventListener("click", () => importFile.click());
|
24 |
-
importFile.addEventListener("change", e => this.importData(e));
|
25 |
-
}
|
26 |
-
|
27 |
-
const cleanButton = document.getElementById("clean-old-data");
|
28 |
-
if (cleanButton) {
|
29 |
-
cleanButton.addEventListener("click", async () => {
|
30 |
-
if (!this.db) return;
|
31 |
-
|
32 |
-
const confirmClean = confirm(
|
33 |
-
"Delete all conversation messages?\n\n" +
|
34 |
-
"This will remove all chat history but keep your preferences and settings.\n\n" +
|
35 |
-
"This action cannot be undone."
|
36 |
-
);
|
37 |
-
|
38 |
-
if (!confirmClean) {
|
39 |
-
return;
|
40 |
-
}
|
41 |
-
|
42 |
-
try {
|
43 |
-
// Clear all conversations directly
|
44 |
-
await this.db.db.conversations.clear();
|
45 |
-
|
46 |
-
// Clear chat UI
|
47 |
-
const chatMessages = document.getElementById("chat-messages");
|
48 |
-
if (chatMessages) {
|
49 |
-
chatMessages.textContent = "";
|
50 |
-
}
|
51 |
-
|
52 |
-
// Reload chat history
|
53 |
-
if (typeof window.loadChatHistory === "function") {
|
54 |
-
window.loadChatHistory();
|
55 |
-
}
|
56 |
-
|
57 |
-
await this.updateStorageInfo();
|
58 |
-
alert("All conversation messages have been deleted successfully!");
|
59 |
-
} catch (error) {
|
60 |
-
console.error("Error cleaning conversations:", error);
|
61 |
-
alert("Error while cleaning conversations. Please try again.");
|
62 |
-
}
|
63 |
-
});
|
64 |
-
}
|
65 |
-
|
66 |
-
const resetButton = document.getElementById("reset-all-data");
|
67 |
-
if (resetButton) {
|
68 |
-
resetButton.addEventListener("click", () => this.resetAllData());
|
69 |
-
}
|
70 |
-
}
|
71 |
-
|
72 |
-
async exportAllData() {
|
73 |
-
if (!this.db) {
|
74 |
-
console.error("Database not available");
|
75 |
-
return;
|
76 |
-
}
|
77 |
-
|
78 |
-
try {
|
79 |
-
const conversations = await this.db.getAllConversations();
|
80 |
-
const preferencesObj = await this.db.getAllPreferences();
|
81 |
-
// Export preferences as an array of {key,value} so export is directly re-importable
|
82 |
-
const preferences = Array.isArray(preferencesObj)
|
83 |
-
? preferencesObj
|
84 |
-
: Object.keys(preferencesObj).map(k => ({ key: k, value: preferencesObj[k] }));
|
85 |
-
const personalityTraits = await this.db.getAllPersonalityTraits();
|
86 |
-
const models = await this.db.getAllLLMModels();
|
87 |
-
const memories = await this.db.getAllMemories();
|
88 |
-
|
89 |
-
const exportData = {
|
90 |
-
version: "1.0",
|
91 |
-
exportDate: new Date().toISOString(),
|
92 |
-
conversations: conversations,
|
93 |
-
preferences: preferences,
|
94 |
-
personalityTraits: personalityTraits,
|
95 |
-
models: models,
|
96 |
-
memories: memories,
|
97 |
-
metadata: {
|
98 |
-
totalConversations: conversations.length,
|
99 |
-
totalPreferences: Object.keys(preferences).length,
|
100 |
-
totalTraits: Object.keys(personalityTraits).length,
|
101 |
-
totalModels: models.length,
|
102 |
-
totalMemories: memories.length
|
103 |
-
}
|
104 |
-
};
|
105 |
-
|
106 |
-
const dataStr = JSON.stringify(exportData, null, 2);
|
107 |
-
const dataBlob = new Blob([dataStr], { type: "application/json" });
|
108 |
-
|
109 |
-
const url = URL.createObjectURL(dataBlob);
|
110 |
-
const a = document.createElement("a");
|
111 |
-
a.href = url;
|
112 |
-
a.download = `kimi-backup-${new Date().toISOString().split("T")[0]}.json`;
|
113 |
-
document.body.appendChild(a);
|
114 |
-
a.click();
|
115 |
-
document.body.removeChild(a);
|
116 |
-
URL.revokeObjectURL(url);
|
117 |
-
} catch (error) {
|
118 |
-
console.error("Error during export:", error);
|
119 |
-
}
|
120 |
-
}
|
121 |
-
|
122 |
-
async importData(event) {
|
123 |
-
const file = event.target.files[0];
|
124 |
-
if (!file) {
|
125 |
-
alert("No file selected.");
|
126 |
-
return;
|
127 |
-
}
|
128 |
-
const reader = new FileReader();
|
129 |
-
reader.onload = async e => {
|
130 |
-
try {
|
131 |
-
const data = JSON.parse(e.target.result);
|
132 |
-
try {
|
133 |
-
console.log("Import file keys:", Object.keys(data));
|
134 |
-
} catch (ex) {}
|
135 |
-
|
136 |
-
if (data.preferences) {
|
137 |
-
try {
|
138 |
-
const isArray = Array.isArray(data.preferences);
|
139 |
-
const len = isArray ? data.preferences.length : Object.keys(data.preferences).length;
|
140 |
-
console.log("Import: preferences type=", isArray ? "array" : "object", "length=", len);
|
141 |
-
} catch (ex) {}
|
142 |
-
await this.db.setPreferencesBatch(data.preferences);
|
143 |
-
} else {
|
144 |
-
console.log("Import: no preferences found");
|
145 |
-
}
|
146 |
-
|
147 |
-
if (data.conversations) {
|
148 |
-
try {
|
149 |
-
console.log(
|
150 |
-
"Import: conversations length=",
|
151 |
-
Array.isArray(data.conversations) ? data.conversations.length : "not-array"
|
152 |
-
);
|
153 |
-
} catch (ex) {}
|
154 |
-
await this.db.setConversationsBatch(data.conversations);
|
155 |
-
} else {
|
156 |
-
console.log("Import: no conversations found");
|
157 |
-
}
|
158 |
-
|
159 |
-
if (data.personalityTraits) {
|
160 |
-
try {
|
161 |
-
console.log("Import: personalityTraits type=", typeof data.personalityTraits);
|
162 |
-
} catch (ex) {}
|
163 |
-
await this.db.setPersonalityBatch(data.personalityTraits);
|
164 |
-
} else {
|
165 |
-
console.log("Import: no personalityTraits found");
|
166 |
-
}
|
167 |
-
|
168 |
-
if (data.models) {
|
169 |
-
try {
|
170 |
-
console.log("Import: models length=", Array.isArray(data.models) ? data.models.length : "not-array");
|
171 |
-
} catch (ex) {}
|
172 |
-
await this.db.setLLMModelsBatch(data.models);
|
173 |
-
} else {
|
174 |
-
console.log("Import: no models found");
|
175 |
-
}
|
176 |
-
|
177 |
-
if (data.memories) {
|
178 |
-
try {
|
179 |
-
console.log(
|
180 |
-
"Import: memories length=",
|
181 |
-
Array.isArray(data.memories) ? data.memories.length : "not-array"
|
182 |
-
);
|
183 |
-
} catch (ex) {}
|
184 |
-
await this.db.setAllMemories(data.memories);
|
185 |
-
} else {
|
186 |
-
console.log("Import: no memories found");
|
187 |
-
}
|
188 |
-
|
189 |
-
alert("Import successful!");
|
190 |
-
await this.updateStorageInfo();
|
191 |
-
|
192 |
-
// Reload the page to ensure all UI state is rebuilt from the newly imported DB
|
193 |
-
setTimeout(() => {
|
194 |
-
location.reload();
|
195 |
-
}, 200);
|
196 |
-
} catch (err) {
|
197 |
-
console.error("Import failed:", err);
|
198 |
-
alert("Import failed. Invalid file or format.");
|
199 |
-
}
|
200 |
-
};
|
201 |
-
reader.readAsText(file);
|
202 |
-
}
|
203 |
-
|
204 |
-
async cleanOldData() {
|
205 |
-
if (!this.db) {
|
206 |
-
console.error("Database not available");
|
207 |
-
return;
|
208 |
-
}
|
209 |
-
|
210 |
-
const confirmClean = confirm("Do you want to delete ALL conversations?\n\nThis action is irreversible!");
|
211 |
-
if (!confirmClean) {
|
212 |
-
return;
|
213 |
-
}
|
214 |
-
|
215 |
-
try {
|
216 |
-
// Centralized: use kimi-database.js cleanOldConversations for all deletion logic
|
217 |
-
await this.db.cleanOldConversations();
|
218 |
-
|
219 |
-
if (typeof window.loadChatHistory === "function") {
|
220 |
-
window.loadChatHistory();
|
221 |
-
}
|
222 |
-
const chatMessages = document.getElementById("chat-messages");
|
223 |
-
if (chatMessages) {
|
224 |
-
chatMessages.textContent = "";
|
225 |
-
}
|
226 |
-
|
227 |
-
await this.updateStorageInfo();
|
228 |
-
} catch (error) {
|
229 |
-
console.error("Error during cleaning:", error);
|
230 |
-
}
|
231 |
-
}
|
232 |
-
|
233 |
-
async resetAllData() {
|
234 |
-
if (!this.db) {
|
235 |
-
console.error("Database not available");
|
236 |
-
return;
|
237 |
-
}
|
238 |
-
|
239 |
-
const confirmReset = confirm(
|
240 |
-
"WARNING!\n\n" +
|
241 |
-
"Do you REALLY want to delete ALL data?\n\n" +
|
242 |
-
"• All conversations\n" +
|
243 |
-
"• All preferences\n" +
|
244 |
-
"• All configured models\n" +
|
245 |
-
"• All personality traits\n\n" +
|
246 |
-
"This action is IRREVERSIBLE!"
|
247 |
-
);
|
248 |
-
|
249 |
-
if (!confirmReset) {
|
250 |
-
return;
|
251 |
-
}
|
252 |
-
|
253 |
-
try {
|
254 |
-
if (this.db.db) {
|
255 |
-
this.db.db.close();
|
256 |
-
}
|
257 |
-
|
258 |
-
const deleteRequest = indexedDB.deleteDatabase(this.db.dbName);
|
259 |
-
|
260 |
-
deleteRequest.onsuccess = () => {
|
261 |
-
setTimeout(() => {
|
262 |
-
alert("The page will reload to complete the reset.");
|
263 |
-
location.reload();
|
264 |
-
}, 500);
|
265 |
-
};
|
266 |
-
|
267 |
-
deleteRequest.onerror = () => {
|
268 |
-
alert("Error while deleting the database. Please try again.");
|
269 |
-
};
|
270 |
-
} catch (error) {
|
271 |
-
console.error("Error during reset:", error);
|
272 |
-
alert("Error during reset. Please try again.");
|
273 |
-
}
|
274 |
-
}
|
275 |
-
|
276 |
-
async updateStorageInfo() {
|
277 |
-
if (!this.db) return;
|
278 |
-
|
279 |
-
try {
|
280 |
-
// Add a small delay to ensure database operations are complete
|
281 |
-
await new Promise(resolve => setTimeout(resolve, 100));
|
282 |
-
|
283 |
-
const stats = await this.db.getStorageStats();
|
284 |
-
|
285 |
-
const dbSizeEl = document.getElementById("db-size");
|
286 |
-
const storageUsedEl = document.getElementById("storage-used");
|
287 |
-
|
288 |
-
if (dbSizeEl) {
|
289 |
-
dbSizeEl.textContent = this.formatFileSize(stats.totalSize || 0);
|
290 |
-
}
|
291 |
-
|
292 |
-
if (storageUsedEl) {
|
293 |
-
const estimate = navigator.storage && navigator.storage.estimate ? await navigator.storage.estimate() : null;
|
294 |
-
|
295 |
-
if (estimate) {
|
296 |
-
storageUsedEl.textContent = this.formatFileSize(estimate.usage || 0);
|
297 |
-
} else {
|
298 |
-
storageUsedEl.textContent = "N/A";
|
299 |
-
}
|
300 |
-
}
|
301 |
-
} catch (error) {
|
302 |
-
console.error("Error while calculating storage:", error);
|
303 |
-
|
304 |
-
const dbSizeEl = document.getElementById("db-size");
|
305 |
-
const storageUsedEl = document.getElementById("storage-used");
|
306 |
-
|
307 |
-
if (dbSizeEl) dbSizeEl.textContent = "Error";
|
308 |
-
if (storageUsedEl) storageUsedEl.textContent = "Error";
|
309 |
-
}
|
310 |
-
}
|
311 |
-
}
|
312 |
|
313 |
// Fonctions utilitaires et logique (référencent window.*)
|
314 |
|
@@ -2052,8 +1744,7 @@ function setupSettingsListeners(kimiDB, kimiMemory) {
|
|
2052 |
}
|
2053 |
}
|
2054 |
|
2055 |
-
// Exposer globalement
|
2056 |
-
window.KimiDataManager = KimiDataManager;
|
2057 |
window.updateFavorabilityLabel = updateFavorabilityLabel;
|
2058 |
window.loadCharacterSection = loadCharacterSection;
|
2059 |
window.getBasicResponse = getBasicResponse;
|
|
|
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 |
|
|
|
1744 |
}
|
1745 |
}
|
1746 |
|
1747 |
+
// Exposer globalement (KimiDataManager already exposed in kimi-data-manager.js)
|
|
|
1748 |
window.updateFavorabilityLabel = updateFavorabilityLabel;
|
1749 |
window.loadCharacterSection = loadCharacterSection;
|
1750 |
window.getBasicResponse = getBasicResponse;
|
kimi-js/kimi-script.js
CHANGED
@@ -3,6 +3,7 @@ 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 |
|
7 |
document.addEventListener("DOMContentLoaded", async function () {
|
8 |
const DEFAULT_SYSTEM_PROMPT = window.DEFAULT_SYSTEM_PROMPT;
|
|
|
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 |
|
8 |
document.addEventListener("DOMContentLoaded", async function () {
|
9 |
const DEFAULT_SYSTEM_PROMPT = window.DEFAULT_SYSTEM_PROMPT;
|