Spaces:
Running
Running
Upload 36 files
Browse files- kimi-js/kimi-llm-manager.js +41 -25
- kimi-js/kimi-module.js +16 -3
- kimi-js/kimi-script.js +59 -4
kimi-js/kimi-llm-manager.js
CHANGED
|
@@ -933,6 +933,8 @@ class KimiLLMManager {
|
|
| 933 |
? window.getUnifiedDefaults()
|
| 934 |
: { temperature: 0.9, maxTokens: 400, top_p: 0.9, frequency_penalty: 0.9, presence_penalty: 0.8 };
|
| 935 |
|
|
|
|
|
|
|
| 936 |
const llmSettings = {
|
| 937 |
temperature: await this.db.getPreference("llmTemperature", unifiedDefaults.temperature),
|
| 938 |
maxTokens: await this.db.getPreference("llmMaxTokens", unifiedDefaults.maxTokens),
|
|
@@ -944,7 +946,7 @@ class KimiLLMManager {
|
|
| 944 |
const payload = {
|
| 945 |
model: this.currentModel,
|
| 946 |
messages: messages,
|
| 947 |
-
stream:
|
| 948 |
temperature: typeof options.temperature === "number" ? options.temperature : llmSettings.temperature,
|
| 949 |
max_tokens: typeof options.maxTokens === "number" ? options.maxTokens : llmSettings.maxTokens,
|
| 950 |
top_p: typeof options.topP === "number" ? options.topP : llmSettings.top_p,
|
|
@@ -1061,6 +1063,8 @@ class KimiLLMManager {
|
|
| 1061 |
? window.getUnifiedDefaults()
|
| 1062 |
: { temperature: 0.9, maxTokens: 400, top_p: 0.9, frequency_penalty: 0.9, presence_penalty: 0.8 };
|
| 1063 |
|
|
|
|
|
|
|
| 1064 |
const llmSettings = {
|
| 1065 |
temperature: await this.db.getPreference("llmTemperature", unifiedDefaults.temperature),
|
| 1066 |
maxTokens: await this.db.getPreference("llmMaxTokens", unifiedDefaults.maxTokens),
|
|
@@ -1072,7 +1076,7 @@ class KimiLLMManager {
|
|
| 1072 |
const payload = {
|
| 1073 |
model: this.currentModel,
|
| 1074 |
messages: messages,
|
| 1075 |
-
stream:
|
| 1076 |
temperature: typeof options.temperature === "number" ? options.temperature : llmSettings.temperature,
|
| 1077 |
max_tokens: typeof options.maxTokens === "number" ? options.maxTokens : llmSettings.maxTokens,
|
| 1078 |
top_p: typeof options.topP === "number" ? options.topP : llmSettings.top_p,
|
|
@@ -1171,6 +1175,7 @@ class KimiLLMManager {
|
|
| 1171 |
|
| 1172 |
async chatWithLocalStreaming(userMessage, onToken, options = {}) {
|
| 1173 |
const systemPromptContent = await this.assemblePrompt(userMessage);
|
|
|
|
| 1174 |
|
| 1175 |
const payload = {
|
| 1176 |
model: this.currentModel || "llama2",
|
|
@@ -1179,7 +1184,7 @@ class KimiLLMManager {
|
|
| 1179 |
...this.conversationContext.slice(-this.maxContextLength),
|
| 1180 |
{ role: "user", content: userMessage }
|
| 1181 |
],
|
| 1182 |
-
stream:
|
| 1183 |
};
|
| 1184 |
|
| 1185 |
try {
|
|
@@ -1195,36 +1200,47 @@ class KimiLLMManager {
|
|
| 1195 |
throw new Error("Ollama not available");
|
| 1196 |
}
|
| 1197 |
|
| 1198 |
-
const reader = response.body.getReader();
|
| 1199 |
-
const decoder = new TextDecoder();
|
| 1200 |
let fullResponse = "";
|
| 1201 |
|
| 1202 |
-
|
| 1203 |
-
|
| 1204 |
-
|
| 1205 |
-
|
| 1206 |
|
| 1207 |
-
|
| 1208 |
-
|
|
|
|
|
|
|
| 1209 |
|
| 1210 |
-
|
| 1211 |
-
|
| 1212 |
-
|
| 1213 |
-
|
| 1214 |
-
|
| 1215 |
-
|
| 1216 |
-
|
| 1217 |
-
|
| 1218 |
-
|
| 1219 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1220 |
}
|
| 1221 |
-
} catch (parseError) {
|
| 1222 |
-
console.warn("Failed to parse Ollama streaming chunk:", parseError);
|
| 1223 |
}
|
| 1224 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1225 |
}
|
| 1226 |
-
} finally {
|
| 1227 |
-
reader.releaseLock();
|
| 1228 |
}
|
| 1229 |
|
| 1230 |
// Add to context
|
|
|
|
| 933 |
? window.getUnifiedDefaults()
|
| 934 |
: { temperature: 0.9, maxTokens: 400, top_p: 0.9, frequency_penalty: 0.9, presence_penalty: 0.8 };
|
| 935 |
|
| 936 |
+
const enableStreaming = await this.db.getPreference("enableStreaming", true);
|
| 937 |
+
|
| 938 |
const llmSettings = {
|
| 939 |
temperature: await this.db.getPreference("llmTemperature", unifiedDefaults.temperature),
|
| 940 |
maxTokens: await this.db.getPreference("llmMaxTokens", unifiedDefaults.maxTokens),
|
|
|
|
| 946 |
const payload = {
|
| 947 |
model: this.currentModel,
|
| 948 |
messages: messages,
|
| 949 |
+
stream: enableStreaming, // Use user preference for streaming
|
| 950 |
temperature: typeof options.temperature === "number" ? options.temperature : llmSettings.temperature,
|
| 951 |
max_tokens: typeof options.maxTokens === "number" ? options.maxTokens : llmSettings.maxTokens,
|
| 952 |
top_p: typeof options.topP === "number" ? options.topP : llmSettings.top_p,
|
|
|
|
| 1063 |
? window.getUnifiedDefaults()
|
| 1064 |
: { temperature: 0.9, maxTokens: 400, top_p: 0.9, frequency_penalty: 0.9, presence_penalty: 0.8 };
|
| 1065 |
|
| 1066 |
+
const enableStreaming = await this.db.getPreference("enableStreaming", true);
|
| 1067 |
+
|
| 1068 |
const llmSettings = {
|
| 1069 |
temperature: await this.db.getPreference("llmTemperature", unifiedDefaults.temperature),
|
| 1070 |
maxTokens: await this.db.getPreference("llmMaxTokens", unifiedDefaults.maxTokens),
|
|
|
|
| 1076 |
const payload = {
|
| 1077 |
model: this.currentModel,
|
| 1078 |
messages: messages,
|
| 1079 |
+
stream: enableStreaming,
|
| 1080 |
temperature: typeof options.temperature === "number" ? options.temperature : llmSettings.temperature,
|
| 1081 |
max_tokens: typeof options.maxTokens === "number" ? options.maxTokens : llmSettings.maxTokens,
|
| 1082 |
top_p: typeof options.topP === "number" ? options.topP : llmSettings.top_p,
|
|
|
|
| 1175 |
|
| 1176 |
async chatWithLocalStreaming(userMessage, onToken, options = {}) {
|
| 1177 |
const systemPromptContent = await this.assemblePrompt(userMessage);
|
| 1178 |
+
const enableStreaming = await this.db.getPreference("enableStreaming", true);
|
| 1179 |
|
| 1180 |
const payload = {
|
| 1181 |
model: this.currentModel || "llama2",
|
|
|
|
| 1184 |
...this.conversationContext.slice(-this.maxContextLength),
|
| 1185 |
{ role: "user", content: userMessage }
|
| 1186 |
],
|
| 1187 |
+
stream: enableStreaming
|
| 1188 |
};
|
| 1189 |
|
| 1190 |
try {
|
|
|
|
| 1200 |
throw new Error("Ollama not available");
|
| 1201 |
}
|
| 1202 |
|
|
|
|
|
|
|
| 1203 |
let fullResponse = "";
|
| 1204 |
|
| 1205 |
+
if (enableStreaming) {
|
| 1206 |
+
// Streaming mode
|
| 1207 |
+
const reader = response.body.getReader();
|
| 1208 |
+
const decoder = new TextDecoder();
|
| 1209 |
|
| 1210 |
+
try {
|
| 1211 |
+
while (true) {
|
| 1212 |
+
const { done, value } = await reader.read();
|
| 1213 |
+
if (done) break;
|
| 1214 |
|
| 1215 |
+
const chunk = decoder.decode(value, { stream: true });
|
| 1216 |
+
const lines = chunk.split("\n").filter(line => line.trim());
|
| 1217 |
+
|
| 1218 |
+
for (const line of lines) {
|
| 1219 |
+
try {
|
| 1220 |
+
const parsed = JSON.parse(line);
|
| 1221 |
+
const content = parsed.message?.content;
|
| 1222 |
+
if (content) {
|
| 1223 |
+
fullResponse += content;
|
| 1224 |
+
onToken(content);
|
| 1225 |
+
}
|
| 1226 |
+
if (parsed.done) {
|
| 1227 |
+
break;
|
| 1228 |
+
}
|
| 1229 |
+
} catch (parseError) {
|
| 1230 |
+
console.warn("Failed to parse Ollama streaming chunk:", parseError);
|
| 1231 |
}
|
|
|
|
|
|
|
| 1232 |
}
|
| 1233 |
}
|
| 1234 |
+
} finally {
|
| 1235 |
+
reader.releaseLock();
|
| 1236 |
+
}
|
| 1237 |
+
} else {
|
| 1238 |
+
// Non-streaming mode
|
| 1239 |
+
const data = await response.json();
|
| 1240 |
+
fullResponse = data.message?.content || "";
|
| 1241 |
+
if (fullResponse && onToken) {
|
| 1242 |
+
onToken(fullResponse);
|
| 1243 |
}
|
|
|
|
|
|
|
| 1244 |
}
|
| 1245 |
|
| 1246 |
// Add to context
|
kimi-js/kimi-module.js
CHANGED
|
@@ -777,7 +777,8 @@ async function loadSettingsData() {
|
|
| 777 |
"llmMaxTokens",
|
| 778 |
"llmTopP",
|
| 779 |
"llmFrequencyPenalty",
|
| 780 |
-
"llmPresencePenalty"
|
|
|
|
| 781 |
];
|
| 782 |
const preferences = await kimiDB.getPreferencesBatch(preferenceKeys);
|
| 783 |
|
|
@@ -796,6 +797,7 @@ async function loadSettingsData() {
|
|
| 796 |
const llmTopP = preferences.llmTopP !== undefined ? preferences.llmTopP : 0.9;
|
| 797 |
const llmFrequencyPenalty = preferences.llmFrequencyPenalty !== undefined ? preferences.llmFrequencyPenalty : 0.9;
|
| 798 |
const llmPresencePenalty = preferences.llmPresencePenalty !== undefined ? preferences.llmPresencePenalty : 0.8;
|
|
|
|
| 799 |
|
| 800 |
// Update UI with voice settings
|
| 801 |
const languageSelect = document.getElementById("language-selection");
|
|
@@ -811,6 +813,17 @@ async function loadSettingsData() {
|
|
| 811 |
updateSlider("llm-frequency-penalty", llmFrequencyPenalty);
|
| 812 |
updateSlider("llm-presence-penalty", llmPresencePenalty);
|
| 813 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 814 |
// Batch load personality traits
|
| 815 |
const traitNames = ["affection", "playfulness", "intelligence", "empathy", "humor", "romance"];
|
| 816 |
const personality = await kimiDB.getPersonalityTraitsBatch(traitNames, selectedCharacter);
|
|
@@ -832,10 +845,10 @@ async function loadSettingsData() {
|
|
| 832 |
await updateStats();
|
| 833 |
|
| 834 |
// Update API key input
|
| 835 |
-
const apiKeyInput = document.getElementById("
|
| 836 |
if (apiKeyInput) {
|
| 837 |
const keyPref = window.KimiProviderUtils ? window.KimiProviderUtils.getKeyPrefForProvider(provider) : null;
|
| 838 |
-
const providerKey = keyPref && preferences[keyPref] ? preferences[keyPref] :
|
| 839 |
apiKeyInput.value = providerKey || "";
|
| 840 |
}
|
| 841 |
const providerSelect = document.getElementById("llm-provider");
|
|
|
|
| 777 |
"llmMaxTokens",
|
| 778 |
"llmTopP",
|
| 779 |
"llmFrequencyPenalty",
|
| 780 |
+
"llmPresencePenalty",
|
| 781 |
+
"enableStreaming"
|
| 782 |
];
|
| 783 |
const preferences = await kimiDB.getPreferencesBatch(preferenceKeys);
|
| 784 |
|
|
|
|
| 797 |
const llmTopP = preferences.llmTopP !== undefined ? preferences.llmTopP : 0.9;
|
| 798 |
const llmFrequencyPenalty = preferences.llmFrequencyPenalty !== undefined ? preferences.llmFrequencyPenalty : 0.9;
|
| 799 |
const llmPresencePenalty = preferences.llmPresencePenalty !== undefined ? preferences.llmPresencePenalty : 0.8;
|
| 800 |
+
const enableStreaming = preferences.enableStreaming !== undefined ? preferences.enableStreaming : true;
|
| 801 |
|
| 802 |
// Update UI with voice settings
|
| 803 |
const languageSelect = document.getElementById("language-selection");
|
|
|
|
| 813 |
updateSlider("llm-frequency-penalty", llmFrequencyPenalty);
|
| 814 |
updateSlider("llm-presence-penalty", llmPresencePenalty);
|
| 815 |
|
| 816 |
+
// Update streaming toggle
|
| 817 |
+
const streamingToggle = document.getElementById("enable-streaming");
|
| 818 |
+
if (streamingToggle) {
|
| 819 |
+
if (enableStreaming) {
|
| 820 |
+
streamingToggle.classList.add("active");
|
| 821 |
+
} else {
|
| 822 |
+
streamingToggle.classList.remove("active");
|
| 823 |
+
}
|
| 824 |
+
streamingToggle.setAttribute("aria-checked", String(enableStreaming));
|
| 825 |
+
}
|
| 826 |
+
|
| 827 |
// Batch load personality traits
|
| 828 |
const traitNames = ["affection", "playfulness", "intelligence", "empathy", "humor", "romance"];
|
| 829 |
const personality = await kimiDB.getPersonalityTraitsBatch(traitNames, selectedCharacter);
|
|
|
|
| 845 |
await updateStats();
|
| 846 |
|
| 847 |
// Update API key input
|
| 848 |
+
const apiKeyInput = document.getElementById("provider-api-key");
|
| 849 |
if (apiKeyInput) {
|
| 850 |
const keyPref = window.KimiProviderUtils ? window.KimiProviderUtils.getKeyPrefForProvider(provider) : null;
|
| 851 |
+
const providerKey = keyPref && preferences[keyPref] ? preferences[keyPref] : apiKey;
|
| 852 |
apiKeyInput.value = providerKey || "";
|
| 853 |
}
|
| 854 |
const providerSelect = document.getElementById("llm-provider");
|
kimi-js/kimi-script.js
CHANGED
|
@@ -140,7 +140,14 @@ document.addEventListener("DOMContentLoaded", async function () {
|
|
| 140 |
const baseUrlInput = ApiUi.baseUrlInput();
|
| 141 |
const modelIdInput = ApiUi.modelIdInput();
|
| 142 |
const apiKeyInput = ApiUi.apiKeyInput();
|
| 143 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 144 |
// Only prefill model for OpenRouter, others should show placeholder only
|
| 145 |
if (modelIdInput) {
|
| 146 |
if (provider === "openrouter") {
|
|
@@ -193,6 +200,11 @@ document.addEventListener("DOMContentLoaded", async function () {
|
|
| 193 |
}
|
| 194 |
});
|
| 195 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 196 |
const providerSelectEl = document.getElementById("llm-provider");
|
| 197 |
if (providerSelectEl) {
|
| 198 |
providerSelectEl.addEventListener("change", async function (e) {
|
|
@@ -241,7 +253,21 @@ document.addEventListener("DOMContentLoaded", async function () {
|
|
| 241 |
const p = placeholders[provider] || placeholders.openai;
|
| 242 |
if (baseUrlInput) {
|
| 243 |
baseUrlInput.placeholder = p.url;
|
| 244 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 245 |
}
|
| 246 |
if (apiKeyInput) {
|
| 247 |
apiKeyInput.placeholder = p.keyPh;
|
|
@@ -263,7 +289,7 @@ document.addEventListener("DOMContentLoaded", async function () {
|
|
| 263 |
}
|
| 264 |
if (window.kimiDB) {
|
| 265 |
await window.kimiDB.setPreference("llmProvider", provider);
|
| 266 |
-
|
| 267 |
const apiKeyLabel = document.getElementById("api-key-label");
|
| 268 |
// Load provider-specific key into the input for clarity
|
| 269 |
const keyPref = window.KimiProviderUtils
|
|
@@ -296,6 +322,15 @@ document.addEventListener("DOMContentLoaded", async function () {
|
|
| 296 |
}
|
| 297 |
}
|
| 298 |
ApiUi.clearStatus();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 299 |
}
|
| 300 |
});
|
| 301 |
|
|
@@ -315,6 +350,27 @@ document.addEventListener("DOMContentLoaded", async function () {
|
|
| 315 |
}
|
| 316 |
});
|
| 317 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 318 |
}
|
| 319 |
|
| 320 |
// Loading screen management
|
|
@@ -773,7 +829,6 @@ document.addEventListener("DOMContentLoaded", async function () {
|
|
| 773 |
[],
|
| 774 |
500
|
| 775 |
);
|
| 776 |
-
|
| 777 |
kimiInit.register(
|
| 778 |
"dataManager",
|
| 779 |
async () => {
|
|
|
|
| 140 |
const baseUrlInput = ApiUi.baseUrlInput();
|
| 141 |
const modelIdInput = ApiUi.modelIdInput();
|
| 142 |
const apiKeyInput = ApiUi.apiKeyInput();
|
| 143 |
+
|
| 144 |
+
// Set base URL based on modifiability
|
| 145 |
+
if (baseUrlInput) {
|
| 146 |
+
const isModifiable = isUrlModifiable(provider);
|
| 147 |
+
baseUrlInput.value = baseUrl || "";
|
| 148 |
+
baseUrlInput.disabled = !isModifiable;
|
| 149 |
+
baseUrlInput.style.opacity = isModifiable ? "1" : "0.6";
|
| 150 |
+
}
|
| 151 |
// Only prefill model for OpenRouter, others should show placeholder only
|
| 152 |
if (modelIdInput) {
|
| 153 |
if (provider === "openrouter") {
|
|
|
|
| 200 |
}
|
| 201 |
});
|
| 202 |
|
| 203 |
+
// Helper function to check if URL is modifiable for current provider
|
| 204 |
+
function isUrlModifiable(provider) {
|
| 205 |
+
return provider === "openai-compatible" || provider === "ollama";
|
| 206 |
+
}
|
| 207 |
+
|
| 208 |
const providerSelectEl = document.getElementById("llm-provider");
|
| 209 |
if (providerSelectEl) {
|
| 210 |
providerSelectEl.addEventListener("change", async function (e) {
|
|
|
|
| 253 |
const p = placeholders[provider] || placeholders.openai;
|
| 254 |
if (baseUrlInput) {
|
| 255 |
baseUrlInput.placeholder = p.url;
|
| 256 |
+
// Only allow URL modification for custom and ollama providers
|
| 257 |
+
const isModifiable = isUrlModifiable(provider);
|
| 258 |
+
|
| 259 |
+
if (isModifiable) {
|
| 260 |
+
// For custom and ollama: load saved URL or use default
|
| 261 |
+
const savedUrl = await window.kimiDB.getPreference("llmBaseUrl", p.url);
|
| 262 |
+
baseUrlInput.value = savedUrl;
|
| 263 |
+
baseUrlInput.disabled = false;
|
| 264 |
+
baseUrlInput.style.opacity = "1";
|
| 265 |
+
} else {
|
| 266 |
+
// For other providers: fixed URL, not modifiable
|
| 267 |
+
baseUrlInput.value = p.url;
|
| 268 |
+
baseUrlInput.disabled = true;
|
| 269 |
+
baseUrlInput.style.opacity = "0.6";
|
| 270 |
+
}
|
| 271 |
}
|
| 272 |
if (apiKeyInput) {
|
| 273 |
apiKeyInput.placeholder = p.keyPh;
|
|
|
|
| 289 |
}
|
| 290 |
if (window.kimiDB) {
|
| 291 |
await window.kimiDB.setPreference("llmProvider", provider);
|
| 292 |
+
|
| 293 |
const apiKeyLabel = document.getElementById("api-key-label");
|
| 294 |
// Load provider-specific key into the input for clarity
|
| 295 |
const keyPref = window.KimiProviderUtils
|
|
|
|
| 322 |
}
|
| 323 |
}
|
| 324 |
ApiUi.clearStatus();
|
| 325 |
+
|
| 326 |
+
// Save URL after all UI updates are complete
|
| 327 |
+
const isModifiable = isUrlModifiable(provider);
|
| 328 |
+
if (isModifiable && baseUrlInput) {
|
| 329 |
+
await window.kimiDB.setPreference("llmBaseUrl", baseUrlInput.value);
|
| 330 |
+
} else {
|
| 331 |
+
// For fixed providers, save the standard URL
|
| 332 |
+
await window.kimiDB.setPreference("llmBaseUrl", p.url);
|
| 333 |
+
}
|
| 334 |
}
|
| 335 |
});
|
| 336 |
|
|
|
|
| 350 |
}
|
| 351 |
});
|
| 352 |
}
|
| 353 |
+
|
| 354 |
+
// Listen for Base URL changes and save for modifiable providers
|
| 355 |
+
const baseUrlInput = ApiUi.baseUrlInput();
|
| 356 |
+
if (baseUrlInput) {
|
| 357 |
+
baseUrlInput.addEventListener("blur", async function (e) {
|
| 358 |
+
const providerSelect = ApiUi.providerSelect();
|
| 359 |
+
const provider = providerSelect ? providerSelect.value : "openrouter";
|
| 360 |
+
const isModifiable = isUrlModifiable(provider);
|
| 361 |
+
|
| 362 |
+
if (isModifiable && window.kimiDB) {
|
| 363 |
+
const newUrl = e.target.value.trim();
|
| 364 |
+
if (newUrl) {
|
| 365 |
+
try {
|
| 366 |
+
await window.kimiDB.setPreference("llmBaseUrl", newUrl);
|
| 367 |
+
} catch (error) {
|
| 368 |
+
console.warn("Failed to save base URL:", error.message);
|
| 369 |
+
}
|
| 370 |
+
}
|
| 371 |
+
}
|
| 372 |
+
});
|
| 373 |
+
}
|
| 374 |
}
|
| 375 |
|
| 376 |
// Loading screen management
|
|
|
|
| 829 |
[],
|
| 830 |
500
|
| 831 |
);
|
|
|
|
| 832 |
kimiInit.register(
|
| 833 |
"dataManager",
|
| 834 |
async () => {
|