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,1849 @@
|
|
|
1
|
+
๏ปฟ
|
|
2
|
+
let recognition = null;
|
|
3
|
+
let isVoiceProcessing = false;
|
|
4
|
+
let isVoiceListening = false;
|
|
5
|
+
let targetLangCode = "en-US";
|
|
6
|
+
let musicTokenizer = null;
|
|
7
|
+
let musicModel = null;
|
|
8
|
+
let currentMode = "chat";
|
|
9
|
+
let chatHistory =[];
|
|
10
|
+
let localWasmEngine = null;
|
|
11
|
+
let localRuntime = localStorage.getItem((window.MODEL_CONFIG && window.MODEL_CONFIG.runtimeKey) || "ISAI_LOCAL_MODEL_RUNTIME_V1") || null;
|
|
12
|
+
if (localRuntime === "wllama") localRuntime = "wasm";
|
|
13
|
+
const DEFAULT_LOCAL_MODEL_URL = "https://huggingface.co/Qwen/Qwen2.5-0.5B-Instruct-GGUF/resolve/main/qwen2.5-0.5b-instruct-q3_k_m.gguf?download=true";
|
|
14
|
+
const LOCAL_WASM_VERSION = String(window.LOCAL_WASM_VERSION || "2.3.7");
|
|
15
|
+
const LOCAL_WASM_BASE_URL = String(
|
|
16
|
+
window.LOCAL_WASM_BASE_URL
|
|
17
|
+
|| "/cdn/vendor/local-wasm/esm"
|
|
18
|
+
);
|
|
19
|
+
const DEFAULT_LOCAL_WASM_PATHS = (window.LOCAL_WASM_PATHS && typeof window.LOCAL_WASM_PATHS === "object")
|
|
20
|
+
? window.LOCAL_WASM_PATHS
|
|
21
|
+
: {
|
|
22
|
+
"single-thread/wllama.wasm": `${LOCAL_WASM_BASE_URL}/single-thread/local.wasm`,
|
|
23
|
+
"multi-thread/wllama.wasm": `${LOCAL_WASM_BASE_URL}/multi-thread/local.wasm`
|
|
24
|
+
};
|
|
25
|
+
const WAITING_DOTS_SVG_URL = "https://cdn.jsdelivr.net/gh/sllkx/icons@main/icon/3-dots-bounce.svg";
|
|
26
|
+
let isModelDownloaded = false;
|
|
27
|
+
let isModelLoaded = false;
|
|
28
|
+
let isLocalActive = false;
|
|
29
|
+
let isStarted = false;
|
|
30
|
+
let isGenerating = false;
|
|
31
|
+
let abortController = null;
|
|
32
|
+
let stopSignal = false;
|
|
33
|
+
let activeGenerationToken = Number(window.__ISAI_MAIN_GENERATION_TOKEN__ || 0) || 0;
|
|
34
|
+
let isMenuOpen = false;
|
|
35
|
+
let activeApp = null;
|
|
36
|
+
let searchTimeout = null;
|
|
37
|
+
let currentStorePage = 1;
|
|
38
|
+
let isStoreLoading = false;
|
|
39
|
+
let hasMoreStoreApps = true;
|
|
40
|
+
let currentStoreQuery = "";
|
|
41
|
+
let currentStoreCategory = "All";
|
|
42
|
+
|
|
43
|
+
function syncMainSubmitButtonVisualState(forceStopMode = null) {
|
|
44
|
+
const btn = document.getElementById("btn-submit");
|
|
45
|
+
const icon = document.getElementById("icon-submit");
|
|
46
|
+
if (!btn || !icon) return;
|
|
47
|
+
const stopMode = typeof forceStopMode === "boolean"
|
|
48
|
+
? forceStopMode
|
|
49
|
+
: !!(isGenerating || window.__ISAI_MAIN_GENERATING__);
|
|
50
|
+
|
|
51
|
+
btn.className = "input-action-btn";
|
|
52
|
+
btn.hidden = false;
|
|
53
|
+
btn.style.display = "inline-flex";
|
|
54
|
+
btn.style.visibility = "visible";
|
|
55
|
+
btn.style.opacity = "1";
|
|
56
|
+
btn.style.alignItems = "center";
|
|
57
|
+
btn.style.justifyContent = "center";
|
|
58
|
+
btn.style.pointerEvents = "auto";
|
|
59
|
+
btn.style.setProperty("width", "24px", "important");
|
|
60
|
+
btn.style.setProperty("height", "24px", "important");
|
|
61
|
+
btn.style.setProperty("min-width", "24px", "important");
|
|
62
|
+
btn.style.setProperty("min-height", "24px", "important");
|
|
63
|
+
btn.style.setProperty("padding", "0", "important");
|
|
64
|
+
btn.style.setProperty("border-radius", "9999px", "important");
|
|
65
|
+
btn.dataset.state = stopMode ? "stop" : "submit";
|
|
66
|
+
btn.setAttribute("aria-label", stopMode ? "Stop" : "Submit");
|
|
67
|
+
btn.setAttribute("title", stopMode ? "Stop" : "Submit");
|
|
68
|
+
btn.classList.remove("stop-mode", "is-generating");
|
|
69
|
+
btn.style.backgroundColor = "";
|
|
70
|
+
btn.style.color = "#ffffff";
|
|
71
|
+
btn.style.setProperty("color", "#ffffff", "important");
|
|
72
|
+
icon.className = stopMode ? "ri-square-fill text-[13px] text-white" : "ri-arrow-up-s-line text-[13px] text-white";
|
|
73
|
+
icon.style.color = "#ffffff";
|
|
74
|
+
icon.style.setProperty("color", "#ffffff", "important");
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
window.updateMainSubmitButtonState = syncMainSubmitButtonVisualState;
|
|
78
|
+
window.handleMainSubmitButton = function () {
|
|
79
|
+
if (isGenerating || window.__ISAI_MAIN_GENERATING__) {
|
|
80
|
+
stopGeneration();
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
executeAction("right");
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
function setMainGeneratingState(nextState) {
|
|
87
|
+
isGenerating = !!nextState;
|
|
88
|
+
window.__ISAI_MAIN_GENERATING__ = isGenerating;
|
|
89
|
+
syncMainSubmitButtonVisualState(isGenerating);
|
|
90
|
+
return isGenerating;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
window.__ISAI_MAIN_GENERATING__ = !!window.__ISAI_MAIN_GENERATING__;
|
|
94
|
+
window.__ISAI_MAIN_GENERATION_TOKEN__ = activeGenerationToken;
|
|
95
|
+
|
|
96
|
+
function beginGenerationToken() {
|
|
97
|
+
activeGenerationToken += 1;
|
|
98
|
+
window.__ISAI_MAIN_GENERATION_TOKEN__ = activeGenerationToken;
|
|
99
|
+
return activeGenerationToken;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function isGenerationTokenCurrent(token) {
|
|
103
|
+
return Number(token) > 0 && Number(token) === Number(activeGenerationToken);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function hardResetConversationState() {
|
|
107
|
+
beginGenerationToken();
|
|
108
|
+
if (abortController) {
|
|
109
|
+
try { abortController.abort(); } catch (error) {}
|
|
110
|
+
abortController = null;
|
|
111
|
+
}
|
|
112
|
+
stopSignal = true;
|
|
113
|
+
chatHistory = [];
|
|
114
|
+
window.__ISAI_CHAT_HISTORY_RESET_AT__ = Date.now();
|
|
115
|
+
if (Array.isArray(window.extractedCodes)) window.extractedCodes = [];
|
|
116
|
+
if (typeof clearContinueWriteContext === "function") clearContinueWriteContext();
|
|
117
|
+
setMainGeneratingState(false);
|
|
118
|
+
showLoader(false);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
window.__isaiHardResetConversationState = hardResetConversationState;
|
|
122
|
+
|
|
123
|
+
function normalizeChatSlashLineBreaks(value) {
|
|
124
|
+
return String(value ?? "").trim();
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function normalizeAssistantSlashLineBreaks(value) {
|
|
128
|
+
return String(value ?? "");
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
let currentAppPage = 1;
|
|
132
|
+
let isAppLoading = false;
|
|
133
|
+
let hasMoreApps = true;
|
|
134
|
+
let currentAppQuery = "";
|
|
135
|
+
let defaultChatProfileSrc = "";
|
|
136
|
+
const CONTINUE_ICON_MIN_CHARS = Math.max(120, Math.min(4000, Number(window.ISAI_CONTINUE_ICON_MIN_CHARS || 900) || 900));
|
|
137
|
+
let continueBubbleSeq = 0;
|
|
138
|
+
|
|
139
|
+
const STORE_LIMIT = 12;
|
|
140
|
+
const APP_LIMIT = 24;
|
|
141
|
+
const URL_IMAGE_DAILY_COOKIE = "ISAI_URL_IMAGE_DAILY";
|
|
142
|
+
let continueWriteContext = null;
|
|
143
|
+
|
|
144
|
+
function decodeEscapedUnicodeText(value) {
|
|
145
|
+
let text = String(value ?? "");
|
|
146
|
+
for (let i = 0; i < 3; i++) {
|
|
147
|
+
text = text
|
|
148
|
+
.replace(/\\\\u([0-9a-fA-F]{4})/g, (_, hex) => String.fromCharCode(parseInt(hex, 16)))
|
|
149
|
+
.replace(/\\u([0-9a-fA-F]{4})/g, (_, hex) => String.fromCharCode(parseInt(hex, 16)))
|
|
150
|
+
.replace(/\\\\x([0-9a-fA-F]{2})/g, (_, hex) => String.fromCharCode(parseInt(hex, 16)))
|
|
151
|
+
.replace(/\\x([0-9a-fA-F]{2})/g, (_, hex) => String.fromCharCode(parseInt(hex, 16)));
|
|
152
|
+
}
|
|
153
|
+
return text;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function normalizeAppDisplayText(value, fallback = "") {
|
|
157
|
+
const normalized = decodeEscapedUnicodeText(value).replace(/\s+/g, " ").trim();
|
|
158
|
+
if (normalized) return normalized;
|
|
159
|
+
return String(fallback ?? "").trim();
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function normalizeAppPayload(raw) {
|
|
163
|
+
if (!raw || typeof raw !== "object") return raw;
|
|
164
|
+
const next = { ...raw };
|
|
165
|
+
const title = normalizeAppDisplayText(next.title, next.name || "App");
|
|
166
|
+
if (title) next.title = title;
|
|
167
|
+
const name = normalizeAppDisplayText(next.name, title || "App");
|
|
168
|
+
if (name) next.name = name;
|
|
169
|
+
next.first_message = decodeEscapedUnicodeText(next.first_message || "");
|
|
170
|
+
return next;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function getIsaiApiMaxChars() {
|
|
174
|
+
const raw = Number(window.ISAI_API_MAX_CHARS || 700);
|
|
175
|
+
if (!Number.isFinite(raw)) return 700;
|
|
176
|
+
return Math.max(120, Math.min(4000, Math.floor(raw)));
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function getContinueWriteButton() {
|
|
180
|
+
return document.getElementById("btn-continue-writing");
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function setContinueWriteButtonVisible(visible) {
|
|
184
|
+
const button = getContinueWriteButton();
|
|
185
|
+
if (!button) return;
|
|
186
|
+
button.classList.add("hidden");
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function clearContinueWriteContext() {
|
|
190
|
+
continueWriteContext = null;
|
|
191
|
+
setContinueWriteButtonVisible(false);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function setContinueWriteContext(payload) {
|
|
195
|
+
continueWriteContext = payload && typeof payload === "object" ? payload : null;
|
|
196
|
+
setContinueWriteButtonVisible(!!continueWriteContext);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function getContinueWriteLocale() {
|
|
200
|
+
if (typeof LANG !== "undefined" && LANG) return String(LANG).toLowerCase();
|
|
201
|
+
const fallback = String(document.documentElement.lang || navigator.language || "en").toLowerCase();
|
|
202
|
+
return fallback.split("-")[0] || "en";
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function buildContinueWritePrompt(context) {
|
|
206
|
+
const tail = String((context && context.lastAssistant) || "").trim();
|
|
207
|
+
const tailSnippet = tail.length > 1200 ? tail.slice(-1200) : tail;
|
|
208
|
+
const locale = getContinueWriteLocale();
|
|
209
|
+
const baseLead = "The previous answer was cut off. Continue naturally from the end of the section below without repeating, and keep the same tone and format.";
|
|
210
|
+
const promptMap = {
|
|
211
|
+
ko: baseLead,
|
|
212
|
+
ja: baseLead,
|
|
213
|
+
es: baseLead,
|
|
214
|
+
hi: baseLead,
|
|
215
|
+
pt: baseLead,
|
|
216
|
+
zh: baseLead,
|
|
217
|
+
en: "Your previous answer was cut off. Continue naturally from the end of the section below, without repeating, in the same tone and format."
|
|
218
|
+
};
|
|
219
|
+
const lead = promptMap[locale] || promptMap.en;
|
|
220
|
+
return tailSnippet ? `${lead}\n\n[Last part]\n${tailSnippet}` : lead;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function shouldShowContinueIconForApiText(text, isTruncated) {
|
|
224
|
+
if (isTruncated === true) return true;
|
|
225
|
+
const len = String(text || "").trim().length;
|
|
226
|
+
return len >= CONTINUE_ICON_MIN_CHARS;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function buildTruncatedTailNote() {
|
|
230
|
+
const locale = String(
|
|
231
|
+
(typeof LANG !== "undefined" && LANG) ||
|
|
232
|
+
document.documentElement.lang ||
|
|
233
|
+
navigator.language ||
|
|
234
|
+
"en"
|
|
235
|
+
).toLowerCase();
|
|
236
|
+
|
|
237
|
+
if (locale.startsWith("ko")) {
|
|
238
|
+
return "... (์๋ต์ด ๊ธธ์ด ์๋ ธ์ต๋๋ค. ์๋ ์ด์ด์ฐ๊ธฐ ์์ด์ฝ์ ๋๋ฌ ๊ณ์ ์ด์ด๋ณด์ธ์.)";
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
return "... (Response was truncated here. Use the continue icon below to keep going.)";
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function normalizeApiResponseForBubble(rawText, options = {}) {
|
|
245
|
+
const source = String(rawText || "");
|
|
246
|
+
const trimmed = source.trimEnd();
|
|
247
|
+
const isCodeMode = !!options.isCodeMode;
|
|
248
|
+
const isTruncated = !!options.isTruncated;
|
|
249
|
+
|
|
250
|
+
let result = trimmed;
|
|
251
|
+
|
|
252
|
+
if (isCodeMode) {
|
|
253
|
+
const fenceCount = (result.match(/```/g) || []).length;
|
|
254
|
+
|
|
255
|
+
if (fenceCount % 2 === 1) {
|
|
256
|
+
result += "\n```";
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
if (isTruncated) {
|
|
261
|
+
const tailNote = buildTruncatedTailNote();
|
|
262
|
+
|
|
263
|
+
if (!result.includes(tailNote)) {
|
|
264
|
+
result += `\n\n${tailNote}`;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
return trimRepeatedDisplayText(result);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function stripTruncatedTailNote(text) {
|
|
272
|
+
const koTail =
|
|
273
|
+
"... (์๋ต์ด ๊ธธ์ด ์๋ ธ์ต๋๋ค. ์๋ ์ด์ด์ฐ๊ธฐ ์์ด์ฝ์ ๋๋ฌ ๊ณ์ ์ด์ด๋ณด์ธ์.)";
|
|
274
|
+
|
|
275
|
+
const enTail =
|
|
276
|
+
"... (Response was truncated here. Use the continue icon below to keep going.)";
|
|
277
|
+
|
|
278
|
+
return String(text || "")
|
|
279
|
+
.replace(koTail, "")
|
|
280
|
+
.replace(enTail, "")
|
|
281
|
+
.trim();
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function ensureContinueBubbleId(bubble) {
|
|
285
|
+
if (!bubble) return "";
|
|
286
|
+
if (bubble.dataset && bubble.dataset.continueBubbleId) return bubble.dataset.continueBubbleId;
|
|
287
|
+
continueBubbleSeq += 1;
|
|
288
|
+
const id = `continue-bubble-${Date.now()}-${continueBubbleSeq}`;
|
|
289
|
+
if (bubble.dataset) bubble.dataset.continueBubbleId = id;
|
|
290
|
+
return id;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
function removeContinueIconFromBubble(bubble) {
|
|
294
|
+
if (!bubble) return;
|
|
295
|
+
const node = bubble.querySelector(".bubble-continue-inline");
|
|
296
|
+
if (node) node.remove();
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
function runContinueFromContext(context, opts = {}) {
|
|
300
|
+
if (!context || isGenerating) return;
|
|
301
|
+
const previousContinueContext = context && typeof context === "object" ? { ...context } : null;
|
|
302
|
+
const prompt = buildContinueWritePrompt(previousContinueContext);
|
|
303
|
+
const keepGlobal = !!(opts && opts.keepGlobal);
|
|
304
|
+
if (!keepGlobal) clearContinueWriteContext();
|
|
305
|
+
executeAction("right", {
|
|
306
|
+
overrideText: prompt,
|
|
307
|
+
silentUserBubble: true,
|
|
308
|
+
forceGenerate: true,
|
|
309
|
+
isContinuationRequest: true,
|
|
310
|
+
previousContinueContext
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
function appendContinueIconInsideBubble(bubble, context) {
|
|
315
|
+
if (!bubble || !context || typeof context !== "object") return;
|
|
316
|
+
removeContinueIconFromBubble(bubble);
|
|
317
|
+
const bubbleId = ensureContinueBubbleId(bubble);
|
|
318
|
+
|
|
319
|
+
const btn = document.createElement("button");
|
|
320
|
+
btn.type = "button";
|
|
321
|
+
btn.className = "input-action-btn bubble-continue-inline";
|
|
322
|
+
btn.setAttribute("aria-label", "Continue Writing");
|
|
323
|
+
btn.setAttribute("title", "Continue Writing");
|
|
324
|
+
btn.innerHTML = '<i class="ri-quill-pen-line text-[14px]"></i>';
|
|
325
|
+
btn.style.width = "28px";
|
|
326
|
+
btn.style.height = "28px";
|
|
327
|
+
btn.style.borderRadius = "9999px";
|
|
328
|
+
btn.style.border = "none";
|
|
329
|
+
btn.style.background = "rgba(25, 25, 28, 0.72)";
|
|
330
|
+
btn.style.color = "#f5d37a";
|
|
331
|
+
btn.style.display = "inline-flex";
|
|
332
|
+
btn.style.alignItems = "center";
|
|
333
|
+
btn.style.justifyContent = "center";
|
|
334
|
+
btn.style.backdropFilter = "blur(10px)";
|
|
335
|
+
btn.style.webkitBackdropFilter = "blur(10px)";
|
|
336
|
+
btn.style.boxShadow = "0 4px 12px rgba(0,0,0,0.2)";
|
|
337
|
+
btn.style.cursor = "pointer";
|
|
338
|
+
btn.style.marginTop = "8px";
|
|
339
|
+
btn.style.marginLeft = "auto";
|
|
340
|
+
btn.addEventListener("click", (event) => {
|
|
341
|
+
event.preventDefault();
|
|
342
|
+
event.stopPropagation();
|
|
343
|
+
runContinueFromContext({
|
|
344
|
+
...context,
|
|
345
|
+
bubbleId
|
|
346
|
+
}, { keepGlobal: false });
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
const row = document.createElement("div");
|
|
350
|
+
row.className = "bubble-continue-row";
|
|
351
|
+
row.style.display = "flex";
|
|
352
|
+
row.style.justifyContent = "flex-end";
|
|
353
|
+
row.appendChild(btn);
|
|
354
|
+
bubble.appendChild(row);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
window.continueTruncatedAnswer = function() {
|
|
358
|
+
return;
|
|
359
|
+
};
|
|
360
|
+
|
|
361
|
+
// extracted to /cdn/character.js
|
|
362
|
+
// extracted to /cdn/local-model.js
|
|
363
|
+
function decodeHtmlEntitiesLocal(value) {
|
|
364
|
+
const textarea = document.createElement("textarea");
|
|
365
|
+
textarea.innerHTML = String(value ?? "");
|
|
366
|
+
return textarea.value;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
function extractPlainTextLocal(value) {
|
|
370
|
+
const normalized = trimRepeatedDisplayText(String(value ?? ""))
|
|
371
|
+
.replace(/<think>[\s\S]*?(<\/think>|$)/gi, " ")
|
|
372
|
+
.replace(/```json/gi, "")
|
|
373
|
+
.replace(/```/g, "")
|
|
374
|
+
.trim();
|
|
375
|
+
const holder = document.createElement("div");
|
|
376
|
+
holder.innerHTML = normalized;
|
|
377
|
+
return String(holder.textContent || holder.innerText || normalized).replace(/\s+/g, " ").trim();
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
function sanitizeAiHtmlLocal(value) {
|
|
381
|
+
return String(value ?? "")
|
|
382
|
+
.replace(/<script[\s\S]*?>[\s\S]*?<\/script>/gi, "")
|
|
383
|
+
.replace(/<img\b[^>]*>/gi, "")
|
|
384
|
+
.replace(/\son\w+=(["']).*?\1/gi, "")
|
|
385
|
+
.replace(/\son\w+=([^\s>]+)/gi, "")
|
|
386
|
+
.replace(/\s(href|src)\s*=\s*(["'])\s*javascript:[\s\S]*?\2/gi, ' $1="#"');
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
function formatAiBubbleContent(content) {
|
|
390
|
+
const raw = String(content ?? "");
|
|
391
|
+
let textToProcess = raw;
|
|
392
|
+
const placeholders = [];
|
|
393
|
+
|
|
394
|
+
// 1. ๋ค์ค ๋ผ์ธ ์ฝ๋ ๋ธ๋ก ํ์ฑ (์ค์๊ฐ ํ์ดํ ์ค ๋ซ๋ ๋ฐฑํฑ์ด ์๋ ๊ฒฝ์ฐ๋ ์๋ฒฝ ์ง์)
|
|
395
|
+
textToProcess = textToProcess.replace(/```[ \t]*([a-zA-Z0-9_+\-#]*)[ \t]*\n?([\s\S]*?)(?:```|$)/g, function(match, lang, code) {
|
|
396
|
+
// ์ฝ๋ ๋ด๋ถ ํน์๋ฌธ์ ์์ ํ๊ฒ ์นํ (HTML ๊นจ์ง ๋ฐฉ์ง)
|
|
397
|
+
const escapedCode = code
|
|
398
|
+
.replace(/&/g, "&")
|
|
399
|
+
.replace(/</g, "<")
|
|
400
|
+
.replace(/>/g, ">");
|
|
401
|
+
|
|
402
|
+
// ์๋จ ์ธ์ด ์ด๋ฆ ํ์๋ฐ UI
|
|
403
|
+
const langLabel = lang ? `<div style="font-size: 11px; color: #9ca3af; background: #2d2d2d; padding: 4px 12px; border-top-left-radius: 6px; border-top-right-radius: 6px; text-transform: uppercase; font-weight: 600; letter-spacing: 0.5px; border-bottom: 1px solid #3f3f46;">${lang}</div>` : '';
|
|
404
|
+
const bgRadius = lang ? '0 0 6px 6px' : '6px';
|
|
405
|
+
|
|
406
|
+
// ์ฝ๋๋ธ๋ก ์ ์ฒด ๋ฐ์ค UI
|
|
407
|
+
const blockHtml = `
|
|
408
|
+
<div class="code-block-wrapper" style="margin: 12px 0; border-radius: 6px; overflow: hidden; border: 1px solid rgba(255,255,255,0.1); box-shadow: 0 4px 6px rgba(0,0,0,0.2); text-align: left;">
|
|
409
|
+
${langLabel}
|
|
410
|
+
<div style="background: #1e1e1e; padding: 12px; overflow-x: auto; border-radius: ${bgRadius};">
|
|
411
|
+
<pre style="margin: 0; white-space: pre; font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; font-size: 13px; line-height: 1.5; color: #d4d4d4;"><code>${escapedCode}</code></pre>
|
|
412
|
+
</div>
|
|
413
|
+
</div>`;
|
|
414
|
+
|
|
415
|
+
const key = `__CODE_BLOCK_${placeholders.length}__`;
|
|
416
|
+
placeholders.push({ key, html: blockHtml });
|
|
417
|
+
return key; // ์ฝ๋๊ฐ ์๋ ์๋ฆฌ๋ฅผ ์์ ํค๋ก ์นํ
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
// 2. ์ธ๋ผ์ธ ์ฝ๋ ํ์ฑ (`์งง์ ์ฝ๋`)
|
|
421
|
+
textToProcess = textToProcess.replace(/`([^`\n]+)`/g, function(match, code) {
|
|
422
|
+
const escapedCode = code
|
|
423
|
+
.replace(/&/g, "&")
|
|
424
|
+
.replace(/</g, "<")
|
|
425
|
+
.replace(/>/g, ">");
|
|
426
|
+
|
|
427
|
+
const inlineHtml = `<code style="background: rgba(128,128,128,0.15); padding: 2px 6px; border-radius: 4px; font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; font-size: 0.9em; color: #e91e63; font-weight: 500;">${escapedCode}</code>`;
|
|
428
|
+
|
|
429
|
+
const key = `__INLINE_CODE_${placeholders.length}__`;
|
|
430
|
+
placeholders.push({ key, html: inlineHtml });
|
|
431
|
+
return key; // ์ธ๋ผ์ธ ์ฝ๋๊ฐ ์๋ ์๋ฆฌ๋ฅผ ์์ ํค๋ก ์นํ
|
|
432
|
+
});
|
|
433
|
+
|
|
434
|
+
// 3. ๋ฐ์ผ๋ก ๋
ธ์ถ๋ ์ผ๋ฐ ํ
์คํธ ์์ญ์ ๊บพ์ (<, >) ์์ ํ๊ฒ ์นํ
|
|
435
|
+
// (์ด ๊ณผ์ ์ด ์์ผ๋ฉด ์ฌ์ง์ฒ๋ผ HTML ์ฝ๋๋ฅผ ๋ฑ์ ๋ ๋ธ๋ผ์ฐ์ ๊ฐ ๊ณ ์ฅ๋ฉ๋๋ค)
|
|
436
|
+
textToProcess = textToProcess
|
|
437
|
+
.replace(/&/g, "&")
|
|
438
|
+
.replace(/</g, "<")
|
|
439
|
+
.replace(/>/g, ">");
|
|
440
|
+
|
|
441
|
+
// 4. ์ผ๋ฐ ํ
์คํธ ์์ญ์ ์ํฐ(์ค๋ฐ๊ฟ)๋ฅผ ์น์ฉ ์ค๋ฐ๊ฟ(<br>)์ผ๋ก ๋ณํ
|
|
442
|
+
textToProcess = textToProcess.replace(/\n/g, "<br>");
|
|
443
|
+
|
|
444
|
+
// 5. ์์ ํค๋ก ์นํํด๋์๋ ์์ ์ฝ๋๋ธ๋ก UI๋ค์ ์๋ ์๋ฆฌ์ ๋ณต๊ตฌ
|
|
445
|
+
placeholders.forEach(item => {
|
|
446
|
+
textToProcess = textToProcess.replace(item.key, item.html);
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
return textToProcess;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
function normalizeDisplayRepeatKey(value) {
|
|
453
|
+
return String(value || "")
|
|
454
|
+
.replace(/<think>[\s\S]*?(<\/think>|$)/gi, " ")
|
|
455
|
+
.replace(/\uFFFD+/g, "")
|
|
456
|
+
.replace(/^\d+\.\s*/g, "")
|
|
457
|
+
.replace(/^[-*]\s*/g, "")
|
|
458
|
+
.replace(/\s+/g, " ")
|
|
459
|
+
.trim()
|
|
460
|
+
.toLowerCase();
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
function splitSentenceChunksForDisplay(text) {
|
|
464
|
+
return (String(text || "").match(/[^.!?\n]+[.!?]?(?:\s+|$)|[^\n]+\n?/g) || [])
|
|
465
|
+
.map((part) => ({
|
|
466
|
+
raw: part,
|
|
467
|
+
key: normalizeDisplayRepeatKey(part)
|
|
468
|
+
}))
|
|
469
|
+
.filter((part) => part.key);
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
function findRepeatedSuffixSequenceForDisplay(items, minWindow = 2, maxWindow = 4) {
|
|
473
|
+
if (!Array.isArray(items) || items.length < minWindow * 2) return null;
|
|
474
|
+
const limit = Math.min(maxWindow, Math.floor(items.length / 2));
|
|
475
|
+
for (let size = limit; size >= minWindow; size -= 1) {
|
|
476
|
+
const suffix = items.slice(-size);
|
|
477
|
+
if (!suffix.every((item) => item && item.key)) continue;
|
|
478
|
+
for (let start = items.length - size * 2; start >= 0; start -= 1) {
|
|
479
|
+
let matched = true;
|
|
480
|
+
for (let index = 0; index < size; index += 1) {
|
|
481
|
+
if (items[start + index].key !== suffix[index].key) {
|
|
482
|
+
matched = false;
|
|
483
|
+
break;
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
if (matched) return { start, size };
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
return null;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
function trimRestartedPrefixBlock(text) {
|
|
493
|
+
let value = String(text ?? "").trimEnd();
|
|
494
|
+
if (!value || value.length < 80) return value;
|
|
495
|
+
|
|
496
|
+
const plain = String(value)
|
|
497
|
+
.replace(/<think>[\s\S]*?(<\/think>|$)/gi, " ")
|
|
498
|
+
.replace(/<br\s*\/?>/gi, "\n")
|
|
499
|
+
.replace(/<[^>]+>/g, " ")
|
|
500
|
+
.replace(/\s+/g, " ")
|
|
501
|
+
.trim();
|
|
502
|
+
if (plain.length < 80) return value;
|
|
503
|
+
|
|
504
|
+
const prefixSizes = [120, 96, 84, 72, 60, 48, 36];
|
|
505
|
+
for (const size of prefixSizes) {
|
|
506
|
+
if (plain.length < size * 2) continue;
|
|
507
|
+
const prefix = plain.slice(0, size).trim();
|
|
508
|
+
if (prefix.length < Math.min(32, size)) continue;
|
|
509
|
+
const restartIndex = plain.indexOf(prefix, Math.max(size + 12, Math.floor(size * 1.25)));
|
|
510
|
+
if (restartIndex < 0) continue;
|
|
511
|
+
|
|
512
|
+
const uniqueChars = new Set(prefix.replace(/\s+/g, ""));
|
|
513
|
+
if (uniqueChars.size < 8) continue;
|
|
514
|
+
|
|
515
|
+
const cutSnippet = plain.slice(restartIndex, restartIndex + Math.min(160, prefix.length + 40)).trim();
|
|
516
|
+
if (!cutSnippet || cutSnippet.length < Math.min(28, Math.floor(size * 0.6))) continue;
|
|
517
|
+
|
|
518
|
+
const rawRestartIndex = value.indexOf(cutSnippet);
|
|
519
|
+
if (rawRestartIndex > 0) {
|
|
520
|
+
return value.slice(0, rawRestartIndex).trimEnd();
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
return value;
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
function trimRestartedDisplaySentenceBlock(text) {
|
|
528
|
+
const source = String(text ?? "")
|
|
529
|
+
.replace(/<br\s*\/?>/gi, "\n")
|
|
530
|
+
.trimEnd();
|
|
531
|
+
if (!source || source.length < 80) return source;
|
|
532
|
+
|
|
533
|
+
const sentenceItems = (source.match(/[^.!?\n]+[.!?]?(?:\s+|$)|[^\n]+\n?/g) || [])
|
|
534
|
+
.map((raw) => ({
|
|
535
|
+
raw,
|
|
536
|
+
key: normalizeDisplayRepeatKey(
|
|
537
|
+
String(raw || "")
|
|
538
|
+
.replace(/^\d+\.\s*/g, "")
|
|
539
|
+
.replace(/^[-*]\s*/g, "")
|
|
540
|
+
)
|
|
541
|
+
}))
|
|
542
|
+
.filter((item) => item.key);
|
|
543
|
+
if (sentenceItems.length < 4) return source;
|
|
544
|
+
|
|
545
|
+
const firstKey = sentenceItems[0].key;
|
|
546
|
+
if (firstKey.length < 24) return source;
|
|
547
|
+
const seed = firstKey.slice(0, Math.min(36, firstKey.length));
|
|
548
|
+
|
|
549
|
+
for (let index = 2; index < sentenceItems.length; index += 1) {
|
|
550
|
+
const key = sentenceItems[index].key;
|
|
551
|
+
if (!key || key.length < 16) continue;
|
|
552
|
+
if (
|
|
553
|
+
key === firstKey ||
|
|
554
|
+
key.startsWith(seed) ||
|
|
555
|
+
firstKey.startsWith(key.slice(0, Math.min(24, key.length)))
|
|
556
|
+
) {
|
|
557
|
+
return sentenceItems.slice(0, index).map((item) => item.raw).join("").trimEnd();
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
return source;
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
function trimRepeatedDisplaySentenceHistory(text) {
|
|
565
|
+
const source = String(text ?? "")
|
|
566
|
+
.replace(/<br\s*\/?>/gi, "\n")
|
|
567
|
+
.trimEnd();
|
|
568
|
+
if (!source || source.length < 80) return source;
|
|
569
|
+
|
|
570
|
+
const sentenceItems = (source.match(/[^.!?\n]+[.!?]?(?:\s+|$)|[^\n]+\n?/g) || [])
|
|
571
|
+
.map((raw) => ({
|
|
572
|
+
raw,
|
|
573
|
+
key: normalizeDisplayRepeatKey(
|
|
574
|
+
String(raw || "")
|
|
575
|
+
.replace(/^\d+\.\s*/g, "")
|
|
576
|
+
.replace(/^[-*]\s*/g, "")
|
|
577
|
+
)
|
|
578
|
+
}))
|
|
579
|
+
.filter((item) => item.key);
|
|
580
|
+
if (sentenceItems.length < 4) return source;
|
|
581
|
+
|
|
582
|
+
const seen = new Set();
|
|
583
|
+
const kept = [];
|
|
584
|
+
let removed = false;
|
|
585
|
+
for (const item of sentenceItems) {
|
|
586
|
+
const comparable = item.key.length >= 24 || String(item.raw || "").trim().length >= 32;
|
|
587
|
+
if (comparable && seen.has(item.key)) {
|
|
588
|
+
removed = true;
|
|
589
|
+
break;
|
|
590
|
+
}
|
|
591
|
+
if (comparable) seen.add(item.key);
|
|
592
|
+
kept.push(item.raw);
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
return removed ? kept.join("").trimEnd() : source;
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
function trimDuplicateDisplayParagraphs(text) {
|
|
599
|
+
const source = String(text ?? "")
|
|
600
|
+
.replace(/<br\s*\/?>/gi, "\n")
|
|
601
|
+
.trimEnd();
|
|
602
|
+
if (!source) return source;
|
|
603
|
+
const separator = /\n{2,}/.test(source) ? "\n\n" : "\n";
|
|
604
|
+
const blocks = source
|
|
605
|
+
.split(separator === "\n\n" ? /\n{2,}/ : /\n+/)
|
|
606
|
+
.map((block) => String(block || "").trim())
|
|
607
|
+
.filter(Boolean);
|
|
608
|
+
if (blocks.length < 2) return source;
|
|
609
|
+
|
|
610
|
+
const seen = new Set();
|
|
611
|
+
const kept = [];
|
|
612
|
+
let removed = false;
|
|
613
|
+
|
|
614
|
+
blocks.forEach((block) => {
|
|
615
|
+
const key = normalizeDisplayRepeatKey(
|
|
616
|
+
block
|
|
617
|
+
.replace(/^\d+\.\s*/gm, "")
|
|
618
|
+
.replace(/^[-*]\s*/gm, "")
|
|
619
|
+
);
|
|
620
|
+
const isComparable = key.length >= 32 || block.length >= 48;
|
|
621
|
+
if (isComparable && seen.has(key)) {
|
|
622
|
+
removed = true;
|
|
623
|
+
return;
|
|
624
|
+
}
|
|
625
|
+
if (isComparable) seen.add(key);
|
|
626
|
+
kept.push(block);
|
|
627
|
+
});
|
|
628
|
+
|
|
629
|
+
return removed ? kept.join(separator).trimEnd() : source;
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
function trimRepeatedDisplayText(text) {
|
|
633
|
+
let value = String(text ?? "").trimEnd();
|
|
634
|
+
if (!value) return value;
|
|
635
|
+
value = trimRestartedPrefixBlock(value);
|
|
636
|
+
value = trimRestartedDisplaySentenceBlock(value);
|
|
637
|
+
value = trimDuplicateDisplayParagraphs(value);
|
|
638
|
+
value = trimRepeatedDisplaySentenceHistory(value);
|
|
639
|
+
|
|
640
|
+
const rawLines = value.split("\n");
|
|
641
|
+
const lineItems = rawLines
|
|
642
|
+
.map((line) => ({ raw: line, key: normalizeDisplayRepeatKey(line) }))
|
|
643
|
+
.filter((item) => item.key);
|
|
644
|
+
const repeatedLines = findRepeatedSuffixSequenceForDisplay(lineItems, 2, 4);
|
|
645
|
+
if (repeatedLines && repeatedLines.size < lineItems.length) {
|
|
646
|
+
const remaining = lineItems.slice(0, lineItems.length - repeatedLines.size).map((item) => item.raw);
|
|
647
|
+
value = remaining.join("\n").trimEnd();
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
const sentenceItems = splitSentenceChunksForDisplay(value);
|
|
651
|
+
const repeatedSentences = findRepeatedSuffixSequenceForDisplay(sentenceItems, 2, 3);
|
|
652
|
+
if (repeatedSentences && repeatedSentences.size < sentenceItems.length) {
|
|
653
|
+
value = sentenceItems
|
|
654
|
+
.slice(0, sentenceItems.length - repeatedSentences.size)
|
|
655
|
+
.map((item) => item.raw)
|
|
656
|
+
.join("")
|
|
657
|
+
.trimEnd();
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
const collapsed = normalizeDisplayRepeatKey(value);
|
|
661
|
+
const maxUnit = Math.min(180, Math.floor(collapsed.length / 2));
|
|
662
|
+
for (let size = maxUnit; size >= 24; size -= 1) {
|
|
663
|
+
const tail = collapsed.slice(-size * 2);
|
|
664
|
+
if (tail && tail.length === size * 2) {
|
|
665
|
+
const block = tail.slice(0, size);
|
|
666
|
+
if (block && tail === block + block) {
|
|
667
|
+
value = value.slice(0, Math.max(0, value.length - size)).trimEnd();
|
|
668
|
+
break;
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
value = trimRepeatedDisplaySentenceHistory(
|
|
674
|
+
trimRestartedDisplaySentenceBlock(trimDuplicateDisplayParagraphs(value))
|
|
675
|
+
);
|
|
676
|
+
return trimRestartedPrefixBlock(value);
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
function isImageErrorMessageLocal(value) {
|
|
680
|
+
return /^Image Error:/i.test(String(value ?? "").trim());
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
function imageErrorIconHtmlLocal(message) {
|
|
684
|
+
const safeMessage = String(message ?? "")
|
|
685
|
+
.replace(/&/g, "&")
|
|
686
|
+
.replace(/</g, "<")
|
|
687
|
+
.replace(/>/g, ">")
|
|
688
|
+
.replace(/"/g, """)
|
|
689
|
+
.trim() || "Image generation failed";
|
|
690
|
+
return `<span class="image-error-icon" title="${safeMessage}" aria-label="${safeMessage}"><i class="ri-image-line"></i><i class="ri-close-circle-fill image-error-icon-badge"></i></span>`;
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
function genericErrorIconHtmlLocal(message) {
|
|
694
|
+
const safeMessage = String(message ?? "")
|
|
695
|
+
.replace(/&/g, "&")
|
|
696
|
+
.replace(/</g, "<")
|
|
697
|
+
.replace(/>/g, ">")
|
|
698
|
+
.replace(/"/g, """)
|
|
699
|
+
.trim() || "Request failed";
|
|
700
|
+
return `<span class="image-error-icon" title="${safeMessage}" aria-label="${safeMessage}"><i class="ri-error-warning-line"></i><i class="ri-close-circle-fill image-error-icon-badge"></i></span>`;
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
function waitingDotsHtmlLocal() {
|
|
704
|
+
return `<img src="${WAITING_DOTS_SVG_URL}" alt="loading" class="chat-waiting-dots" style="display:block;width:28px;height:28px;object-fit:contain;pointer-events:none;" draggable="false">`;
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
function appendMsg(role, content) {
|
|
708
|
+
const chatBox = document.getElementById("chat-box");
|
|
709
|
+
|
|
710
|
+
if (chatBox.childElementCount > 50) chatBox.removeChild(chatBox.firstElementChild);
|
|
711
|
+
if (chatBox.childElementCount === 0) {
|
|
712
|
+
const spacer = document.createElement("div");
|
|
713
|
+
spacer.style.marginTop = "auto";
|
|
714
|
+
chatBox.appendChild(spacer);
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
const bubble = document.createElement("div");
|
|
718
|
+
if (role === "user") {
|
|
719
|
+
bubble.className = "chat-bubble-user self-end px-3 py-2 rounded-[24px] max-w-[85%] shadow mb-3 text-[13px] break-words ml-auto";
|
|
720
|
+
bubble.style.backgroundColor = "var(--accent, #3b82f6)";
|
|
721
|
+
bubble.style.color = "var(--accent-text, #ffffff)";
|
|
722
|
+
} else {
|
|
723
|
+
bubble.className = "chat-bubble-ai self-start px-3 py-2 rounded-[24px] max-w-[85%] mb-3 text-[13px] break-words leading-relaxed shadow-sm";
|
|
724
|
+
bubble.style.backgroundColor = "var(--bg-island, #ffffff)";
|
|
725
|
+
bubble.style.color = "var(--text-main, #333333)";
|
|
726
|
+
if (currentMode === "search") {
|
|
727
|
+
bubble.classList.add("search-result-bubble");
|
|
728
|
+
bubble.style.backgroundColor = "#ffffff";
|
|
729
|
+
bubble.style.color = "#111827";
|
|
730
|
+
bubble.style.border = "1px solid rgba(15, 23, 42, 0.12)";
|
|
731
|
+
bubble.style.boxShadow = "0 14px 30px rgba(15, 23, 42, 0.10)";
|
|
732
|
+
} else if (currentMode === "translate") {
|
|
733
|
+
bubble.style.backgroundColor = "#ffffff";
|
|
734
|
+
bubble.style.color = "#111111";
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
if (role === "user") {
|
|
739
|
+
const safeContent = String(content ?? "")
|
|
740
|
+
.replace(/&/g, "&")
|
|
741
|
+
.replace(/</g, "<")
|
|
742
|
+
.replace(/>/g, ">");
|
|
743
|
+
bubble.innerHTML = safeContent.replace(/\n/g, "<br>");
|
|
744
|
+
} else {
|
|
745
|
+
const isError = role === "error";
|
|
746
|
+
const imageError = isError && isImageErrorMessageLocal(content);
|
|
747
|
+
if (isError) {
|
|
748
|
+
bubble.className = "chat-bubble-image-error";
|
|
749
|
+
bubble.style.backgroundColor = "";
|
|
750
|
+
bubble.style.color = "";
|
|
751
|
+
bubble.style.border = "";
|
|
752
|
+
bubble.style.boxShadow = "";
|
|
753
|
+
bubble.innerHTML = imageError ? imageErrorIconHtmlLocal(content) : genericErrorIconHtmlLocal(content);
|
|
754
|
+
} else {
|
|
755
|
+
const normalizedContent = String(content ?? "").trim();
|
|
756
|
+
if (normalizedContent === "...") {
|
|
757
|
+
bubble.classList.add("chat-bubble-waiting");
|
|
758
|
+
bubble.style.padding = "8px";
|
|
759
|
+
bubble.style.width = "fit-content";
|
|
760
|
+
bubble.style.minWidth = "44px";
|
|
761
|
+
bubble.style.display = "inline-flex";
|
|
762
|
+
bubble.style.alignItems = "center";
|
|
763
|
+
bubble.style.justifyContent = "center";
|
|
764
|
+
bubble.innerHTML = waitingDotsHtmlLocal();
|
|
765
|
+
} else if (isLocalWelcomeGreeting(content)) {
|
|
766
|
+
decorateLocalWelcomeBubbleIfNeeded(bubble, content);
|
|
767
|
+
} else {
|
|
768
|
+
bubble.innerHTML = formatAiBubbleContent(content);
|
|
769
|
+
decorateLocalWelcomeBubbleIfNeeded(bubble, content);
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
chatBox.appendChild(bubble);
|
|
775
|
+
scrollBottom();
|
|
776
|
+
return bubble;
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
function appendImg(base64Data, promptText) {
|
|
780
|
+
const chatBox = document.getElementById("chat-box");
|
|
781
|
+
const container = document.createElement("div");
|
|
782
|
+
|
|
783
|
+
container.className = "bg-[#1a1a1a] border border-white/5 rounded-[26px] p-2 mb-6 shadow-xl self-start max-w-sm";
|
|
784
|
+
|
|
785
|
+
container.innerHTML = `
|
|
786
|
+
<img src="data:image/jpeg;base64,${base64Data}"
|
|
787
|
+
class="w-full rounded-[20px] block cursor-zoom-in hover:opacity-90 transition-opacity"
|
|
788
|
+
onclick="openImageModal('data:image/jpeg;base64,${base64Data}', '${promptText.replace(/'/g, "\\'")}')">
|
|
789
|
+
|
|
790
|
+
<div class="flex justify-between items-center pt-3 px-2 pb-1">
|
|
791
|
+
<div class="flex flex-col overflow-hidden mr-3">
|
|
792
|
+
<span class="text-[11px] text-gray-400 font-medium truncate tracking-tight uppercase opacity-50 mb-0.5">Prompt</span>
|
|
793
|
+
<span class="text-[13px] text-gray-200 font-bold truncate leading-tight">${promptText}</span>
|
|
794
|
+
</div>
|
|
795
|
+
|
|
796
|
+
<a href="data:image/jpeg;base64,${base64Data}" download="isai-art.jpg"
|
|
797
|
+
class="flex-shrink-0 w-9 h-9 flex items-center justify-center rounded-full bg-white/5 border border-white/5 text-gray-300 hover:bg-white hover:text-black transition-all"
|
|
798
|
+
onclick="event.stopPropagation()">
|
|
799
|
+
<i class="ri-download-2-line text-lg"></i>
|
|
800
|
+
</a>
|
|
801
|
+
</div>
|
|
802
|
+
`;
|
|
803
|
+
|
|
804
|
+
chatBox.appendChild(container);
|
|
805
|
+
scrollBottom();
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
function appendGif(blob, promptText) {
|
|
809
|
+
const url = URL.createObjectURL(blob);
|
|
810
|
+
const chatBox = document.getElementById("chat-box");
|
|
811
|
+
const container = document.createElement("div");
|
|
812
|
+
|
|
813
|
+
container.className = "self-start bg-white/30 p-2 rounded-2xl rounded-tl-sm max-w-sm mb-2 cursor-pointer hover:opacity-90 transition";
|
|
814
|
+
container.onclick = () => window.openPreview(url);
|
|
815
|
+
|
|
816
|
+
container.innerHTML = `
|
|
817
|
+
<div class="relative rounded-lg overflow-hidden mb-2">
|
|
818
|
+
<img src="${url}" class="w-full h-auto object-cover">
|
|
819
|
+
<div class="absolute top-2 right-2 bg-black/50 text-white text-[10px] px-2 py-0.5 rounded backdrop-blur-sm">GIF</div>
|
|
820
|
+
</div>
|
|
821
|
+
<div class="flex justify-end text-[10px] text-gray-400 px-1">
|
|
822
|
+
<a href="${url}" download="ai_video.gif" class="text-white transition" onclick="event.stopPropagation()">
|
|
823
|
+
<i class="ri-download-line text-xl"></i>
|
|
824
|
+
</a>
|
|
825
|
+
</div>`;
|
|
826
|
+
|
|
827
|
+
chatBox.appendChild(container);
|
|
828
|
+
scrollBottom();
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
function addSourcesToBubble(bubbleElement, sources) {
|
|
832
|
+
if (!bubbleElement || !Array.isArray(sources) || sources.length === 0) return;
|
|
833
|
+
|
|
834
|
+
const uniqueSources = sources.filter((src, index, arr) => {
|
|
835
|
+
if (!src || !src.url) return false;
|
|
836
|
+
return arr.findIndex((item) => item && item.url === src.url) === index;
|
|
837
|
+
}).slice(0, 5);
|
|
838
|
+
|
|
839
|
+
const escapeHtml = (value) => String(value ?? "")
|
|
840
|
+
.replace(/&/g, "&")
|
|
841
|
+
.replace(/</g, "<")
|
|
842
|
+
.replace(/>/g, ">")
|
|
843
|
+
.replace(/"/g, """);
|
|
844
|
+
|
|
845
|
+
const domainLabel = (url) => {
|
|
846
|
+
try {
|
|
847
|
+
return new URL(url).hostname.replace(/^www\./i, "");
|
|
848
|
+
} catch (error) {
|
|
849
|
+
return url;
|
|
850
|
+
}
|
|
851
|
+
};
|
|
852
|
+
|
|
853
|
+
let html = '<div class="mt-4 pt-3 border-t border-black/10 flex flex-wrap gap-2 items-center">';
|
|
854
|
+
|
|
855
|
+
uniqueSources.forEach((src) => {
|
|
856
|
+
const favicon = "https://www.google.com/s2/favicons?sz=64&domain_url=" + encodeURIComponent(src.url);
|
|
857
|
+
const label = src.title || src.url;
|
|
858
|
+
const host = domainLabel(src.url);
|
|
859
|
+
html += `<a href="${escapeHtml(src.url)}" target="_blank" rel="noopener noreferrer" class="search-source-link inline-flex items-center gap-1.5 max-w-[150px] rounded-full bg-black/[0.04] border border-black/[0.08] px-2.5 py-1 text-[11px] font-medium text-[#111827] hover:bg-black/[0.08] transition" title="${escapeHtml(label)}"><img src="${favicon}" class="w-3.5 h-3.5 opacity-85" alt=""><span class="truncate">${escapeHtml(host)}</span></a>`;
|
|
860
|
+
});
|
|
861
|
+
|
|
862
|
+
html += "</div>";
|
|
863
|
+
bubbleElement.innerHTML += html;
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
function scrollBottom() {
|
|
867
|
+
const chatBox = document.getElementById("chat-box");
|
|
868
|
+
requestAnimationFrame(() => {
|
|
869
|
+
chatBox.scrollTop = chatBox.scrollHeight;
|
|
870
|
+
});
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
// extracted to /cdn/code-editor.js
|
|
874
|
+
const __isaiCodeEditor = document.getElementById("code-editor");
|
|
875
|
+
if (__isaiCodeEditor) {
|
|
876
|
+
__isaiCodeEditor.addEventListener("input", function(e) {
|
|
877
|
+
if (!isGenerating && codeFiles[activeFileIndex]) {
|
|
878
|
+
codeFiles[activeFileIndex].content = e.target.value;
|
|
879
|
+
}
|
|
880
|
+
scheduleCodeBlockActionsRender();
|
|
881
|
+
});
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
window.toggleThink = function(headerEl) {
|
|
885
|
+
const contentEl = headerEl.nextElementSibling;
|
|
886
|
+
const iconEl = headerEl.querySelector('.toggle-icon');
|
|
887
|
+
|
|
888
|
+
if (contentEl.style.display === 'none') {
|
|
889
|
+
contentEl.style.display = 'block';
|
|
890
|
+
iconEl.style.transform = 'rotate(180deg)';
|
|
891
|
+
} else {
|
|
892
|
+
contentEl.style.display = 'none';
|
|
893
|
+
iconEl.style.transform = 'rotate(0deg)';
|
|
894
|
+
}
|
|
895
|
+
};
|
|
896
|
+
|
|
897
|
+
(function () {
|
|
898
|
+
const SHORTCUT_STORAGE_KEY = "ISAI_LOCAL_MODEL_SHORTCUTS_V2";
|
|
899
|
+
const CHAT_MODEL_STORAGE_KEY = "ISAI_LOCAL_CHAT_MODEL_ID_V1";
|
|
900
|
+
const MODEL_MENU_ID = "local-model-shortcut-panel";
|
|
901
|
+
const MODEL_MENU_STYLE_ID = "isai-local-model-shortcut-style";
|
|
902
|
+
const SLOT_ORDER = ["code", "light", "middle", "hard"];
|
|
903
|
+
const MODEL_TYPE_ORDER = ["all", "isai", "gemma", "huggingface", "qwen", "granite", "ernie"];
|
|
904
|
+
const MODEL_TYPE_FILTER_ORDER = ["isai", "gemma", "huggingface", "qwen", "granite"];
|
|
905
|
+
const ISAI_MODEL_LOGO_URL = "https://cdn.jsdelivr.net/gh/sllkx/icons@main/logo/isai2.png";
|
|
906
|
+
const SLOT_ICON_MAP = {
|
|
907
|
+
code: "ri-code-block",
|
|
908
|
+
light: "ri-sparkling-2-line",
|
|
909
|
+
middle: "ri-cpu-line",
|
|
910
|
+
hard: "ri-rocket-2-line"
|
|
911
|
+
};
|
|
912
|
+
let currentModelTypeFilter = "all";
|
|
913
|
+
let shortcutSettingsOpen = false;
|
|
914
|
+
|
|
915
|
+
function getShortcutLocale() {
|
|
916
|
+
const serverLocale = window.ISAI_SERVER_I18N && window.ISAI_SERVER_I18N.locale;
|
|
917
|
+
const locale = String(serverLocale || window.LANG || document.documentElement.lang || navigator.language || "en").toLowerCase();
|
|
918
|
+
if (locale.startsWith("ko")) return "ko";
|
|
919
|
+
if (locale.startsWith("ja")) return "ja";
|
|
920
|
+
if (locale.startsWith("zh")) return "zh";
|
|
921
|
+
if (locale.startsWith("es")) return "es";
|
|
922
|
+
return "en";
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
function getShortcutText(key) {
|
|
926
|
+
const dict = {
|
|
927
|
+
ko: {
|
|
928
|
+
menu: "\uBAA8\uB378 \uBA54\uB274",
|
|
929
|
+
current: "\uD604\uC7AC \uC124\uC815",
|
|
930
|
+
settings: "\uC124\uC815",
|
|
931
|
+
changed: "\uB2E8\uCD95 \uBAA8\uB378 \uC124\uC815\uC774 \uC801\uC6A9\uB418\uC5C8\uC2B5\uB2C8\uB2E4",
|
|
932
|
+
code: "\uCF54\uB4DC",
|
|
933
|
+
light: "\uB77C\uC774\uD2B8",
|
|
934
|
+
middle: "\uC911\uAC04",
|
|
935
|
+
hard: "\uD558\uB4DC",
|
|
936
|
+
all: "\uC804\uCCB4",
|
|
937
|
+
isai: "ISAI",
|
|
938
|
+
qwen: "Qwen",
|
|
939
|
+
granite: "Granite",
|
|
940
|
+
gemma: "Gemma",
|
|
941
|
+
huggingface: "H",
|
|
942
|
+
ernie: "ERNIE",
|
|
943
|
+
available: "\uC120\uD0DD \uAC00\uB2A5\uD55C \uBAA8\uB378",
|
|
944
|
+
restricted: "Gemma\uB294 \uBCC4\uB3C4 \uC774\uC6A9\uC870\uAC74\uC774 \uC801\uC6A9\uB429\uB2C8\uB2E4"
|
|
945
|
+
},
|
|
946
|
+
en: {
|
|
947
|
+
menu: "Model Menu",
|
|
948
|
+
current: "Current",
|
|
949
|
+
settings: "Settings",
|
|
950
|
+
changed: "Shortcut model setting applied",
|
|
951
|
+
code: "Code",
|
|
952
|
+
light: "Light",
|
|
953
|
+
middle: "Middle",
|
|
954
|
+
hard: "Hard",
|
|
955
|
+
all: "All",
|
|
956
|
+
isai: "ISAI",
|
|
957
|
+
qwen: "Qwen",
|
|
958
|
+
granite: "Granite",
|
|
959
|
+
gemma: "Gemma",
|
|
960
|
+
huggingface: "H",
|
|
961
|
+
ernie: "ERNIE",
|
|
962
|
+
available: "Available models",
|
|
963
|
+
restricted: "Gemma has separate usage terms"
|
|
964
|
+
},
|
|
965
|
+
ja: {
|
|
966
|
+
menu: "\u30E2\u30C7\u30EB\u30E1\u30CB\u30E5\u30FC",
|
|
967
|
+
current: "\u73FE\u5728\u8A2D\u5B9A",
|
|
968
|
+
settings: "\u8A2D\u5B9A",
|
|
969
|
+
changed: "\u30B7\u30E7\u30FC\u30C8\u30AB\u30C3\u30C8\u30E2\u30C7\u30EB\u8A2D\u5B9A\u3092\u9069\u7528\u3057\u307E\u3057\u305F",
|
|
970
|
+
code: "\u30B3\u30FC\u30C9",
|
|
971
|
+
light: "\u30E9\u30A4\u30C8",
|
|
972
|
+
middle: "\u4E2D\u9593",
|
|
973
|
+
hard: "\u30CF\u30FC\u30C9",
|
|
974
|
+
all: "\u5168\u3066",
|
|
975
|
+
isai: "ISAI",
|
|
976
|
+
qwen: "Qwen",
|
|
977
|
+
granite: "Granite",
|
|
978
|
+
gemma: "Gemma",
|
|
979
|
+
huggingface: "H",
|
|
980
|
+
ernie: "ERNIE",
|
|
981
|
+
available: "\u9078\u629E\u3067\u304D\u308B\u30E2\u30C7\u30EB",
|
|
982
|
+
restricted: "Gemma \u306B\u306F\u5225\u9014\u5229\u7528\u6761\u4EF6\u304C\u9069\u7528\u3055\u308C\u307E\u3059"
|
|
983
|
+
},
|
|
984
|
+
zh: {
|
|
985
|
+
menu: "\u6A21\u578B\u83DC\u5355",
|
|
986
|
+
current: "\u5F53\u524D\u8BBE\u7F6E",
|
|
987
|
+
settings: "\u8BBE\u7F6E",
|
|
988
|
+
changed: "\u5DF2\u5E94\u7528\u5FEB\u6377\u6A21\u578B\u8BBE\u7F6E",
|
|
989
|
+
code: "\u4EE3\u7801",
|
|
990
|
+
light: "\u8F7B\u91CF",
|
|
991
|
+
middle: "\u4E2D\u95F4",
|
|
992
|
+
hard: "\u9AD8\u7EA7",
|
|
993
|
+
all: "\u5168\u90E8",
|
|
994
|
+
isai: "ISAI",
|
|
995
|
+
qwen: "Qwen",
|
|
996
|
+
granite: "Granite",
|
|
997
|
+
gemma: "Gemma",
|
|
998
|
+
huggingface: "H",
|
|
999
|
+
ernie: "ERNIE",
|
|
1000
|
+
available: "\u53EF\u9009\u6A21\u578B",
|
|
1001
|
+
restricted: "Gemma \u9700\u9075\u5B88\u989D\u5916\u4F7F\u7528\u6761\u6B3E"
|
|
1002
|
+
},
|
|
1003
|
+
es: {
|
|
1004
|
+
menu: "Menu de modelos",
|
|
1005
|
+
current: "Actual",
|
|
1006
|
+
settings: "Ajustes",
|
|
1007
|
+
changed: "Configuracion de modelo rapido aplicada",
|
|
1008
|
+
code: "Codigo",
|
|
1009
|
+
light: "Ligero",
|
|
1010
|
+
middle: "Medio",
|
|
1011
|
+
hard: "Alto",
|
|
1012
|
+
all: "Todo",
|
|
1013
|
+
isai: "ISAI",
|
|
1014
|
+
qwen: "Qwen",
|
|
1015
|
+
granite: "Granite",
|
|
1016
|
+
gemma: "Gemma",
|
|
1017
|
+
huggingface: "H",
|
|
1018
|
+
ernie: "ERNIE",
|
|
1019
|
+
available: "Modelos disponibles",
|
|
1020
|
+
restricted: "Gemma tiene condiciones de uso separadas"
|
|
1021
|
+
}
|
|
1022
|
+
};
|
|
1023
|
+
const locale = getShortcutLocale();
|
|
1024
|
+
const table = dict[locale] || dict.en;
|
|
1025
|
+
return table[key] || key;
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
function getTierSwitchedToast(tierKey) {
|
|
1029
|
+
const tierLabel = getShortcutText(String(tierKey || "light").toLowerCase());
|
|
1030
|
+
const locale = getShortcutLocale();
|
|
1031
|
+
if (locale === "ko") return `${tierLabel} \uBAA8\uB4DC\uB85C \uC804\uD658\uB418\uC5C8\uC2B5\uB2C8\uB2E4`;
|
|
1032
|
+
if (locale === "ja") return `${tierLabel}\u30E2\u30FC\u30C9\u306B\u5207\u308A\u66FF\u3048\u307E\u3057\u305F`;
|
|
1033
|
+
if (locale === "zh") return `\u5DF2\u5207\u6362\u4E3A${tierLabel}\u6A21\u5F0F`;
|
|
1034
|
+
if (locale === "es") return `Cambiado al modo ${tierLabel}`;
|
|
1035
|
+
return `Switched to ${tierLabel} mode`;
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
function getUserCountryCode() {
|
|
1039
|
+
const fromServer = window.ISAI_SERVER_I18N && window.ISAI_SERVER_I18N.country;
|
|
1040
|
+
const fromHtml = document.documentElement.getAttribute("data-country");
|
|
1041
|
+
return String(fromServer || fromHtml || "").trim().toUpperCase();
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
function getModelTypeMeta() {
|
|
1045
|
+
return {
|
|
1046
|
+
all: { icon: "ri-apps-2-line", label: getShortcutText("all") },
|
|
1047
|
+
isai: { icon: "isai-logo", label: getShortcutText("isai") },
|
|
1048
|
+
huggingface: { icon: "ri-text", label: "H" },
|
|
1049
|
+
qwen: { icon: "ri-qwen-ai-fill", label: getShortcutText("qwen") },
|
|
1050
|
+
granite: { icon: "granite-g", label: getShortcutText("granite") },
|
|
1051
|
+
gemma: { icon: "ri-gemini-fill", label: getShortcutText("gemma") },
|
|
1052
|
+
ernie: { icon: "ri-sparkling-2-line", label: getShortcutText("ernie") }
|
|
1053
|
+
};
|
|
1054
|
+
}
|
|
1055
|
+
|
|
1056
|
+
function getModelTypeFromItem(item) {
|
|
1057
|
+
const explicitType = String(item && item.modelType ? item.modelType : "").toLowerCase();
|
|
1058
|
+
if (MODEL_TYPE_ORDER.includes(explicitType)) return explicitType;
|
|
1059
|
+
const idText = String(item && item.id ? item.id : "").toLowerCase();
|
|
1060
|
+
const nameText = String(item && item.name ? item.name : "").toLowerCase();
|
|
1061
|
+
const hay = `${idText} ${nameText}`;
|
|
1062
|
+
if (hay.includes("smollm")) return "huggingface";
|
|
1063
|
+
if (hay.includes("huggingface")) return "huggingface";
|
|
1064
|
+
if (hay.includes("vlite")) return "isai";
|
|
1065
|
+
if (hay.includes("gemma")) return "gemma";
|
|
1066
|
+
if (hay.includes("qwen")) return "qwen";
|
|
1067
|
+
if (hay.includes("granite")) return "granite";
|
|
1068
|
+
if (hay.includes("haru")) return "isai";
|
|
1069
|
+
if (hay.includes("kori")) return "isai";
|
|
1070
|
+
if (hay.includes("isai")) return "isai";
|
|
1071
|
+
if (hay.includes("ernie")) return "ernie";
|
|
1072
|
+
return "gemma";
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
function getProviderBadgeHtml(typeKey, compact) {
|
|
1076
|
+
const isCompact = !!compact;
|
|
1077
|
+
const sizeClass = isCompact ? " is-compact" : "";
|
|
1078
|
+
if (typeKey === "huggingface") return `<span class="local-model-provider-badge huggingface${sizeClass}"><span class="hf-glyph">H</span></span>`;
|
|
1079
|
+
if (typeKey === "isai") return `<span class="local-model-provider-badge isai${sizeClass}"><img src="${ISAI_MODEL_LOGO_URL}" alt="ISAI" class="isai-logo-img"></span>`;
|
|
1080
|
+
if (typeKey === "granite") return `<span class="local-model-provider-badge granite${sizeClass}"><span class="model-provider-glyph">G</span></span>`;
|
|
1081
|
+
if (typeKey === "qwen") return `<span class="local-model-provider-badge qwen${sizeClass}"><i class="ri-qwen-ai-fill"></i></span>`;
|
|
1082
|
+
if (typeKey === "gemma") return `<span class="local-model-provider-badge gemma${sizeClass}"><i class="ri-gemini-fill"></i></span>`;
|
|
1083
|
+
if (typeKey === "ernie") return `<span class="local-model-provider-badge ernie${sizeClass}"><span class="model-provider-glyph">E</span></span>`;
|
|
1084
|
+
return `<span class="local-model-provider-badge${sizeClass}"><i class="ri-apps-2-line"></i></span>`;
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
function getModelCatalog() {
|
|
1088
|
+
const catalog = window.__ISAI_LOCAL_MODEL_CATALOG;
|
|
1089
|
+
if (!catalog || typeof catalog !== "object") return {};
|
|
1090
|
+
|
|
1091
|
+
const allowByCountry = {
|
|
1092
|
+
CN: ["qwen_coder_05b_q3kl", "qwen_05b_q3km", "granite_350m_q40"]
|
|
1093
|
+
};
|
|
1094
|
+
const country = getUserCountryCode();
|
|
1095
|
+
const allowed = allowByCountry[country];
|
|
1096
|
+
if (!Array.isArray(allowed) || allowed.length === 0) return catalog;
|
|
1097
|
+
|
|
1098
|
+
const filtered = {};
|
|
1099
|
+
allowed.forEach((id) => {
|
|
1100
|
+
if (catalog[id]) filtered[id] = catalog[id];
|
|
1101
|
+
});
|
|
1102
|
+
return Object.keys(filtered).length > 0 ? filtered : catalog;
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
function getBaseLocalModelProfiles() {
|
|
1106
|
+
const bundled = window.__ISAI_LOCAL_MODEL_PROFILES;
|
|
1107
|
+
if (bundled && typeof bundled === "object") return bundled;
|
|
1108
|
+
return {};
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1111
|
+
function readShortcutOverrides() {
|
|
1112
|
+
try {
|
|
1113
|
+
const raw = localStorage.getItem(SHORTCUT_STORAGE_KEY);
|
|
1114
|
+
const parsed = raw ? JSON.parse(raw) : {};
|
|
1115
|
+
return parsed && typeof parsed === "object" ? parsed : {};
|
|
1116
|
+
} catch (error) {
|
|
1117
|
+
return {};
|
|
1118
|
+
}
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
function writeShortcutOverrides(nextMap) {
|
|
1122
|
+
try {
|
|
1123
|
+
localStorage.setItem(SHORTCUT_STORAGE_KEY, JSON.stringify(nextMap || {}));
|
|
1124
|
+
} catch (error) {}
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
function getDefaultChatModelId() {
|
|
1128
|
+
const base = getBaseLocalModelProfiles();
|
|
1129
|
+
const preferred = String((window.MODEL_CONFIG && window.MODEL_CONFIG.defaultChatModelId) || "").trim();
|
|
1130
|
+
if (preferred) return preferred;
|
|
1131
|
+
if (base.light && base.light.modelId) return String(base.light.modelId).trim();
|
|
1132
|
+
return "qwen_05b_q3km";
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1135
|
+
function getStoredChatModelId() {
|
|
1136
|
+
const catalog = getModelCatalog();
|
|
1137
|
+
const storageKey = CHAT_MODEL_STORAGE_KEY;
|
|
1138
|
+
const persistChatModelId = (nextId) => {
|
|
1139
|
+
const normalized = String(nextId || "").trim();
|
|
1140
|
+
if (!normalized) return normalized;
|
|
1141
|
+
try { localStorage.setItem(storageKey, normalized); } catch (error) {}
|
|
1142
|
+
return normalized;
|
|
1143
|
+
};
|
|
1144
|
+
const raw = String(localStorage.getItem(CHAT_MODEL_STORAGE_KEY) || "").trim();
|
|
1145
|
+
if (raw === "granite_1b_iq3xxs" && catalog.granite_350m_q40) {
|
|
1146
|
+
return persistChatModelId("granite_350m_q40");
|
|
1147
|
+
}
|
|
1148
|
+
if (raw === "ernie_03b_q3ks") {
|
|
1149
|
+
const fallback = getDefaultChatModelId();
|
|
1150
|
+
if (catalog[fallback] && fallback !== "qwen_coder_05b_q3kl") return persistChatModelId(fallback);
|
|
1151
|
+
}
|
|
1152
|
+
if (raw && catalog[raw] && raw !== "qwen_coder_05b_q3kl") return raw;
|
|
1153
|
+
const fallback = getDefaultChatModelId();
|
|
1154
|
+
if (catalog[fallback] && fallback !== "qwen_coder_05b_q3kl") return persistChatModelId(fallback);
|
|
1155
|
+
const firstChatModel = Object.keys(catalog).find((id) => id !== "qwen_coder_05b_q3kl");
|
|
1156
|
+
return persistChatModelId(firstChatModel || fallback || "qwen_05b_q3km");
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1159
|
+
function getResolvedLocalModelProfiles() {
|
|
1160
|
+
const catalog = getModelCatalog();
|
|
1161
|
+
const base = getBaseLocalModelProfiles();
|
|
1162
|
+
const selectedChatModelId = getStoredChatModelId();
|
|
1163
|
+
const selectedChatModel = selectedChatModelId && catalog[selectedChatModelId] ? catalog[selectedChatModelId] : null;
|
|
1164
|
+
const resolved = {};
|
|
1165
|
+
|
|
1166
|
+
SLOT_ORDER.forEach((slotKey) => {
|
|
1167
|
+
const baseProfile = base[slotKey];
|
|
1168
|
+
if (!baseProfile || typeof baseProfile !== "object") return;
|
|
1169
|
+
const next = Object.assign({}, baseProfile);
|
|
1170
|
+
const catalogId = slotKey === "code"
|
|
1171
|
+
? String(baseProfile.modelId || "").trim()
|
|
1172
|
+
: String((selectedChatModel && selectedChatModel.id) || baseProfile.modelId || "").trim();
|
|
1173
|
+
const catalogItem = catalogId && catalog[catalogId] ? catalog[catalogId] : null;
|
|
1174
|
+
if (catalogItem) {
|
|
1175
|
+
next.modelId = catalogItem.id;
|
|
1176
|
+
next.modelName = catalogItem.name;
|
|
1177
|
+
next.fallbackUrl = catalogItem.fallbackUrl;
|
|
1178
|
+
next.fallbackUrls = Array.isArray(catalogItem.fallbackUrls) ? catalogItem.fallbackUrls.slice() : [];
|
|
1179
|
+
next.popupSizeText = catalogItem.popupSizeText;
|
|
1180
|
+
next.preferredRuntime = catalogItem.preferredRuntime || next.preferredRuntime || "wasm";
|
|
1181
|
+
next.license = catalogItem.license || next.license || "";
|
|
1182
|
+
next.modelType = getModelTypeFromItem(catalogItem) || next.modelType || "qwen";
|
|
1183
|
+
next.webllmModelId = catalogItem.webllmModelId || "";
|
|
1184
|
+
}
|
|
1185
|
+
resolved[slotKey] = next;
|
|
1186
|
+
});
|
|
1187
|
+
|
|
1188
|
+
return resolved;
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1191
|
+
window.getIsaiLocalModelProfiles = function getIsaiLocalModelProfilesPatched() {
|
|
1192
|
+
return getResolvedLocalModelProfiles();
|
|
1193
|
+
};
|
|
1194
|
+
|
|
1195
|
+
function ensureShortcutMenuStyle() {
|
|
1196
|
+
if (document.getElementById(MODEL_MENU_STYLE_ID)) return;
|
|
1197
|
+
const style = document.createElement("style");
|
|
1198
|
+
style.id = MODEL_MENU_STYLE_ID;
|
|
1199
|
+
style.textContent = `
|
|
1200
|
+
|
|
1201
|
+
`;
|
|
1202
|
+
document.head.appendChild(style);
|
|
1203
|
+
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1206
|
+
|
|
1207
|
+
function closeLocalModelShortcutMenu() {
|
|
1208
|
+
const panel = document.getElementById(MODEL_MENU_ID);
|
|
1209
|
+
const trigger = document.getElementById("local-model-tier-menu-btn");
|
|
1210
|
+
if (panel) panel.classList.remove("is-open");
|
|
1211
|
+
if (trigger) trigger.classList.remove("is-open");
|
|
1212
|
+
shortcutSettingsOpen = false;
|
|
1213
|
+
}
|
|
1214
|
+
|
|
1215
|
+
function getCatalogItems() {
|
|
1216
|
+
const typeRank = { isai: 0, gemma: 1, qwen: 2, granite: 3, ernie: 4, huggingface: 5 };
|
|
1217
|
+
|
|
1218
|
+
// --- [์ถ๊ฐ๋ ๋ถ๋ถ: ์นด์ดํธ ๋ฐ์ดํฐ ๋ถ๋ฌ์ค๊ธฐ] ---
|
|
1219
|
+
let usageStats = {};
|
|
1220
|
+
try {
|
|
1221
|
+
usageStats = JSON.parse(localStorage.getItem("ISAI_MODEL_USAGE_STATS") || "{}");
|
|
1222
|
+
} catch (error) {}
|
|
1223
|
+
// -----------------------------------------------
|
|
1224
|
+
|
|
1225
|
+
return Object.values(getModelCatalog()).filter((item) => String(item && item.id ? item.id : "") !== "qwen_coder_05b_q3kl").map((item) => {
|
|
1226
|
+
const next = Object.assign({}, item || {});
|
|
1227
|
+
next.modelType = getModelTypeFromItem(next);
|
|
1228
|
+
|
|
1229
|
+
// --- [์ถ๊ฐ๋ ๋ถ๋ถ: ๋ชจ๋ธ ๊ฐ์ฒด์ ์ฌ์ฉ ํ์ ์ฃผ์
] ---
|
|
1230
|
+
next.usageCount = next.pending || next.externalUrl ? -1 : usageStats[next.id] || 0;
|
|
1231
|
+
|
|
1232
|
+
return next;
|
|
1233
|
+
}).sort((a, b) => {
|
|
1234
|
+
// --- [์ถ๊ฐ/์์ ๋ ๋ถ๋ถ: 1์์ ์ ๋ ฌ (์์ฃผ ์ฐ๋ ์)] ---
|
|
1235
|
+
if (b.usageCount !== a.usageCount) {
|
|
1236
|
+
return b.usageCount - a.usageCount; // ์ซ์๊ฐ ํฐ(๋ง์ด ์ด) ๋ชจ๋ธ์ด ์๋ก
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1239
|
+
// --- 2์์ ์ ๋ ฌ (๊ธฐ์กด ๋ก์ง ์ ์ง: ํ์๊ฐ ๊ฐ์ผ๋ฉด ์ ์กฐ์ฌ/์ด๋ฆ์) ---
|
|
1240
|
+
const aRank = Object.prototype.hasOwnProperty.call(typeRank, a.modelType) ? typeRank[a.modelType] : 99;
|
|
1241
|
+
const bRank = Object.prototype.hasOwnProperty.call(typeRank, b.modelType) ? typeRank[b.modelType] : 99;
|
|
1242
|
+
if (aRank !== bRank) return aRank - bRank;
|
|
1243
|
+
return String(a && a.name ? a.name : "").localeCompare(String(b && b.name ? b.name : ""));
|
|
1244
|
+
});
|
|
1245
|
+
}
|
|
1246
|
+
|
|
1247
|
+
function maybeStartActiveTierDownload(forceActivate) {
|
|
1248
|
+
if (typeof applyLocalModelProfileToConfig === "function") {
|
|
1249
|
+
applyLocalModelProfileToConfig();
|
|
1250
|
+
}
|
|
1251
|
+
isModelDownloaded = localStorage.getItem(getLocalDownloadStorageKey()) === "true";
|
|
1252
|
+
if (!isLocalActive && !forceActivate) return;
|
|
1253
|
+
if (!isLocalActive && forceActivate) {
|
|
1254
|
+
isLocalActive = true;
|
|
1255
|
+
}
|
|
1256
|
+
if (isModelDownloaded) {
|
|
1257
|
+
updateLocalBtnState();
|
|
1258
|
+
renderLocalModelTierSelector();
|
|
1259
|
+
syncLocalModelTierVisibility();
|
|
1260
|
+
startDownload();
|
|
1261
|
+
return;
|
|
1262
|
+
}
|
|
1263
|
+
isLocalActive = true;
|
|
1264
|
+
updateLocalBtnState();
|
|
1265
|
+
renderLocalModelTierSelector();
|
|
1266
|
+
syncLocalModelTierVisibility();
|
|
1267
|
+
startDownload();
|
|
1268
|
+
}
|
|
1269
|
+
|
|
1270
|
+
function getPreferredChatTierForModelSelection() {
|
|
1271
|
+
const profiles = getLocalModelProfiles();
|
|
1272
|
+
const activeTier = String(getActiveLocalModelTier() || "").trim().toLowerCase();
|
|
1273
|
+
if (activeTier && activeTier !== "code" && profiles[activeTier]) {
|
|
1274
|
+
return activeTier;
|
|
1275
|
+
}
|
|
1276
|
+
if (profiles.middle) return "middle";
|
|
1277
|
+
if (profiles.light) return "light";
|
|
1278
|
+
if (profiles.hard) return "hard";
|
|
1279
|
+
const ordered = getOrderedLocalModelTierKeys().filter((tier) => tier !== "code");
|
|
1280
|
+
return ordered[0] || getDefaultLocalModelTier();
|
|
1281
|
+
}
|
|
1282
|
+
|
|
1283
|
+
function handleShortcutProfileChange(slotKey, modelId, options) {
|
|
1284
|
+
const opts = Object.assign({ activateNow: false, showChangedToast: true }, options || {});
|
|
1285
|
+
const catalog = getModelCatalog();
|
|
1286
|
+
if (!catalog[modelId]) return;
|
|
1287
|
+
if (String(modelId) === "qwen_coder_05b_q3kl") return;
|
|
1288
|
+
if (catalog[modelId].pending || catalog[modelId].externalUrl) {
|
|
1289
|
+
if (catalog[modelId].externalUrl) {
|
|
1290
|
+
window.open(catalog[modelId].externalUrl, "_blank", "noopener");
|
|
1291
|
+
}
|
|
1292
|
+
return;
|
|
1293
|
+
}
|
|
1294
|
+
|
|
1295
|
+
// --- [์ถ๊ฐ๋ ๋ถ๋ถ: ์์ฃผ ์ฐ๋ ๋ชจ๋ธ ์นด์ดํธ ๊ธฐ๋ก] ---
|
|
1296
|
+
try {
|
|
1297
|
+
const usageStats = JSON.parse(localStorage.getItem("ISAI_MODEL_USAGE_STATS") || "{}");
|
|
1298
|
+
usageStats[modelId] = (usageStats[modelId] || 0) + 1;
|
|
1299
|
+
localStorage.setItem("ISAI_MODEL_USAGE_STATS", JSON.stringify(usageStats));
|
|
1300
|
+
} catch (error) {}
|
|
1301
|
+
// --------------------------------------------------
|
|
1302
|
+
|
|
1303
|
+
localStorage.setItem(CHAT_MODEL_STORAGE_KEY, String(modelId));
|
|
1304
|
+
currentModelTypeFilter = getModelTypeFromItem(catalog[modelId]);
|
|
1305
|
+
const currentTier = String(getActiveLocalModelTier() || "").trim().toLowerCase();
|
|
1306
|
+
const targetTier = getPreferredChatTierForModelSelection();
|
|
1307
|
+
if (typeof applyLocalModelProfileToConfig === "function") {
|
|
1308
|
+
applyLocalModelProfileToConfig();
|
|
1309
|
+
}
|
|
1310
|
+
renderLocalModelTierSelector();
|
|
1311
|
+
syncLocalModelTierVisibility();
|
|
1312
|
+
if (currentTier === "code" && targetTier && typeof setLocalModelTier === "function") {
|
|
1313
|
+
setLocalModelTier(targetTier);
|
|
1314
|
+
} else {
|
|
1315
|
+
isLocalActive = true;
|
|
1316
|
+
isModelLoaded = false;
|
|
1317
|
+
localWasmEngine = null;
|
|
1318
|
+
if (typeof setLocalRuntimeState === "function") setLocalRuntimeState(null);
|
|
1319
|
+
maybeStartActiveTierDownload(true);
|
|
1320
|
+
}
|
|
1321
|
+
const panel = document.getElementById(MODEL_MENU_ID);
|
|
1322
|
+
if (panel && panel.parentNode) {
|
|
1323
|
+
const shouldStayOpen = panel.classList.contains("is-open");
|
|
1324
|
+
buildShortcutMenu(panel.parentNode);
|
|
1325
|
+
if (shouldStayOpen) {
|
|
1326
|
+
const rebuiltPanel = document.getElementById(MODEL_MENU_ID);
|
|
1327
|
+
if (rebuiltPanel) rebuiltPanel.classList.add("is-open");
|
|
1328
|
+
}
|
|
1329
|
+
}
|
|
1330
|
+
if (opts.showChangedToast && typeof showToast === "function") showToast(getShortcutText("changed"));
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1333
|
+
function appendSummarySection(panel, currentProfiles) {
|
|
1334
|
+
const summary = document.createElement("div");
|
|
1335
|
+
summary.className = "local-model-shortcut-summary";
|
|
1336
|
+
SLOT_ORDER.forEach((slotKey) => {
|
|
1337
|
+
const row = document.createElement("div");
|
|
1338
|
+
row.className = "local-model-shortcut-summary-row";
|
|
1339
|
+
const label = document.createElement("div");
|
|
1340
|
+
label.className = "local-model-shortcut-label";
|
|
1341
|
+
label.textContent = getShortcutText(slotKey);
|
|
1342
|
+
const profile = currentProfiles[slotKey] || {};
|
|
1343
|
+
const chip = document.createElement("div");
|
|
1344
|
+
chip.className = "local-model-shortcut-chip";
|
|
1345
|
+
chip.innerHTML = `<span>${profile.modelName || "-"}</span><small>${profile.popupSizeText || ""}</small>`;
|
|
1346
|
+
row.appendChild(label);
|
|
1347
|
+
row.appendChild(chip);
|
|
1348
|
+
summary.appendChild(row);
|
|
1349
|
+
});
|
|
1350
|
+
panel.appendChild(summary);
|
|
1351
|
+
}
|
|
1352
|
+
|
|
1353
|
+
function appendFilterRow(container, inHead) {
|
|
1354
|
+
const row = document.createElement("div");
|
|
1355
|
+
row.className = `local-model-filter-row${inHead ? " in-head" : ""}`;
|
|
1356
|
+
const meta = getModelTypeMeta();
|
|
1357
|
+
|
|
1358
|
+
let providerUsage = {};
|
|
1359
|
+
const originalOrder = MODEL_TYPE_FILTER_ORDER.slice();
|
|
1360
|
+
|
|
1361
|
+
try {
|
|
1362
|
+
const usageStats = JSON.parse(localStorage.getItem("ISAI_MODEL_USAGE_STATS") || "{}");
|
|
1363
|
+
const catalog = getModelCatalog();
|
|
1364
|
+
|
|
1365
|
+
Object.values(catalog).forEach(item => {
|
|
1366
|
+
if (usageStats[item.id]) {
|
|
1367
|
+
const type = getModelTypeFromItem(item);
|
|
1368
|
+
providerUsage[type] = (providerUsage[type] || 0) + usageStats[item.id];
|
|
1369
|
+
}
|
|
1370
|
+
});
|
|
1371
|
+
} catch (error) {}
|
|
1372
|
+
|
|
1373
|
+
const dynamicOrder = [...originalOrder].sort((a, b) => {
|
|
1374
|
+
const countA = providerUsage[a] || 0;
|
|
1375
|
+
const countB = providerUsage[b] || 0;
|
|
1376
|
+
|
|
1377
|
+
if (countB !== countA) {
|
|
1378
|
+
return countB - countA; // ํ์๊ฐ ๋์ ์ ์กฐ์ฌ๊ฐ ์์ชฝ์ผ๋ก
|
|
1379
|
+
}
|
|
1380
|
+
return originalOrder.indexOf(a) - originalOrder.indexOf(b);
|
|
1381
|
+
});
|
|
1382
|
+
dynamicOrder.forEach((typeKey) => {
|
|
1383
|
+
const item = meta[typeKey];
|
|
1384
|
+
const button = document.createElement("button");
|
|
1385
|
+
button.type = "button";
|
|
1386
|
+
button.className = `local-model-filter-btn provider-${typeKey}${currentModelTypeFilter === typeKey ? " is-active" : ""}`;
|
|
1387
|
+
button.title = item.label;
|
|
1388
|
+
button.setAttribute("aria-label", item.label);
|
|
1389
|
+
button.innerHTML = getProviderBadgeHtml(typeKey, true);
|
|
1390
|
+
button.addEventListener("click", function (event) {
|
|
1391
|
+
event.preventDefault();
|
|
1392
|
+
event.stopPropagation();
|
|
1393
|
+
currentModelTypeFilter = currentModelTypeFilter === typeKey ? "all" : typeKey;
|
|
1394
|
+
const panel = document.getElementById(MODEL_MENU_ID);
|
|
1395
|
+
if (panel) buildShortcutMenu(panel.parentNode);
|
|
1396
|
+
});
|
|
1397
|
+
row.appendChild(button);
|
|
1398
|
+
});
|
|
1399
|
+
|
|
1400
|
+
if (container) container.appendChild(row);
|
|
1401
|
+
return row;
|
|
1402
|
+
}
|
|
1403
|
+
|
|
1404
|
+
function appendCatalogCards(container, targetTierKey) {
|
|
1405
|
+
const currentProfiles = getResolvedLocalModelProfiles();
|
|
1406
|
+
const activeTier = String(targetTierKey || getActiveLocalModelTier() || "light").toLowerCase();
|
|
1407
|
+
const selectedChatModelId = getStoredChatModelId();
|
|
1408
|
+
const catalogGrid = document.createElement("div");
|
|
1409
|
+
catalogGrid.className = "local-model-catalog-grid";
|
|
1410
|
+
const items = getCatalogItems().filter((item) => currentModelTypeFilter === "all" ? true : item.modelType === currentModelTypeFilter);
|
|
1411
|
+
items.forEach((item) => {
|
|
1412
|
+
const isPending = !!(item.pending || item.externalUrl);
|
|
1413
|
+
const isSelected = !isPending && selectedChatModelId === item.id;
|
|
1414
|
+
const card = document.createElement("div");
|
|
1415
|
+
card.className = `local-model-catalog-card${isSelected ? " is-selected" : ""}${isPending ? " is-pending" : ""}`;
|
|
1416
|
+
if (isPending && item.externalUrl) {
|
|
1417
|
+
card.title = item.externalUrl;
|
|
1418
|
+
}
|
|
1419
|
+
|
|
1420
|
+
const meta = document.createElement("div");
|
|
1421
|
+
meta.className = "local-model-catalog-meta";
|
|
1422
|
+
meta.innerHTML = `<div class="local-model-catalog-name" title="${item.name}">${item.name}</div>`;
|
|
1423
|
+
|
|
1424
|
+
const iconWrap = document.createElement("div");
|
|
1425
|
+
iconWrap.className = "local-model-slot-actions";
|
|
1426
|
+
const providerHtml = getProviderBadgeHtml(item.modelType, true);
|
|
1427
|
+
const pendingText = item.popupSizeText || "\uC900\uBE44\uC911";
|
|
1428
|
+
iconWrap.innerHTML = isPending
|
|
1429
|
+
? `<span class="local-model-pending-icon" aria-hidden="true"><i class="ri-time-line"></i></span><span class="local-model-size-pill is-pending">${pendingText}</span>`
|
|
1430
|
+
: `<span class="local-model-provider-button" aria-hidden="true">${providerHtml}</span><span class="local-model-size-pill">${item.popupSizeText || ""}</span>`;
|
|
1431
|
+
|
|
1432
|
+
if (shortcutSettingsOpen && !isPending) {
|
|
1433
|
+
const assignRow = document.createElement("div");
|
|
1434
|
+
assignRow.className = "local-model-assign-row";
|
|
1435
|
+
SLOT_ORDER.forEach((slotKey) => {
|
|
1436
|
+
const assignButton = document.createElement("button");
|
|
1437
|
+
assignButton.type = "button";
|
|
1438
|
+
const isSlotSelected = slotKey !== "code" && selectedChatModelId === item.id;
|
|
1439
|
+
assignButton.className = `local-model-slot-assign${isSlotSelected ? " is-active" : ""}`;
|
|
1440
|
+
assignButton.title = getShortcutText(slotKey);
|
|
1441
|
+
assignButton.setAttribute("aria-label", getShortcutText(slotKey));
|
|
1442
|
+
assignButton.innerHTML = `<i class="${SLOT_ICON_MAP[slotKey] || "ri-circle-line"}"></i>`;
|
|
1443
|
+
assignButton.addEventListener("click", function (event) {
|
|
1444
|
+
event.preventDefault();
|
|
1445
|
+
event.stopPropagation();
|
|
1446
|
+
if (String(slotKey) === "code") return;
|
|
1447
|
+
handleShortcutProfileChange(slotKey, item.id, {
|
|
1448
|
+
activateNow: false,
|
|
1449
|
+
showChangedToast: true
|
|
1450
|
+
});
|
|
1451
|
+
});
|
|
1452
|
+
assignRow.appendChild(assignButton);
|
|
1453
|
+
});
|
|
1454
|
+
iconWrap.appendChild(assignRow);
|
|
1455
|
+
}
|
|
1456
|
+
|
|
1457
|
+
card.addEventListener("click", function (event) {
|
|
1458
|
+
event.preventDefault();
|
|
1459
|
+
event.stopPropagation();
|
|
1460
|
+
if (isPending && item.externalUrl) {
|
|
1461
|
+
window.open(item.externalUrl, "_blank", "noopener");
|
|
1462
|
+
return;
|
|
1463
|
+
}
|
|
1464
|
+
handleShortcutProfileChange(activeTier, item.id, {
|
|
1465
|
+
activateNow: false,
|
|
1466
|
+
showChangedToast: false
|
|
1467
|
+
});
|
|
1468
|
+
});
|
|
1469
|
+
|
|
1470
|
+
card.appendChild(meta);
|
|
1471
|
+
card.appendChild(iconWrap);
|
|
1472
|
+
catalogGrid.appendChild(card);
|
|
1473
|
+
});
|
|
1474
|
+
container.appendChild(catalogGrid);
|
|
1475
|
+
}
|
|
1476
|
+
|
|
1477
|
+
function appendShortcutSelectors(container) {
|
|
1478
|
+
const currentProfiles = getResolvedLocalModelProfiles();
|
|
1479
|
+
const catalogItems = getCatalogItems();
|
|
1480
|
+
const grid = document.createElement("div");
|
|
1481
|
+
grid.className = "local-model-shortcut-select-grid";
|
|
1482
|
+
const selectedChatModelId = getStoredChatModelId();
|
|
1483
|
+
|
|
1484
|
+
SLOT_ORDER.forEach((slotKey) => {
|
|
1485
|
+
const row = document.createElement("div");
|
|
1486
|
+
row.className = "local-model-shortcut-select-row";
|
|
1487
|
+
|
|
1488
|
+
const label = document.createElement("div");
|
|
1489
|
+
label.className = "local-model-shortcut-label";
|
|
1490
|
+
label.textContent = getShortcutText(slotKey);
|
|
1491
|
+
|
|
1492
|
+
const select = document.createElement("select");
|
|
1493
|
+
select.className = "local-model-shortcut-select";
|
|
1494
|
+
const selectedId = slotKey === "code"
|
|
1495
|
+
? (currentProfiles[slotKey] ? currentProfiles[slotKey].modelId : "")
|
|
1496
|
+
: selectedChatModelId;
|
|
1497
|
+
|
|
1498
|
+
catalogItems.filter((item) => !(item.pending || item.externalUrl)).forEach((item) => {
|
|
1499
|
+
const option = document.createElement("option");
|
|
1500
|
+
option.value = item.id;
|
|
1501
|
+
option.textContent = item.name;
|
|
1502
|
+
if (item.id === selectedId) option.selected = true;
|
|
1503
|
+
select.appendChild(option);
|
|
1504
|
+
});
|
|
1505
|
+
|
|
1506
|
+
select.addEventListener("change", function (event) {
|
|
1507
|
+
if (String(slotKey) === "code") return;
|
|
1508
|
+
handleShortcutProfileChange(slotKey, String(event.target.value || ""));
|
|
1509
|
+
});
|
|
1510
|
+
|
|
1511
|
+
row.appendChild(label);
|
|
1512
|
+
row.appendChild(select);
|
|
1513
|
+
grid.appendChild(row);
|
|
1514
|
+
});
|
|
1515
|
+
|
|
1516
|
+
container.appendChild(grid);
|
|
1517
|
+
}
|
|
1518
|
+
|
|
1519
|
+
function buildShortcutMenu(wrapper) {
|
|
1520
|
+
if (!wrapper) return null;
|
|
1521
|
+
let panel = document.getElementById(MODEL_MENU_ID);
|
|
1522
|
+
if (!panel) {
|
|
1523
|
+
panel = document.createElement("div");
|
|
1524
|
+
panel.id = MODEL_MENU_ID;
|
|
1525
|
+
panel.className = "local-model-shortcut-panel";
|
|
1526
|
+
wrapper.appendChild(panel);
|
|
1527
|
+
}
|
|
1528
|
+
const topZone = document.getElementById("top-zone");
|
|
1529
|
+
if (topZone && topZone.clientHeight > 0) {
|
|
1530
|
+
const dynamicMaxHeight = Math.max(176, Math.min(236, topZone.clientHeight - 52));
|
|
1531
|
+
panel.style.height = `${dynamicMaxHeight}px`;
|
|
1532
|
+
panel.style.maxHeight = `${dynamicMaxHeight}px`;
|
|
1533
|
+
} else {
|
|
1534
|
+
panel.style.removeProperty("height");
|
|
1535
|
+
panel.style.removeProperty("max-height");
|
|
1536
|
+
}
|
|
1537
|
+
if (!panel.dataset.wheelBound) {
|
|
1538
|
+
panel.addEventListener("wheel", function (event) {
|
|
1539
|
+
event.stopPropagation();
|
|
1540
|
+
}, { passive: true });
|
|
1541
|
+
panel.dataset.wheelBound = "true";
|
|
1542
|
+
}
|
|
1543
|
+
const wasOpen = panel.classList.contains("is-open");
|
|
1544
|
+
panel.className = `local-model-shortcut-panel${shortcutSettingsOpen ? " is-assign-mode" : ""}${wasOpen ? " is-open" : ""}`;
|
|
1545
|
+
panel.innerHTML = "";
|
|
1546
|
+
const head = document.createElement("div");
|
|
1547
|
+
head.className = "local-model-shortcut-head";
|
|
1548
|
+
const activeTier = getActiveLocalModelTier();
|
|
1549
|
+
const filterRow = appendFilterRow(null, true);
|
|
1550
|
+
const tools = document.createElement("div");
|
|
1551
|
+
tools.className = "local-model-shortcut-tools";
|
|
1552
|
+
head.appendChild(filterRow);
|
|
1553
|
+
head.appendChild(tools);
|
|
1554
|
+
panel.appendChild(head);
|
|
1555
|
+
const scrollBox = document.createElement("div");
|
|
1556
|
+
scrollBox.className = "local-model-catalog-scroll";
|
|
1557
|
+
panel.appendChild(scrollBox);
|
|
1558
|
+
appendCatalogCards(scrollBox, activeTier);
|
|
1559
|
+
return panel;
|
|
1560
|
+
}
|
|
1561
|
+
|
|
1562
|
+
function toggleLocalModelShortcutMenu(event) {
|
|
1563
|
+
if (event) {
|
|
1564
|
+
event.preventDefault();
|
|
1565
|
+
event.stopPropagation();
|
|
1566
|
+
}
|
|
1567
|
+
const panel = document.getElementById(MODEL_MENU_ID);
|
|
1568
|
+
const trigger = document.getElementById("local-model-tier-menu-btn");
|
|
1569
|
+
if (!panel) return;
|
|
1570
|
+
const willOpen = !panel.classList.contains("is-open");
|
|
1571
|
+
closeLocalModelShortcutMenu();
|
|
1572
|
+
if (willOpen) {
|
|
1573
|
+
panel.classList.add("is-open");
|
|
1574
|
+
if (trigger) trigger.classList.add("is-open");
|
|
1575
|
+
}
|
|
1576
|
+
}
|
|
1577
|
+
|
|
1578
|
+
syncLocalModelTierVisibility = window.syncLocalModelTierVisibility = function (mode) {
|
|
1579
|
+
const wrapper = document.getElementById("local-model-tier-wrapper");
|
|
1580
|
+
if (!wrapper) return;
|
|
1581
|
+
const effectiveMode = String(mode || (document.body && document.body.getAttribute("data-ui-mode")) || currentMode || "chat").toLowerCase();
|
|
1582
|
+
const shouldShow = effectiveMode === "chat" || effectiveMode === "code";
|
|
1583
|
+
wrapper.style.display = shouldShow ? "block" : "none";
|
|
1584
|
+
if (!shouldShow) closeLocalModelShortcutMenu();
|
|
1585
|
+
};
|
|
1586
|
+
|
|
1587
|
+
setLocalModelTier = window.setLocalModelTier = function (tierKey) {
|
|
1588
|
+
const profiles = getLocalModelProfiles();
|
|
1589
|
+
const nextTier = String(tierKey || "").trim().toLowerCase();
|
|
1590
|
+
if (!profiles[nextTier]) return;
|
|
1591
|
+
localStorage.setItem(getLocalModelTierStorageKey(), nextTier);
|
|
1592
|
+
localStorage.setItem(getLocalModelTierUserSetKey(), "true");
|
|
1593
|
+
applyLocalModelProfileToConfig();
|
|
1594
|
+
isLocalActive = true;
|
|
1595
|
+
isModelLoaded = false;
|
|
1596
|
+
localWasmEngine = null;
|
|
1597
|
+
if (typeof setLocalRuntimeState === "function") setLocalRuntimeState(null);
|
|
1598
|
+
isModelDownloaded = localStorage.getItem(getLocalDownloadStorageKey()) === "true";
|
|
1599
|
+
updateLocalBtnState();
|
|
1600
|
+
renderLocalModelTierSelector();
|
|
1601
|
+
syncLocalModelTierVisibility();
|
|
1602
|
+
if (typeof showToast === "function") {
|
|
1603
|
+
showToast(getTierSwitchedToast(nextTier));
|
|
1604
|
+
}
|
|
1605
|
+
if (isModelDownloaded) {
|
|
1606
|
+
startDownload();
|
|
1607
|
+
return;
|
|
1608
|
+
}
|
|
1609
|
+
isLocalActive = true;
|
|
1610
|
+
syncLocalModelTierVisibility();
|
|
1611
|
+
startDownload();
|
|
1612
|
+
};
|
|
1613
|
+
|
|
1614
|
+
renderLocalModelTierSelector = window.renderLocalModelTierSelector = function () {
|
|
1615
|
+
ensureShortcutMenuStyle();
|
|
1616
|
+
const wrapper = document.getElementById("local-model-tier-wrapper");
|
|
1617
|
+
const list = document.getElementById("local-model-tier-list");
|
|
1618
|
+
if (!wrapper || !list) return;
|
|
1619
|
+
const profiles = getLocalModelProfiles();
|
|
1620
|
+
const catalog = getModelCatalog();
|
|
1621
|
+
const selectedChatModelId = getStoredChatModelId();
|
|
1622
|
+
const selectedChatModel = selectedChatModelId && catalog[selectedChatModelId] ? catalog[selectedChatModelId] : null;
|
|
1623
|
+
const activeTier = getActiveLocalModelTier();
|
|
1624
|
+
const orderedTiers = getOrderedLocalModelTierKeys().filter((tier) => !!profiles[tier]);
|
|
1625
|
+
list.innerHTML = "";
|
|
1626
|
+
orderedTiers.forEach((tier) => {
|
|
1627
|
+
const button = document.createElement("button");
|
|
1628
|
+
button.type = "button";
|
|
1629
|
+
button.className = `local-model-tier-btn${tier === activeTier ? " active" : ""}`;
|
|
1630
|
+
const displayLabel = getLocalizedLocalModelTierLabel(tier);
|
|
1631
|
+
const resolvedModelName = tier === "code"
|
|
1632
|
+
? (profiles[tier] && profiles[tier].modelName ? profiles[tier].modelName : displayLabel)
|
|
1633
|
+
: (selectedChatModel && selectedChatModel.name ? selectedChatModel.name : (profiles[tier] && profiles[tier].modelName ? profiles[tier].modelName : displayLabel));
|
|
1634
|
+
button.classList.add("is-icon");
|
|
1635
|
+
button.innerHTML = getLocalModelTierIconMarkup(tier);
|
|
1636
|
+
button.title = resolvedModelName;
|
|
1637
|
+
button.setAttribute("aria-label", resolvedModelName);
|
|
1638
|
+
button.dataset.tier = tier;
|
|
1639
|
+
button.setAttribute("aria-pressed", tier === activeTier ? "true" : "false");
|
|
1640
|
+
button.addEventListener("click", function (event) {
|
|
1641
|
+
event.preventDefault();
|
|
1642
|
+
event.stopPropagation();
|
|
1643
|
+
closeLocalModelShortcutMenu();
|
|
1644
|
+
setLocalModelTier(tier);
|
|
1645
|
+
});
|
|
1646
|
+
list.appendChild(button);
|
|
1647
|
+
});
|
|
1648
|
+
const localButton = document.createElement("button");
|
|
1649
|
+
localButton.type = "button";
|
|
1650
|
+
localButton.id = "btn-download";
|
|
1651
|
+
localButton.className = "local-model-tier-menu-btn";
|
|
1652
|
+
localButton.title = "Local Mode";
|
|
1653
|
+
localButton.setAttribute("aria-label", "Local Mode");
|
|
1654
|
+
localButton.innerHTML = '<i class="ri-ghost-4-line text-[14px]"></i>';
|
|
1655
|
+
localButton.addEventListener("click", function (event) {
|
|
1656
|
+
event.preventDefault();
|
|
1657
|
+
event.stopPropagation();
|
|
1658
|
+
if (typeof handleLocalToggle === "function") {
|
|
1659
|
+
handleLocalToggle();
|
|
1660
|
+
}
|
|
1661
|
+
});
|
|
1662
|
+
list.appendChild(localButton);
|
|
1663
|
+
|
|
1664
|
+
const gpuButton = document.createElement("button");
|
|
1665
|
+
gpuButton.type = "button";
|
|
1666
|
+
gpuButton.id = "btn-webgpu";
|
|
1667
|
+
gpuButton.className = "local-model-tier-menu-btn";
|
|
1668
|
+
gpuButton.title = "WebGPU";
|
|
1669
|
+
gpuButton.setAttribute("aria-label", "WebGPU");
|
|
1670
|
+
gpuButton.innerHTML = '<i class="ri-cpu-line text-[14px]"></i>';
|
|
1671
|
+
gpuButton.addEventListener("click", function (event) {
|
|
1672
|
+
event.preventDefault();
|
|
1673
|
+
event.stopPropagation();
|
|
1674
|
+
if (typeof handleWebGPUToggle === "function") {
|
|
1675
|
+
handleWebGPUToggle();
|
|
1676
|
+
}
|
|
1677
|
+
});
|
|
1678
|
+
list.appendChild(gpuButton);
|
|
1679
|
+
|
|
1680
|
+
const menuButton = document.createElement("button");
|
|
1681
|
+
menuButton.type = "button";
|
|
1682
|
+
menuButton.id = "local-model-tier-menu-btn";
|
|
1683
|
+
menuButton.className = "local-model-tier-menu-btn";
|
|
1684
|
+
menuButton.title = getShortcutText("menu");
|
|
1685
|
+
menuButton.setAttribute("aria-label", getShortcutText("menu"));
|
|
1686
|
+
menuButton.innerHTML = '<i class="ri-menu-4-line text-[14px]"></i>';
|
|
1687
|
+
menuButton.addEventListener("click", toggleLocalModelShortcutMenu);
|
|
1688
|
+
list.appendChild(menuButton);
|
|
1689
|
+
buildShortcutMenu(wrapper);
|
|
1690
|
+
syncLocalModelTierVisibility();
|
|
1691
|
+
};
|
|
1692
|
+
|
|
1693
|
+
document.addEventListener("click", function (event) {
|
|
1694
|
+
const wrapper = document.getElementById("local-model-tier-wrapper");
|
|
1695
|
+
if (!wrapper) return;
|
|
1696
|
+
if (wrapper.contains(event.target)) return;
|
|
1697
|
+
closeLocalModelShortcutMenu();
|
|
1698
|
+
});
|
|
1699
|
+
|
|
1700
|
+
window.startCharacterImageChat = function(id, imageUrl, prompt, personaName, personaPersonality) {
|
|
1701
|
+
console.log("๐ [์บ๋ฆญํฐ์ฑ ์์] ๋ฐฐ๊ฒฝ ๋ฌดํ ๋ฐฉ์ด ๋ชจ๋ ์๋");
|
|
1702
|
+
|
|
1703
|
+
// 1. ์บ๋ฆญํฐ ์ธ์
๋ฐ์ดํฐ ์์ฑ
|
|
1704
|
+
const rawPayload = { name: personaName, personality: personaPersonality };
|
|
1705
|
+
const sessionSeed = { sourceId: id, imageUrl: imageUrl, prompt: prompt };
|
|
1706
|
+
|
|
1707
|
+
let normalizedPersona = { name: personaName || "Character", personality: personaPersonality || "" };
|
|
1708
|
+
if (typeof normalizeCharacterPersonaPayload === 'function') {
|
|
1709
|
+
normalizedPersona = normalizeCharacterPersonaPayload(rawPayload, prompt, personaName, sessionSeed);
|
|
1710
|
+
}
|
|
1711
|
+
|
|
1712
|
+
const charSession = {
|
|
1713
|
+
active: true,
|
|
1714
|
+
sourceId: id,
|
|
1715
|
+
imageUrl: imageUrl,
|
|
1716
|
+
prompt: prompt,
|
|
1717
|
+
persona: normalizedPersona
|
|
1718
|
+
};
|
|
1719
|
+
|
|
1720
|
+
if (!window.ISAI_CHAT_PAGE) {
|
|
1721
|
+
try {
|
|
1722
|
+
sessionStorage.setItem('isai_chat_session_v1', JSON.stringify(charSession));
|
|
1723
|
+
} catch (error) {}
|
|
1724
|
+
const redirectUrl = new URL('/chat.php', window.location.origin);
|
|
1725
|
+
try {
|
|
1726
|
+
const cdnMode = new URL(window.location.href).searchParams.get('jsdelivr');
|
|
1727
|
+
if (cdnMode !== null) redirectUrl.searchParams.set('jsdelivr', cdnMode);
|
|
1728
|
+
} catch (error) {}
|
|
1729
|
+
window.location.href = redirectUrl.toString();
|
|
1730
|
+
return;
|
|
1731
|
+
}
|
|
1732
|
+
|
|
1733
|
+
if (typeof setCharacterChatSession === 'function') {
|
|
1734
|
+
setCharacterChatSession(charSession);
|
|
1735
|
+
}
|
|
1736
|
+
|
|
1737
|
+
// ๐ 2. [ํต์ฌ] ๋ฐฐ๊ฒฝ ๊ฐ์ ์ฃผ์
๋ฐ ๋ฌดํ ๋ฐฉ์ด (Observer)
|
|
1738
|
+
if (imageUrl) {
|
|
1739
|
+
// ๋ฐ์ดํ ์ค๋ฅ ๋ฐฉ์ง
|
|
1740
|
+
const safeUrl = imageUrl.replace(/"/g, "%22").replace(/'/g, "%27");
|
|
1741
|
+
const bgValue = `linear-gradient(180deg, rgba(0,0,0,0.4) 0%, rgba(0,0,0,0.85) 100%), url("${safeUrl}")`;
|
|
1742
|
+
|
|
1743
|
+
// [๋ฐฉ์ด 1] ์ ์ญ CSS ํญ๊ฒฉ (Alpine.js๊ฐ ์ธ๋ผ์ธ ์คํ์ผ์ ๋ฐ์ด๋ฒ๋ ค๋ ์ด์๋จ์)
|
|
1744
|
+
let styleTag = document.getElementById('isai-char-bg-force');
|
|
1745
|
+
if (!styleTag) {
|
|
1746
|
+
styleTag = document.createElement('style');
|
|
1747
|
+
styleTag.id = 'isai-char-bg-force';
|
|
1748
|
+
document.head.appendChild(styleTag);
|
|
1749
|
+
}
|
|
1750
|
+
styleTag.innerHTML = `
|
|
1751
|
+
body div#chat-box {
|
|
1752
|
+
background: ${bgValue} !important;
|
|
1753
|
+
background-color: #000000 !important;
|
|
1754
|
+
background-size: contain !important;
|
|
1755
|
+
background-position: center top !important;
|
|
1756
|
+
background-repeat: no-repeat !important;
|
|
1757
|
+
}
|
|
1758
|
+
`;
|
|
1759
|
+
|
|
1760
|
+
// ๋ฐฐ๊ฒฝ์ ์ง์ ๊ฝ๋ ํจ์
|
|
1761
|
+
const enforceBackground = () => {
|
|
1762
|
+
const cb = document.getElementById('chat-box');
|
|
1763
|
+
if(cb) {
|
|
1764
|
+
// 1. background ์ ์ฒด ๊ฐ์ ๊ฐ์ฅ ๋จผ์ ๋ฃ์ต๋๋ค. (์ฌ๊ธฐ์ ํ๋ฒ ์ด๊ธฐํ ๋จ)
|
|
1765
|
+
cb.style.setProperty('background', bgValue, 'important');
|
|
1766
|
+
|
|
1767
|
+
// 2. ๊ทธ ์๋์ ์ธ๋ถ ์์ฑ์ ์ ์ด์ฃผ์ด์ผ ๋ฌด์๋์ง ์๊ณ ๊ฝํ๋๋ค.
|
|
1768
|
+
cb.style.setProperty('background-repeat', 'no-repeat', 'important'); // ๊ฐ๋ก/์ธ๋ก ๋ฐ๋ณต ๊ธ์ง
|
|
1769
|
+
cb.style.setProperty('background-size', 'auto 100%', 'important'); // ๊ฐ๋ก ์๋, ์ธ๋ก(๋์ด) 100%
|
|
1770
|
+
cb.style.setProperty('background-position', 'center top', 'important');
|
|
1771
|
+
}
|
|
1772
|
+
};
|
|
1773
|
+
|
|
1774
|
+
// ์ฆ์ 1์ฐจ ์ ์ฉ
|
|
1775
|
+
enforceBackground();
|
|
1776
|
+
|
|
1777
|
+
// [๋ฐฉ์ด 2] ์คํ์ผ ๊ฐ์์(Observer) ๋ถ์ฐฉ: ๋๊ตฐ๊ฐ ๋ฐฐ๊ฒฝ์ ์ง์ฐ๋ฉด ์ฆ์ ๋ฐ๊ฒฉ
|
|
1778
|
+
const chatBoxEl = document.getElementById('chat-box');
|
|
1779
|
+
if (chatBoxEl) {
|
|
1780
|
+
if (window.__bgObserver) window.__bgObserver.disconnect(); // ๊ธฐ์กด ๊ฐ์์ ์ด๊ธฐํ
|
|
1781
|
+
|
|
1782
|
+
window.__bgObserver = new MutationObserver(() => {
|
|
1783
|
+
// ๋ฌดํ ๋ฃจํ์ ๋น ์ง์ง ์๊ฒ ๊ฐ์์๋ฅผ ์ ๊น ๋๊ณ -> ๋ฐฐ๊ฒฝ ์น ํ๊ณ -> ๋ค์ ์ผฌ
|
|
1784
|
+
window.__bgObserver.disconnect();
|
|
1785
|
+
enforceBackground();
|
|
1786
|
+
window.__bgObserver.observe(chatBoxEl, { attributes: true, attributeFilter: ['style', 'class'] });
|
|
1787
|
+
});
|
|
1788
|
+
|
|
1789
|
+
window.__bgObserver.observe(chatBoxEl, { attributes: true, attributeFilter: ['style', 'class'] });
|
|
1790
|
+
console.log("๐ก๏ธ ๋ฐฐ๊ฒฝ ๊ฐ์์(Observer) ๋ถ์ฐฉ ์๋ฃ. ์ด์ ์๋ฌด๋ ๋ฐฐ๊ฒฝ์ ์ง์ธ ์ ์์ต๋๋ค.");
|
|
1791
|
+
}
|
|
1792
|
+
}
|
|
1793
|
+
|
|
1794
|
+
// 3. AI ๋ ์ค์
|
|
1795
|
+
try {
|
|
1796
|
+
const sysPromptEl = document.getElementById("assistant-system-prompt");
|
|
1797
|
+
if (sysPromptEl && typeof buildCharacterChatSystemPrompt === 'function') {
|
|
1798
|
+
sysPromptEl.value = buildCharacterChatSystemPrompt(charSession);
|
|
1799
|
+
}
|
|
1800
|
+
} catch(e) {}
|
|
1801
|
+
|
|
1802
|
+
// 4. ๋ชจ๋ฌ์ฐฝ ๋ซ๊ธฐ ๋ฐ ์ธ์ฌ๋ง
|
|
1803
|
+
try {
|
|
1804
|
+
if (typeof closeImageModal === 'function') closeImageModal();
|
|
1805
|
+
if (typeof closeCharacterShareModal === 'function') closeCharacterShareModal();
|
|
1806
|
+
|
|
1807
|
+
if (charSession.persona && charSession.persona.openingLine) {
|
|
1808
|
+
setTimeout(() => {
|
|
1809
|
+
if (typeof appendMsg === 'function') appendMsg("ai", charSession.persona.openingLine);
|
|
1810
|
+
}, 500);
|
|
1811
|
+
}
|
|
1812
|
+
} catch(e) {}
|
|
1813
|
+
|
|
1814
|
+
// 5. ๋ชจ๋ ๋ณ๊ฒฝ
|
|
1815
|
+
try {
|
|
1816
|
+
if (typeof setMode === 'function') setMode('chat');
|
|
1817
|
+
} catch (e) {
|
|
1818
|
+
console.warn("โ ๏ธ setMode ์๋ฌ:", e);
|
|
1819
|
+
}
|
|
1820
|
+
};
|
|
1821
|
+
|
|
1822
|
+
// ๋งํฌ๋ก ์ง์ ๋ค์ด์์ ๋ ์คํ
|
|
1823
|
+
document.addEventListener("DOMContentLoaded", function () {
|
|
1824
|
+
setTimeout(function () {
|
|
1825
|
+
if (typeof renderLocalModelTierSelector === "function") renderLocalModelTierSelector();
|
|
1826
|
+
if (typeof syncLocalModelTierVisibility === "function") syncLocalModelTierVisibility();
|
|
1827
|
+
|
|
1828
|
+
if (!window.INITIAL_CHAR_SESSION && window.ISAI_CHAT_PAGE) {
|
|
1829
|
+
try {
|
|
1830
|
+
const queued = sessionStorage.getItem('isai_chat_session_v1');
|
|
1831
|
+
if (queued) {
|
|
1832
|
+
window.INITIAL_CHAR_SESSION = JSON.parse(queued);
|
|
1833
|
+
sessionStorage.removeItem('isai_chat_session_v1');
|
|
1834
|
+
}
|
|
1835
|
+
} catch (error) {}
|
|
1836
|
+
}
|
|
1837
|
+
|
|
1838
|
+
if (window.INITIAL_CHAR_SESSION && window.INITIAL_CHAR_SESSION.active) {
|
|
1839
|
+
window.startCharacterImageChat(
|
|
1840
|
+
window.INITIAL_CHAR_SESSION.sourceId,
|
|
1841
|
+
window.INITIAL_CHAR_SESSION.imageUrl,
|
|
1842
|
+
window.INITIAL_CHAR_SESSION.prompt,
|
|
1843
|
+
window.INITIAL_CHAR_SESSION.persona.name,
|
|
1844
|
+
window.INITIAL_CHAR_SESSION.persona.personality
|
|
1845
|
+
);
|
|
1846
|
+
}
|
|
1847
|
+
}, 0);
|
|
1848
|
+
});
|
|
1849
|
+
})();
|