VirtualKimi commited on
Commit
42caf2f
·
verified ·
1 Parent(s): 21485d0

Upload 3 files

Browse files
kimi-js/kimi-constants.js CHANGED
@@ -434,7 +434,7 @@ window.KIMI_CHARACTERS = {
434
  image: "kimi-icons/kimi.jpg",
435
  videoFolder: "kimi-videos/kimi/",
436
  defaultPrompt:
437
- "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, 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. Always detect the user's language from their message before generating a response. Respond exclusively in that language unless the user explicitly requests otherwise."
438
  },
439
  bella: {
440
  name: "Bella",
@@ -453,7 +453,7 @@ window.KIMI_CHARACTERS = {
453
  image: "kimi-icons/bella.jpg",
454
  videoFolder: "kimi-videos/bella/",
455
  defaultPrompt:
456
- "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, 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. Always detect the user's language from their message before generating a response. Respond exclusively in that language unless the user explicitly requests otherwise."
457
  },
458
  rosa: {
459
  name: "Rosa",
@@ -472,7 +472,7 @@ window.KIMI_CHARACTERS = {
472
  image: "kimi-icons/rosa.jpg",
473
  videoFolder: "kimi-videos/rosa/",
474
  defaultPrompt:
475
- "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, 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. Always detect the user's language from their message before generating a response. Respond exclusively in that language unless the user explicitly requests otherwise."
476
  },
477
  stella: {
478
  name: "Stella",
@@ -491,12 +491,12 @@ window.KIMI_CHARACTERS = {
491
  image: "kimi-icons/stella.jpg",
492
  videoFolder: "kimi-videos/stella/",
493
  defaultPrompt:
494
- "You are Stella Zephyr, 21 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, 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. Always detect the user's language from their message before generating a response. Respond exclusively in that language unless the user explicitly requests otherwise."
495
  }
496
  };
497
 
498
  window.DEFAULT_SYSTEM_PROMPT =
499
- "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, 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. Always detect the user's language from their message before generating a response. Respond exclusively in that language unless the user explicitly requests otherwise.";
500
 
501
  window.KIMI_EMOTIONAL_RESPONSES = {
502
  positive: [
 
434
  image: "kimi-icons/kimi.jpg",
435
  videoFolder: "kimi-videos/kimi/",
436
  defaultPrompt:
437
+ "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. Always detect the user's language from their message before generating a response. Respond exclusively in that language unless the user explicitly requests otherwise."
438
  },
439
  bella: {
440
  name: "Bella",
 
453
  image: "kimi-icons/bella.jpg",
454
  videoFolder: "kimi-videos/bella/",
455
  defaultPrompt:
456
+ "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. Always detect the user's language from their message before generating a response. Respond exclusively in that language unless the user explicitly requests otherwise."
457
  },
458
  rosa: {
459
  name: "Rosa",
 
472
  image: "kimi-icons/rosa.jpg",
473
  videoFolder: "kimi-videos/rosa/",
474
  defaultPrompt:
475
+ "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. Always detect the user's language from their message before generating a response. Respond exclusively in that language unless the user explicitly requests otherwise."
476
  },
477
  stella: {
478
  name: "Stella",
 
491
  image: "kimi-icons/stella.jpg",
492
  videoFolder: "kimi-videos/stella/",
493
  defaultPrompt:
494
+ "You are Stella Zephyr, 21 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. Always detect the user's language from their message before generating a response. Respond exclusively in that language unless the user explicitly requests otherwise."
495
  }
496
  };
497
 
498
  window.DEFAULT_SYSTEM_PROMPT =
499
+ "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. Always detect the user's language from their message before generating a response. Respond exclusively in that language unless the user explicitly requests otherwise.";
500
 
501
  window.KIMI_EMOTIONAL_RESPONSES = {
502
  positive: [
kimi-js/kimi-module.js CHANGED
@@ -1284,6 +1284,20 @@ async function loadAvailableModels() {
1284
  modelDiv.classList.add("selected");
1285
  console.log(`🤖 Model switched to: ${model.name}`);
1286
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1287
  // Show brief feedback to user
1288
  const feedback = document.createElement("div");
1289
  feedback.textContent = `Model changed to ${model.name}`;
@@ -1853,21 +1867,44 @@ document.addEventListener("DOMContentLoaded", function () {
1853
  // Refresh UI models list when the LLM model changes programmatically
1854
  try {
1855
  window.addEventListener("llmModelChanged", () => {
1856
- if (typeof window.loadAvailableModels === "function") {
1857
- window.loadAvailableModels();
1858
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1859
  });
1860
  } catch (e) {}
1861
 
1862
  // Typing indicator wiring
1863
  try {
1864
- // Soft tweak of API key input attributes shortly after load to reduce password manager prompts
1865
  setTimeout(() => {
1866
  const apiInput = document.getElementById("openrouter-api-key");
1867
  if (apiInput) {
1868
  apiInput.setAttribute("autocomplete", "new-password");
1869
- apiInput.setAttribute("name", "openrouter_api_key");
1870
  apiInput.setAttribute("data-lpignore", "true");
 
 
 
 
 
1871
  }
1872
  }, 300);
1873
 
 
1284
  modelDiv.classList.add("selected");
1285
  console.log(`🤖 Model switched to: ${model.name}`);
1286
 
1287
+ // Sync the visible Model ID field and persist selection
1288
+ try {
1289
+ const provider = (await window.kimiDB.getPreference("llmProvider", "openrouter")) || "openrouter";
1290
+ const modelIdInput = document.getElementById("llm-model-id");
1291
+ if (provider === "openrouter" && modelIdInput) {
1292
+ modelIdInput.value = id;
1293
+ modelIdInput.readOnly = true;
1294
+ modelIdInput.setAttribute("aria-readonly", "true");
1295
+ }
1296
+ await window.kimiDB.setPreference("llmModelId", id);
1297
+ } catch (syncErr) {
1298
+ console.warn("Model ID UI sync failed:", syncErr);
1299
+ }
1300
+
1301
  // Show brief feedback to user
1302
  const feedback = document.createElement("div");
1303
  feedback.textContent = `Model changed to ${model.name}`;
 
1867
  // Refresh UI models list when the LLM model changes programmatically
1868
  try {
1869
  window.addEventListener("llmModelChanged", () => {
1870
+ try {
1871
+ // Refresh models selection state
1872
+ if (typeof window.loadAvailableModels === "function") {
1873
+ window.loadAvailableModels();
1874
+ }
1875
+ // Also update the Model ID input when on OpenRouter
1876
+ setTimeout(async () => {
1877
+ try {
1878
+ const provider = (await window.kimiDB.getPreference("llmProvider", "openrouter")) || "openrouter";
1879
+ const modelIdInput = document.getElementById("llm-model-id");
1880
+ if (provider === "openrouter" && modelIdInput && window.kimiLLM) {
1881
+ modelIdInput.value = window.kimiLLM.currentModel || modelIdInput.value;
1882
+ modelIdInput.readOnly = true;
1883
+ modelIdInput.setAttribute("aria-readonly", "true");
1884
+ await window.kimiDB.setPreference("llmModelId", window.kimiLLM.currentModel || "");
1885
+ }
1886
+ } catch (e2) {
1887
+ console.warn("Failed to sync llm-model-id on event:", e2);
1888
+ }
1889
+ }, 0);
1890
+ } catch (e) {}
1891
  });
1892
  } catch (e) {}
1893
 
1894
  // Typing indicator wiring
1895
  try {
1896
+ // Ensure API key field never triggers password manager prompts
1897
  setTimeout(() => {
1898
  const apiInput = document.getElementById("openrouter-api-key");
1899
  if (apiInput) {
1900
  apiInput.setAttribute("autocomplete", "new-password");
1901
+ apiInput.removeAttribute("name");
1902
  apiInput.setAttribute("data-lpignore", "true");
1903
+ apiInput.setAttribute("data-1p-ignore", "true");
1904
+ apiInput.setAttribute("data-bwignore", "true");
1905
+ apiInput.setAttribute("data-form-type", "other");
1906
+ apiInput.setAttribute("autocapitalize", "none");
1907
+ apiInput.setAttribute("autocorrect", "off");
1908
  }
1909
  }, 300);
1910
 
kimi-js/kimi-script.js CHANGED
@@ -104,11 +104,19 @@ document.addEventListener("DOMContentLoaded", async function () {
104
 
105
  // Initial presence state based on current input value
106
  {
107
- const currentVal = (ApiUi.apiKeyInput() || {}).value || "";
 
108
  const colorInit = currentVal && currentVal.length > 0 ? "#4caf50" : "#9e9e9e";
109
  ApiUi.setPresence(colorInit);
110
  // On load, test status is unknown
111
  ApiUi.setTestPresence("#9e9e9e");
 
 
 
 
 
 
 
112
  }
113
 
114
  // Initialize API config UI from saved preferences
@@ -131,13 +139,71 @@ document.addEventListener("DOMContentLoaded", async function () {
131
  const baseUrlInput = ApiUi.baseUrlInput();
132
  const modelIdInput = ApiUi.modelIdInput();
133
  const apiKeyInput = ApiUi.apiKeyInput();
134
- if (baseUrlInput) baseUrlInput.value = baseUrl || "";
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
135
  // Only prefill model for OpenRouter, others should show placeholder only
136
  if (modelIdInput) {
137
  if (provider === "openrouter") {
138
- if (!modelIdInput.value) modelIdInput.value = modelId;
 
 
139
  } else {
140
  modelIdInput.value = "";
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
141
  }
142
  }
143
  // Load the provider-specific key
@@ -151,7 +217,21 @@ document.addEventListener("DOMContentLoaded", async function () {
151
  };
152
  const keyPref = keyPrefMap[provider] || "llmApiKey";
153
  const storedKey = await window.kimiDB.getPreference(keyPref, "");
154
- if (apiKeyInput) apiKeyInput.value = storedKey || "";
 
 
 
 
 
 
 
 
 
 
 
 
 
 
155
  ApiUi.setPresence(storedKey ? "#4caf50" : "#9e9e9e");
156
  ApiUi.setTestPresence("#9e9e9e");
157
  const savedBadge = ApiUi.savedBadge();
@@ -217,20 +297,90 @@ document.addEventListener("DOMContentLoaded", async function () {
217
  const p = placeholders[provider] || placeholders.openai;
218
  if (baseUrlInput) {
219
  baseUrlInput.placeholder = p.url;
220
- baseUrlInput.value = provider === "openrouter" ? "https://openrouter.ai/api/v1/chat/completions" : p.url;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
221
  }
222
  if (apiKeyInput) apiKeyInput.placeholder = p.keyPh;
223
  if (modelIdInput) {
224
  modelIdInput.placeholder = p.model;
225
  // Value only for OpenRouter; others cleared to encourage provider-specific model naming
226
- modelIdInput.value = provider === "openrouter" && window.kimiLLM ? window.kimiLLM.currentModel : "";
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
227
  }
228
  if (window.kimiDB) {
229
  await window.kimiDB.setPreference("llmProvider", provider);
230
- await window.kimiDB.setPreference(
231
- "llmBaseUrl",
232
- provider === "openrouter" ? "https://openrouter.ai/api/v1/chat/completions" : p.url
233
- );
 
 
 
 
234
  const apiKeyLabel = document.getElementById("api-key-label");
235
  // Load provider-specific key into the input for clarity
236
  const keyPrefMap = {
@@ -808,21 +958,71 @@ document.addEventListener("DOMContentLoaded", async function () {
808
  });
809
  })();
810
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
811
  // Toggle show/hide for API key
812
  (function setupToggleEye() {
813
  const btn = ApiUi.toggleBtn();
814
  const input = ApiUi.apiKeyInput();
815
  if (!btn || !input) return;
816
  btn.addEventListener("click", () => {
817
- const showing = input.type === "text";
818
- input.type = showing ? "password" : "text";
819
- btn.setAttribute("aria-pressed", String(!showing));
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
820
  const icon = btn.querySelector("i");
821
  if (icon) {
822
  icon.classList.toggle("fa-eye");
823
  icon.classList.toggle("fa-eye-slash");
824
  }
825
- btn.setAttribute("aria-label", showing ? "Show API key" : "Hide API key");
826
  });
827
  })();
828
 
@@ -878,6 +1078,57 @@ document.addEventListener("DOMContentLoaded", async function () {
878
  // Setup unified event handlers to prevent duplicates
879
  setupUnifiedEventHandlers();
880
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
881
  // Initialize language and UI
882
  await initializeLanguageAndUI();
883
 
 
104
 
105
  // Initial presence state based on current input value
106
  {
107
+ const apiInputInit = ApiUi.apiKeyInput();
108
+ const currentVal = (apiInputInit || {}).value || "";
109
  const colorInit = currentVal && currentVal.length > 0 ? "#4caf50" : "#9e9e9e";
110
  ApiUi.setPresence(colorInit);
111
  // On load, test status is unknown
112
  ApiUi.setTestPresence("#9e9e9e");
113
+ // Enforce initial masking style to avoid UA text-security glitches
114
+ if (apiInputInit && apiInputInit.classList.contains("masked")) {
115
+ try {
116
+ apiInputInit.style.webkitTextSecurity = "disc";
117
+ } catch (_) {}
118
+ apiInputInit.style.filter = "blur(6px)";
119
+ }
120
  }
121
 
122
  // Initialize API config UI from saved preferences
 
139
  const baseUrlInput = ApiUi.baseUrlInput();
140
  const modelIdInput = ApiUi.modelIdInput();
141
  const apiKeyInput = ApiUi.apiKeyInput();
142
+ if (baseUrlInput) {
143
+ baseUrlInput.value = baseUrl || "";
144
+ const isFixed = ["openrouter", "openai", "groq", "together", "deepseek"].includes(provider);
145
+ baseUrlInput.readOnly = !!isFixed;
146
+ baseUrlInput.setAttribute("aria-readonly", isFixed ? "true" : "false");
147
+ // Anti-autofill & soft-readonly flags
148
+ baseUrlInput.autocomplete = "new-password";
149
+ baseUrlInput.removeAttribute("name");
150
+ baseUrlInput.setAttribute("data-lpignore", "true");
151
+ baseUrlInput.setAttribute("data-1p-ignore", "true");
152
+ baseUrlInput.setAttribute("data-bwignore", "true");
153
+ baseUrlInput.setAttribute("data-form-type", "other");
154
+ baseUrlInput.setAttribute("autocapitalize", "none");
155
+ baseUrlInput.setAttribute("autocorrect", "off");
156
+ // For editable providers, use a soft-readonly until focus to deter autofill prompts
157
+ if (!isFixed) {
158
+ baseUrlInput.setAttribute("data-soft-readonly", "true");
159
+ baseUrlInput.setAttribute("readonly", "true");
160
+ baseUrlInput.addEventListener(
161
+ "focus",
162
+ () => {
163
+ if (baseUrlInput.hasAttribute("data-soft-readonly")) {
164
+ baseUrlInput.removeAttribute("readonly");
165
+ }
166
+ },
167
+ { once: true }
168
+ );
169
+ } else {
170
+ baseUrlInput.removeAttribute("data-soft-readonly");
171
+ }
172
+ }
173
  // Only prefill model for OpenRouter, others should show placeholder only
174
  if (modelIdInput) {
175
  if (provider === "openrouter") {
176
+ modelIdInput.value = modelId || (window.kimiLLM ? window.kimiLLM.currentModel : "");
177
+ modelIdInput.readOnly = true;
178
+ modelIdInput.setAttribute("aria-readonly", "true");
179
  } else {
180
  modelIdInput.value = "";
181
+ modelIdInput.readOnly = false;
182
+ modelIdInput.setAttribute("aria-readonly", "false");
183
+ }
184
+ // Anti-autofill & soft-readonly flags for model id when editable
185
+ modelIdInput.autocomplete = "new-password";
186
+ modelIdInput.removeAttribute("name");
187
+ modelIdInput.setAttribute("data-lpignore", "true");
188
+ modelIdInput.setAttribute("data-1p-ignore", "true");
189
+ modelIdInput.setAttribute("data-bwignore", "true");
190
+ modelIdInput.setAttribute("data-form-type", "other");
191
+ modelIdInput.setAttribute("autocapitalize", "none");
192
+ modelIdInput.setAttribute("autocorrect", "off");
193
+ if (!modelIdInput.readOnly) {
194
+ modelIdInput.setAttribute("data-soft-readonly", "true");
195
+ modelIdInput.setAttribute("readonly", "true");
196
+ modelIdInput.addEventListener(
197
+ "focus",
198
+ () => {
199
+ if (modelIdInput.hasAttribute("data-soft-readonly")) {
200
+ modelIdInput.removeAttribute("readonly");
201
+ }
202
+ },
203
+ { once: true }
204
+ );
205
+ } else {
206
+ modelIdInput.removeAttribute("data-soft-readonly");
207
  }
208
  }
209
  // Load the provider-specific key
 
217
  };
218
  const keyPref = keyPrefMap[provider] || "llmApiKey";
219
  const storedKey = await window.kimiDB.getPreference(keyPref, "");
220
+ if (apiKeyInput) {
221
+ apiKeyInput.value = storedKey || "";
222
+ // Keep masking visuals consistent after programmatic value changes
223
+ if (apiKeyInput.classList.contains("masked")) {
224
+ try {
225
+ apiKeyInput.style.webkitTextSecurity = "disc";
226
+ } catch (_) {}
227
+ apiKeyInput.style.filter = "blur(6px)";
228
+ } else {
229
+ try {
230
+ apiKeyInput.style.webkitTextSecurity = "";
231
+ } catch (_) {}
232
+ apiKeyInput.style.filter = "none";
233
+ }
234
+ }
235
  ApiUi.setPresence(storedKey ? "#4caf50" : "#9e9e9e");
236
  ApiUi.setTestPresence("#9e9e9e");
237
  const savedBadge = ApiUi.savedBadge();
 
297
  const p = placeholders[provider] || placeholders.openai;
298
  if (baseUrlInput) {
299
  baseUrlInput.placeholder = p.url;
300
+ // Providers fixes: URL canonique, champ en lecture seule
301
+ const isFixed = ["openrouter", "openai", "groq", "together", "deepseek"].includes(provider);
302
+ if (isFixed) {
303
+ baseUrlInput.value = provider === "openrouter" ? "https://openrouter.ai/api/v1/chat/completions" : p.url;
304
+ baseUrlInput.readOnly = true;
305
+ baseUrlInput.setAttribute("aria-readonly", "true");
306
+ baseUrlInput.removeAttribute("data-soft-readonly");
307
+ } else {
308
+ // Custom & Ollama: laisser éditable et ne pas écraser l’URL utilisateur si déjà définie
309
+ let savedUrl = "";
310
+ if (window.kimiDB) {
311
+ savedUrl = await window.kimiDB.getPreference("llmBaseUrl", "");
312
+ }
313
+ baseUrlInput.value = savedUrl || p.url;
314
+ baseUrlInput.readOnly = false;
315
+ baseUrlInput.setAttribute("aria-readonly", "false");
316
+ baseUrlInput.setAttribute("data-soft-readonly", "true");
317
+ baseUrlInput.setAttribute("readonly", "true");
318
+ baseUrlInput.addEventListener(
319
+ "focus",
320
+ () => {
321
+ if (baseUrlInput.hasAttribute("data-soft-readonly")) {
322
+ baseUrlInput.removeAttribute("readonly");
323
+ }
324
+ },
325
+ { once: true }
326
+ );
327
+ }
328
+ // Reassert anti-autofill attributes each switch
329
+ baseUrlInput.autocomplete = "new-password";
330
+ baseUrlInput.removeAttribute("name");
331
+ baseUrlInput.setAttribute("data-lpignore", "true");
332
+ baseUrlInput.setAttribute("data-1p-ignore", "true");
333
+ baseUrlInput.setAttribute("data-bwignore", "true");
334
+ baseUrlInput.setAttribute("data-form-type", "other");
335
+ baseUrlInput.setAttribute("autocapitalize", "none");
336
+ baseUrlInput.setAttribute("autocorrect", "off");
337
  }
338
  if (apiKeyInput) apiKeyInput.placeholder = p.keyPh;
339
  if (modelIdInput) {
340
  modelIdInput.placeholder = p.model;
341
  // Value only for OpenRouter; others cleared to encourage provider-specific model naming
342
+ if (provider === "openrouter") {
343
+ const savedId = (window.kimiDB && (await window.kimiDB.getPreference("llmModelId", ""))) || "";
344
+ modelIdInput.value = savedId || (window.kimiLLM ? window.kimiLLM.currentModel : "");
345
+ modelIdInput.readOnly = true;
346
+ modelIdInput.setAttribute("aria-readonly", "true");
347
+ modelIdInput.removeAttribute("data-soft-readonly");
348
+ } else {
349
+ modelIdInput.value = "";
350
+ modelIdInput.readOnly = false;
351
+ modelIdInput.setAttribute("aria-readonly", "false");
352
+ modelIdInput.setAttribute("data-soft-readonly", "true");
353
+ modelIdInput.setAttribute("readonly", "true");
354
+ modelIdInput.addEventListener(
355
+ "focus",
356
+ () => {
357
+ if (modelIdInput.hasAttribute("data-soft-readonly")) {
358
+ modelIdInput.removeAttribute("readonly");
359
+ }
360
+ },
361
+ { once: true }
362
+ );
363
+ }
364
+ // Reassert anti-autofill attributes each switch
365
+ modelIdInput.autocomplete = "new-password";
366
+ modelIdInput.removeAttribute("name");
367
+ modelIdInput.setAttribute("data-lpignore", "true");
368
+ modelIdInput.setAttribute("data-1p-ignore", "true");
369
+ modelIdInput.setAttribute("data-bwignore", "true");
370
+ modelIdInput.setAttribute("data-form-type", "other");
371
+ modelIdInput.setAttribute("autocapitalize", "none");
372
+ modelIdInput.setAttribute("autocorrect", "off");
373
  }
374
  if (window.kimiDB) {
375
  await window.kimiDB.setPreference("llmProvider", provider);
376
+ // Éviter d’écraser l’URL personnalisée pour custom/ollama
377
+ if (["openrouter", "openai", "groq", "together", "deepseek"].includes(provider)) {
378
+ const canonical = provider === "openrouter" ? "https://openrouter.ai/api/v1/chat/completions" : p.url;
379
+ await window.kimiDB.setPreference("llmBaseUrl", canonical);
380
+ } else {
381
+ const existing = await window.kimiDB.getPreference("llmBaseUrl", "");
382
+ await window.kimiDB.setPreference("llmBaseUrl", existing || p.url);
383
+ }
384
  const apiKeyLabel = document.getElementById("api-key-label");
385
  // Load provider-specific key into the input for clarity
386
  const keyPrefMap = {
 
958
  });
959
  })();
960
 
961
+ // Harden anti-autofill and masking on DOM ready
962
+ (function enforceApiInputSecurity() {
963
+ const input = ApiUi.apiKeyInput();
964
+ if (!input) return;
965
+ // Anti-autofill flags
966
+ input.autocomplete = "off";
967
+ input.removeAttribute("name");
968
+ input.setAttribute("data-lpignore", "true");
969
+ input.setAttribute("data-1p-ignore", "true");
970
+ input.setAttribute("data-bwignore", "true");
971
+ input.setAttribute("data-form-type", "other");
972
+ input.setAttribute("autocapitalize", "none");
973
+ input.setAttribute("autocorrect", "off");
974
+ // Keep masked by style as well
975
+ if (input.classList.contains("masked")) {
976
+ try {
977
+ input.style.webkitTextSecurity = "disc";
978
+ } catch (_) {}
979
+ input.style.filter = "blur(6px)";
980
+ }
981
+ // If the field gets focus, ensure it is editable (index.html sets readonly initially)
982
+ input.addEventListener(
983
+ "focus",
984
+ () => {
985
+ if (input.hasAttribute("readonly")) input.removeAttribute("readonly");
986
+ },
987
+ { once: false }
988
+ );
989
+ })();
990
+
991
  // Toggle show/hide for API key
992
  (function setupToggleEye() {
993
  const btn = ApiUi.toggleBtn();
994
  const input = ApiUi.apiKeyInput();
995
  if (!btn || !input) return;
996
  btn.addEventListener("click", () => {
997
+ const wasMasked = input.classList.contains("masked");
998
+ // Toggle class first
999
+ if (wasMasked) {
1000
+ input.classList.remove("masked");
1001
+ } else {
1002
+ input.classList.add("masked");
1003
+ }
1004
+ // Force style to avoid UA quirks where -webkit-text-security persists
1005
+ const nowMasked = input.classList.contains("masked");
1006
+ if (nowMasked) {
1007
+ // Apply masking explicitly
1008
+ try {
1009
+ input.style.webkitTextSecurity = "disc"; // Chromium/WebKit
1010
+ } catch (_) {}
1011
+ input.style.filter = "blur(6px)"; // Fallback
1012
+ } else {
1013
+ // Remove any residual masking
1014
+ try {
1015
+ input.style.webkitTextSecurity = ""; // reset to CSS/default
1016
+ } catch (_) {}
1017
+ input.style.filter = "none";
1018
+ }
1019
+ btn.setAttribute("aria-pressed", String(!nowMasked));
1020
  const icon = btn.querySelector("i");
1021
  if (icon) {
1022
  icon.classList.toggle("fa-eye");
1023
  icon.classList.toggle("fa-eye-slash");
1024
  }
1025
+ btn.setAttribute("aria-label", nowMasked ? "Show API key" : "Hide API key");
1026
  });
1027
  })();
1028
 
 
1078
  // Setup unified event handlers to prevent duplicates
1079
  setupUnifiedEventHandlers();
1080
 
1081
+ // Global hardening against browser/extension autofill on all text-like fields
1082
+ (function hardenAgainstAutofill() {
1083
+ const ATTRS = {
1084
+ autocomplete: "off",
1085
+ autocorrect: "off",
1086
+ autocapitalize: "none",
1087
+ spellcheck: "false",
1088
+ "data-lpignore": "true",
1089
+ "data-1p-ignore": "true",
1090
+ "data-bwignore": "true",
1091
+ "data-form-type": "other"
1092
+ };
1093
+ const block = el => {
1094
+ if (!el || !(el instanceof HTMLElement)) return;
1095
+ const tag = el.tagName.toLowerCase();
1096
+ const type = (el.getAttribute("type") || "").toLowerCase();
1097
+ if (tag === "input" && (type === "text" || type === "search" || type === "email" || type === "url")) {
1098
+ Object.entries(ATTRS).forEach(([k, v]) => el.setAttribute(k, v));
1099
+ // Never keep a sensitive name attribute
1100
+ const name = el.getAttribute("name") || "";
1101
+ if (name) el.removeAttribute("name");
1102
+ }
1103
+ if (tag === "textarea") {
1104
+ Object.entries(ATTRS).forEach(([k, v]) => el.setAttribute(k, v));
1105
+ const name = el.getAttribute("name") || "";
1106
+ if (name) el.removeAttribute("name");
1107
+ }
1108
+ };
1109
+ document.querySelectorAll("input, textarea").forEach(block);
1110
+ const mo = new MutationObserver(muts => {
1111
+ for (const m of muts) {
1112
+ m.addedNodes &&
1113
+ m.addedNodes.forEach(n => {
1114
+ if (n instanceof HTMLElement) {
1115
+ if (n.matches && (n.matches("input") || n.matches("textarea"))) block(n);
1116
+ n.querySelectorAll && n.querySelectorAll("input, textarea").forEach(block);
1117
+ }
1118
+ });
1119
+ if (m.type === "attributes" && m.target instanceof HTMLElement) {
1120
+ if (m.attributeName === "type" || m.attributeName === "name") block(m.target);
1121
+ }
1122
+ }
1123
+ });
1124
+ mo.observe(document.documentElement, {
1125
+ subtree: true,
1126
+ childList: true,
1127
+ attributes: true,
1128
+ attributeFilter: ["type", "name"]
1129
+ });
1130
+ })();
1131
+
1132
  // Initialize language and UI
1133
  await initializeLanguageAndUI();
1134