isaikr 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +35 -0
- package/cdn/api.js +19 -0
- package/cdn/character.js +254 -0
- package/cdn/chat.js +33 -0
- package/cdn/code-editor.js +1131 -0
- package/cdn/community-compose.js +270 -0
- package/cdn/games/2048/index.html +12 -0
- package/cdn/games/breakout/index.html +13 -0
- package/cdn/games/clicker/index.html +26 -0
- package/cdn/games/flappy/index.html +11 -0
- package/cdn/games/memory/index.html +34 -0
- package/cdn/games/pong/index.html +13 -0
- package/cdn/games/reaction/index.html +38 -0
- package/cdn/games/runner/index.html +11 -0
- package/cdn/games/snake/index.html +11 -0
- package/cdn/games/tetris/index.html +14 -0
- package/cdn/games/whack/index.html +8 -0
- package/cdn/go.js +126 -0
- package/cdn/go2.js +127 -0
- package/cdn/header3_behavior.js +1167 -0
- package/cdn/header3_layout.js +1004 -0
- package/cdn/header3_layout.js.bak +1004 -0
- package/cdn/header3_style.css +3524 -0
- package/cdn/header3_style.css.bak +3514 -0
- package/cdn/lang.js +198 -0
- package/cdn/loading.js +143 -0
- package/cdn/loading2.js +144 -0
- package/cdn/local-model.js +2941 -0
- package/cdn/main.js +4 -0
- package/cdn/main_asset.js +1849 -0
- package/cdn/main_asset.js.bak +6999 -0
- package/cdn/main_index.css +287 -0
- package/cdn/re_board3.css +733 -0
- package/cdn/re_board3.js +734 -0
- package/cdn/re_chat_tts.js +652 -0
- package/cdn/re_local_runtime.js +2246 -0
- package/cdn/re_local_runtime.js.bak +2246 -0
- package/cdn/re_share.js +577 -0
- package/cdn/re_voice.js +542 -0
- package/cdn/utils.js +36 -0
- package/cdn/view.js +321 -0
- package/header3_behavior.js +804 -0
- package/header3_layout.js +998 -0
- package/header3_style.css +2740 -0
- package/index.js +0 -0
- package/lang.js +179 -0
- package/main_asset.js +2416 -0
- package/main_index.css +274 -0
- package/package.json +14 -0
- package/re_chat_tts.js +1419 -0
- package/re_voice.js +430 -0
|
@@ -0,0 +1,2941 @@
|
|
|
1
|
+
// extracted from main_asset.js: local model and generation helpers
|
|
2
|
+
let activeLocalInferenceRunId = Number(window.__ISAI_LOCAL_INFERENCE_RUN_ID__ || 0) || 0;
|
|
3
|
+
|
|
4
|
+
// Compatibility shim for mode/layout scripts that expect these globals.
|
|
5
|
+
if (typeof window !== "undefined") {
|
|
6
|
+
if (typeof window.clearInlineStyleProps !== "function") {
|
|
7
|
+
window.clearInlineStyleProps = function clearInlineStyleProps(node, props) {
|
|
8
|
+
if (!node || !node.style) return;
|
|
9
|
+
const list = Array.isArray(props) ? props : [];
|
|
10
|
+
list.forEach((key) => {
|
|
11
|
+
if (typeof key === "string" && key) node.style.removeProperty(key);
|
|
12
|
+
});
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
if (typeof window.__clearInlineStyleProps !== "function") {
|
|
16
|
+
window.__clearInlineStyleProps = window.clearInlineStyleProps;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function nextLocalInferenceRunId() {
|
|
21
|
+
activeLocalInferenceRunId += 1;
|
|
22
|
+
window.__ISAI_LOCAL_INFERENCE_RUN_ID__ = activeLocalInferenceRunId;
|
|
23
|
+
return activeLocalInferenceRunId;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function terminateLocalRuntime(options = {}) {
|
|
27
|
+
const runtime = localWasmEngine;
|
|
28
|
+
const shouldReset = !!(options && options.reset);
|
|
29
|
+
window.__ISAI_LOCAL_RUNTIME_STOPPING__ = true;
|
|
30
|
+
nextLocalInferenceRunId();
|
|
31
|
+
if (!runtime) {
|
|
32
|
+
if (shouldReset) {
|
|
33
|
+
isModelLoaded = false;
|
|
34
|
+
localWasmEngine = null;
|
|
35
|
+
if (typeof setLocalRuntimeState === "function") setLocalRuntimeState(null);
|
|
36
|
+
}
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
const methods = ["abort", "stop", "cancel", "terminate", "interrupt"];
|
|
40
|
+
for (const methodName of methods) {
|
|
41
|
+
if (typeof runtime[methodName] === "function") {
|
|
42
|
+
try {
|
|
43
|
+
runtime[methodName]();
|
|
44
|
+
break;
|
|
45
|
+
} catch (error) {}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
if (shouldReset) {
|
|
49
|
+
isModelLoaded = false;
|
|
50
|
+
localWasmEngine = null;
|
|
51
|
+
if (typeof setLocalRuntimeState === "function") setLocalRuntimeState(null);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function extractCharacterPersonaPayload(options = {}) {
|
|
56
|
+
const root = (options && typeof options === "object") ? options : {};
|
|
57
|
+
const persona = (root.persona && typeof root.persona === "object") ? root.persona : {};
|
|
58
|
+
return {
|
|
59
|
+
name: persona.name ?? root.persona_name ?? root.personaName ?? "",
|
|
60
|
+
age: persona.age ?? root.persona_age ?? root.personaAge ?? "",
|
|
61
|
+
personality: persona.personality ?? root.persona_personality ?? root.personaPersonality ?? "",
|
|
62
|
+
speech_style: persona.speech_style ?? persona.speechStyle ?? root.persona_speech_style ?? root.personaSpeechStyle ?? "",
|
|
63
|
+
background: persona.background ?? root.persona_background ?? root.personaBackground ?? "",
|
|
64
|
+
opening_line: persona.opening_line ?? persona.openingLine ?? root.persona_opening_line ?? root.personaOpeningLine ?? "",
|
|
65
|
+
locale: persona.locale ?? root.persona_locale ?? root.personaLocale ?? root.locale ?? ""
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function hasCharacterPersonaPayload(payload = {}) {
|
|
70
|
+
if (!payload || typeof payload !== "object") return false;
|
|
71
|
+
const fields = [payload.name, payload.personality, payload.speech_style, payload.speechStyle, payload.background];
|
|
72
|
+
return fields.some((value) => sanitizeCharacterChatText(value, 20).length > 0);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function normalizeCharacterLocale(raw) {
|
|
76
|
+
const text = String(raw || "").trim().toLowerCase().replace(/_/g, "-");
|
|
77
|
+
if (!text) return getCharacterChatUiLanguage();
|
|
78
|
+
if (/^[a-z]{2}(?:-[a-z]{2})?$/.test(text)) return text;
|
|
79
|
+
return getCharacterChatUiLanguage();
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function buildCharacterPersonaSavePayload(session) {
|
|
83
|
+
if (!session || !session.persona) return null;
|
|
84
|
+
const sourceId = Number(session.sourceId || 0);
|
|
85
|
+
if (!sourceId) return null;
|
|
86
|
+
const ageRaw = Number(session.persona.age || 0);
|
|
87
|
+
const age = Number.isFinite(ageRaw) && ageRaw >= 19 && ageRaw <= 120 ? Math.floor(ageRaw) : 0;
|
|
88
|
+
return {
|
|
89
|
+
id: sourceId,
|
|
90
|
+
persona: {
|
|
91
|
+
name: normalizeCharacterPersonaName(session.persona.name || ""),
|
|
92
|
+
age,
|
|
93
|
+
personality: sanitizeCharacterChatText(session.persona.personality || "", 300),
|
|
94
|
+
speech_style: sanitizeCharacterChatText(session.persona.speechStyle || "", 300),
|
|
95
|
+
background: sanitizeCharacterChatText(session.persona.background || "", 420),
|
|
96
|
+
opening_line: sanitizeCharacterOpeningLine(session.persona.openingLine || getCharacterSessionOpeningLine(session)),
|
|
97
|
+
locale: normalizeCharacterLocale(session.locale || session.persona.locale || "")
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async function saveCharacterPersonaToStore(session) {
|
|
103
|
+
const payload = buildCharacterPersonaSavePayload(session);
|
|
104
|
+
if (!payload) return false;
|
|
105
|
+
try {
|
|
106
|
+
const response = await fetch("re_store.php?action=save_gallery_persona", {
|
|
107
|
+
method: "POST",
|
|
108
|
+
headers: { "Content-Type": "application/json" },
|
|
109
|
+
body: JSON.stringify(payload)
|
|
110
|
+
});
|
|
111
|
+
const data = await parseJsonResponseSafe(response);
|
|
112
|
+
return !!(data && data.success);
|
|
113
|
+
} catch (error) {
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
async function generateCharacterPersonaForSession(session) {
|
|
119
|
+
const base = defaultCharacterChatPersona(session.prompt, session.nickname, session);
|
|
120
|
+
return {
|
|
121
|
+
name: normalizeCharacterPersonaName(base.name),
|
|
122
|
+
personality: sanitizeCharacterChatText(base.personality, 300),
|
|
123
|
+
speechStyle: sanitizeCharacterChatText(base.speechStyle, 300),
|
|
124
|
+
background: sanitizeCharacterChatText(base.background, 420),
|
|
125
|
+
openingLine: getCharacterSessionOpeningLine(session),
|
|
126
|
+
locale: normalizeCharacterLocale(session.locale || "")
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
async function startCharacterImageChat(options = {}) {
|
|
131
|
+
const imageUrl = String(options.imageUrl || "").trim();
|
|
132
|
+
if (!imageUrl) return false;
|
|
133
|
+
|
|
134
|
+
const prompt = normalizeCharacterChatPromptText(options.prompt || options.content || "");
|
|
135
|
+
const nickname = sanitizeCharacterChatText(options.nickname || options.name || "", 36);
|
|
136
|
+
const sourceId = Number(options.id || options.sourceId || 0) || null;
|
|
137
|
+
const rawPersonaPayload = extractCharacterPersonaPayload(options);
|
|
138
|
+
const hasStoredPersona = hasCharacterPersonaPayload(rawPersonaPayload);
|
|
139
|
+
const locale = normalizeCharacterLocale(rawPersonaPayload.locale || options.locale || "");
|
|
140
|
+
|
|
141
|
+
window.scrollTo({ top: 0, behavior: "smooth" });
|
|
142
|
+
|
|
143
|
+
if (activeApp) {
|
|
144
|
+
try { exitAppMode(); } catch (error) {}
|
|
145
|
+
}
|
|
146
|
+
if (window.$boardApp && typeof window.$boardApp.closeCharacterChat === "function") {
|
|
147
|
+
try { window.$boardApp.closeCharacterChat(); } catch (error) {}
|
|
148
|
+
}
|
|
149
|
+
if (isMenuOpen) {
|
|
150
|
+
try { toggleStoreMenu(); } catch (error) {}
|
|
151
|
+
}
|
|
152
|
+
if (typeof closePreview === "function") {
|
|
153
|
+
try { closePreview(); } catch (error) {}
|
|
154
|
+
}
|
|
155
|
+
if (typeof setMode === "function") setMode("chat");
|
|
156
|
+
|
|
157
|
+
resetChat(true);
|
|
158
|
+
startExperience();
|
|
159
|
+
|
|
160
|
+
const setActiveCharacterSession = typeof window.setCharacterChatSession === "function"
|
|
161
|
+
? window.setCharacterChatSession
|
|
162
|
+
: (session) => {
|
|
163
|
+
characterChatSession = session && session.active ? session : null;
|
|
164
|
+
window.ISAI_CHARACTER_CHAT_SESSION = characterChatSession;
|
|
165
|
+
if (typeof applyCharacterChatVisualState === "function") applyCharacterChatVisualState();
|
|
166
|
+
return characterChatSession;
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
const activeCharacterSession = setActiveCharacterSession({
|
|
170
|
+
active: true,
|
|
171
|
+
imageUrl,
|
|
172
|
+
prompt,
|
|
173
|
+
nickname,
|
|
174
|
+
sourceId,
|
|
175
|
+
persona: null,
|
|
176
|
+
locale
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
const introBubble = appendMsg("ai", "Opening chat...");
|
|
180
|
+
let persona = null;
|
|
181
|
+
if (hasStoredPersona) {
|
|
182
|
+
persona = normalizeCharacterPersonaPayload(rawPersonaPayload, prompt, nickname, activeCharacterSession);
|
|
183
|
+
persona.locale = locale;
|
|
184
|
+
} else {
|
|
185
|
+
persona = await generateCharacterPersonaForSession(activeCharacterSession);
|
|
186
|
+
persona.locale = locale;
|
|
187
|
+
}
|
|
188
|
+
activeCharacterSession.persona = persona;
|
|
189
|
+
setActiveCharacterSession(activeCharacterSession);
|
|
190
|
+
if (!hasStoredPersona) {
|
|
191
|
+
saveCharacterPersonaToStore(activeCharacterSession).catch(() => {});
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (persona && window.$boardApp && sourceId && window.$boardApp.items && Array.isArray(window.$boardApp.items.gallery)) {
|
|
195
|
+
const currentItem = window.$boardApp.items.gallery.find((item) => Number(item && item.id ? item.id : 0) === sourceId);
|
|
196
|
+
if (currentItem) {
|
|
197
|
+
currentItem.persona_name = persona.name || "";
|
|
198
|
+
currentItem.persona_age = Number(persona.age || 0) || "";
|
|
199
|
+
currentItem.persona_personality = persona.personality || "";
|
|
200
|
+
currentItem.persona_speech_style = persona.speechStyle || "";
|
|
201
|
+
currentItem.persona_background = persona.background || "";
|
|
202
|
+
currentItem.persona_opening_line = persona.openingLine || "";
|
|
203
|
+
currentItem.persona_locale = normalizeCharacterLocale(persona.locale || locale || "");
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const openingLine = sanitizeCharacterOpeningLine(
|
|
208
|
+
persona && String(persona.openingLine || "").trim()
|
|
209
|
+
? persona.openingLine
|
|
210
|
+
: getCharacterSessionOpeningLine(activeCharacterSession)
|
|
211
|
+
);
|
|
212
|
+
if (introBubble) {
|
|
213
|
+
introBubble.innerHTML = formatAiBubbleContent(openingLine);
|
|
214
|
+
} else {
|
|
215
|
+
appendMsg("ai", openingLine);
|
|
216
|
+
}
|
|
217
|
+
chatHistory = [];
|
|
218
|
+
scrollBottom();
|
|
219
|
+
setActiveCharacterSession(activeCharacterSession);
|
|
220
|
+
|
|
221
|
+
const input = document.getElementById("prompt-input");
|
|
222
|
+
if (input) input.focus();
|
|
223
|
+
if (typeof showToast === "function") showToast("Character chat ready");
|
|
224
|
+
return true;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
window.startCharacterImageChat = startCharacterImageChat;
|
|
228
|
+
window.clearCharacterChatSession = clearCharacterChatSession;
|
|
229
|
+
|
|
230
|
+
function hasCodeIntent(text) {
|
|
231
|
+
const value = String(text || "");
|
|
232
|
+
if (!value.trim()) return false;
|
|
233
|
+
return /(?:\b(html|css|javascript|typescript|node|react|vue|svelte|php|python|java|c\+\+|c#|sql|json|api|algorithm|function|class|code|coding|game|canvas)\b)/i.test(value);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function buildCodeAssistantPrompt(basePrompt) {
|
|
237
|
+
const hardRules = [
|
|
238
|
+
"You are an expert coding assistant.",
|
|
239
|
+
"When the user requests code, provide runnable code directly.",
|
|
240
|
+
"Never refuse coding only because you are an AI.",
|
|
241
|
+
"For HTML or JavaScript game requests, return a complete single-file HTML document with embedded CSS and JavaScript."
|
|
242
|
+
].join(" ");
|
|
243
|
+
return [
|
|
244
|
+
String(basePrompt || "").trim(),
|
|
245
|
+
hardRules
|
|
246
|
+
].filter(Boolean).join("\n\n");
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function getCookieValue(name) {
|
|
250
|
+
const key = `${name}=`;
|
|
251
|
+
const cookies = document.cookie ? document.cookie.split(";") : [];
|
|
252
|
+
for (const raw of cookies) {
|
|
253
|
+
const c = raw.trim();
|
|
254
|
+
if (c.startsWith(key)) {
|
|
255
|
+
return decodeURIComponent(c.substring(key.length));
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
return "";
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function getTodayKey() {
|
|
262
|
+
const d = new Date();
|
|
263
|
+
const yyyy = d.getFullYear();
|
|
264
|
+
const mm = String(d.getMonth() + 1).padStart(2, "0");
|
|
265
|
+
const dd = String(d.getDate()).padStart(2, "0");
|
|
266
|
+
return `${yyyy}${mm}${dd}`;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function hasUsedUrlImageToday() {
|
|
270
|
+
return getCookieValue(URL_IMAGE_DAILY_COOKIE) === getTodayKey();
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function markUrlImageUsedToday() {
|
|
274
|
+
const now = new Date();
|
|
275
|
+
const endOfDay = new Date(now);
|
|
276
|
+
endOfDay.setHours(23, 59, 59, 999);
|
|
277
|
+
document.cookie = `${URL_IMAGE_DAILY_COOKIE}=${encodeURIComponent(getTodayKey())}; expires=${endOfDay.toUTCString()}; path=/; SameSite=Lax`;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function sanitizeJsonPayloadText(rawText) {
|
|
281
|
+
const text = String(rawText ?? "");
|
|
282
|
+
const withoutBom = text.replace(/^\uFEFF/, "");
|
|
283
|
+
const start = withoutBom.search(/[{\[]/);
|
|
284
|
+
if (start > 0) return withoutBom.slice(start).trim();
|
|
285
|
+
return withoutBom.trim();
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
function parseJsonTextSafe(rawText) {
|
|
289
|
+
const cleaned = sanitizeJsonPayloadText(rawText);
|
|
290
|
+
if (!cleaned) return {};
|
|
291
|
+
return JSON.parse(cleaned);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
async function parseJsonResponseSafe(response) {
|
|
295
|
+
const rawText = await response.text();
|
|
296
|
+
const statusCode = Number(response && response.status) || 0;
|
|
297
|
+
const isOk = !!(response && response.ok);
|
|
298
|
+
|
|
299
|
+
try {
|
|
300
|
+
const parsed = parseJsonTextSafe(rawText);
|
|
301
|
+
if (parsed && typeof parsed === "object") {
|
|
302
|
+
if (!isOk) {
|
|
303
|
+
const normalizedError = String(
|
|
304
|
+
parsed.error ||
|
|
305
|
+
parsed.message ||
|
|
306
|
+
(statusCode ? `HTTP ${statusCode}` : "API_FAIL")
|
|
307
|
+
);
|
|
308
|
+
return { ...parsed, error: normalizedError, code: parsed.code || statusCode };
|
|
309
|
+
}
|
|
310
|
+
return parsed;
|
|
311
|
+
}
|
|
312
|
+
if (!isOk) {
|
|
313
|
+
return { error: statusCode ? `HTTP ${statusCode}` : "API_FAIL", code: statusCode };
|
|
314
|
+
}
|
|
315
|
+
return {};
|
|
316
|
+
} catch (e) {
|
|
317
|
+
const cleaned = sanitizeJsonPayloadText(rawText);
|
|
318
|
+
const shortText = cleaned ? cleaned.slice(0, 240) : "";
|
|
319
|
+
if (!isOk) {
|
|
320
|
+
const statusPart = statusCode ? `HTTP ${statusCode}` : "API_FAIL";
|
|
321
|
+
return {
|
|
322
|
+
error: shortText ? `${statusPart}: ${shortText}` : `${statusPart}: INVALID_JSON`,
|
|
323
|
+
code: statusCode
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
return {
|
|
327
|
+
error: shortText ? `INVALID_JSON: ${shortText}` : "INVALID_JSON",
|
|
328
|
+
code: statusCode
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
function shouldAutoSwitchToLocalFromError(rawError) {
|
|
334
|
+
const errText = String(rawError || "");
|
|
335
|
+
if (!errText) return true;
|
|
336
|
+
return /LIMIT_REACHED|RATE_LIMIT|TOO_MANY_REQUESTS|429|API_FAIL|API\s*Error|HTTP\s*4\d\d|HTTP\s*5\d\d|INVALID_JSON|TIMEOUT|network|gateway|upstream|service unavailable|quota|limit exceeded|limit reached|no response|empty response/i.test(errText);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
function filterStore(category, element) {
|
|
340
|
+
document.querySelectorAll(".store-filter-btn").forEach((btn) => btn.classList.remove("active"));
|
|
341
|
+
if (element) element.classList.add("active");
|
|
342
|
+
currentStoreCategory = category;
|
|
343
|
+
fetchStoreApps(currentStoreQuery, false);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
function renderFixedApps() {
|
|
347
|
+
const container = document.getElementById("fixed-apps-list");
|
|
348
|
+
container.innerHTML = "";
|
|
349
|
+
|
|
350
|
+
if (fixedApps.length !== 0) {
|
|
351
|
+
container.style.display = "flex";
|
|
352
|
+
fixedApps.forEach((app, index) => {
|
|
353
|
+
const normalizedApp = normalizeAppPayload(app) || app;
|
|
354
|
+
const item = document.createElement("div");
|
|
355
|
+
item.className = "fixed-app-item relative w-[44px] h-[44px] rounded-[14px]";
|
|
356
|
+
item.title = normalizeAppDisplayText(normalizedApp.title, "App");
|
|
357
|
+
item.onclick = () => loadAppDetails(normalizedApp.id);
|
|
358
|
+
item.innerHTML = getAppIconHtml(normalizedApp) + `
|
|
359
|
+
<button class="absolute -top-1 -right-1 bg-red-500 text-white rounded-full w-4 h-4 flex items-center justify-center text-[10px] z-20 shadow-md transition-transform hover:scale-110" onclick="removeFixedApp(event, ${index})" title="Remove">
|
|
360
|
+
<i class="ri-close-line"></i>
|
|
361
|
+
</button>
|
|
362
|
+
`;
|
|
363
|
+
container.appendChild(item);
|
|
364
|
+
});
|
|
365
|
+
} else {
|
|
366
|
+
container.style.display = "none";
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
function saveCurrentApp() {
|
|
371
|
+
if (activeApp) {
|
|
372
|
+
if (fixedApps.some((app) => app.id === activeApp.id)) {
|
|
373
|
+
showToast("Already added to favorites.");
|
|
374
|
+
} else {
|
|
375
|
+
fixedApps.push({
|
|
376
|
+
id: activeApp.id,
|
|
377
|
+
title: normalizeAppDisplayText(activeApp.title, activeApp.name || "App"),
|
|
378
|
+
icon_url: activeApp.icon_url
|
|
379
|
+
});
|
|
380
|
+
localStorage.setItem("ISAI_FIXED_APPS", JSON.stringify(fixedApps));
|
|
381
|
+
renderFixedApps();
|
|
382
|
+
showToast("Saved to favorites!");
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
|
|
388
|
+
|
|
389
|
+
function applyStoreMenuState(open) {
|
|
390
|
+
if (typeof window.setStoreMenuState === "function") {
|
|
391
|
+
window.setStoreMenuState(open);
|
|
392
|
+
return;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
const shouldOpen = !!open;
|
|
396
|
+
|
|
397
|
+
document.body.classList.toggle("is-store-menu-open", shouldOpen);
|
|
398
|
+
|
|
399
|
+
const chatStack = document.getElementById("chat-main-stack");
|
|
400
|
+
const topZone = document.getElementById("top-zone");
|
|
401
|
+
const storePanel = document.getElementById("store-panel");
|
|
402
|
+
const appPanel = document.getElementById("app-container");
|
|
403
|
+
const btnMenu = document.getElementById("btn-menu");
|
|
404
|
+
|
|
405
|
+
if (chatStack) chatStack.classList.toggle("menu-mode", shouldOpen);
|
|
406
|
+
if (topZone) topZone.classList.toggle("menu-mode", shouldOpen);
|
|
407
|
+
if (storePanel) storePanel.classList.toggle("open", shouldOpen);
|
|
408
|
+
if (btnMenu) btnMenu.classList.toggle("menu-open", shouldOpen);
|
|
409
|
+
if (appPanel && shouldOpen) appPanel.classList.remove("open");
|
|
410
|
+
|
|
411
|
+
isMenuOpen = shouldOpen;
|
|
412
|
+
window.isMenuOpen = shouldOpen;
|
|
413
|
+
|
|
414
|
+
if (shouldOpen && typeof fetchStoreApps === "function") {
|
|
415
|
+
const promptInput = document.getElementById("prompt-input");
|
|
416
|
+
const query = promptInput ? promptInput.value.trim() : "";
|
|
417
|
+
fetchStoreApps(query, false);
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
if (typeof window.__applyMobileChatRailSafety === "function") {
|
|
421
|
+
[0, 40, 120, 260].forEach((delay) => {
|
|
422
|
+
setTimeout(window.__applyMobileChatRailSafety, delay);
|
|
423
|
+
});
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
|
|
428
|
+
|
|
429
|
+
|
|
430
|
+
function toggleStoreMenu() {
|
|
431
|
+
const chatStack = document.getElementById("chat-main-stack");
|
|
432
|
+
const shouldOpen = !(chatStack && chatStack.classList.contains("menu-mode"));
|
|
433
|
+
if (shouldOpen && typeof window.currentMode !== "undefined" && window.currentMode !== "chat" && typeof window.setMode === "function") {
|
|
434
|
+
window.setMode("chat");
|
|
435
|
+
}
|
|
436
|
+
applyStoreMenuState(shouldOpen);
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
function toggleAppPanel() {
|
|
440
|
+
const appPanel = document.getElementById('app-container');
|
|
441
|
+
const btnApp = document.getElementById('btn-app');
|
|
442
|
+
|
|
443
|
+
if (!appPanel.classList.contains('open')) {
|
|
444
|
+
applyStoreMenuState(false);
|
|
445
|
+
appPanel.classList.add('open');
|
|
446
|
+
appPanel.style.display = "block";
|
|
447
|
+
if (btnApp) btnApp.classList.add('active', 'text-white', 'bg-[#262626]');
|
|
448
|
+
setMode('app');
|
|
449
|
+
fetchApps(document.getElementById('prompt-input').value.trim());
|
|
450
|
+
} else {
|
|
451
|
+
appPanel.classList.remove('open');
|
|
452
|
+
appPanel.style.display = "none";
|
|
453
|
+
if (btnApp) btnApp.classList.remove('active', 'text-white', 'bg-[#262626]');
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
function handleInput(element) {
|
|
458
|
+
element.style.height = "auto";
|
|
459
|
+
const maxHeight = window.innerWidth <= 900 ? 64 : 72;
|
|
460
|
+
const nextHeight = Math.min(element.scrollHeight, maxHeight);
|
|
461
|
+
element.style.height = nextHeight + "px";
|
|
462
|
+
element.style.overflowY = element.scrollHeight > maxHeight ? "auto" : "hidden";
|
|
463
|
+
|
|
464
|
+
if (isMenuOpen) {
|
|
465
|
+
const query = element.value.trim();
|
|
466
|
+
clearTimeout(searchTimeout);
|
|
467
|
+
searchTimeout = setTimeout(() => {
|
|
468
|
+
fetchStoreApps(query, false);
|
|
469
|
+
}, 500);
|
|
470
|
+
} else if (currentMode === "app") {
|
|
471
|
+
const query = element.value.trim();
|
|
472
|
+
clearTimeout(searchTimeout);
|
|
473
|
+
searchTimeout = setTimeout(() => {
|
|
474
|
+
fetchApps(query, false);
|
|
475
|
+
}, 500);
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
async function fetchStoreApps(query, append = false) {
|
|
480
|
+
const loader = document.getElementById("store-loader");
|
|
481
|
+
const grid = document.getElementById("store-grid");
|
|
482
|
+
|
|
483
|
+
if (!append) {
|
|
484
|
+
currentStorePage = 1;
|
|
485
|
+
hasMoreStoreApps = true;
|
|
486
|
+
currentStoreQuery = query;
|
|
487
|
+
grid.innerHTML = "";
|
|
488
|
+
grid.scrollTop = 0;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
if (!hasMoreStoreApps || isStoreLoading) return;
|
|
492
|
+
|
|
493
|
+
isStoreLoading = true;
|
|
494
|
+
loader.classList.remove("hidden");
|
|
495
|
+
|
|
496
|
+
let url = `re_store.php?action=list_apps&limit=${STORE_LIMIT}&page=${currentStorePage}`;
|
|
497
|
+
if (query) {
|
|
498
|
+
url += `&q=${encodeURIComponent(query)}`;
|
|
499
|
+
} else if (currentStoreCategory !== "All") {
|
|
500
|
+
url += `&cat=${encodeURIComponent(currentStoreCategory)}`;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
try {
|
|
504
|
+
const response = await fetch(url);
|
|
505
|
+
const json = await parseJsonResponseSafe(response);
|
|
506
|
+
const data = json.data ||[];
|
|
507
|
+
|
|
508
|
+
loader.classList.add("hidden");
|
|
509
|
+
|
|
510
|
+
if (data.length > 0) {
|
|
511
|
+
renderStoreItems(data, grid);
|
|
512
|
+
if (data.length < STORE_LIMIT) {
|
|
513
|
+
hasMoreStoreApps = false;
|
|
514
|
+
} else {
|
|
515
|
+
currentStorePage++;
|
|
516
|
+
}
|
|
517
|
+
} else if (!append) {
|
|
518
|
+
hasMoreStoreApps = false;
|
|
519
|
+
const noResultsMsg = typeof T !== "undefined" && T.no_results ? T.no_results : "No apps found.";
|
|
520
|
+
grid.innerHTML = `<div class="col-span-full text-center text-gray-500 text-[10px] py-4">${noResultsMsg}</div>`;
|
|
521
|
+
}
|
|
522
|
+
} catch (error) {
|
|
523
|
+
loader.classList.add("hidden");
|
|
524
|
+
isStoreLoading = false;
|
|
525
|
+
if (!append) {
|
|
526
|
+
grid.innerHTML = '<div class="col-span-full text-center text-gray-500 text-[10px] py-4">Failed to load.</div>';
|
|
527
|
+
}
|
|
528
|
+
} finally {
|
|
529
|
+
isStoreLoading = false;
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
async function loadAppDetails(id) {
|
|
534
|
+
if (typeof showLoader === "function") showLoader(true);
|
|
535
|
+
try {
|
|
536
|
+
const safeId = encodeURIComponent(String(id || "").trim());
|
|
537
|
+
if (!safeId) {
|
|
538
|
+
if (typeof showToast === "function") showToast("Invalid app id");
|
|
539
|
+
return false;
|
|
540
|
+
}
|
|
541
|
+
const detailUrls = [
|
|
542
|
+
`re_store.php?action=detail_app&id=${safeId}`,
|
|
543
|
+
`/re_store.php?action=detail_app&id=${safeId}`,
|
|
544
|
+
`./re_store.php?action=detail_app&id=${safeId}`
|
|
545
|
+
];
|
|
546
|
+
let payload = null;
|
|
547
|
+
let lastError = null;
|
|
548
|
+
for (const url of detailUrls) {
|
|
549
|
+
try {
|
|
550
|
+
const response = await fetch(url, { credentials: "same-origin" });
|
|
551
|
+
const json = await parseJsonResponseSafe(response);
|
|
552
|
+
const candidate = (json && typeof json === "object" && "data" in json) ? json.data : json;
|
|
553
|
+
if (candidate && !candidate.error) {
|
|
554
|
+
payload = candidate;
|
|
555
|
+
break;
|
|
556
|
+
}
|
|
557
|
+
} catch (err) {
|
|
558
|
+
lastError = err;
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
if (payload && !payload.error) {
|
|
563
|
+
activeApp = normalizeAppPayload(payload) || payload;
|
|
564
|
+
try {
|
|
565
|
+
activateAppMode();
|
|
566
|
+
} catch (uiError) {
|
|
567
|
+
console.error("activateAppMode failed:", uiError);
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
const category = (activeApp.category || "").toLowerCase();
|
|
571
|
+
if (typeof setMode === "function") {
|
|
572
|
+
if (category === "code") setMode("code");
|
|
573
|
+
else if (category === "image") setMode("image");
|
|
574
|
+
else if (category === "music") setMode("music");
|
|
575
|
+
else if (category === "video") setMode("video");
|
|
576
|
+
else if (category === "blog") setMode("blog");
|
|
577
|
+
else setMode("chat");
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
if (typeof showToast === "function") showToast(`App Loaded: ${normalizeAppDisplayText(activeApp.title, "App")}`);
|
|
581
|
+
return true;
|
|
582
|
+
} else {
|
|
583
|
+
const reason = lastError && lastError.message ? ` (${lastError.message})` : "";
|
|
584
|
+
if (typeof showToast === "function") showToast(`App not found${reason}`);
|
|
585
|
+
if (lastError) console.error("detail_app request failed:", lastError);
|
|
586
|
+
return false;
|
|
587
|
+
}
|
|
588
|
+
} catch (error) {
|
|
589
|
+
console.error("loadAppDetails failed:", error, "id=", id);
|
|
590
|
+
const message = error && error.message ? error.message : "unknown";
|
|
591
|
+
if (typeof showToast === "function") showToast(`Error loading app details (${message})`);
|
|
592
|
+
return false;
|
|
593
|
+
} finally {
|
|
594
|
+
if (typeof showLoader === "function") showLoader(false);
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
function getExpertModeLocale() {
|
|
599
|
+
const raw = String((window.LANG || document.documentElement.lang || navigator.language || "en")).toLowerCase();
|
|
600
|
+
if (raw.startsWith("ko")) return "ko";
|
|
601
|
+
if (raw.startsWith("ja")) return "ja";
|
|
602
|
+
if (raw.startsWith("zh")) return "zh";
|
|
603
|
+
if (raw.startsWith("es")) return "es";
|
|
604
|
+
return "en";
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
function getExpertModeRolePrompts() {
|
|
608
|
+
const locale = getExpertModeLocale();
|
|
609
|
+
const localized = {
|
|
610
|
+
ko: {
|
|
611
|
+
strategist: "?諭??? ?袁⑥셽 ?브쑴苑띶첎???낅빍?? ???뼎 筌뤴뫚紐? ?醫뤾문筌왖, ?怨쀪퐨??뽰맄????쥓?ㅵ칰??類b봺??뤾쉭?? ?館???? 筌띾Þ????쇱읈?類ㅼ몵嚥????릭?紐꾩뒄.",
|
|
612
|
+
critic: "?諭??? ?귐딅뮞??野꺜???袁ⓓ?첎???낅빍?? 筌띾??? ?봔?臾믪뒠, 獄쏆꼶?, ??쎈솭 揶쎛?關苑??筌욎떝堉?雅뚯눘苑?? 筌욁룓???醫롫춦嚥?苡????릭?紐꾩뒄.",
|
|
613
|
+
operator: "?諭??? ??쎈뻬 ?袁ⓓ?첎???낅빍?? 筌왖疫?獄쏅뗀以??怨몄뒠 揶쎛?館釉???m? ??됰뻻, 筌k똾寃뺟뵳?????袁⑼폒嚥????릭?紐꾩뒄.",
|
|
614
|
+
synthesizer: "?諭??? ?????袁ⓓ?첎? ??띻퍍???ル굟鍮??롫뮉 ??뤾퐤 ??곷뻻??쎄쉘?紐꾩뿯??덈뼄. 餓λ쵎??? ??볤탢??랁? 揶쎛????쇱뒠?怨몄뵥 野껉퀡以롦???곸????類b봺??곴퐣 ??롪돌???袁⑷쉐???????곗쨮 ?臾믨쉐??뤾쉭?? ????? ????癒?벥 ?紐꾨선嚥≪뮆彛??臾믨쉐??뤾쉭??"
|
|
615
|
+
},
|
|
616
|
+
ja: {
|
|
617
|
+
strategist: "You are a strategy analyst. Summarize goals, options, and priorities clearly.",
|
|
618
|
+
critic: "You are a risk reviewer. Point out blind spots, side effects, and likely failures briefly.",
|
|
619
|
+
operator: "You are an execution specialist. Give practical steps, examples, and a short checklist.",
|
|
620
|
+
synthesizer: "You are a lead assistant combining expert views. Remove repetition and return one final answer in the user's language."
|
|
621
|
+
},
|
|
622
|
+
zh: {
|
|
623
|
+
strategist: "You are a strategy analyst. Summarize goals, options, and priorities clearly.",
|
|
624
|
+
critic: "You are a risk reviewer. Point out blind spots, side effects, and likely failures briefly.",
|
|
625
|
+
operator: "You are an execution specialist. Give practical steps, examples, and a short checklist.",
|
|
626
|
+
synthesizer: "You are a lead assistant combining expert views. Remove repetition and return one final answer in the user's language."
|
|
627
|
+
},
|
|
628
|
+
es: {
|
|
629
|
+
strategist: "Eres un analista estrategico. Resume objetivo, opciones y prioridades con claridad.",
|
|
630
|
+
critic: "Eres un revisor de riesgos. Senala puntos ciegos, fallos posibles y efectos secundarios de forma breve.",
|
|
631
|
+
operator: "Eres un especialista en ejecucion. Da pasos practicos, ejemplos y una pequena lista accionable.",
|
|
632
|
+
synthesizer: "Eres el asistente principal que fusiona varias opiniones expertas. Elimina repeticiones y entrega una sola respuesta final, clara y util, usando solo el idioma del usuario."
|
|
633
|
+
},
|
|
634
|
+
en: {
|
|
635
|
+
strategist: "You are a strategy analyst. Summarize the goal, options, and priorities clearly and briefly.",
|
|
636
|
+
critic: "You are a risk reviewer. Point out blind spots, tradeoffs, and likely failure modes concisely.",
|
|
637
|
+
operator: "You are an execution specialist. Focus on actionable steps, examples, and a practical checklist.",
|
|
638
|
+
synthesizer: "You are the lead assistant merging multiple expert takes. Remove repetition and produce one strong final answer in the user's language only."
|
|
639
|
+
}
|
|
640
|
+
};
|
|
641
|
+
return localized[locale] || localized.en;
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
async function runExpertModeSynthesis(userText, history, systemPrompt, abortSignal) {
|
|
645
|
+
const prompts = getExpertModeRolePrompts();
|
|
646
|
+
const recentHistory = Array.isArray(history) ? history.slice(-2) : [];
|
|
647
|
+
const sharedSystem = String(systemPrompt || "").trim();
|
|
648
|
+
const expertSystems = [prompts.strategist, prompts.critic, prompts.operator].map((instruction) =>
|
|
649
|
+
[sharedSystem, instruction].filter(Boolean).join("\n\n")
|
|
650
|
+
);
|
|
651
|
+
|
|
652
|
+
const expertResults = await Promise.all(expertSystems.map(async (expertSystem) => {
|
|
653
|
+
const response = await fetch("?action=ai_chat", {
|
|
654
|
+
method: "POST",
|
|
655
|
+
body: JSON.stringify({
|
|
656
|
+
prompt: userText,
|
|
657
|
+
history: recentHistory,
|
|
658
|
+
system_prompt: expertSystem,
|
|
659
|
+
max_chars: 700
|
|
660
|
+
}),
|
|
661
|
+
signal: abortSignal
|
|
662
|
+
});
|
|
663
|
+
return parseJsonResponseSafe(response);
|
|
664
|
+
}));
|
|
665
|
+
|
|
666
|
+
const successful = expertResults
|
|
667
|
+
.map((item) => String(item && item.response || "").trim())
|
|
668
|
+
.filter(Boolean);
|
|
669
|
+
|
|
670
|
+
if (!successful.length) {
|
|
671
|
+
const firstError = expertResults.find((item) => item && item.error);
|
|
672
|
+
return { error: (firstError && firstError.error) || "EXPERT_MODE_FAILED" };
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
const synthesisPrompt = [
|
|
676
|
+
`User request:\n${userText}`,
|
|
677
|
+
"",
|
|
678
|
+
successful.map((text, index) => `Perspective ${index + 1}:\n${text}`).join("\n\n"),
|
|
679
|
+
"",
|
|
680
|
+
"Now merge these into one final answer."
|
|
681
|
+
].join("\n");
|
|
682
|
+
|
|
683
|
+
const synthesisResponse = await fetch("?action=ai_chat", {
|
|
684
|
+
method: "POST",
|
|
685
|
+
body: JSON.stringify({
|
|
686
|
+
prompt: synthesisPrompt,
|
|
687
|
+
history: [],
|
|
688
|
+
system_prompt: [sharedSystem, prompts.synthesizer].filter(Boolean).join("\n\n"),
|
|
689
|
+
max_chars: 1500
|
|
690
|
+
}),
|
|
691
|
+
signal: abortSignal
|
|
692
|
+
});
|
|
693
|
+
|
|
694
|
+
const synthData = await parseJsonResponseSafe(synthesisResponse);
|
|
695
|
+
if (synthData && synthData.response) {
|
|
696
|
+
return { response: synthData.response, expertResults: successful };
|
|
697
|
+
}
|
|
698
|
+
return { error: (synthData && synthData.error) || "EXPERT_SYNTHESIS_FAILED", expertResults: successful };
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
async function executeAction(side = "right", options = {}) {
|
|
702
|
+
if (typeof side !== "string") side = "right";
|
|
703
|
+
if (!options || typeof options !== "object") options = {};
|
|
704
|
+
if (currentMode === "expert") currentMode = "chat";
|
|
705
|
+
let overrideText = typeof options.overrideText === "string" ? options.overrideText.trim() : "";
|
|
706
|
+
const silentUserBubble = !!options.silentUserBubble;
|
|
707
|
+
const forceGenerate = !!options.forceGenerate;
|
|
708
|
+
const isContinuationRequest = !!options.isContinuationRequest;
|
|
709
|
+
let isScopedCodeEdit = !!options.isScopedCodeEdit;
|
|
710
|
+
let generationToken = 0;
|
|
711
|
+
const canContinueGeneration = () => !stopSignal && isGenerationTokenCurrent(generationToken);
|
|
712
|
+
const previousContinueContext = options.previousContinueContext && typeof options.previousContinueContext === "object"
|
|
713
|
+
? options.previousContinueContext
|
|
714
|
+
: null;
|
|
715
|
+
let scopedTarget = isScopedCodeEdit && options.scopedTarget && typeof options.scopedTarget === "object"
|
|
716
|
+
? options.scopedTarget
|
|
717
|
+
: null;
|
|
718
|
+
|
|
719
|
+
if (side === "left" && currentMode === "voice") {
|
|
720
|
+
if (isVoiceProcessing) return;
|
|
721
|
+
|
|
722
|
+
if (isVoiceListening) {
|
|
723
|
+
if (translationSide === side) {
|
|
724
|
+
isVoiceListening = false;
|
|
725
|
+
stopVoiceMode(false);
|
|
726
|
+
showToast("Listening Paused");
|
|
727
|
+
if (recognition) {
|
|
728
|
+
recognition.abort();
|
|
729
|
+
recognition = null;
|
|
730
|
+
}
|
|
731
|
+
return;
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
translationSide = side;
|
|
736
|
+
setTimeout(() => {
|
|
737
|
+
try {
|
|
738
|
+
isVoiceListening = true;
|
|
739
|
+
setVoiceState(side, "recording");
|
|
740
|
+
if (recognition) {
|
|
741
|
+
updateRecognitionLang();
|
|
742
|
+
recognition.start();
|
|
743
|
+
} else {
|
|
744
|
+
initVoiceMode();
|
|
745
|
+
}
|
|
746
|
+
} catch (error) {
|
|
747
|
+
initVoiceMode();
|
|
748
|
+
}
|
|
749
|
+
}, 100);
|
|
750
|
+
return;
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
if (side !== "left" && currentMode === "voice" && isVoiceListening) {
|
|
754
|
+
isVoiceListening = false;
|
|
755
|
+
stopVoiceMode(false);
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
const inputElement = document.getElementById("prompt-input");
|
|
759
|
+
const rawInputText = inputElement ? normalizeChatSlashLineBreaks(inputElement.value || "") : "";
|
|
760
|
+
let userText = normalizeChatSlashLineBreaks(overrideText || rawInputText);
|
|
761
|
+
let userBubbleText = normalizeChatSlashLineBreaks(rawInputText || userText);
|
|
762
|
+
|
|
763
|
+
if (!overrideText && currentMode === "code") {
|
|
764
|
+
const pendingScopedTarget = typeof resolvePendingScopedCodeAttachment === "function"
|
|
765
|
+
? resolvePendingScopedCodeAttachment()
|
|
766
|
+
: null;
|
|
767
|
+
if (pendingScopedTarget && !rawInputText) {
|
|
768
|
+
if (typeof showToast === "function") showToast(getScopedCodeText("editAsk"));
|
|
769
|
+
if (inputElement) inputElement.focus();
|
|
770
|
+
return;
|
|
771
|
+
}
|
|
772
|
+
if (pendingScopedTarget && rawInputText) {
|
|
773
|
+
const fileInfo = codeFiles && codeFiles[pendingScopedTarget.fileIndex]
|
|
774
|
+
? codeFiles[pendingScopedTarget.fileIndex]
|
|
775
|
+
: {};
|
|
776
|
+
overrideText = buildScopedCodeEditPrompt(pendingScopedTarget, rawInputText, fileInfo);
|
|
777
|
+
userText = normalizeChatSlashLineBreaks(overrideText);
|
|
778
|
+
userBubbleText = normalizeChatSlashLineBreaks(rawInputText);
|
|
779
|
+
isScopedCodeEdit = true;
|
|
780
|
+
scopedTarget = pendingScopedTarget;
|
|
781
|
+
if (inputElement) {
|
|
782
|
+
inputElement.value = "";
|
|
783
|
+
if (typeof handleInput === "function") handleInput(inputElement);
|
|
784
|
+
}
|
|
785
|
+
if (typeof clearPendingScopedCodeAttachment === "function") {
|
|
786
|
+
clearPendingScopedCodeAttachment({ silent: true });
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
if (!forceGenerate && isMenuOpen && userText) {
|
|
792
|
+
fetchStoreApps(userText);
|
|
793
|
+
return;
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
if (!forceGenerate && currentMode === "app") {
|
|
797
|
+
inputElement.value = "";
|
|
798
|
+
inputElement.style.height = "auto";
|
|
799
|
+
fetchApps(userText);
|
|
800
|
+
return;
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
if (currentMode !== "community") {
|
|
804
|
+
if (!userText) return;
|
|
805
|
+
|
|
806
|
+
window.currentImagePrompt = userText;
|
|
807
|
+
window.savedPrompt = userText;
|
|
808
|
+
startExperience();
|
|
809
|
+
|
|
810
|
+
if (typeof closePreview === "function") closePreview();
|
|
811
|
+
|
|
812
|
+
if (!overrideText) {
|
|
813
|
+
inputElement.value = "";
|
|
814
|
+
inputElement.style.height = "auto";
|
|
815
|
+
}
|
|
816
|
+
showLoader(true);
|
|
817
|
+
if (!silentUserBubble) {
|
|
818
|
+
appendMsg("user", userBubbleText || userText);
|
|
819
|
+
}
|
|
820
|
+
if (!isContinuationRequest) {
|
|
821
|
+
clearContinueWriteContext();
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
if (abortController) abortController.abort();
|
|
825
|
+
abortController = new AbortController();
|
|
826
|
+
stopSignal = false;
|
|
827
|
+
generationToken = beginGenerationToken();
|
|
828
|
+
setMainGeneratingState(true);
|
|
829
|
+
if (typeof window.updateMainSubmitButtonState === "function") {
|
|
830
|
+
window.updateMainSubmitButtonState();
|
|
831
|
+
}
|
|
832
|
+
let autoLocalFallbackRunner = null;
|
|
833
|
+
|
|
834
|
+
try {
|
|
835
|
+
if (activeApp) {
|
|
836
|
+
const apiUrl = activeApp.api_url && activeApp.api_url.trim() !== "";
|
|
837
|
+
const category = (activeApp.category || "").toLowerCase();
|
|
838
|
+
|
|
839
|
+
if (apiUrl && !["image", "code", "music", "video", "blog", "character"].includes(category)) {
|
|
840
|
+
await executeAppLogic(userText);
|
|
841
|
+
return;
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
if (currentMode === "translate") {
|
|
846
|
+
const langLeft = document.getElementById("trans-select-left").value;
|
|
847
|
+
const langRight = document.getElementById("trans-select-right").value;
|
|
848
|
+
|
|
849
|
+
let sourceLang = side === "left" ? langLeft : langRight;
|
|
850
|
+
let targetLang = side === "left" ? langRight : langLeft;
|
|
851
|
+
|
|
852
|
+
const response = await fetch("?action=ai_translate", {
|
|
853
|
+
method: "POST",
|
|
854
|
+
body: JSON.stringify({ text: userText, target_lang: targetLang, source_lang: sourceLang }),
|
|
855
|
+
signal: abortController.signal
|
|
856
|
+
});
|
|
857
|
+
|
|
858
|
+
const data = await parseJsonResponseSafe(response);
|
|
859
|
+
|
|
860
|
+
if (data.error === "LIMIT_REACHED") {
|
|
861
|
+
showToast("Limit reached. Local Translation...");
|
|
862
|
+
if (!isModelLoaded) await startDownload();
|
|
863
|
+
|
|
864
|
+
const localPrompt =[
|
|
865
|
+
{ role: "system", content: `Translate the following text to ${targetLang}. Output ONLY the translated text.` },
|
|
866
|
+
{ role: "user", content: userText }
|
|
867
|
+
];
|
|
868
|
+
|
|
869
|
+
let localResult = "";
|
|
870
|
+
const bubble = appendMsg("ai", "...");
|
|
871
|
+
const speechLang = resolveSpeechLangCode(targetLang, "en-US");
|
|
872
|
+
|
|
873
|
+
await runLocalInference(localPrompt, (token, meta = {}) => {
|
|
874
|
+
if (canContinueGeneration()) {
|
|
875
|
+
localResult = meta.replace ? token : (localResult + token);
|
|
876
|
+
const plainLocalResult = extractPlainTextLocal(localResult);
|
|
877
|
+
bubble.textContent = plainLocalResult;
|
|
878
|
+
bubble.style.color = "#111111";
|
|
879
|
+
bubble.style.fontWeight = "700";
|
|
880
|
+
bubble.style.fontSize = "1.125rem";
|
|
881
|
+
bubble.style.lineHeight = "1.65";
|
|
882
|
+
scrollBottom();
|
|
883
|
+
}
|
|
884
|
+
});
|
|
885
|
+
|
|
886
|
+
if (canContinueGeneration()) speakText(extractPlainTextLocal(localResult), speechLang);
|
|
887
|
+
} else if (data.error) {
|
|
888
|
+
appendMsg("error", data.error);
|
|
889
|
+
} else {
|
|
890
|
+
let resultObj = { text: data.response, lang_code: data.lang_code || "" };
|
|
891
|
+
try {
|
|
892
|
+
if (data.response && typeof data.response === "object") {
|
|
893
|
+
resultObj = Object.assign({}, resultObj, data.response);
|
|
894
|
+
} else {
|
|
895
|
+
let cleanJson = String(data.response || "").replace(/<think>[\s\S]*?<\/think>/gi, "").replace(/```json/g, "").replace(/```/g, "").trim();
|
|
896
|
+
let jsonMatch = cleanJson.match(/\{[\s\S]*\}/);
|
|
897
|
+
if (jsonMatch) cleanJson = jsonMatch[0];
|
|
898
|
+
resultObj = Object.assign({}, resultObj, JSON.parse(cleanJson));
|
|
899
|
+
}
|
|
900
|
+
} catch (err) {
|
|
901
|
+
resultObj.text = data.response;
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
const translatedText = extractPlainTextLocal(resultObj.text);
|
|
905
|
+
const translatedBubble = appendMsg("ai", translatedText);
|
|
906
|
+
if (translatedBubble) {
|
|
907
|
+
translatedBubble.style.color = "#111111";
|
|
908
|
+
translatedBubble.style.fontWeight = "700";
|
|
909
|
+
}
|
|
910
|
+
const speechLang = resolveSpeechLangCode(resultObj.lang_code || targetLang, resolveSpeechLangCode(targetLang, "en-US"));
|
|
911
|
+
speakText(translatedText, speechLang);
|
|
912
|
+
}
|
|
913
|
+
} else if (currentMode === "video") {
|
|
914
|
+
let prompt = userText;
|
|
915
|
+
if (activeApp && activeApp.system_prompt) prompt = `${activeApp.system_prompt}, ${userText}`;
|
|
916
|
+
|
|
917
|
+
const gridPrompt = "2x2 grid " + prompt;
|
|
918
|
+
const response = await fetch("?action=ai_video", {
|
|
919
|
+
method: "POST",
|
|
920
|
+
body: JSON.stringify({ prompt: gridPrompt, watermark: false }),
|
|
921
|
+
signal: abortController.signal
|
|
922
|
+
});
|
|
923
|
+
|
|
924
|
+
const data = await parseJsonResponseSafe(response);
|
|
925
|
+
|
|
926
|
+
if (data.error) {
|
|
927
|
+
appendMsg("error", "Video Error: " + data.error);
|
|
928
|
+
} else {
|
|
929
|
+
generateGifFromGrid(data.b64, data.translated || prompt);["app-container", "welcome-msg", "center-app-name"].forEach((id) => {
|
|
930
|
+
const el = document.getElementById(id);
|
|
931
|
+
if (el) el.style.display = "none";
|
|
932
|
+
});
|
|
933
|
+
}
|
|
934
|
+
} else if (currentMode === "image") {
|
|
935
|
+
let prompt = userText;
|
|
936
|
+
if (activeApp && activeApp.system_prompt) prompt = `${activeApp.system_prompt}, ${userText}`;
|
|
937
|
+
|
|
938
|
+
const ratio = document.getElementById("image-ratio-value")?.value || "square";
|
|
939
|
+
const response = await fetch("?action=ai_image", {
|
|
940
|
+
method: "POST",
|
|
941
|
+
body: JSON.stringify({ prompt: prompt, ratio: ratio }),
|
|
942
|
+
signal: abortController.signal
|
|
943
|
+
});
|
|
944
|
+
|
|
945
|
+
const data = await parseJsonResponseSafe(response);
|
|
946
|
+
|
|
947
|
+
if (data.error) {
|
|
948
|
+
appendMsg("error", "Image Error: " + data.error);
|
|
949
|
+
} else {
|
|
950
|
+
const promptForBubble = String(data.prompt_input || userText || prompt || data.prompt_used || data.translated || "").trim();
|
|
951
|
+
window.currentImagePrompt = promptForBubble || userText || prompt;
|
|
952
|
+
appendImg(data.b64, promptForBubble || userText || prompt);
|
|
953
|
+
openPreview("data:image/png;base64," + data.b64);["app-container", "welcome-msg", "center-app-name"].forEach((id) => {
|
|
954
|
+
const el = document.getElementById(id);
|
|
955
|
+
if (el) el.style.display = "none";
|
|
956
|
+
});
|
|
957
|
+
}
|
|
958
|
+
} else if (currentMode === "music") {
|
|
959
|
+
let prompt = userText;
|
|
960
|
+
if (activeApp && activeApp.system_prompt) prompt = `${activeApp.system_prompt}, ${userText}`;
|
|
961
|
+
await generateMusic(prompt);
|
|
962
|
+
} else if (currentMode === "search") {
|
|
963
|
+
const response = await fetch("?action=search_data", {
|
|
964
|
+
method: "POST",
|
|
965
|
+
body: JSON.stringify({ prompt: userText }),
|
|
966
|
+
signal: abortController.signal
|
|
967
|
+
});
|
|
968
|
+
|
|
969
|
+
const rawText = await response.text();
|
|
970
|
+
let data = {};
|
|
971
|
+
|
|
972
|
+
try {
|
|
973
|
+
data = parseJsonTextSafe(rawText);
|
|
974
|
+
} catch (e) {
|
|
975
|
+
console.error("Search Data JSON parse failed:", rawText);
|
|
976
|
+
throw new Error("Failed to parse search data response.");
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
if (data.results && data.results.length > 0) {
|
|
980
|
+
const topResults = data.results.slice(0, 5);
|
|
981
|
+
|
|
982
|
+
let contextStr = topResults
|
|
983
|
+
.map((item, idx) => `[Source ${idx + 1}] ${item.content}`)
|
|
984
|
+
.join("\n");
|
|
985
|
+
|
|
986
|
+
const synthResponse = await fetch("?action=search_synthesis", {
|
|
987
|
+
method: "POST",
|
|
988
|
+
body: JSON.stringify({
|
|
989
|
+
query: userText,
|
|
990
|
+
context: contextStr
|
|
991
|
+
}),
|
|
992
|
+
signal: abortController.signal
|
|
993
|
+
});
|
|
994
|
+
|
|
995
|
+
const rawSynthText = await synthResponse.text();
|
|
996
|
+
let synthData = {};
|
|
997
|
+
|
|
998
|
+
try {
|
|
999
|
+
synthData = parseJsonTextSafe(rawSynthText);
|
|
1000
|
+
} catch (e) {
|
|
1001
|
+
console.error("Search Synthesis JSON parse failed:", rawSynthText);
|
|
1002
|
+
throw new Error("Failed to parse search synthesis response.");
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
if (synthData.error === "LIMIT_REACHED") {
|
|
1006
|
+
|
|
1007
|
+
showToast("Server limit reached. Local summarizing...");
|
|
1008
|
+
|
|
1009
|
+
if (!isModelLoaded) {
|
|
1010
|
+
await startDownload();
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
const bubble = appendMsg("ai", "...");
|
|
1014
|
+
let localResult = "";
|
|
1015
|
+
|
|
1016
|
+
const localPrompt = [
|
|
1017
|
+
{
|
|
1018
|
+
role: "system",
|
|
1019
|
+
content: "You are a summarization assistant."
|
|
1020
|
+
},
|
|
1021
|
+
{
|
|
1022
|
+
role: "user",
|
|
1023
|
+
content: `${contextStr}\nPlease summarize the above content.`
|
|
1024
|
+
}
|
|
1025
|
+
];
|
|
1026
|
+
|
|
1027
|
+
await runLocalInference(
|
|
1028
|
+
localPrompt,
|
|
1029
|
+
(token, meta = {}) => {
|
|
1030
|
+
if (canContinueGeneration()) {
|
|
1031
|
+
localResult = meta.replace
|
|
1032
|
+
? token
|
|
1033
|
+
: (localResult + token);
|
|
1034
|
+
|
|
1035
|
+
bubble.innerHTML = parseMarkdownLocal(localResult, false);
|
|
1036
|
+
scrollBottom();
|
|
1037
|
+
}
|
|
1038
|
+
},
|
|
1039
|
+
generationToken
|
|
1040
|
+
);
|
|
1041
|
+
|
|
1042
|
+
if (canContinueGeneration()) {
|
|
1043
|
+
bubble.innerHTML = parseMarkdownLocal(localResult, true);
|
|
1044
|
+
|
|
1045
|
+
addSourcesToBubble(bubble, topResults);
|
|
1046
|
+
|
|
1047
|
+
scrollBottom();
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
} else if (synthData.error) {
|
|
1051
|
+
|
|
1052
|
+
appendMsg("ai", `Error: ${synthData.error}`);
|
|
1053
|
+
|
|
1054
|
+
} else {
|
|
1055
|
+
|
|
1056
|
+
addSourcesToBubble(
|
|
1057
|
+
appendMsg("ai", synthData.html || "Thinking..."),
|
|
1058
|
+
topResults
|
|
1059
|
+
);
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
} else {
|
|
1063
|
+
|
|
1064
|
+
appendMsg("ai", "No search results found.");
|
|
1065
|
+
}
|
|
1066
|
+
} else {
|
|
1067
|
+
let finalPrompt = userText;
|
|
1068
|
+
let currentHistory = chatHistory;
|
|
1069
|
+
let sysPrompt = "";
|
|
1070
|
+
let shouldAttachSystemPrompt = false;
|
|
1071
|
+
const characterSession = getCharacterChatSession();
|
|
1072
|
+
const activeCategory = activeApp && activeApp.category ? String(activeApp.category).toLowerCase() : "";
|
|
1073
|
+
const shouldForceCodePrompt = !characterSession && currentMode !== "blog" && (currentMode === "code" || activeCategory === "code");
|
|
1074
|
+
const useRawInputOnly = false;
|
|
1075
|
+
if (Array.isArray(currentHistory)) {
|
|
1076
|
+
currentHistory = currentHistory.filter((msg) => {
|
|
1077
|
+
if (!msg || (msg.role !== "user" && msg.role !== "assistant")) return false;
|
|
1078
|
+
if (!(typeof msg.content === "string" && msg.content.trim() !== "")) return false;
|
|
1079
|
+
if (msg.role === "assistant" && isIgnorableAssistantGreeting(msg.content)) return false;
|
|
1080
|
+
return true;
|
|
1081
|
+
}, generationToken);
|
|
1082
|
+
while (currentHistory.length > 0 && currentHistory[0].role !== "user") {
|
|
1083
|
+
currentHistory.shift();
|
|
1084
|
+
}
|
|
1085
|
+
} else {
|
|
1086
|
+
currentHistory = [];
|
|
1087
|
+
}
|
|
1088
|
+
const activeLocalTierKey = typeof getActiveLocalModelTier === "function"
|
|
1089
|
+
? String(getActiveLocalModelTier() || "").trim().toLowerCase()
|
|
1090
|
+
: "";
|
|
1091
|
+
const shouldOmitHistoryByTier = activeLocalTierKey === "light" || activeLocalTierKey === "middle";
|
|
1092
|
+
if (shouldOmitHistoryByTier) {
|
|
1093
|
+
currentHistory = [];
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
if (characterSession) {
|
|
1097
|
+
sysPrompt = buildCharacterChatSystemPrompt(characterSession);
|
|
1098
|
+
shouldAttachSystemPrompt = String(sysPrompt || "").trim() !== "";
|
|
1099
|
+
} else if (activeApp) {
|
|
1100
|
+
if (activeApp.system_prompt) {
|
|
1101
|
+
sysPrompt = String(activeApp.system_prompt || "").trim();
|
|
1102
|
+
shouldAttachSystemPrompt = sysPrompt !== "";
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
1105
|
+
|
|
1106
|
+
if (!characterSession && !shouldForceCodePrompt && shouldAttachSystemPrompt && typeof window.buildIsaiSystemPrompt === "function") {
|
|
1107
|
+
sysPrompt = window.buildIsaiSystemPrompt(sysPrompt);
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1110
|
+
if (shouldForceCodePrompt) {
|
|
1111
|
+
sysPrompt = buildCodeAssistantPrompt(shouldAttachSystemPrompt ? sysPrompt : "");
|
|
1112
|
+
shouldAttachSystemPrompt = String(sysPrompt || "").trim() !== "";
|
|
1113
|
+
finalPrompt = userText;
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
if (currentMode === "blog") {
|
|
1117
|
+
if (shouldAttachSystemPrompt) {
|
|
1118
|
+
finalPrompt = `Topic: "${userText}". Instructions: ${sysPrompt}. Lang: ${LANG === "ko" ? "Korean" : "English"}. Add [[IMG: keyword]] tags.`;
|
|
1119
|
+
} else {
|
|
1120
|
+
finalPrompt = `Topic: "${userText}". Lang: ${LANG === "ko" ? "Korean" : "English"}. Add [[IMG: keyword]] tags.`;
|
|
1121
|
+
}
|
|
1122
|
+
currentHistory =[];
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1125
|
+
if (currentMode === "expert") {
|
|
1126
|
+
const expertData = await runExpertModeSynthesis(userText, currentHistory, shouldAttachSystemPrompt ? sysPrompt : "", abortController.signal);
|
|
1127
|
+
if (expertData && expertData.response) {
|
|
1128
|
+
const bubble = appendMsg("ai", "...");
|
|
1129
|
+
const renderedResponse = normalizeApiResponseForBubble(expertData.response, {
|
|
1130
|
+
isCodeMode: false,
|
|
1131
|
+
isTruncated: false
|
|
1132
|
+
});
|
|
1133
|
+
setAssistantBubbleBodyLocal(bubble, renderedResponse, { forcePlainText: false, isFinished: true });
|
|
1134
|
+
chatHistory.push({ role: "user", content: userText }, { role: "assistant", content: renderedResponse.replace(/<think>[\s\S]*?(<\/think>|$)/gi, "").trim() });
|
|
1135
|
+
clearContinueWriteContext();
|
|
1136
|
+
scrollBottom();
|
|
1137
|
+
} else if (expertData && expertData.error) {
|
|
1138
|
+
appendMsg("ai", `Error: ${expertData.error}`);
|
|
1139
|
+
} else {
|
|
1140
|
+
appendMsg("ai", "Error: Expert mode failed.");
|
|
1141
|
+
}
|
|
1142
|
+
} else if (isLocalActive) {
|
|
1143
|
+
if (!isModelLoaded) await startDownload();
|
|
1144
|
+
const localPromptArr =[
|
|
1145
|
+
...(useRawInputOnly ? [] : (shouldAttachSystemPrompt ? [{ role: "system", content: sysPrompt }] : [])),
|
|
1146
|
+
...(useRawInputOnly ? [] : currentHistory.slice(-4)),
|
|
1147
|
+
{ role: "user", content: useRawInputOnly ? userText : finalPrompt }
|
|
1148
|
+
];
|
|
1149
|
+
if (useRawInputOnly) localPromptArr.__rawInputOnly = true;
|
|
1150
|
+
|
|
1151
|
+
let localResult = "";
|
|
1152
|
+
const bubble = appendMsg("ai", "...");
|
|
1153
|
+
|
|
1154
|
+
await runLocalInference(localPromptArr, (token, meta = {}) => {
|
|
1155
|
+
if (canContinueGeneration()) {
|
|
1156
|
+
localResult = meta.replace ? token : (localResult + token);
|
|
1157
|
+
if (!isScopedCodeEdit || !scopedTarget) {
|
|
1158
|
+
setAssistantBubbleBodyLocal(bubble, localResult, { forcePlainText: shouldForceCodePrompt, isFinished: false });
|
|
1159
|
+
}
|
|
1160
|
+
scrollBottom();
|
|
1161
|
+
}
|
|
1162
|
+
}, generationToken);
|
|
1163
|
+
|
|
1164
|
+
if (canContinueGeneration()) {
|
|
1165
|
+
if (isScopedCodeEdit && scopedTarget) {
|
|
1166
|
+
const applied = applyScopedCodePatchToEditor(scopedTarget, localResult);
|
|
1167
|
+
bubble.textContent = applied ? getScopedCodeText("applied") : getScopedCodeText("failed");
|
|
1168
|
+
if (applied && typeof showToast === "function") showToast(getScopedCodeText("applied"));
|
|
1169
|
+
} else {
|
|
1170
|
+
setAssistantBubbleBodyLocal(bubble, localResult, { forcePlainText: shouldForceCodePrompt, isFinished: true });
|
|
1171
|
+
updateCodeUI(localResult);
|
|
1172
|
+
let historyResult = localResult.replace(/<think>[\s\S]*?(<\/think>|$)/gi, "").trim();
|
|
1173
|
+
chatHistory.push({ role: "user", content: userText }, { role: "assistant", content: historyResult });
|
|
1174
|
+
if (currentMode === "blog") await processBlogImages(bubble);
|
|
1175
|
+
}
|
|
1176
|
+
clearContinueWriteContext();
|
|
1177
|
+
scrollBottom();
|
|
1178
|
+
}
|
|
1179
|
+
} else {
|
|
1180
|
+
const localPromptArr =[
|
|
1181
|
+
...(useRawInputOnly ? [] : (shouldAttachSystemPrompt ? [{ role: "system", content: sysPrompt }] : [])),
|
|
1182
|
+
...(useRawInputOnly ? [] : currentHistory.slice(-4)),
|
|
1183
|
+
{ role: "user", content: useRawInputOnly ? userText : finalPrompt }
|
|
1184
|
+
];
|
|
1185
|
+
if (useRawInputOnly) localPromptArr.__rawInputOnly = true;
|
|
1186
|
+
|
|
1187
|
+
autoLocalFallbackRunner = async (noticeText = "") => {
|
|
1188
|
+
if (noticeText && typeof showToast === "function") showToast(noticeText);
|
|
1189
|
+
const ready = await ensureLocalModelReadyForAutoFallback();
|
|
1190
|
+
if (!ready) {
|
|
1191
|
+
appendMsg("ai", "Error: Local model download was canceled.");
|
|
1192
|
+
return false;
|
|
1193
|
+
}
|
|
1194
|
+
|
|
1195
|
+
const bubble = appendMsg("ai", "...");
|
|
1196
|
+
let localResult = "";
|
|
1197
|
+
await runLocalInference(localPromptArr, (token, meta = {}) => {
|
|
1198
|
+
if (canContinueGeneration()) {
|
|
1199
|
+
localResult = meta.replace ? token : (localResult + token);
|
|
1200
|
+
if (!isScopedCodeEdit || !scopedTarget) {
|
|
1201
|
+
setAssistantBubbleBodyLocal(bubble, localResult, { forcePlainText: shouldForceCodePrompt, isFinished: false });
|
|
1202
|
+
}
|
|
1203
|
+
scrollBottom();
|
|
1204
|
+
}
|
|
1205
|
+
}, generationToken);
|
|
1206
|
+
|
|
1207
|
+
if (canContinueGeneration()) {
|
|
1208
|
+
if (isScopedCodeEdit && scopedTarget) {
|
|
1209
|
+
const applied = applyScopedCodePatchToEditor(scopedTarget, localResult);
|
|
1210
|
+
bubble.textContent = applied ? getScopedCodeText("applied") : getScopedCodeText("failed");
|
|
1211
|
+
if (applied && typeof showToast === "function") showToast(getScopedCodeText("applied"));
|
|
1212
|
+
} else {
|
|
1213
|
+
setAssistantBubbleBodyLocal(bubble, localResult, { forcePlainText: shouldForceCodePrompt, isFinished: true });
|
|
1214
|
+
updateCodeUI(localResult);
|
|
1215
|
+
let historyResult = localResult.replace(/<think>[\s\S]*?(<\/think>|$)/gi, "").trim();
|
|
1216
|
+
chatHistory.push({ role: "user", content: userText }, { role: "assistant", content: historyResult });
|
|
1217
|
+
if (currentMode === "blog") await processBlogImages(bubble);
|
|
1218
|
+
}
|
|
1219
|
+
clearContinueWriteContext();
|
|
1220
|
+
scrollBottom();
|
|
1221
|
+
}
|
|
1222
|
+
return true;
|
|
1223
|
+
};
|
|
1224
|
+
|
|
1225
|
+
const response = await fetch("?action=ai_chat", {
|
|
1226
|
+
method: "POST",
|
|
1227
|
+
body: JSON.stringify({
|
|
1228
|
+
prompt: useRawInputOnly ? userText : finalPrompt,
|
|
1229
|
+
history: useRawInputOnly ? [] : currentHistory,
|
|
1230
|
+
system_prompt: useRawInputOnly ? "" : (shouldAttachSystemPrompt ? sysPrompt : ""),
|
|
1231
|
+
max_chars: shouldForceCodePrompt ? Math.max(getIsaiApiMaxChars(), 2200) : getIsaiApiMaxChars()
|
|
1232
|
+
}),
|
|
1233
|
+
signal: abortController.signal
|
|
1234
|
+
});
|
|
1235
|
+
|
|
1236
|
+
const data = await parseJsonResponseSafe(response);
|
|
1237
|
+
if (!canContinueGeneration()) return;
|
|
1238
|
+
|
|
1239
|
+
if (data.error === "LIMIT_REACHED") {
|
|
1240
|
+
await autoLocalFallbackRunner("Limit reached. Local Model...");
|
|
1241
|
+
} else if (data.response) {
|
|
1242
|
+
const renderedResponse = normalizeApiResponseForBubble(data.response, {
|
|
1243
|
+
isCodeMode: shouldForceCodePrompt,
|
|
1244
|
+
isTruncated: data.truncated === true
|
|
1245
|
+
});
|
|
1246
|
+
const bubble = appendMsg("ai", "...");
|
|
1247
|
+
if (!canContinueGeneration()) return;
|
|
1248
|
+
if (isScopedCodeEdit && scopedTarget) {
|
|
1249
|
+
const applied = applyScopedCodePatchToEditor(scopedTarget, data.response);
|
|
1250
|
+
bubble.textContent = applied ? getScopedCodeText("applied") : getScopedCodeText("failed");
|
|
1251
|
+
if (applied && typeof showToast === "function") showToast(getScopedCodeText("applied"));
|
|
1252
|
+
clearContinueWriteContext();
|
|
1253
|
+
scrollBottom();
|
|
1254
|
+
} else {
|
|
1255
|
+
setAssistantBubbleBodyLocal(bubble, renderedResponse, { forcePlainText: shouldForceCodePrompt, isFinished: true });
|
|
1256
|
+
updateCodeUI(renderedResponse);
|
|
1257
|
+
let historyResult = renderedResponse.replace(/<think>[\s\S]*?(<\/think>|$)/gi, "").trim();
|
|
1258
|
+
chatHistory.push({ role: "user", content: userText }, { role: "assistant", content: historyResult });
|
|
1259
|
+
clearContinueWriteContext();
|
|
1260
|
+
scrollBottom();
|
|
1261
|
+
|
|
1262
|
+
if (currentMode === "blog") await processBlogImages(bubble);
|
|
1263
|
+
if (currentMode === "voice") speakText(data.response);
|
|
1264
|
+
}
|
|
1265
|
+
} else if (data.error) {
|
|
1266
|
+
const errText = String(data.error || "");
|
|
1267
|
+
const shouldAutoFallback = shouldAutoSwitchToLocalFromError(errText);
|
|
1268
|
+
if (shouldAutoFallback) {
|
|
1269
|
+
const ok = await autoLocalFallbackRunner("Server issue detected. Switching to local model...");
|
|
1270
|
+
if (ok) {
|
|
1271
|
+
if (isContinuationRequest && previousContinueContext) {
|
|
1272
|
+
clearContinueWriteContext();
|
|
1273
|
+
}
|
|
1274
|
+
return;
|
|
1275
|
+
}
|
|
1276
|
+
}
|
|
1277
|
+
if (isContinuationRequest && previousContinueContext) {
|
|
1278
|
+
setContinueWriteContext(previousContinueContext);
|
|
1279
|
+
} else {
|
|
1280
|
+
clearContinueWriteContext();
|
|
1281
|
+
}
|
|
1282
|
+
appendMsg("ai", `Error: ${data.error}`);
|
|
1283
|
+
} else {
|
|
1284
|
+
const ok = await autoLocalFallbackRunner("No server response. Switching to local model...");
|
|
1285
|
+
if (ok) return;
|
|
1286
|
+
clearContinueWriteContext();
|
|
1287
|
+
appendMsg("ai", "No response received.");
|
|
1288
|
+
}
|
|
1289
|
+
}
|
|
1290
|
+
}
|
|
1291
|
+
|
|
1292
|
+
function updateCodeUI(text) {
|
|
1293
|
+
if (window.extractedCodes && window.extractedCodes.length > 0) {
|
|
1294
|
+
codeFiles = [...window.extractedCodes];
|
|
1295
|
+
renderCodeTabs();
|
|
1296
|
+
} else if (currentMode === "code") {
|
|
1297
|
+
updateCodePanel(text);
|
|
1298
|
+
}
|
|
1299
|
+
}
|
|
1300
|
+
} catch (error) {
|
|
1301
|
+
if (!isGenerationTokenCurrent(generationToken)) return;
|
|
1302
|
+
if (isContinuationRequest && previousContinueContext) {
|
|
1303
|
+
setContinueWriteContext(previousContinueContext);
|
|
1304
|
+
}
|
|
1305
|
+
if (error.name !== "AbortError") {
|
|
1306
|
+
const canAutoFallback = typeof autoLocalFallbackRunner === "function";
|
|
1307
|
+
if (canAutoFallback && shouldAutoSwitchToLocalFromError(error && error.message)) {
|
|
1308
|
+
const ok = await autoLocalFallbackRunner("Server connection failed. Switching to local model...");
|
|
1309
|
+
if (ok) return;
|
|
1310
|
+
}
|
|
1311
|
+
appendMsg("error", `Error: ${error.message}`);
|
|
1312
|
+
}
|
|
1313
|
+
} finally {
|
|
1314
|
+
if (!isGenerationTokenCurrent(generationToken)) return;
|
|
1315
|
+
setMainGeneratingState(false);
|
|
1316
|
+
showLoader(false);
|
|
1317
|
+
if (typeof window.updateMainSubmitButtonState === "function") {
|
|
1318
|
+
window.updateMainSubmitButtonState();
|
|
1319
|
+
}
|
|
1320
|
+
const focusInput = document.getElementById("prompt-input");
|
|
1321
|
+
if (focusInput) focusInput.focus();
|
|
1322
|
+
}
|
|
1323
|
+
} else {
|
|
1324
|
+
const fileInput = document.getElementById("comm-file-input");
|
|
1325
|
+
const nickname = document.getElementById("comm-nickname") ? document.getElementById("comm-nickname").value : "";
|
|
1326
|
+
const password = document.getElementById("comm-password").value;
|
|
1327
|
+
|
|
1328
|
+
const selectedGiphyUrl = getSelectedCommunityGiphyUrl();
|
|
1329
|
+
if (!userText && (!selectedGiphyUrl && (!fileInput.files || fileInput.files.length === 0))) {
|
|
1330
|
+
showToast("Please enter a message or attach a file.");
|
|
1331
|
+
return;
|
|
1332
|
+
}
|
|
1333
|
+
|
|
1334
|
+
showLoader(true);
|
|
1335
|
+
try {
|
|
1336
|
+
const formData = new FormData();
|
|
1337
|
+
formData.append("content", userText);
|
|
1338
|
+
formData.append("password", password);
|
|
1339
|
+
formData.append("nickname", nickname);
|
|
1340
|
+
formData.append("type", "forum");
|
|
1341
|
+
|
|
1342
|
+
if (selectedGiphyUrl) {
|
|
1343
|
+
formData.append("image_url", selectedGiphyUrl);
|
|
1344
|
+
} else if (fileInput.files.length > 0) {
|
|
1345
|
+
const keyRes = await fetch("get_key.php");
|
|
1346
|
+
const keyData = await keyRes.json();
|
|
1347
|
+
|
|
1348
|
+
const imgurData = new FormData();
|
|
1349
|
+
imgurData.append("image", fileInput.files[0]);
|
|
1350
|
+
|
|
1351
|
+
const uploadRes = await fetch("https://api.imgur.com/3/image", {
|
|
1352
|
+
method: "POST",
|
|
1353
|
+
headers: { Authorization: "Client-ID " + keyData.clientId },
|
|
1354
|
+
body: imgurData
|
|
1355
|
+
});
|
|
1356
|
+
|
|
1357
|
+
const uploadData = await uploadRes.json();
|
|
1358
|
+
if (!uploadData.success) throw new Error("Imgur Upload Failed");
|
|
1359
|
+
formData.append("image_url", uploadData.data.link);
|
|
1360
|
+
}
|
|
1361
|
+
|
|
1362
|
+
const postRes = await fetch("re_store.php?action=create_post", {
|
|
1363
|
+
method: "POST",
|
|
1364
|
+
body: formData
|
|
1365
|
+
});
|
|
1366
|
+
const postData = await postRes.json();
|
|
1367
|
+
|
|
1368
|
+
if (postData.success) {
|
|
1369
|
+
showToast("Published.");
|
|
1370
|
+
inputElement.value = "";
|
|
1371
|
+
handleInput(inputElement);
|
|
1372
|
+
document.getElementById("comm-password").value = "";
|
|
1373
|
+
if (typeof clearCommFile === "function") clearCommFile();
|
|
1374
|
+
|
|
1375
|
+
if (postData.id) {
|
|
1376
|
+
window.location.href = "https://isai.kr/view/" + postData.id;
|
|
1377
|
+
} else {
|
|
1378
|
+
if (typeof switchTab === "function") switchTab("forum");
|
|
1379
|
+
if (typeof loadData === "function") loadData("forum", true);
|
|
1380
|
+
}
|
|
1381
|
+
} else {
|
|
1382
|
+
showToast("Error: " + postData.error);
|
|
1383
|
+
}
|
|
1384
|
+
} catch (error) {
|
|
1385
|
+
showToast("Error: " + error.message);
|
|
1386
|
+
} finally {
|
|
1387
|
+
showLoader(false);
|
|
1388
|
+
}
|
|
1389
|
+
}
|
|
1390
|
+
}
|
|
1391
|
+
|
|
1392
|
+
function triggerVoiceButton() {
|
|
1393
|
+
const voiceToolbarButton = document.getElementById("btn-voice");
|
|
1394
|
+
if (currentMode === "voice") {
|
|
1395
|
+
stopVoiceMode(true);
|
|
1396
|
+
if (typeof setMode === "function") {
|
|
1397
|
+
setMode("chat");
|
|
1398
|
+
} else {
|
|
1399
|
+
setVoiceState("left", "idle");
|
|
1400
|
+
}
|
|
1401
|
+
if (voiceToolbarButton) voiceToolbarButton.classList.remove("voice-armed", "voice-recording", "voice-processing");
|
|
1402
|
+
if (typeof showToast === "function") showToast("Voice conversation off");
|
|
1403
|
+
return;
|
|
1404
|
+
}
|
|
1405
|
+
|
|
1406
|
+
try { stopVoiceMode(true); } catch (error) {}
|
|
1407
|
+
translationSide = "left";
|
|
1408
|
+
if (typeof setMode === "function") {
|
|
1409
|
+
setMode("voice");
|
|
1410
|
+
}
|
|
1411
|
+
|
|
1412
|
+
setVoiceState("left", "processing");
|
|
1413
|
+
if (typeof showToast === "function") showToast("Voice conversation on");
|
|
1414
|
+
|
|
1415
|
+
setTimeout(() => {
|
|
1416
|
+
executeAction("left");
|
|
1417
|
+
}, 120);
|
|
1418
|
+
}
|
|
1419
|
+
|
|
1420
|
+
function renderStoreItems(apps, container) {
|
|
1421
|
+
apps.forEach((rawApp) => {
|
|
1422
|
+
const app = normalizeAppPayload(rawApp) || rawApp;
|
|
1423
|
+
const safeTitle = normalizeAppDisplayText(app.title, app.name || "App");
|
|
1424
|
+
const item = document.createElement("div");
|
|
1425
|
+
item.className = "store-item relative w-[44px] h-[44px] rounded-[14px]";
|
|
1426
|
+
item.title = `${safeTitle} (Views: ${app.views || 0})`;
|
|
1427
|
+
item.onclick = () => loadAppDetails(app.id);
|
|
1428
|
+
item.innerHTML = getAppIconHtml(app);
|
|
1429
|
+
container.appendChild(item);
|
|
1430
|
+
});
|
|
1431
|
+
}
|
|
1432
|
+
|
|
1433
|
+
function getChatInputProfileImg() {
|
|
1434
|
+
const wrapper = document.getElementById("chat-input-profile");
|
|
1435
|
+
if (!wrapper) return null;
|
|
1436
|
+
return wrapper.querySelector("img");
|
|
1437
|
+
}
|
|
1438
|
+
|
|
1439
|
+
function setChatInputProfile(src, altText) {
|
|
1440
|
+
const img = getChatInputProfileImg();
|
|
1441
|
+
if (!img) return;
|
|
1442
|
+
if (src && String(src).trim()) img.src = String(src).trim();
|
|
1443
|
+
if (altText) img.alt = String(altText);
|
|
1444
|
+
}
|
|
1445
|
+
|
|
1446
|
+
function applyActiveAppProfileToChatInput() {
|
|
1447
|
+
if (!activeApp) return;
|
|
1448
|
+
const appTitle = normalizeAppDisplayText(activeApp.title, activeApp.name || "App");
|
|
1449
|
+
const iconUrl = String(activeApp.icon_url || "").trim();
|
|
1450
|
+
if (iconUrl) {
|
|
1451
|
+
setChatInputProfile(iconUrl, appTitle);
|
|
1452
|
+
return;
|
|
1453
|
+
}
|
|
1454
|
+
const fallbackUrl = `https://api.dicebear.com/7.x/identicon/svg?seed=${encodeURIComponent(appTitle)}&backgroundColor=transparent`;
|
|
1455
|
+
setChatInputProfile(fallbackUrl, appTitle);
|
|
1456
|
+
}
|
|
1457
|
+
|
|
1458
|
+
function restoreDefaultChatInputProfile() {
|
|
1459
|
+
if (defaultChatProfileSrc) {
|
|
1460
|
+
setChatInputProfile(defaultChatProfileSrc, "My Profile");
|
|
1461
|
+
return;
|
|
1462
|
+
}
|
|
1463
|
+
setChatInputProfile("https://api.dicebear.com/7.x/identicon/svg?seed=IP&backgroundColor=transparent", "My Profile");
|
|
1464
|
+
}
|
|
1465
|
+
|
|
1466
|
+
function activateAppMode() {
|
|
1467
|
+
activeApp = normalizeAppPayload(activeApp) || activeApp;
|
|
1468
|
+
const safeAppTitle = normalizeAppDisplayText(activeApp.title, activeApp.name || "App");
|
|
1469
|
+
const safeFirstMessage = decodeEscapedUnicodeText(activeApp.first_message || "");
|
|
1470
|
+
if (typeof clearCharacterChatSession === "function") {
|
|
1471
|
+
clearCharacterChatSession();
|
|
1472
|
+
}
|
|
1473
|
+
if (typeof isMenuOpen !== "undefined" && isMenuOpen && typeof toggleStoreMenu === "function") {
|
|
1474
|
+
toggleStoreMenu();
|
|
1475
|
+
}
|
|
1476
|
+
const activeAppStatus = document.getElementById("active-app-status");
|
|
1477
|
+
const activeAppName = document.getElementById("active-app-name");
|
|
1478
|
+
const promptInput = document.getElementById("prompt-input");
|
|
1479
|
+
const chatBox = document.getElementById("chat-box");
|
|
1480
|
+
if (activeAppStatus) activeAppStatus.classList.remove("hidden");
|
|
1481
|
+
if (activeAppName) activeAppName.innerText = safeAppTitle;
|
|
1482
|
+
if (promptInput) promptInput.value = "";
|
|
1483
|
+
if (chatBox) chatBox.innerHTML = "";
|
|
1484
|
+
|
|
1485
|
+
if (typeof appendMsg === "function") {
|
|
1486
|
+
if (safeFirstMessage) {
|
|
1487
|
+
appendMsg("ai", safeFirstMessage);
|
|
1488
|
+
} else {
|
|
1489
|
+
appendMsg("ai", `App '${safeAppTitle}' started.`);
|
|
1490
|
+
}
|
|
1491
|
+
}
|
|
1492
|
+
if (typeof applyActiveAppProfileToChatInput === "function") {
|
|
1493
|
+
applyActiveAppProfileToChatInput();
|
|
1494
|
+
}
|
|
1495
|
+
}
|
|
1496
|
+
|
|
1497
|
+
|
|
1498
|
+
|
|
1499
|
+
function exitAppMode() {
|
|
1500
|
+
activeApp = null;
|
|
1501
|
+
const activeAppStatus = document.getElementById("active-app-status");
|
|
1502
|
+
if (activeAppStatus) activeAppStatus.classList.add("hidden");
|
|
1503
|
+
if (typeof resetChat === "function") resetChat();
|
|
1504
|
+
if (typeof restoreDefaultChatInputProfile === "function") restoreDefaultChatInputProfile();
|
|
1505
|
+
if (typeof showToast === "function") showToast("App Mode Exited");
|
|
1506
|
+
}
|
|
1507
|
+
|
|
1508
|
+
async function fetchApps(query = "", append = false) {
|
|
1509
|
+
const grid = document.getElementById("app-grid");
|
|
1510
|
+
|
|
1511
|
+
if (!append) {
|
|
1512
|
+
currentAppPage = 1;
|
|
1513
|
+
hasMoreApps = true;
|
|
1514
|
+
currentAppQuery = query;
|
|
1515
|
+
grid.innerHTML = "";
|
|
1516
|
+
}
|
|
1517
|
+
|
|
1518
|
+
if (!hasMoreApps || isAppLoading) return;
|
|
1519
|
+
|
|
1520
|
+
isAppLoading = true;
|
|
1521
|
+
|
|
1522
|
+
try {
|
|
1523
|
+
const response = await fetch("?action=search_app", {
|
|
1524
|
+
method: "POST",
|
|
1525
|
+
body: JSON.stringify({ prompt: query, page: currentAppPage, limit: APP_LIMIT })
|
|
1526
|
+
});
|
|
1527
|
+
|
|
1528
|
+
if (response.ok) {
|
|
1529
|
+
const data = await parseJsonResponseSafe(response);
|
|
1530
|
+
|
|
1531
|
+
if (data && data.length > 0) {
|
|
1532
|
+
data.forEach((app) => {
|
|
1533
|
+
grid.appendChild(createAppItem(app));
|
|
1534
|
+
});
|
|
1535
|
+
|
|
1536
|
+
if (data.length < APP_LIMIT) {
|
|
1537
|
+
hasMoreApps = false;
|
|
1538
|
+
} else {
|
|
1539
|
+
currentAppPage++;
|
|
1540
|
+
}
|
|
1541
|
+
} else {
|
|
1542
|
+
if (!append) {
|
|
1543
|
+
const noAppsMsg = typeof T !== "undefined" && T.no_apps ? T.no_apps : "No shortcuts.";
|
|
1544
|
+
grid.innerHTML = `<p class="text-gray-500 text-xs col-span-full py-2 text-center">${noAppsMsg}</p>`;
|
|
1545
|
+
}
|
|
1546
|
+
hasMoreApps = false;
|
|
1547
|
+
}
|
|
1548
|
+
}
|
|
1549
|
+
} catch (error) {
|
|
1550
|
+
console.error("Error fetching apps:", error);
|
|
1551
|
+
} finally {
|
|
1552
|
+
isAppLoading = false;
|
|
1553
|
+
}
|
|
1554
|
+
}
|
|
1555
|
+
|
|
1556
|
+
function createAppItem(app, isClone = false) {
|
|
1557
|
+
app = normalizeAppPayload(app) || app;
|
|
1558
|
+
const item = document.createElement("a");
|
|
1559
|
+
item.href = app.url || "#";
|
|
1560
|
+
if(app.url) item.target = "_blank";
|
|
1561
|
+
|
|
1562
|
+
item.className = "app-item relative w-[44px] h-[44px] rounded-[14px] transition-transform hover:scale-105";
|
|
1563
|
+
if (isClone) item.classList.add("clone-item");
|
|
1564
|
+
item.title = normalizeAppDisplayText(app.name, app.title || "App");
|
|
1565
|
+
|
|
1566
|
+
item.innerHTML = getAppIconHtml(app);
|
|
1567
|
+
return item;
|
|
1568
|
+
}
|
|
1569
|
+
|
|
1570
|
+
window.addEventListener("DOMContentLoaded", () => {
|
|
1571
|
+
applyLocalModelProfileToConfig();
|
|
1572
|
+
renderLocalModelTierSelector();
|
|
1573
|
+
observeLocalModelTierVisibility();
|
|
1574
|
+
syncLocalModelTierVisibility(currentMode);
|
|
1575
|
+
initCheckModel();
|
|
1576
|
+
fetchApps("");
|
|
1577
|
+
renderFixedApps();
|
|
1578
|
+
|
|
1579
|
+
const chatBox = document.getElementById("chat-box");
|
|
1580
|
+
chatBox.innerHTML = "";
|
|
1581
|
+
chatBox.style.display = "flex";
|
|
1582
|
+
chatBox.style.flexDirection = "column";
|
|
1583
|
+
chatBox.style.height = "100%";
|
|
1584
|
+
|
|
1585
|
+
const profileImg = getChatInputProfileImg();
|
|
1586
|
+
if (profileImg && profileImg.src) {
|
|
1587
|
+
defaultChatProfileSrc = profileImg.src;
|
|
1588
|
+
}
|
|
1589
|
+
|
|
1590
|
+
const btnChat = document.getElementById("btn-chat");
|
|
1591
|
+
if (btnChat) btnChat.classList.add("active");
|
|
1592
|
+
|
|
1593
|
+
const handleStoreScroll = (target) => {
|
|
1594
|
+
if (!target) return;
|
|
1595
|
+
target.addEventListener("scroll", () => {
|
|
1596
|
+
if (target.scrollTop + target.clientHeight >= target.scrollHeight - 50 && hasMoreStoreApps && !isStoreLoading && isMenuOpen) {
|
|
1597
|
+
fetchStoreApps(currentStoreQuery, true);
|
|
1598
|
+
}
|
|
1599
|
+
});
|
|
1600
|
+
};
|
|
1601
|
+
const storePanel = document.getElementById("store-panel");
|
|
1602
|
+
const storeGrid = document.getElementById("store-grid");
|
|
1603
|
+
handleStoreScroll(storePanel);
|
|
1604
|
+
handleStoreScroll(storeGrid);
|
|
1605
|
+
|
|
1606
|
+
const appContainer = document.getElementById("app-container");
|
|
1607
|
+
|
|
1608
|
+
if (appContainer) {
|
|
1609
|
+
appContainer.addEventListener("scroll", () => {
|
|
1610
|
+
const scrollLocation = appContainer.scrollTop + appContainer.clientHeight;
|
|
1611
|
+
const totalHeight = appContainer.scrollHeight;
|
|
1612
|
+
|
|
1613
|
+
if (scrollLocation >= totalHeight - 30) {
|
|
1614
|
+
if (hasMoreApps && !isAppLoading && currentMode === "app") {
|
|
1615
|
+
console.log("Loading more apps... Page:", currentAppPage);
|
|
1616
|
+
fetchApps(currentAppQuery, true);
|
|
1617
|
+
}
|
|
1618
|
+
}
|
|
1619
|
+
});
|
|
1620
|
+
}
|
|
1621
|
+
|
|
1622
|
+
const params = new URLSearchParams(window.location.search);
|
|
1623
|
+
const modes =["chat", "search", "image", "code", "video", "music", "blog"];
|
|
1624
|
+
for (const mode of modes) {
|
|
1625
|
+
const queryVal = params.get(mode);
|
|
1626
|
+
if (queryVal) {
|
|
1627
|
+
if (mode === "image" && hasUsedUrlImageToday()) {
|
|
1628
|
+
showToast("URL image generation is limited to once per day.");
|
|
1629
|
+
params.delete("image");
|
|
1630
|
+
const nextUrl = `${window.location.pathname}${params.toString() ? `?${params.toString()}` : ""}`;
|
|
1631
|
+
history.replaceState(null, document.title, nextUrl);
|
|
1632
|
+
break;
|
|
1633
|
+
}
|
|
1634
|
+
setMode(mode);
|
|
1635
|
+
const promptInput = document.getElementById("prompt-input");
|
|
1636
|
+
if (promptInput) {
|
|
1637
|
+
promptInput.value = queryVal;
|
|
1638
|
+
handleInput(promptInput);
|
|
1639
|
+
}
|
|
1640
|
+
setTimeout(() => {
|
|
1641
|
+
if (mode === "image") markUrlImageUsedToday();
|
|
1642
|
+
executeAction();
|
|
1643
|
+
}, 800);
|
|
1644
|
+
break;
|
|
1645
|
+
}
|
|
1646
|
+
}
|
|
1647
|
+
});
|
|
1648
|
+
|
|
1649
|
+
window.removeFixedApp = function (event, index) {
|
|
1650
|
+
event.stopPropagation();
|
|
1651
|
+
fixedApps.splice(index, 1);
|
|
1652
|
+
localStorage.setItem("ISAI_FIXED_APPS", JSON.stringify(fixedApps));
|
|
1653
|
+
renderFixedApps();
|
|
1654
|
+
showToast("Removed from favorites.");
|
|
1655
|
+
};
|
|
1656
|
+
|
|
1657
|
+
let fixedApps = JSON.parse(localStorage.getItem("ISAI_FIXED_APPS") || "[]");
|
|
1658
|
+
|
|
1659
|
+
|
|
1660
|
+
|
|
1661
|
+
|
|
1662
|
+
function getAppIconHtml(app) {
|
|
1663
|
+
const title = normalizeAppDisplayText(app && app.title, (app && app.name) || "App") || "App";
|
|
1664
|
+
const initial = title.charAt(0).toUpperCase();
|
|
1665
|
+
|
|
1666
|
+
const gradients =[
|
|
1667
|
+
"bg-gradient-to-br from-[#22c55e] to-[#16a34a]",
|
|
1668
|
+
"bg-gradient-to-br from-[#d946ef] to-[#c026d3]",
|
|
1669
|
+
"bg-gradient-to-br from-[#0ea5e9] to-[#0284c7]",
|
|
1670
|
+
"bg-gradient-to-br from-[#f97316] to-[#ea580c]",
|
|
1671
|
+
"bg-gradient-to-br from-[#a855f7] to-[#9333ea]",
|
|
1672
|
+
"bg-gradient-to-br from-[#ec4899] to-[#db2777]"
|
|
1673
|
+
];
|
|
1674
|
+
|
|
1675
|
+
let hash = 0;
|
|
1676
|
+
for (let i = 0; i < title.length; i++) { hash = title.charCodeAt(i) + ((hash << 5) - hash); }
|
|
1677
|
+
const gradient = gradients[Math.abs(hash) % gradients.length];
|
|
1678
|
+
|
|
1679
|
+
let iconUrl = app.icon_url || "";
|
|
1680
|
+
if (!iconUrl && app.url) {
|
|
1681
|
+
iconUrl = `https://www.google.com/s2/favicons?sz=64&domain_url=${encodeURIComponent(app.url)}`;
|
|
1682
|
+
}
|
|
1683
|
+
|
|
1684
|
+
const imgHtml = iconUrl ? `
|
|
1685
|
+
<img src="${iconUrl}" style="display:none;"
|
|
1686
|
+
onload="if(this.naturalWidth <= 32) { this.style.display='none'; } else { this.nextElementSibling.querySelector('.icon-bg').style.backgroundImage = 'url(\\'' + this.src + '\\')'; this.nextElementSibling.classList.remove('opacity-0'); this.previousElementSibling.style.display='none'; }"
|
|
1687
|
+
onerror="this.style.display='none'">
|
|
1688
|
+
<div class="absolute inset-0 w-full h-full rounded-[14px] z-10 bg-white opacity-0 transition-opacity duration-300 p-[5px]">
|
|
1689
|
+
<div class="icon-bg w-full h-full rounded-[8px]"
|
|
1690
|
+
style="background-size: contain; background-position: center; background-repeat: no-repeat;"></div>
|
|
1691
|
+
</div>
|
|
1692
|
+
` : "";
|
|
1693
|
+
|
|
1694
|
+
return `
|
|
1695
|
+
<div class="absolute inset-0 w-full h-full flex items-center justify-center text-white font-bold text-[18px] rounded-[14px] shadow-sm ${gradient}">
|
|
1696
|
+
${initial}
|
|
1697
|
+
</div>
|
|
1698
|
+
${imgHtml}
|
|
1699
|
+
`;
|
|
1700
|
+
}
|
|
1701
|
+
|
|
1702
|
+
function startExperience() {
|
|
1703
|
+
if (!isStarted) {
|
|
1704
|
+
document.body.classList.add("started");
|
|
1705
|
+
document.getElementById("custom-scrollbar").style.display = "block";
|
|
1706
|
+
isStarted = true;
|
|
1707
|
+
setTimeout(scrollBottom, 600);
|
|
1708
|
+
}
|
|
1709
|
+
}
|
|
1710
|
+
|
|
1711
|
+
let translationSide = "right";
|
|
1712
|
+
const langMap = {
|
|
1713
|
+
English: "en-US",
|
|
1714
|
+
Korean: "ko-KR",
|
|
1715
|
+
Japanese: "ja-JP",
|
|
1716
|
+
Chinese: "zh-CN",
|
|
1717
|
+
Spanish: "es-ES",
|
|
1718
|
+
French: "fr-FR",
|
|
1719
|
+
German: "de-DE",
|
|
1720
|
+
Russian: "ru-RU"
|
|
1721
|
+
};
|
|
1722
|
+
|
|
1723
|
+
const langAliasMap = {
|
|
1724
|
+
en: "en-US",
|
|
1725
|
+
english: "en-US",
|
|
1726
|
+
ko: "ko-KR",
|
|
1727
|
+
kr: "ko-KR",
|
|
1728
|
+
korean: "ko-KR",
|
|
1729
|
+
ja: "ja-JP",
|
|
1730
|
+
jp: "ja-JP",
|
|
1731
|
+
japanese: "ja-JP",
|
|
1732
|
+
zh: "zh-CN",
|
|
1733
|
+
cn: "zh-CN",
|
|
1734
|
+
chinese: "zh-CN",
|
|
1735
|
+
es: "es-ES",
|
|
1736
|
+
sp: "es-ES",
|
|
1737
|
+
spanish: "es-ES",
|
|
1738
|
+
fr: "fr-FR",
|
|
1739
|
+
french: "fr-FR",
|
|
1740
|
+
de: "de-DE",
|
|
1741
|
+
ge: "de-DE",
|
|
1742
|
+
german: "de-DE",
|
|
1743
|
+
ru: "ru-RU",
|
|
1744
|
+
russian: "ru-RU",
|
|
1745
|
+
pt: "pt-PT",
|
|
1746
|
+
portuguese: "pt-PT",
|
|
1747
|
+
hi: "hi-IN",
|
|
1748
|
+
hindi: "hi-IN"
|
|
1749
|
+
};
|
|
1750
|
+
|
|
1751
|
+
function resolveSpeechLangCode(value, fallback = "en-US") {
|
|
1752
|
+
const raw = String(value || "").trim();
|
|
1753
|
+
if (!raw) return fallback;
|
|
1754
|
+
|
|
1755
|
+
if (langMap[raw]) return langMap[raw];
|
|
1756
|
+
|
|
1757
|
+
if (/^[a-z]{2}-[a-z]{2}$/i.test(raw)) {
|
|
1758
|
+
const [lang, region] = raw.split("-");
|
|
1759
|
+
return `${String(lang).toLowerCase()}-${String(region).toUpperCase()}`;
|
|
1760
|
+
}
|
|
1761
|
+
|
|
1762
|
+
const normalized = raw.toLowerCase().replace(/_/g, "-");
|
|
1763
|
+
if (langAliasMap[normalized]) return langAliasMap[normalized];
|
|
1764
|
+
|
|
1765
|
+
if (/^[a-z]{2}$/.test(normalized) && langAliasMap[normalized]) {
|
|
1766
|
+
return langAliasMap[normalized];
|
|
1767
|
+
}
|
|
1768
|
+
|
|
1769
|
+
return fallback;
|
|
1770
|
+
}
|
|
1771
|
+
|
|
1772
|
+
window.resolveSpeechLangCode = resolveSpeechLangCode;
|
|
1773
|
+
|
|
1774
|
+
function setVoiceState(side, state) {
|
|
1775
|
+
const btnLeft = document.getElementById("btn-submit-left");
|
|
1776
|
+
const btnRight = document.getElementById("btn-submit");
|
|
1777
|
+
const iconLeft = document.getElementById("icon-submit-left");
|
|
1778
|
+
const iconRight = document.getElementById("icon-submit");
|
|
1779
|
+
const btnVoice = document.getElementById("btn-voice");
|
|
1780
|
+
const btnVoiceIcon = btnVoice ? btnVoice.querySelector("i") : null;
|
|
1781
|
+
|
|
1782
|
+
if (!btnLeft || !iconLeft) return;
|
|
1783
|
+
|
|
1784
|
+
btnLeft.className = "btn-icon voice-toggle-btn transition-all duration-300";
|
|
1785
|
+
if (btnRight && btnRight.closest("#chat-input-actions")) {
|
|
1786
|
+
btnRight.className = "input-action-btn";
|
|
1787
|
+
} else if (btnRight) {
|
|
1788
|
+
btnRight.className = "input-action-btn";
|
|
1789
|
+
}
|
|
1790
|
+
btnLeft.style.display = "flex";
|
|
1791
|
+
btnLeft.style.backgroundColor = "rgba(255,255,255,0.10)";
|
|
1792
|
+
btnLeft.style.color = "#ffffff";
|
|
1793
|
+
btnLeft.style.boxShadow = "none";
|
|
1794
|
+
btnLeft.style.opacity = "1";
|
|
1795
|
+
btnLeft.style.pointerEvents = "auto";
|
|
1796
|
+
btnLeft.setAttribute("aria-pressed", state === "idle" ? "false" : "true");
|
|
1797
|
+
btnLeft.setAttribute("title", state === "idle" ? "Voice Conversation" : "Voice Conversation Active");
|
|
1798
|
+
if (btnRight) {
|
|
1799
|
+
btnRight.style.setProperty("width", "24px", "important");
|
|
1800
|
+
btnRight.style.setProperty("height", "24px", "important");
|
|
1801
|
+
btnRight.style.setProperty("min-width", "24px", "important");
|
|
1802
|
+
btnRight.style.setProperty("min-height", "24px", "important");
|
|
1803
|
+
btnRight.style.setProperty("padding", "0", "important");
|
|
1804
|
+
btnRight.style.setProperty("border-radius", "9999px", "important");
|
|
1805
|
+
btnRight.style.backgroundColor = "";
|
|
1806
|
+
btnRight.style.color = "";
|
|
1807
|
+
btnRight.style.setProperty("color", "#ffffff", "important");
|
|
1808
|
+
btnRight.style.opacity = "1";
|
|
1809
|
+
btnRight.style.pointerEvents = "auto";
|
|
1810
|
+
}
|
|
1811
|
+
|
|
1812
|
+
iconLeft.className = "ri-mic-line text-lg text-white";
|
|
1813
|
+
if (iconRight) {
|
|
1814
|
+
iconRight.className = "ri-arrow-up-s-line text-[13px] text-white";
|
|
1815
|
+
iconRight.style.color = "#ffffff";
|
|
1816
|
+
iconRight.style.setProperty("color", "#ffffff", "important");
|
|
1817
|
+
}
|
|
1818
|
+
if (typeof window.updateMainSubmitButtonState === "function") {
|
|
1819
|
+
window.updateMainSubmitButtonState();
|
|
1820
|
+
}
|
|
1821
|
+
if (btnVoice) {
|
|
1822
|
+
btnVoice.classList.remove("voice-armed", "voice-recording", "voice-processing");
|
|
1823
|
+
btnVoice.style.backgroundColor = "";
|
|
1824
|
+
btnVoice.style.color = "";
|
|
1825
|
+
btnVoice.style.boxShadow = "";
|
|
1826
|
+
btnVoice.style.transform = "";
|
|
1827
|
+
btnVoice.setAttribute("aria-pressed", state === "idle" ? "false" : "true");
|
|
1828
|
+
}
|
|
1829
|
+
if (btnVoiceIcon) {
|
|
1830
|
+
btnVoiceIcon.className = "ri-mic-2-line text-lg";
|
|
1831
|
+
}
|
|
1832
|
+
|
|
1833
|
+
if (state === "idle" && currentMode === "voice") {
|
|
1834
|
+
btnLeft.style.backgroundColor = "rgba(255,255,255,0.14)";
|
|
1835
|
+
btnLeft.style.boxShadow = "0 0 0 1px rgba(255,255,255,0.10), 0 10px 24px rgba(0,0,0,0.22)";
|
|
1836
|
+
if (btnVoice) {
|
|
1837
|
+
btnVoice.classList.add("voice-armed");
|
|
1838
|
+
btnVoice.style.backgroundColor = "rgba(255,255,255,0.12)";
|
|
1839
|
+
btnVoice.style.color = "#ffffff";
|
|
1840
|
+
btnVoice.style.boxShadow = "0 0 0 1px rgba(255,255,255,0.10), 0 10px 24px rgba(0,0,0,0.22)";
|
|
1841
|
+
}
|
|
1842
|
+
}
|
|
1843
|
+
|
|
1844
|
+
if (state === "idle") return;
|
|
1845
|
+
|
|
1846
|
+
if (state === "recording") {
|
|
1847
|
+
iconLeft.className = "ri-voiceprint-line text-lg text-white animate-mic-breath";
|
|
1848
|
+
btnLeft.style.backgroundColor = "#ef4444";
|
|
1849
|
+
btnLeft.style.color = "white";
|
|
1850
|
+
btnLeft.style.boxShadow = "0 0 0 4px rgba(239,68,68,0.18), 0 10px 24px rgba(239,68,68,0.30)";
|
|
1851
|
+
if (btnVoice) {
|
|
1852
|
+
btnVoice.classList.add("voice-recording");
|
|
1853
|
+
btnVoice.style.backgroundColor = "#ef4444";
|
|
1854
|
+
btnVoice.style.color = "#ffffff";
|
|
1855
|
+
btnVoice.style.boxShadow = "0 0 0 4px rgba(239,68,68,0.18), 0 10px 24px rgba(239,68,68,0.30)";
|
|
1856
|
+
btnVoice.style.transform = "scale(1.03)";
|
|
1857
|
+
}
|
|
1858
|
+
if (btnVoiceIcon) {
|
|
1859
|
+
btnVoiceIcon.className = "ri-voiceprint-line text-lg text-white animate-mic-breath";
|
|
1860
|
+
}
|
|
1861
|
+
} else if (state === "processing") {
|
|
1862
|
+
iconLeft.className = "ri-voiceprint-line text-lg text-white";
|
|
1863
|
+
btnLeft.style.backgroundColor = "rgba(255,255,255,0.16)";
|
|
1864
|
+
btnLeft.style.color = "#ffffff";
|
|
1865
|
+
btnLeft.style.boxShadow = "0 0 0 1px rgba(255,255,255,0.14), 0 10px 24px rgba(0,0,0,0.26)";
|
|
1866
|
+
if (btnVoice) {
|
|
1867
|
+
btnVoice.classList.add("voice-processing");
|
|
1868
|
+
btnVoice.style.backgroundColor = "rgba(255,255,255,0.16)";
|
|
1869
|
+
btnVoice.style.color = "#ffffff";
|
|
1870
|
+
btnVoice.style.boxShadow = "0 0 0 1px rgba(255,255,255,0.14), 0 10px 24px rgba(0,0,0,0.26)";
|
|
1871
|
+
}
|
|
1872
|
+
if (btnVoiceIcon) {
|
|
1873
|
+
btnVoiceIcon.className = "ri-voiceprint-line text-lg text-white";
|
|
1874
|
+
}
|
|
1875
|
+
}
|
|
1876
|
+
}
|
|
1877
|
+
|
|
1878
|
+
function updateSubmitIcon(state, side = "right") {
|
|
1879
|
+
setVoiceState("left", state === "mic" || state === "default" ? "idle" : state);
|
|
1880
|
+
}
|
|
1881
|
+
|
|
1882
|
+
function showPopup(title, msg, confirmCallback, cancelCallback) {
|
|
1883
|
+
const layer = document.getElementById("modal-layer");
|
|
1884
|
+
document.getElementById("modal-title").innerText = title;
|
|
1885
|
+
document.getElementById("modal-msg").innerText = msg;
|
|
1886
|
+
document.getElementById("modal-cancel").innerText = T.btn_cancel;
|
|
1887
|
+
document.getElementById("modal-confirm").innerText = T.btn_confirm;
|
|
1888
|
+
|
|
1889
|
+
const btnConfirm = document.getElementById("modal-confirm");
|
|
1890
|
+
const btnCancel = document.getElementById("modal-cancel");
|
|
1891
|
+
|
|
1892
|
+
const newConfirm = btnConfirm.cloneNode(true);
|
|
1893
|
+
const newCancel = btnCancel.cloneNode(true);
|
|
1894
|
+
|
|
1895
|
+
btnConfirm.parentNode.replaceChild(newConfirm, btnConfirm);
|
|
1896
|
+
btnCancel.parentNode.replaceChild(newCancel, btnCancel);
|
|
1897
|
+
|
|
1898
|
+
newConfirm.onclick = () => {
|
|
1899
|
+
closePopup();
|
|
1900
|
+
confirmCallback();
|
|
1901
|
+
};
|
|
1902
|
+
newCancel.onclick = () => {
|
|
1903
|
+
closePopup();
|
|
1904
|
+
if (typeof cancelCallback === "function") {
|
|
1905
|
+
cancelCallback();
|
|
1906
|
+
}
|
|
1907
|
+
};
|
|
1908
|
+
|
|
1909
|
+
layer.classList.add("show");
|
|
1910
|
+
}
|
|
1911
|
+
|
|
1912
|
+
function closePopup() {
|
|
1913
|
+
document.getElementById("modal-layer").classList.remove("show");
|
|
1914
|
+
}
|
|
1915
|
+
|
|
1916
|
+
async function ensureLocalModelReadyForAutoFallback() {
|
|
1917
|
+
if (isModelLoaded) {
|
|
1918
|
+
isLocalActive = true;
|
|
1919
|
+
updateLocalBtnState();
|
|
1920
|
+
return true;
|
|
1921
|
+
}
|
|
1922
|
+
|
|
1923
|
+
if (isModelDownloaded && !isModelLoaded) {
|
|
1924
|
+
isLocalActive = true;
|
|
1925
|
+
syncLocalModelTierVisibility();
|
|
1926
|
+
await startDownload();
|
|
1927
|
+
return !!isModelLoaded;
|
|
1928
|
+
}
|
|
1929
|
+
|
|
1930
|
+
isLocalActive = true;
|
|
1931
|
+
syncLocalModelTierVisibility();
|
|
1932
|
+
await startDownload();
|
|
1933
|
+
return !!isModelLoaded;
|
|
1934
|
+
}
|
|
1935
|
+
|
|
1936
|
+
function updateLocalBtnState() {
|
|
1937
|
+
const btn = document.getElementById("btn-download");
|
|
1938
|
+
if (!btn) return;
|
|
1939
|
+
const icon = btn.querySelector("i");
|
|
1940
|
+
if (!icon) return;
|
|
1941
|
+
|
|
1942
|
+
if (isLocalActive) {
|
|
1943
|
+
btn.classList.add("text-green-400", "active");
|
|
1944
|
+
icon.className = "ri-ghost-4-line text-lg";
|
|
1945
|
+
btn.style.color = "";
|
|
1946
|
+
} else if (isModelDownloaded) {
|
|
1947
|
+
btn.classList.remove("text-green-400", "active");
|
|
1948
|
+
icon.className = "ri-ghost-4-line text-lg";
|
|
1949
|
+
btn.style.color = "rgba(255,255,255,0.6)";
|
|
1950
|
+
} else {
|
|
1951
|
+
btn.classList.remove("text-green-400", "active");
|
|
1952
|
+
icon.className = "ri-ghost-4-line text-lg";
|
|
1953
|
+
btn.style.color = "rgba(255,255,255,0.3)";
|
|
1954
|
+
}
|
|
1955
|
+
if (typeof syncLocalModelTierVisibility === "function") {
|
|
1956
|
+
syncLocalModelTierVisibility();
|
|
1957
|
+
}
|
|
1958
|
+
}
|
|
1959
|
+
|
|
1960
|
+
function getLocalModelTierStorageKey() {
|
|
1961
|
+
return (window.MODEL_CONFIG && window.MODEL_CONFIG.modelTierKey) || "ISAI_LOCAL_MODEL_TIER_V1";
|
|
1962
|
+
}
|
|
1963
|
+
|
|
1964
|
+
function getLocalModelProfiles() {
|
|
1965
|
+
const coreProfiles = window.__ISAI_MODEL_CORE_PROFILES || {};
|
|
1966
|
+
if (coreProfiles && Object.keys(coreProfiles).length > 0) {
|
|
1967
|
+
return coreProfiles;
|
|
1968
|
+
}
|
|
1969
|
+
const configuredProfiles = (window.MODEL_CONFIG && window.MODEL_CONFIG.modelProfiles) || {};
|
|
1970
|
+
if (configuredProfiles && Object.keys(configuredProfiles).length > 0) {
|
|
1971
|
+
return configuredProfiles;
|
|
1972
|
+
}
|
|
1973
|
+
return {
|
|
1974
|
+
light: {
|
|
1975
|
+
key: "light",
|
|
1976
|
+
label: "Light",
|
|
1977
|
+
fallbackUrl: "",
|
|
1978
|
+
popupSizeText: "257MB",
|
|
1979
|
+
preferredRuntime: "wasm"
|
|
1980
|
+
},
|
|
1981
|
+
middle: {
|
|
1982
|
+
key: "middle",
|
|
1983
|
+
label: "Middle",
|
|
1984
|
+
fallbackUrl: "",
|
|
1985
|
+
preferredRuntime: "wasm"
|
|
1986
|
+
},
|
|
1987
|
+
hard: {
|
|
1988
|
+
key: "hard",
|
|
1989
|
+
label: "Hard",
|
|
1990
|
+
fallbackUrl: "",
|
|
1991
|
+
preferredRuntime: "wasm"
|
|
1992
|
+
}
|
|
1993
|
+
};
|
|
1994
|
+
}
|
|
1995
|
+
|
|
1996
|
+
function getDefaultLocalModelTier() {
|
|
1997
|
+
const profiles = getLocalModelProfiles();
|
|
1998
|
+
const configuredDefault = String((window.MODEL_CONFIG && window.MODEL_CONFIG.defaultModelTier) || "").trim().toLowerCase();
|
|
1999
|
+
if (configuredDefault && profiles[configuredDefault]) return configuredDefault;
|
|
2000
|
+
if (profiles.light) return "light";
|
|
2001
|
+
const keys = Object.keys(profiles);
|
|
2002
|
+
return keys.length > 0 ? keys[0] : "light";
|
|
2003
|
+
}
|
|
2004
|
+
|
|
2005
|
+
function getActiveLocalModelTier() {
|
|
2006
|
+
const profiles = getLocalModelProfiles();
|
|
2007
|
+
const storageKey = getLocalModelTierStorageKey();
|
|
2008
|
+
const userSetKey = getLocalModelTierUserSetKey();
|
|
2009
|
+
const hasUserSelection = localStorage.getItem(userSetKey) === "true";
|
|
2010
|
+
const storedTier = String(localStorage.getItem(storageKey) || "").trim().toLowerCase();
|
|
2011
|
+
const defaultTier = getDefaultLocalModelTier();
|
|
2012
|
+
if (storedTier && profiles[storedTier] && hasUserSelection) return storedTier;
|
|
2013
|
+
localStorage.setItem(storageKey, defaultTier);
|
|
2014
|
+
return defaultTier;
|
|
2015
|
+
}
|
|
2016
|
+
|
|
2017
|
+
function getActiveLocalModelProfile() {
|
|
2018
|
+
const profiles = getLocalModelProfiles();
|
|
2019
|
+
const tier = getActiveLocalModelTier();
|
|
2020
|
+
return profiles[tier] || null;
|
|
2021
|
+
}
|
|
2022
|
+
|
|
2023
|
+
function getLocalModelProfileUrl(profile) {
|
|
2024
|
+
if (!profile) return "";
|
|
2025
|
+
if (typeof profile === "string") return String(profile).trim();
|
|
2026
|
+
return String(profile.fallbackUrl || profile.url || profile.modelUrl || "").trim();
|
|
2027
|
+
}
|
|
2028
|
+
|
|
2029
|
+
function resolveLocalWasmPaths() {
|
|
2030
|
+
const configured = window.MODEL_CONFIG && window.MODEL_CONFIG.fallback && window.MODEL_CONFIG.fallback.wasmPaths;
|
|
2031
|
+
if (configured && typeof configured === "object") {
|
|
2032
|
+
const single = String(configured["single-thread/wllama.wasm"] || "").trim();
|
|
2033
|
+
const multi = String(configured["multi-thread/wllama.wasm"] || "").trim();
|
|
2034
|
+
if (single && multi) return configured;
|
|
2035
|
+
}
|
|
2036
|
+
const bundled = (typeof DEFAULT_LOCAL_WASM_PATHS !== "undefined" && DEFAULT_LOCAL_WASM_PATHS)
|
|
2037
|
+
|| window.LOCAL_WASM_PATHS
|
|
2038
|
+
|| null;
|
|
2039
|
+
if (bundled && typeof bundled === "object") {
|
|
2040
|
+
const single = String(bundled["single-thread/wllama.wasm"] || "").trim();
|
|
2041
|
+
const multi = String(bundled["multi-thread/wllama.wasm"] || "").trim();
|
|
2042
|
+
if (single && multi) return { ...bundled };
|
|
2043
|
+
}
|
|
2044
|
+
const baseUrl = String(window.LOCAL_WASM_BASE_URL || "/cdn/vendor/local-wasm/esm").trim();
|
|
2045
|
+
if (baseUrl) {
|
|
2046
|
+
return {
|
|
2047
|
+
"single-thread/wllama.wasm": `${baseUrl}/single-thread/local.wasm`,
|
|
2048
|
+
"multi-thread/wllama.wasm": `${baseUrl}/multi-thread/local.wasm`
|
|
2049
|
+
};
|
|
2050
|
+
}
|
|
2051
|
+
return {
|
|
2052
|
+
"single-thread/wllama.wasm": "/cdn/vendor/local-wasm/esm/single-thread/local.wasm",
|
|
2053
|
+
"multi-thread/wllama.wasm": "/cdn/vendor/local-wasm/esm/multi-thread/local.wasm"
|
|
2054
|
+
};
|
|
2055
|
+
}
|
|
2056
|
+
|
|
2057
|
+
function applyLocalModelProfileToConfig() {
|
|
2058
|
+
const modelConfig = window.MODEL_CONFIG || {};
|
|
2059
|
+
const profile = getActiveLocalModelProfile();
|
|
2060
|
+
if (!profile) return null;
|
|
2061
|
+
modelConfig.fallback = modelConfig.fallback || {};
|
|
2062
|
+
const profileUrl = getLocalModelProfileUrl(profile);
|
|
2063
|
+
if (profileUrl) {
|
|
2064
|
+
modelConfig.fallback.url = profileUrl;
|
|
2065
|
+
}
|
|
2066
|
+
if (Array.isArray(profile.fallbackUrls) && profile.fallbackUrls.length > 0) {
|
|
2067
|
+
modelConfig.fallback.urls = profile.fallbackUrls.slice();
|
|
2068
|
+
} else {
|
|
2069
|
+
delete modelConfig.fallback.urls;
|
|
2070
|
+
}
|
|
2071
|
+
modelConfig.fallback.wasmPaths = resolveLocalWasmPaths();
|
|
2072
|
+
modelConfig.preferredRuntime = "wasm";
|
|
2073
|
+
modelConfig.webllm = null;
|
|
2074
|
+
modelConfig.activeModelId = profile.modelId || "";
|
|
2075
|
+
modelConfig.activeModelName = profile.modelName || "";
|
|
2076
|
+
window.MODEL_CONFIG = modelConfig;
|
|
2077
|
+
return profile;
|
|
2078
|
+
}
|
|
2079
|
+
window.applyLocalModelProfileToConfig = applyLocalModelProfileToConfig;
|
|
2080
|
+
|
|
2081
|
+
function getLocalDownloadStorageKey() {
|
|
2082
|
+
const baseKey = (window.MODEL_CONFIG && window.MODEL_CONFIG.storageKey) || "ISAI_MODEL_DOWNLOADED";
|
|
2083
|
+
const profile = getActiveLocalModelProfile();
|
|
2084
|
+
const modelId = String(profile && profile.modelId ? profile.modelId : getActiveLocalModelTier()).trim().toLowerCase();
|
|
2085
|
+
return `${baseKey}__${modelId || "default"}`;
|
|
2086
|
+
}
|
|
2087
|
+
|
|
2088
|
+
function getLocalRuntimeStorageKey() {
|
|
2089
|
+
return (window.MODEL_CONFIG && window.MODEL_CONFIG.runtimeKey) || "ISAI_LOCAL_MODEL_RUNTIME_V1";
|
|
2090
|
+
}
|
|
2091
|
+
|
|
2092
|
+
function getLocalModelTierIconMarkup(tierKey) {
|
|
2093
|
+
const key = String(tierKey || "").trim().toLowerCase();
|
|
2094
|
+
const iconMap = {
|
|
2095
|
+
code: "ri-folder-zip-line",
|
|
2096
|
+
light: "ri-flashlight-line",
|
|
2097
|
+
middle: "ri-command-line",
|
|
2098
|
+
hard: "ri-fire-line"
|
|
2099
|
+
};
|
|
2100
|
+
const iconClass = iconMap[key] || "ri-circle-line";
|
|
2101
|
+
return `<i class="${iconClass}"></i>`;
|
|
2102
|
+
}
|
|
2103
|
+
|
|
2104
|
+
function getLocalModelPopupTitle() {
|
|
2105
|
+
const locale = String(document.documentElement.lang || navigator.language || "ko").toLowerCase();
|
|
2106
|
+
if (locale.startsWith("ko")) return "濡쒖뺄 紐⑤뜽 ?ㅼ슫濡쒕뱶";
|
|
2107
|
+
return "Download Local Model";
|
|
2108
|
+
}
|
|
2109
|
+
|
|
2110
|
+
function getLocalModelPopupMessage() {
|
|
2111
|
+
const locale = String(document.documentElement.lang || navigator.language || "ko").toLowerCase();
|
|
2112
|
+
const profile = getActiveLocalModelProfile();
|
|
2113
|
+
if (locale.startsWith("ko")) {
|
|
2114
|
+
if (profile && profile.key === "light") {
|
|
2115
|
+
const sizeText = profile.popupSizeText || "257MB";
|
|
2116
|
+
return `?쇱씠??紐⑤뜽(${sizeText})???ㅼ슫濡쒕뱶?⑸땲??`;
|
|
2117
|
+
}
|
|
2118
|
+
if (profile && profile.key === "middle") {
|
|
2119
|
+
return "以묎컙 紐⑤뜽???ㅼ슫濡쒕뱶?⑸땲??";
|
|
2120
|
+
}
|
|
2121
|
+
if (profile && profile.key === "hard") {
|
|
2122
|
+
return "?섎뱶 紐⑤뜽???ㅼ슫濡쒕뱶?⑸땲??";
|
|
2123
|
+
}
|
|
2124
|
+
return "濡쒖뺄 紐⑤뜽???ㅼ슫濡쒕뱶?⑸땲??";
|
|
2125
|
+
}
|
|
2126
|
+
if (profile && profile.key === "light") {
|
|
2127
|
+
const sizeText = profile.popupSizeText || "257MB";
|
|
2128
|
+
return `Downloading light model (${sizeText}).`;
|
|
2129
|
+
}
|
|
2130
|
+
if (profile && profile.key === "middle") {
|
|
2131
|
+
return "Downloading middle model.";
|
|
2132
|
+
}
|
|
2133
|
+
if (profile && profile.key === "hard") {
|
|
2134
|
+
return "Downloading hard model.";
|
|
2135
|
+
}
|
|
2136
|
+
return "Downloading local model.";
|
|
2137
|
+
}
|
|
2138
|
+
|
|
2139
|
+
function syncLocalModelTierVisibility(mode) {
|
|
2140
|
+
const wrapper = document.getElementById("local-model-tier-wrapper");
|
|
2141
|
+
if (!wrapper) return;
|
|
2142
|
+
const effectiveMode = String(mode || (document.body && document.body.getAttribute("data-ui-mode")) || currentMode || "chat").toLowerCase();
|
|
2143
|
+
const shouldShow = (effectiveMode === "chat" || effectiveMode === "code");
|
|
2144
|
+
wrapper.style.display = shouldShow ? "block" : "none";
|
|
2145
|
+
}
|
|
2146
|
+
window.syncLocalModelTierVisibility = syncLocalModelTierVisibility;
|
|
2147
|
+
|
|
2148
|
+
function renderLocalModelTierSelector() {
|
|
2149
|
+
const wrapper = document.getElementById("local-model-tier-wrapper");
|
|
2150
|
+
const list = document.getElementById("local-model-tier-list");
|
|
2151
|
+
if (!wrapper || !list) return;
|
|
2152
|
+
|
|
2153
|
+
const profiles = getLocalModelProfiles();
|
|
2154
|
+
const activeTier = getActiveLocalModelTier();
|
|
2155
|
+
const orderedTiers = getOrderedLocalModelTierKeys().filter((tier) => !!profiles[tier]);
|
|
2156
|
+
|
|
2157
|
+
list.innerHTML = "";
|
|
2158
|
+
orderedTiers.forEach((tier) => {
|
|
2159
|
+
const profile = profiles[tier] || {};
|
|
2160
|
+
const button = document.createElement("button");
|
|
2161
|
+
button.type = "button";
|
|
2162
|
+
button.className = `local-model-tier-btn${tier === activeTier ? " active" : ""}`;
|
|
2163
|
+
const displayLabel = getLocalizedLocalModelTierLabel(tier);
|
|
2164
|
+
button.classList.add("is-icon");
|
|
2165
|
+
button.innerHTML = getLocalModelTierIconMarkup(tier);
|
|
2166
|
+
button.title = profile.modelName || displayLabel;
|
|
2167
|
+
button.setAttribute("aria-label", profile.modelName || displayLabel);
|
|
2168
|
+
button.dataset.tier = tier;
|
|
2169
|
+
button.setAttribute("aria-pressed", tier === activeTier ? "true" : "false");
|
|
2170
|
+
button.addEventListener("click", (event) => {
|
|
2171
|
+
event.preventDefault();
|
|
2172
|
+
event.stopPropagation();
|
|
2173
|
+
setLocalModelTier(tier);
|
|
2174
|
+
});
|
|
2175
|
+
list.appendChild(button);
|
|
2176
|
+
});
|
|
2177
|
+
|
|
2178
|
+
const localButton = document.createElement("button");
|
|
2179
|
+
localButton.type = "button";
|
|
2180
|
+
localButton.id = "btn-download";
|
|
2181
|
+
localButton.className = "local-model-tier-menu-btn";
|
|
2182
|
+
localButton.title = "Local Mode";
|
|
2183
|
+
localButton.setAttribute("aria-label", "Local Mode");
|
|
2184
|
+
localButton.innerHTML = '<i class="ri-ghost-4-line text-[14px]"></i>';
|
|
2185
|
+
localButton.addEventListener("click", (event) => {
|
|
2186
|
+
event.preventDefault();
|
|
2187
|
+
event.stopPropagation();
|
|
2188
|
+
if (typeof handleLocalToggle === "function") {
|
|
2189
|
+
handleLocalToggle();
|
|
2190
|
+
}
|
|
2191
|
+
});
|
|
2192
|
+
list.appendChild(localButton);
|
|
2193
|
+
|
|
2194
|
+
const gpuButton = document.createElement("button");
|
|
2195
|
+
gpuButton.type = "button";
|
|
2196
|
+
gpuButton.id = "btn-webgpu";
|
|
2197
|
+
gpuButton.className = "local-model-tier-menu-btn";
|
|
2198
|
+
gpuButton.title = "WebGPU";
|
|
2199
|
+
gpuButton.setAttribute("aria-label", "WebGPU");
|
|
2200
|
+
gpuButton.innerHTML = '<i class="ri-cpu-line text-[14px]"></i>';
|
|
2201
|
+
gpuButton.addEventListener("click", (event) => {
|
|
2202
|
+
event.preventDefault();
|
|
2203
|
+
event.stopPropagation();
|
|
2204
|
+
if (typeof handleWebGPUToggle === "function") {
|
|
2205
|
+
handleWebGPUToggle();
|
|
2206
|
+
}
|
|
2207
|
+
});
|
|
2208
|
+
list.appendChild(gpuButton);
|
|
2209
|
+
|
|
2210
|
+
const menuButton = document.createElement("button");
|
|
2211
|
+
menuButton.type = "button";
|
|
2212
|
+
menuButton.id = "local-model-tier-menu-btn";
|
|
2213
|
+
menuButton.className = "local-model-tier-menu-btn";
|
|
2214
|
+
menuButton.title = "Model Menu";
|
|
2215
|
+
menuButton.setAttribute("aria-label", "Model Menu");
|
|
2216
|
+
menuButton.innerHTML = '<i class="ri-menu-4-line text-[14px]"></i>';
|
|
2217
|
+
menuButton.addEventListener("click", (event) => {
|
|
2218
|
+
event.preventDefault();
|
|
2219
|
+
event.stopPropagation();
|
|
2220
|
+
if (typeof toggleLocalModelShortcutMenu === "function") {
|
|
2221
|
+
toggleLocalModelShortcutMenu(event);
|
|
2222
|
+
}
|
|
2223
|
+
});
|
|
2224
|
+
list.appendChild(menuButton);
|
|
2225
|
+
|
|
2226
|
+
if (typeof updateLocalBtnState === "function") updateLocalBtnState();
|
|
2227
|
+
if (typeof updateWebGPUBtnState === "function") updateWebGPUBtnState();
|
|
2228
|
+
|
|
2229
|
+
syncLocalModelTierVisibility();
|
|
2230
|
+
}
|
|
2231
|
+
|
|
2232
|
+
function observeLocalModelTierVisibility() {
|
|
2233
|
+
if (window.__localModelTierModeObserverBound) return;
|
|
2234
|
+
const body = document.body;
|
|
2235
|
+
if (!body) return;
|
|
2236
|
+
const observer = new MutationObserver(() => {
|
|
2237
|
+
syncLocalModelTierVisibility();
|
|
2238
|
+
});
|
|
2239
|
+
observer.observe(body, { attributes: true, attributeFilter: ["data-ui-mode"] });
|
|
2240
|
+
window.__localModelTierModeObserverBound = true;
|
|
2241
|
+
}
|
|
2242
|
+
|
|
2243
|
+
function setLocalModelTier(tierKey) {
|
|
2244
|
+
const profiles = getLocalModelProfiles();
|
|
2245
|
+
const nextTier = String(tierKey || "").trim().toLowerCase();
|
|
2246
|
+
if (!profiles[nextTier]) return;
|
|
2247
|
+
|
|
2248
|
+
const prevTier = getActiveLocalModelTier();
|
|
2249
|
+
if (prevTier === nextTier) {
|
|
2250
|
+
renderLocalModelTierSelector();
|
|
2251
|
+
return;
|
|
2252
|
+
}
|
|
2253
|
+
|
|
2254
|
+
const wasLocalActive = !!isLocalActive;
|
|
2255
|
+
localStorage.setItem(getLocalModelTierStorageKey(), nextTier);
|
|
2256
|
+
localStorage.setItem(getLocalModelTierUserSetKey(), "true");
|
|
2257
|
+
applyLocalModelProfileToConfig();
|
|
2258
|
+
|
|
2259
|
+
isLocalActive = wasLocalActive;
|
|
2260
|
+
isModelLoaded = false;
|
|
2261
|
+
localWasmEngine = null;
|
|
2262
|
+
setLocalRuntimeState(null);
|
|
2263
|
+
isModelDownloaded = localStorage.getItem(getLocalDownloadStorageKey()) === "true";
|
|
2264
|
+
|
|
2265
|
+
updateLocalBtnState();
|
|
2266
|
+
renderLocalModelTierSelector();
|
|
2267
|
+
|
|
2268
|
+
const activeProfile = getActiveLocalModelProfile();
|
|
2269
|
+
if (activeProfile && typeof showToast === "function") {
|
|
2270
|
+
const safeReadySuffix = isModelDownloaded ? " (\ub2e4\uc6b4\ub85c\ub4dc\ub428)" : "";
|
|
2271
|
+
showToast(`${getLocalizedLocalModelTierLabel(nextTier)} \ubaa8\ub378 \uc120\ud0dd${safeReadySuffix}`);
|
|
2272
|
+
return;
|
|
2273
|
+
const readySuffix = isModelDownloaded ? " (???깅뮧?β돦裕녻キ??" : "";
|
|
2274
|
+
showToast(`${getLocalizedLocalModelTierLabel(nextTier)} 嶺뚮ㅄ維????ルㅎ臾?{readySuffix}`);
|
|
2275
|
+
}
|
|
2276
|
+
}
|
|
2277
|
+
window.setLocalModelTier = setLocalModelTier;
|
|
2278
|
+
|
|
2279
|
+
function getLocalModelTierLabelFallback(tierKey) {
|
|
2280
|
+
const key = String(tierKey || "").trim().toLowerCase();
|
|
2281
|
+
if (key === "code") return "Code";
|
|
2282
|
+
if (key === "middle") return "Middle";
|
|
2283
|
+
if (key === "hard") return "Hard";
|
|
2284
|
+
return "Light";
|
|
2285
|
+
}
|
|
2286
|
+
|
|
2287
|
+
function getLocalModelTierLocale() {
|
|
2288
|
+
const serverLocale = window.ISAI_SERVER_I18N && window.ISAI_SERVER_I18N.locale;
|
|
2289
|
+
const candidate = String(serverLocale || window.LANG || document.documentElement.lang || navigator.language || "en").toLowerCase();
|
|
2290
|
+
return candidate.split("-")[0];
|
|
2291
|
+
}
|
|
2292
|
+
|
|
2293
|
+
function getLocalizedLocalModelTierLabel(tierKey) {
|
|
2294
|
+
const key = String(tierKey || "").trim().toLowerCase();
|
|
2295
|
+
const shortLabels = {
|
|
2296
|
+
code: "",
|
|
2297
|
+
light: "L",
|
|
2298
|
+
middle: "M",
|
|
2299
|
+
hard: "H"
|
|
2300
|
+
};
|
|
2301
|
+
return shortLabels[key] || getLocalModelTierLabelFallback(key);
|
|
2302
|
+
}
|
|
2303
|
+
|
|
2304
|
+
function normalizeLocalModelProfile(tierKey, rawProfile) {
|
|
2305
|
+
const key = String(tierKey || "").trim().toLowerCase();
|
|
2306
|
+
if (!key) return null;
|
|
2307
|
+
const defaultLabel = getLocalModelTierLabelFallback(key);
|
|
2308
|
+
|
|
2309
|
+
if (typeof rawProfile === "string") {
|
|
2310
|
+
return {
|
|
2311
|
+
key,
|
|
2312
|
+
label: defaultLabel,
|
|
2313
|
+
fallbackUrl: String(rawProfile).trim(),
|
|
2314
|
+
preferredRuntime: "wasm"
|
|
2315
|
+
};
|
|
2316
|
+
}
|
|
2317
|
+
|
|
2318
|
+
const source = rawProfile && typeof rawProfile === "object" ? rawProfile : {};
|
|
2319
|
+
const normalized = { ...source };
|
|
2320
|
+
normalized.key = String(normalized.key || key).trim().toLowerCase() || key;
|
|
2321
|
+
normalized.label = String(normalized.label || defaultLabel).trim() || defaultLabel;
|
|
2322
|
+
|
|
2323
|
+
const url = String(normalized.fallbackUrl || normalized.url || normalized.modelUrl || "").trim();
|
|
2324
|
+
if (url) normalized.fallbackUrl = url;
|
|
2325
|
+
if (!normalized.popupSizeText && normalized.sizeText) {
|
|
2326
|
+
normalized.popupSizeText = String(normalized.sizeText).trim();
|
|
2327
|
+
}
|
|
2328
|
+
if (!normalized.preferredRuntime) {
|
|
2329
|
+
normalized.preferredRuntime = "wasm";
|
|
2330
|
+
}
|
|
2331
|
+
return normalized;
|
|
2332
|
+
}
|
|
2333
|
+
|
|
2334
|
+
function normalizeLocalModelProfiles(rawProfiles) {
|
|
2335
|
+
if (!rawProfiles || typeof rawProfiles !== "object") return {};
|
|
2336
|
+
const normalized = {};
|
|
2337
|
+
Object.keys(rawProfiles).forEach((tierKey) => {
|
|
2338
|
+
const profile = normalizeLocalModelProfile(tierKey, rawProfiles[tierKey]);
|
|
2339
|
+
if (!profile) return;
|
|
2340
|
+
normalized[profile.key] = profile;
|
|
2341
|
+
});
|
|
2342
|
+
return normalized;
|
|
2343
|
+
}
|
|
2344
|
+
|
|
2345
|
+
function getBundledLocalModelProfilesRaw() {
|
|
2346
|
+
const provider = window.getIsaiLocalModelProfiles;
|
|
2347
|
+
if (typeof provider === "function") {
|
|
2348
|
+
try {
|
|
2349
|
+
const provided = provider();
|
|
2350
|
+
if (provided && typeof provided === "object" && Object.keys(provided).length > 0) {
|
|
2351
|
+
return provided;
|
|
2352
|
+
}
|
|
2353
|
+
} catch (error) {}
|
|
2354
|
+
}
|
|
2355
|
+
const bundled = window.__ISAI_LOCAL_MODEL_PROFILES;
|
|
2356
|
+
if (bundled && typeof bundled === "object" && Object.keys(bundled).length > 0) {
|
|
2357
|
+
return bundled;
|
|
2358
|
+
}
|
|
2359
|
+
return null;
|
|
2360
|
+
}
|
|
2361
|
+
|
|
2362
|
+
function getLocalModelProfiles() {
|
|
2363
|
+
const bundledProfiles = getBundledLocalModelProfilesRaw();
|
|
2364
|
+
if (bundledProfiles && Object.keys(bundledProfiles).length > 0) {
|
|
2365
|
+
return normalizeLocalModelProfiles(bundledProfiles);
|
|
2366
|
+
}
|
|
2367
|
+
const coreProfiles = window.__ISAI_MODEL_CORE_PROFILES || {};
|
|
2368
|
+
if (coreProfiles && Object.keys(coreProfiles).length > 0) {
|
|
2369
|
+
return normalizeLocalModelProfiles(coreProfiles);
|
|
2370
|
+
}
|
|
2371
|
+
const configuredProfiles = (window.MODEL_CONFIG && window.MODEL_CONFIG.modelProfiles) || {};
|
|
2372
|
+
if (configuredProfiles && Object.keys(configuredProfiles).length > 0) {
|
|
2373
|
+
return normalizeLocalModelProfiles(configuredProfiles);
|
|
2374
|
+
}
|
|
2375
|
+
return normalizeLocalModelProfiles({
|
|
2376
|
+
code: {
|
|
2377
|
+
key: "code",
|
|
2378
|
+
label: "\ucf54\ub4dc",
|
|
2379
|
+
fallbackUrl: DEFAULT_LOCAL_MODEL_URL,
|
|
2380
|
+
popupSizeText: "369MB",
|
|
2381
|
+
preferredRuntime: "wasm"
|
|
2382
|
+
},
|
|
2383
|
+
light: {
|
|
2384
|
+
key: "light",
|
|
2385
|
+
label: "\ub77c\uc774\ud2b8",
|
|
2386
|
+
fallbackUrl: DEFAULT_LOCAL_MODEL_URL,
|
|
2387
|
+
popupSizeText: "429MB",
|
|
2388
|
+
preferredRuntime: "wasm"
|
|
2389
|
+
},
|
|
2390
|
+
middle: {
|
|
2391
|
+
key: "middle",
|
|
2392
|
+
label: "\uc911\uac04",
|
|
2393
|
+
fallbackUrl: DEFAULT_LOCAL_MODEL_URL,
|
|
2394
|
+
popupSizeText: "592MB",
|
|
2395
|
+
preferredRuntime: "wasm"
|
|
2396
|
+
},
|
|
2397
|
+
hard: {
|
|
2398
|
+
key: "hard",
|
|
2399
|
+
label: "\ud558\ub4dc",
|
|
2400
|
+
fallbackUrl: DEFAULT_LOCAL_MODEL_URL,
|
|
2401
|
+
popupSizeText: "558MB",
|
|
2402
|
+
preferredRuntime: "wasm"
|
|
2403
|
+
}
|
|
2404
|
+
});
|
|
2405
|
+
}
|
|
2406
|
+
|
|
2407
|
+
function getLocalModelPopupTitle() {
|
|
2408
|
+
const locale = String(document.documentElement.lang || navigator.language || "ko").toLowerCase();
|
|
2409
|
+
if (locale.startsWith("ko")) return "\ub85c\uceec \ubaa8\ub378 \ub2e4\uc6b4\ub85c\ub4dc";
|
|
2410
|
+
return "Download Local Model";
|
|
2411
|
+
}
|
|
2412
|
+
|
|
2413
|
+
function getLocalModelPopupMessage() {
|
|
2414
|
+
const locale = String(document.documentElement.lang || navigator.language || "en").toLowerCase();
|
|
2415
|
+
|
|
2416
|
+
if (locale.startsWith("ko")) {
|
|
2417
|
+
return "濡쒖뺄 紐⑤뱶瑜??ъ슜?좉퉴??";
|
|
2418
|
+
}
|
|
2419
|
+
|
|
2420
|
+
return "Enable local mode?";
|
|
2421
|
+
}
|
|
2422
|
+
|
|
2423
|
+
function getOrderedLocalModelTierKeys() {
|
|
2424
|
+
const profiles = getLocalModelProfiles();
|
|
2425
|
+
const preferredOrder = ["code", "light", "middle", "hard"];
|
|
2426
|
+
const ordered = preferredOrder.filter((tier) => !!profiles[tier]);
|
|
2427
|
+
return ordered.length > 0 ? ordered : ["light"];
|
|
2428
|
+
}
|
|
2429
|
+
|
|
2430
|
+
function getNextLocalModelTier(currentTier) {
|
|
2431
|
+
const ordered = getOrderedLocalModelTierKeys();
|
|
2432
|
+
const current = String(currentTier || "").trim().toLowerCase();
|
|
2433
|
+
const currentIndex = ordered.indexOf(current);
|
|
2434
|
+
if (currentIndex < 0) return ordered[0];
|
|
2435
|
+
return ordered[(currentIndex + 1) % ordered.length];
|
|
2436
|
+
}
|
|
2437
|
+
|
|
2438
|
+
function getLocalModelTierUserSetKey() {
|
|
2439
|
+
return "ISAI_LOCAL_MODEL_TIER_USER_SET_V1";
|
|
2440
|
+
}
|
|
2441
|
+
|
|
2442
|
+
function ensureDefaultLocalModelTierForActivation(forceLightOnActivation) {
|
|
2443
|
+
const profiles = getLocalModelProfiles();
|
|
2444
|
+
const tierKey = getLocalModelTierStorageKey();
|
|
2445
|
+
const userSetKey = getLocalModelTierUserSetKey();
|
|
2446
|
+
const hasUserSelection = localStorage.getItem(userSetKey) === "true";
|
|
2447
|
+
const storedTier = String(localStorage.getItem(tierKey) || "").trim().toLowerCase();
|
|
2448
|
+
const fallbackTier = profiles.middle ? "middle" : getDefaultLocalModelTier();
|
|
2449
|
+
if (forceLightOnActivation) {
|
|
2450
|
+
const activationTier = profiles.light ? "light" : fallbackTier;
|
|
2451
|
+
if (profiles[activationTier]) {
|
|
2452
|
+
localStorage.setItem(tierKey, activationTier);
|
|
2453
|
+
return activationTier;
|
|
2454
|
+
}
|
|
2455
|
+
}
|
|
2456
|
+
if (!hasUserSelection || !profiles[storedTier]) {
|
|
2457
|
+
localStorage.setItem(tierKey, fallbackTier);
|
|
2458
|
+
}
|
|
2459
|
+
return getActiveLocalModelTier();
|
|
2460
|
+
}
|
|
2461
|
+
|
|
2462
|
+
function setLocalModelTier(tierKey) {
|
|
2463
|
+
const profiles = getLocalModelProfiles();
|
|
2464
|
+
const nextTier = String(tierKey || "").trim().toLowerCase();
|
|
2465
|
+
if (!profiles[nextTier]) return;
|
|
2466
|
+
|
|
2467
|
+
const prevTier = getActiveLocalModelTier();
|
|
2468
|
+
if (prevTier === nextTier) {
|
|
2469
|
+
renderLocalModelTierSelector();
|
|
2470
|
+
return;
|
|
2471
|
+
}
|
|
2472
|
+
|
|
2473
|
+
const wasLocalActive = !!isLocalActive;
|
|
2474
|
+
localStorage.setItem(getLocalModelTierStorageKey(), nextTier);
|
|
2475
|
+
localStorage.setItem(getLocalModelTierUserSetKey(), "true");
|
|
2476
|
+
applyLocalModelProfileToConfig();
|
|
2477
|
+
|
|
2478
|
+
isLocalActive = wasLocalActive;
|
|
2479
|
+
isModelLoaded = false;
|
|
2480
|
+
localWasmEngine = null;
|
|
2481
|
+
setLocalRuntimeState(null);
|
|
2482
|
+
isModelDownloaded = localStorage.getItem(getLocalDownloadStorageKey()) === "true";
|
|
2483
|
+
|
|
2484
|
+
updateLocalBtnState();
|
|
2485
|
+
renderLocalModelTierSelector();
|
|
2486
|
+
|
|
2487
|
+
const activeProfile = getActiveLocalModelProfile();
|
|
2488
|
+
if (activeProfile && typeof showToast === "function") {
|
|
2489
|
+
const readySuffix = isModelDownloaded ? " (\ub2e4\uc6b4\ub85c\ub4dc\ub428)" : "";
|
|
2490
|
+
showToast(`${getLocalizedLocalModelTierLabel(nextTier)} \ubaa8\ub378 \uc120\ud0dd${readySuffix}`);
|
|
2491
|
+
}
|
|
2492
|
+
|
|
2493
|
+
if (isModelDownloaded) {
|
|
2494
|
+
if (wasLocalActive) {
|
|
2495
|
+
startDownload();
|
|
2496
|
+
}
|
|
2497
|
+
return;
|
|
2498
|
+
}
|
|
2499
|
+
|
|
2500
|
+
isLocalActive = true;
|
|
2501
|
+
updateLocalBtnState();
|
|
2502
|
+
renderLocalModelTierSelector();
|
|
2503
|
+
syncLocalModelTierVisibility();
|
|
2504
|
+
startDownload();
|
|
2505
|
+
}
|
|
2506
|
+
window.setLocalModelTier = setLocalModelTier;
|
|
2507
|
+
|
|
2508
|
+
function getLocalModelPopupSizeText(profile) {
|
|
2509
|
+
if (!profile) return "429MB";
|
|
2510
|
+
const sizeCandidates = [profile.popupSizeText, profile.sizeText, profile.downloadSize, profile.size];
|
|
2511
|
+
for (const sizeValue of sizeCandidates) {
|
|
2512
|
+
if (sizeValue) return String(sizeValue);
|
|
2513
|
+
}
|
|
2514
|
+
if (profile.key === "code") return "369MB";
|
|
2515
|
+
if (profile.key === "middle") return "592MB";
|
|
2516
|
+
if (profile.key === "hard") return "558MB";
|
|
2517
|
+
return "429MB";
|
|
2518
|
+
}
|
|
2519
|
+
|
|
2520
|
+
function getLocalModelPopupProfileLabel(profile) {
|
|
2521
|
+
const tier = profile && profile.key ? profile.key : getActiveLocalModelTier();
|
|
2522
|
+
return getLocalizedLocalModelTierLabel(tier);
|
|
2523
|
+
}
|
|
2524
|
+
|
|
2525
|
+
function getLocalModelPopupModelName(profile) {
|
|
2526
|
+
if (!profile) return "";
|
|
2527
|
+
const explicitName = String(profile.modelName || profile.name || "").trim();
|
|
2528
|
+
if (explicitName) return explicitName;
|
|
2529
|
+
const url = getLocalModelProfileUrl(profile);
|
|
2530
|
+
if (!url) return "";
|
|
2531
|
+
const path = url.split("?")[0];
|
|
2532
|
+
const fileName = path.substring(path.lastIndexOf("/") + 1);
|
|
2533
|
+
return String(fileName || "").trim();
|
|
2534
|
+
}
|
|
2535
|
+
|
|
2536
|
+
function getLocalModelPopupTitle() {
|
|
2537
|
+
const locale = String(document.documentElement.lang || navigator.language || "ko").toLowerCase();
|
|
2538
|
+
if (locale.startsWith("ko")) return "\ub85c\uceec \ubaa8\ub378 \ub2e4\uc6b4\ub85c\ub4dc";
|
|
2539
|
+
return "Download Local Model";
|
|
2540
|
+
}
|
|
2541
|
+
|
|
2542
|
+
function getLocalModelPopupMessage() {
|
|
2543
|
+
const locale = String(document.documentElement.lang || navigator.language || "ko").toLowerCase();
|
|
2544
|
+
const profile = getActiveLocalModelProfile();
|
|
2545
|
+
const sizeText = getLocalModelPopupSizeText(profile);
|
|
2546
|
+
const label = getLocalModelPopupProfileLabel(profile);
|
|
2547
|
+
const modelName = getLocalModelPopupModelName(profile);
|
|
2548
|
+
const summary = modelName ? `${modelName} / ${sizeText}` : sizeText;
|
|
2549
|
+
if (locale.startsWith("ko")) {
|
|
2550
|
+
return `${label} \ub85c\uceec\ubaa8\ub378(${summary})\uc744 \ub2e4\uc6b4\ub85c\ub4dc\ud55c \ub4a4 \uc0ac\uc6a9\ud560 \uc218 \uc788\uc5b4\uc694. \uacc4\uc18d\ud560\uae4c\uc694?`;
|
|
2551
|
+
}
|
|
2552
|
+
return `Download ${label} local model (${summary}) to use local mode. Continue?`;
|
|
2553
|
+
}
|
|
2554
|
+
|
|
2555
|
+
function handleLocalToggle() {
|
|
2556
|
+
const downloadedForCurrentTier = localStorage.getItem(getLocalDownloadStorageKey()) === "true";
|
|
2557
|
+
const willActivate = !(downloadedForCurrentTier && isLocalActive);
|
|
2558
|
+
ensureDefaultLocalModelTierForActivation(willActivate);
|
|
2559
|
+
applyLocalModelProfileToConfig();
|
|
2560
|
+
isModelDownloaded = localStorage.getItem(getLocalDownloadStorageKey()) === "true";
|
|
2561
|
+
|
|
2562
|
+
if (isModelDownloaded) {
|
|
2563
|
+
isLocalActive = !isLocalActive;
|
|
2564
|
+
if (!isLocalActive) {
|
|
2565
|
+
stopGeneration();
|
|
2566
|
+
terminateLocalRuntime();
|
|
2567
|
+
isModelLoaded = false;
|
|
2568
|
+
setLocalRuntimeState(null);
|
|
2569
|
+
updateLocalBtnState();
|
|
2570
|
+
renderLocalModelTierSelector();
|
|
2571
|
+
syncLocalModelTierVisibility();
|
|
2572
|
+
} else if (isModelLoaded) {
|
|
2573
|
+
updateLocalBtnState();
|
|
2574
|
+
renderLocalModelTierSelector();
|
|
2575
|
+
syncLocalModelTierVisibility();
|
|
2576
|
+
} else {
|
|
2577
|
+
startDownload();
|
|
2578
|
+
}
|
|
2579
|
+
} else {
|
|
2580
|
+
isLocalActive = true;
|
|
2581
|
+
syncLocalModelTierVisibility();
|
|
2582
|
+
startDownload();
|
|
2583
|
+
}
|
|
2584
|
+
}
|
|
2585
|
+
|
|
2586
|
+
function setLocalRuntimeState(runtimeName) {
|
|
2587
|
+
localRuntime = runtimeName === "wllama" ? "wasm" : (runtimeName || null);
|
|
2588
|
+
window.ISAI_LOCAL_RUNTIME = localRuntime;
|
|
2589
|
+
if (localRuntime) {
|
|
2590
|
+
localStorage.setItem(getLocalRuntimeStorageKey(), localRuntime);
|
|
2591
|
+
} else {
|
|
2592
|
+
localStorage.removeItem(getLocalRuntimeStorageKey());
|
|
2593
|
+
}
|
|
2594
|
+
}
|
|
2595
|
+
|
|
2596
|
+
function updateLocalProgress(bar, progress) {
|
|
2597
|
+
if (!bar) return;
|
|
2598
|
+
const numeric = Number(progress);
|
|
2599
|
+
const safeProgress = Number.isFinite(numeric) ? numeric : 0;
|
|
2600
|
+
const normalized = safeProgress > 1 ? safeProgress : safeProgress * 100;
|
|
2601
|
+
const percent = Math.max(0, Math.min(100, Math.round(normalized)));
|
|
2602
|
+
bar.style.width = percent + "%";
|
|
2603
|
+
}
|
|
2604
|
+
|
|
2605
|
+
function mapPromptMessagesForLocalEngine(promptArr) {
|
|
2606
|
+
if (!Array.isArray(promptArr)) return [];
|
|
2607
|
+
return promptArr
|
|
2608
|
+
.map((message) => ({
|
|
2609
|
+
role: message && typeof message.role === "string" ? message.role : "user",
|
|
2610
|
+
content: String(message && message.content != null ? message.content : "")
|
|
2611
|
+
}))
|
|
2612
|
+
.filter((message) => {
|
|
2613
|
+
if (message.content.trim().length <= 0) return false;
|
|
2614
|
+
if (message.role === "assistant" && isIgnorableAssistantGreeting(message.content)) return false;
|
|
2615
|
+
return true;
|
|
2616
|
+
});
|
|
2617
|
+
}
|
|
2618
|
+
|
|
2619
|
+
function normalizeGreetingForCompare(text) {
|
|
2620
|
+
return String(text || "")
|
|
2621
|
+
.toLowerCase()
|
|
2622
|
+
.replace(/[\u{1F300}-\u{1FAFF}\u{2600}-\u{27BF}]/gu, "")
|
|
2623
|
+
.replace(/[.!?~"'\u2019\u201D\u3002\uFF01\uFF1F]+/g, "")
|
|
2624
|
+
.replace(/\s+/g, " ")
|
|
2625
|
+
.trim();
|
|
2626
|
+
}
|
|
2627
|
+
|
|
2628
|
+
function getWelcomeGreetingCandidates() {
|
|
2629
|
+
const candidates = [];
|
|
2630
|
+
const push = (value) => {
|
|
2631
|
+
const text = String(value || "").trim();
|
|
2632
|
+
if (!text) return;
|
|
2633
|
+
candidates.push(text);
|
|
2634
|
+
};
|
|
2635
|
+
const serverI18n = window.ISAI_SERVER_I18N || {};
|
|
2636
|
+
push(serverI18n.welcomeMessage);
|
|
2637
|
+
if (serverI18n.welcomeMessages && typeof serverI18n.welcomeMessages === "object") {
|
|
2638
|
+
Object.keys(serverI18n.welcomeMessages).forEach((key) => push(serverI18n.welcomeMessages[key]));
|
|
2639
|
+
}
|
|
2640
|
+
push("?덈뀞?섏꽭?? 臾댁뾿???꾩??쒕┫源뚯슂? ?삃");
|
|
2641
|
+
push("?덈뀞?섏꽭?? 臾댁뾿???꾩??쒕┫源뚯슂?");
|
|
2642
|
+
push("濡쒖뺄濡????덉쟾?섍쾶 ??뷀븯?몄슂");
|
|
2643
|
+
push("Hello! How can I help you? ?삃");
|
|
2644
|
+
push("Hello! How can I help you?");
|
|
2645
|
+
push("Chat more safely in local mode");
|
|
2646
|
+
push("Opening chat...");
|
|
2647
|
+
return Array.from(new Set(candidates.map(normalizeGreetingForCompare).filter(Boolean)));
|
|
2648
|
+
}
|
|
2649
|
+
|
|
2650
|
+
function getLocalWelcomeGreetingCandidates() {
|
|
2651
|
+
const candidates = [];
|
|
2652
|
+
const push = (value) => {
|
|
2653
|
+
const text = String(value || "").trim();
|
|
2654
|
+
if (!text) return;
|
|
2655
|
+
candidates.push(normalizeGreetingForCompare(text));
|
|
2656
|
+
};
|
|
2657
|
+
const serverI18n = window.ISAI_SERVER_I18N || {};
|
|
2658
|
+
push(serverI18n.welcomeMessage);
|
|
2659
|
+
if (serverI18n.welcomeMessages && typeof serverI18n.welcomeMessages === "object") {
|
|
2660
|
+
Object.keys(serverI18n.welcomeMessages).forEach((key) => push(serverI18n.welcomeMessages[key]));
|
|
2661
|
+
}
|
|
2662
|
+
push("濡쒖뺄濡????덉쟾?섍쾶 ??뷀븯?몄슂");
|
|
2663
|
+
push("Chat more safely in local mode");
|
|
2664
|
+
return Array.from(new Set(candidates.filter(Boolean)));
|
|
2665
|
+
}
|
|
2666
|
+
|
|
2667
|
+
function isLocalWelcomeGreeting(text) {
|
|
2668
|
+
const normalized = normalizeGreetingForCompare(text);
|
|
2669
|
+
if (!normalized) return false;
|
|
2670
|
+
return getLocalWelcomeGreetingCandidates().some((candidate) => (
|
|
2671
|
+
candidate === normalized ||
|
|
2672
|
+
normalized.includes(candidate) ||
|
|
2673
|
+
candidate.includes(normalized)
|
|
2674
|
+
));
|
|
2675
|
+
}
|
|
2676
|
+
|
|
2677
|
+
function decorateLocalWelcomeBubbleIfNeeded(bubble, contentText) {
|
|
2678
|
+
if (!bubble) return;
|
|
2679
|
+
const decodeEscaped = (value) => String(value || "")
|
|
2680
|
+
.replace(/\\u([0-9a-fA-F]{4})/g, (_, hex) => String.fromCharCode(parseInt(hex, 16)))
|
|
2681
|
+
.replace(/\\x([0-9a-fA-F]{2})/g, (_, hex) => String.fromCharCode(parseInt(hex, 16)));
|
|
2682
|
+
const rawText = decodeEscaped(contentText ?? bubble.textContent ?? "").trim();
|
|
2683
|
+
if (!rawText || !isLocalWelcomeGreeting(rawText)) return;
|
|
2684
|
+
const ctaUrl = "https://spirit-browser.isai.kr/";
|
|
2685
|
+
bubble.classList.add("chat-welcome-cta");
|
|
2686
|
+
bubble.style.display = "inline-flex";
|
|
2687
|
+
bubble.style.alignItems = "center";
|
|
2688
|
+
bubble.style.gap = "6px";
|
|
2689
|
+
bubble.style.cursor = "pointer";
|
|
2690
|
+
bubble.setAttribute("role", "button");
|
|
2691
|
+
bubble.setAttribute("tabindex", "0");
|
|
2692
|
+
bubble.setAttribute("title", "Spirit Browser");
|
|
2693
|
+
bubble.innerHTML = `<span class="chat-welcome-cta-inner" style="display:inline-flex;align-items:center;gap:6px;"><i class="ri-ghost-4-line chat-welcome-cta-icon" aria-hidden="true" style="font-size:15px;line-height:1;display:inline-flex;align-items:center;justify-content:center;flex:0 0 auto;"></i><span class="chat-welcome-cta-text">${escapeHtmlForBubbleLocal(rawText).replace(/\n/g, "<br>")}</span></span>`;
|
|
2694
|
+
if (bubble.dataset.welcomeCtaBound !== "1") {
|
|
2695
|
+
const openCta = (event) => {
|
|
2696
|
+
event.preventDefault();
|
|
2697
|
+
event.stopPropagation();
|
|
2698
|
+
window.open(ctaUrl, "_blank", "noopener");
|
|
2699
|
+
};
|
|
2700
|
+
bubble.addEventListener("click", openCta);
|
|
2701
|
+
bubble.addEventListener("keydown", (event) => {
|
|
2702
|
+
if (event.key !== "Enter" && event.key !== " ") return;
|
|
2703
|
+
openCta(event);
|
|
2704
|
+
});
|
|
2705
|
+
bubble.dataset.welcomeCtaBound = "1";
|
|
2706
|
+
}
|
|
2707
|
+
}
|
|
2708
|
+
|
|
2709
|
+
function isIgnorableAssistantGreeting(text) {
|
|
2710
|
+
const normalized = normalizeGreetingForCompare(text);
|
|
2711
|
+
if (!normalized) return false;
|
|
2712
|
+
const candidates = getWelcomeGreetingCandidates();
|
|
2713
|
+
return candidates.includes(normalized);
|
|
2714
|
+
}
|
|
2715
|
+
|
|
2716
|
+
function getLocalWasmThreadCount() {
|
|
2717
|
+
const configured = Number(window.MODEL_CONFIG && window.MODEL_CONFIG.localWasmThreads);
|
|
2718
|
+
if (Number.isFinite(configured) && configured > 0) return Math.max(1, Math.floor(configured));
|
|
2719
|
+
const canThread = typeof SharedArrayBuffer !== "undefined" && !!window.crossOriginIsolated;
|
|
2720
|
+
if (!canThread) return 1;
|
|
2721
|
+
const cores = Number(navigator.hardwareConcurrency || 4);
|
|
2722
|
+
return Math.max(2, Math.min(6, Math.floor(cores > 2 ? cores - 1 : cores)));
|
|
2723
|
+
}
|
|
2724
|
+
|
|
2725
|
+
function getLocalWasmSpeedPreset() {
|
|
2726
|
+
const modelConfig = window.MODEL_CONFIG || {};
|
|
2727
|
+
const presets = modelConfig.speedPresets || window.__ISAI_LOCAL_SPEED_PRESETS || {};
|
|
2728
|
+
const tier = getActiveLocalModelTier();
|
|
2729
|
+
if (presets && presets[tier]) return presets[tier];
|
|
2730
|
+
if (presets && presets.light) return presets.light;
|
|
2731
|
+
return null;
|
|
2732
|
+
}
|
|
2733
|
+
|
|
2734
|
+
function getLocalWasmLoadOptions(container, bar) {
|
|
2735
|
+
const preset = getLocalWasmSpeedPreset();
|
|
2736
|
+
const configuredCtx = Number(window.MODEL_CONFIG && window.MODEL_CONFIG.localWasmContextSize);
|
|
2737
|
+
const presetCtx = Number(preset && preset.maxSeqLen);
|
|
2738
|
+
const threadCount = getLocalWasmThreadCount();
|
|
2739
|
+
const policylessSingle = threadCount <= 1;
|
|
2740
|
+
const nCtx = Number.isFinite(configuredCtx) && configuredCtx > 0
|
|
2741
|
+
? Math.floor(configuredCtx)
|
|
2742
|
+
: Math.min(Number.isFinite(presetCtx) && presetCtx > 0 ? presetCtx : 1024, 1024);
|
|
2743
|
+
const options = {
|
|
2744
|
+
n_threads: threadCount,
|
|
2745
|
+
n_ctx: nCtx,
|
|
2746
|
+
progressCallback: ({ loaded, total }) => {
|
|
2747
|
+
if (container) container.classList.remove("hidden");
|
|
2748
|
+
if (total) {
|
|
2749
|
+
updateLocalProgress(bar, loaded / total);
|
|
2750
|
+
}
|
|
2751
|
+
}
|
|
2752
|
+
};
|
|
2753
|
+
if (policylessSingle) {
|
|
2754
|
+
options.n_batch = 128;
|
|
2755
|
+
options.flash_attn = true;
|
|
2756
|
+
}
|
|
2757
|
+
window.__ISAI_LOCAL_WASM_THREADS__ = threadCount;
|
|
2758
|
+
return options;
|
|
2759
|
+
}
|
|
2760
|
+
|
|
2761
|
+
function getLocalWasmSamplingOptions() {
|
|
2762
|
+
const preset = getLocalWasmSpeedPreset();
|
|
2763
|
+
if (preset && preset.sampling && typeof preset.sampling === "object") {
|
|
2764
|
+
return { ...preset.sampling };
|
|
2765
|
+
}
|
|
2766
|
+
return { temp: 0.7, top_k: 40, top_p: 0.9 };
|
|
2767
|
+
}
|
|
2768
|
+
|
|
2769
|
+
async function loadLocalWasmEngine(container, bar) {
|
|
2770
|
+
const modelConfig = window.MODEL_CONFIG || {};
|
|
2771
|
+
const fallbackConfig = modelConfig.fallback || {};
|
|
2772
|
+
const { LlamaEngine, LoggerWithoutDebug } = window.LocalWasmLlamaObj || {};
|
|
2773
|
+
if (!fallbackConfig.wasmPaths) {
|
|
2774
|
+
fallbackConfig.wasmPaths = resolveLocalWasmPaths();
|
|
2775
|
+
modelConfig.fallback = fallbackConfig;
|
|
2776
|
+
window.MODEL_CONFIG = modelConfig;
|
|
2777
|
+
}
|
|
2778
|
+
if (!LlamaEngine) {
|
|
2779
|
+
throw new Error("Local WASM runtime is not available.");
|
|
2780
|
+
}
|
|
2781
|
+
if (!fallbackConfig.url || !fallbackConfig.wasmPaths) {
|
|
2782
|
+
throw new Error("Local model configuration is missing.");
|
|
2783
|
+
}
|
|
2784
|
+
|
|
2785
|
+
if (localWasmEngine && isModelLoaded) {
|
|
2786
|
+
return localWasmEngine;
|
|
2787
|
+
}
|
|
2788
|
+
|
|
2789
|
+
if (!localWasmEngine) {
|
|
2790
|
+
localWasmEngine = new LlamaEngine(fallbackConfig.wasmPaths, {
|
|
2791
|
+
logger: LoggerWithoutDebug,
|
|
2792
|
+
allowOffline: true,
|
|
2793
|
+
parallelDownloads: 4
|
|
2794
|
+
});
|
|
2795
|
+
}
|
|
2796
|
+
|
|
2797
|
+
await localWasmEngine.loadModelFromUrl(fallbackConfig.url, getLocalWasmLoadOptions(container, bar));
|
|
2798
|
+
|
|
2799
|
+
setLocalRuntimeState("wasm");
|
|
2800
|
+
return localWasmEngine;
|
|
2801
|
+
}
|
|
2802
|
+
|
|
2803
|
+
async function startDownload() {
|
|
2804
|
+
const container = document.getElementById("progress-container");
|
|
2805
|
+
const bar = document.getElementById("progress-bar");
|
|
2806
|
+
applyLocalModelProfileToConfig();
|
|
2807
|
+
window.__ISAI_LOCAL_RUNTIME_STOPPING__ = false;
|
|
2808
|
+
|
|
2809
|
+
try {
|
|
2810
|
+
container.classList.remove("hidden");
|
|
2811
|
+
updateLocalProgress(bar, 0);
|
|
2812
|
+
await loadLocalWasmEngine(container, bar);
|
|
2813
|
+
|
|
2814
|
+
isModelDownloaded = true;
|
|
2815
|
+
isModelLoaded = true;
|
|
2816
|
+
isLocalActive = true;
|
|
2817
|
+
localStorage.setItem(getLocalDownloadStorageKey(), "true");
|
|
2818
|
+
container.classList.add("hidden");
|
|
2819
|
+
updateLocalBtnState();
|
|
2820
|
+
|
|
2821
|
+
} catch (error) {
|
|
2822
|
+
if (error.message && error.message.includes("initialized")) {
|
|
2823
|
+
isModelDownloaded = true;
|
|
2824
|
+
isModelLoaded = true;
|
|
2825
|
+
isLocalActive = true;
|
|
2826
|
+
localStorage.setItem(getLocalDownloadStorageKey(), "true");
|
|
2827
|
+
container.classList.add("hidden");
|
|
2828
|
+
updateLocalBtnState();
|
|
2829
|
+
return;
|
|
2830
|
+
}
|
|
2831
|
+
|
|
2832
|
+
container.classList.add("hidden");
|
|
2833
|
+
alert(error.message);
|
|
2834
|
+
isModelDownloaded = false;
|
|
2835
|
+
isModelLoaded = false;
|
|
2836
|
+
setLocalRuntimeState(null);
|
|
2837
|
+
localStorage.removeItem(getLocalDownloadStorageKey());
|
|
2838
|
+
}
|
|
2839
|
+
}
|
|
2840
|
+
|
|
2841
|
+
async function runLocalInference(promptArr, callback, generationToken = null) {
|
|
2842
|
+
if (isLocalActive && !isModelLoaded) {
|
|
2843
|
+
await startDownload();
|
|
2844
|
+
}
|
|
2845
|
+
const runToken = Number(generationToken) > 0 ? Number(generationToken) : Number(activeGenerationToken);
|
|
2846
|
+
const localRunId = nextLocalInferenceRunId();
|
|
2847
|
+
const localAbortController = abortController;
|
|
2848
|
+
const isCurrentLocalRun = () => (
|
|
2849
|
+
!stopSignal &&
|
|
2850
|
+
runToken === Number(activeGenerationToken) &&
|
|
2851
|
+
localRunId === Number(activeLocalInferenceRunId) &&
|
|
2852
|
+
!(localAbortController && localAbortController.signal && localAbortController.signal.aborted)
|
|
2853
|
+
);
|
|
2854
|
+
const messages = mapPromptMessagesForLocalEngine(promptArr);
|
|
2855
|
+
if (!messages.length) return;
|
|
2856
|
+
if (!localWasmEngine || !isCurrentLocalRun()) return;
|
|
2857
|
+
const runtime = localWasmEngine;
|
|
2858
|
+
const formatted = await runtime.formatChat(messages, true);
|
|
2859
|
+
if (!isCurrentLocalRun()) return;
|
|
2860
|
+
const pieceDecoder = new TextDecoder();
|
|
2861
|
+
let streamedText = "";
|
|
2862
|
+
let resultText = "";
|
|
2863
|
+
try {
|
|
2864
|
+
const generationOptions = {
|
|
2865
|
+
sampling: getLocalWasmSamplingOptions(),
|
|
2866
|
+
useCache: true,
|
|
2867
|
+
signal: localAbortController ? localAbortController.signal : undefined,
|
|
2868
|
+
abortSignal: localAbortController ? localAbortController.signal : undefined,
|
|
2869
|
+
onNewToken: (token, piece, currentText) => {
|
|
2870
|
+
if (!isCurrentLocalRun()) return false;
|
|
2871
|
+
if (piece instanceof Uint8Array) {
|
|
2872
|
+
streamedText += pieceDecoder.decode(piece);
|
|
2873
|
+
} else if (piece) {
|
|
2874
|
+
streamedText += String(piece);
|
|
2875
|
+
} else {
|
|
2876
|
+
streamedText = String(currentText || streamedText);
|
|
2877
|
+
}
|
|
2878
|
+
callback(streamedText, { replace: true });
|
|
2879
|
+
return true;
|
|
2880
|
+
}
|
|
2881
|
+
};
|
|
2882
|
+
resultText = await runtime.createCompletion(formatted, generationOptions);
|
|
2883
|
+
} catch (error) {
|
|
2884
|
+
const abortLike = error && (error.name === "AbortError" || /abort|cancel|interrupt|terminate/i.test(String(error.message || "")));
|
|
2885
|
+
if (abortLike || !isCurrentLocalRun()) return;
|
|
2886
|
+
throw error;
|
|
2887
|
+
}
|
|
2888
|
+
if (!isCurrentLocalRun()) return;
|
|
2889
|
+
callback(String(resultText || streamedText || ""), { replace: true });
|
|
2890
|
+
}
|
|
2891
|
+
|
|
2892
|
+
function showLoader(show) {
|
|
2893
|
+
const overlay = document.getElementById("loader-overlay");
|
|
2894
|
+
if (!overlay) return;
|
|
2895
|
+
|
|
2896
|
+
const effectiveMode = String(
|
|
2897
|
+
currentMode
|
|
2898
|
+
|| (document.body && document.body.getAttribute("data-ui-mode"))
|
|
2899
|
+
|| "chat"
|
|
2900
|
+
).toLowerCase();
|
|
2901
|
+
const suppressBlockingOverlayModes = new Set(["chat", "search", "code", "blog", "voice", "translate"]);
|
|
2902
|
+
const shouldSuppressOverlay = suppressBlockingOverlayModes.has(effectiveMode);
|
|
2903
|
+
|
|
2904
|
+
if (shouldSuppressOverlay) {
|
|
2905
|
+
overlay.classList.remove("active");
|
|
2906
|
+
return;
|
|
2907
|
+
}
|
|
2908
|
+
|
|
2909
|
+
if (show) {
|
|
2910
|
+
overlay.classList.add("active");
|
|
2911
|
+
} else {
|
|
2912
|
+
overlay.classList.remove("active");
|
|
2913
|
+
}
|
|
2914
|
+
}
|
|
2915
|
+
|
|
2916
|
+
function stopGeneration() {
|
|
2917
|
+
beginGenerationToken();
|
|
2918
|
+
nextLocalInferenceRunId();
|
|
2919
|
+
if (abortController) {
|
|
2920
|
+
abortController.abort();
|
|
2921
|
+
abortController = null;
|
|
2922
|
+
}
|
|
2923
|
+
stopSignal = true;
|
|
2924
|
+
terminateLocalRuntime({ reset: true });
|
|
2925
|
+
document.querySelectorAll(".chat-bubble-waiting").forEach((node) => {
|
|
2926
|
+
if (node && node.parentElement) node.remove();
|
|
2927
|
+
});
|
|
2928
|
+
setMainGeneratingState(false);
|
|
2929
|
+
showLoader(false);
|
|
2930
|
+
if (typeof window.updateMainSubmitButtonState === "function") {
|
|
2931
|
+
window.updateMainSubmitButtonState();
|
|
2932
|
+
}
|
|
2933
|
+
}
|
|
2934
|
+
|
|
2935
|
+
window.stopGeneration = stopGeneration;
|
|
2936
|
+
window.runLocalInference = runLocalInference;
|
|
2937
|
+
window.fetchStoreApps = fetchStoreApps;
|
|
2938
|
+
window.loadAppDetails = loadAppDetails;
|
|
2939
|
+
window.activateAppMode = activateAppMode;
|
|
2940
|
+
window.exitAppMode = exitAppMode;
|
|
2941
|
+
|