Spaces:
Running
Running
Upload 3 files
Browse files- kimi-js/kimi-constants.js +5 -5
- kimi-js/kimi-module.js +42 -5
- kimi-js/kimi-script.js +265 -14
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 |
-
|
1857 |
-
|
1858 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1859 |
});
|
1860 |
} catch (e) {}
|
1861 |
|
1862 |
// Typing indicator wiring
|
1863 |
try {
|
1864 |
-
//
|
1865 |
setTimeout(() => {
|
1866 |
const apiInput = document.getElementById("openrouter-api-key");
|
1867 |
if (apiInput) {
|
1868 |
apiInput.setAttribute("autocomplete", "new-password");
|
1869 |
-
apiInput.
|
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
|
|
|
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)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
135 |
// Only prefill model for OpenRouter, others should show placeholder only
|
136 |
if (modelIdInput) {
|
137 |
if (provider === "openrouter") {
|
138 |
-
|
|
|
|
|
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)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
227 |
}
|
228 |
if (window.kimiDB) {
|
229 |
await window.kimiDB.setPreference("llmProvider", provider);
|
230 |
-
|
231 |
-
|
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
|
818 |
-
|
819 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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",
|
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 |
|