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.
Files changed (51) hide show
  1. package/README.md +35 -0
  2. package/cdn/api.js +19 -0
  3. package/cdn/character.js +254 -0
  4. package/cdn/chat.js +33 -0
  5. package/cdn/code-editor.js +1131 -0
  6. package/cdn/community-compose.js +270 -0
  7. package/cdn/games/2048/index.html +12 -0
  8. package/cdn/games/breakout/index.html +13 -0
  9. package/cdn/games/clicker/index.html +26 -0
  10. package/cdn/games/flappy/index.html +11 -0
  11. package/cdn/games/memory/index.html +34 -0
  12. package/cdn/games/pong/index.html +13 -0
  13. package/cdn/games/reaction/index.html +38 -0
  14. package/cdn/games/runner/index.html +11 -0
  15. package/cdn/games/snake/index.html +11 -0
  16. package/cdn/games/tetris/index.html +14 -0
  17. package/cdn/games/whack/index.html +8 -0
  18. package/cdn/go.js +126 -0
  19. package/cdn/go2.js +127 -0
  20. package/cdn/header3_behavior.js +1167 -0
  21. package/cdn/header3_layout.js +1004 -0
  22. package/cdn/header3_layout.js.bak +1004 -0
  23. package/cdn/header3_style.css +3524 -0
  24. package/cdn/header3_style.css.bak +3514 -0
  25. package/cdn/lang.js +198 -0
  26. package/cdn/loading.js +143 -0
  27. package/cdn/loading2.js +144 -0
  28. package/cdn/local-model.js +2941 -0
  29. package/cdn/main.js +4 -0
  30. package/cdn/main_asset.js +1849 -0
  31. package/cdn/main_asset.js.bak +6999 -0
  32. package/cdn/main_index.css +287 -0
  33. package/cdn/re_board3.css +733 -0
  34. package/cdn/re_board3.js +734 -0
  35. package/cdn/re_chat_tts.js +652 -0
  36. package/cdn/re_local_runtime.js +2246 -0
  37. package/cdn/re_local_runtime.js.bak +2246 -0
  38. package/cdn/re_share.js +577 -0
  39. package/cdn/re_voice.js +542 -0
  40. package/cdn/utils.js +36 -0
  41. package/cdn/view.js +321 -0
  42. package/header3_behavior.js +804 -0
  43. package/header3_layout.js +998 -0
  44. package/header3_style.css +2740 -0
  45. package/index.js +0 -0
  46. package/lang.js +179 -0
  47. package/main_asset.js +2416 -0
  48. package/main_index.css +274 -0
  49. package/package.json +14 -0
  50. package/re_chat_tts.js +1419 -0
  51. package/re_voice.js +430 -0
package/re_chat_tts.js ADDED
@@ -0,0 +1,1419 @@
1
+ (function () {
2
+ const ASSISTANT_ICON = "https://api.dicebear.com/7.x/identicon/svg?seed=IP&backgroundColor=transparent";
3
+ const USER_ICON = "https://api.dicebear.com/7.x/identicon/svg?seed=IP&backgroundColor=transparent";
4
+ const SERVER_I18N = window.ISAI_SERVER_I18N || {};
5
+
6
+ const DEFAULT_ASSISTANT = {
7
+ name: "ISAI",
8
+ icon: ASSISTANT_ICON,
9
+ };
10
+
11
+ const DEFAULT_WELCOME_MESSAGES = {
12
+ ko: "\uc548\ub155\ud558\uc138\uc694. \ubb34\uc5c7\uc744 \ub3c4\uc640\ub4dc\ub9b4\uae4c\uc694? \ud83d\ude0a",
13
+ en: "Hello! How can I help you? 😊",
14
+ ja: "こんにちは。どのようにお手伝いできますか? 😊",
15
+ zh: "你好!我可以帮你做什么? 😊",
16
+ "zh-tw": "你好!我可以幫你做什麼? 😊",
17
+ es: "Hola, ¿en qué puedo ayudarte? 😊",
18
+ fr: "Bonjour ! Comment puis-je vous aider ? 😊",
19
+ de: "Hallo! Wie kann ich Ihnen helfen? 😊",
20
+ pt: "Olá! Como posso ajudar? 😊",
21
+ ru: "Здравствуйте! Чем я могу помочь? 😊",
22
+ ar: "مرحبًا! كيف يمكنني مساعدتك؟ 😊",
23
+ hi: "नमस्ते! मैं आपकी कैसे मदद कर सकता हूँ? 😊",
24
+ id: "Halo! Ada yang bisa saya bantu? 😊",
25
+ vi: "Xin chào! Tôi có thể giúp gì cho bạn? 😊",
26
+ th: "สวัสดี! มีอะไรให้ฉันช่วยได้บ้าง? 😊",
27
+ tr: "Merhaba! Size nasıl yardımcı olabilirim? 😊",
28
+ it: "Ciao! Come posso aiutarti? 😊",
29
+ nl: "Hallo! Waarmee kan ik je helpen? 😊",
30
+ pl: "Czesc! W czym moge pomóc? 😊"
31
+ };
32
+
33
+ const MODE_META = {
34
+ chat: {
35
+ badge: "Chat",
36
+ kicker: "ISAI Assistant",
37
+ title: "Ready to help.",
38
+ subtitle: "Chat, search, and generated results stay inside this card.",
39
+ hint: "Type here to keep the conversation and outputs inside this card.",
40
+ placeholder: ""
41
+ },
42
+ search: {
43
+ badge: "Search",
44
+ kicker: "Search Mode",
45
+ title: "Search the web",
46
+ subtitle: "Summaries and source-backed answers stay in the same chat card.",
47
+ hint: "Ask for news, links, or a quick web summary.",
48
+ placeholder: "What should I search for?"
49
+ },
50
+ image: {
51
+ badge: "Image",
52
+ kicker: "Image Mode",
53
+ title: "Create an image",
54
+ subtitle: "Describe a scene and keep the preview in this chat workspace.",
55
+ hint: "Describe the image you want to generate.",
56
+ placeholder: "Describe the image you want..."
57
+ },
58
+ video: {
59
+ badge: "Video",
60
+ kicker: "Video Mode",
61
+ title: "Create a motion clip",
62
+ subtitle: "Storyboard-style generations stay in the same conversation.",
63
+ hint: "Describe the motion or scene for the clip.",
64
+ placeholder: "Describe the video you want..."
65
+ },
66
+ community: {
67
+ badge: "Community",
68
+ kicker: "Community Mode",
69
+ title: "Post to the board",
70
+ subtitle: "Write a forum-style post and attach an image if you want.",
71
+ hint: "Add a nickname, password, and optional image before posting.",
72
+ placeholder: "Write your community post..."
73
+ },
74
+ code: {
75
+ badge: "Code",
76
+ kicker: "Code Mode",
77
+ title: "Build with code",
78
+ subtitle: "Code responses stay paired with the workspace panel on the right.",
79
+ hint: "Describe the code or file you want to generate.",
80
+ placeholder: "Describe the code you want..."
81
+ },
82
+ blog: {
83
+ badge: "Blog",
84
+ kicker: "Blog Mode",
85
+ title: "Draft a blog post",
86
+ subtitle: "Long-form writing and image tags stay in this same thread.",
87
+ hint: "Give the topic, tone, or structure you want for the post.",
88
+ placeholder: "What should the blog post be about?"
89
+ },
90
+ voice: {
91
+ badge: "Voice",
92
+ kicker: "Voice Mode",
93
+ title: "Talk naturally",
94
+ subtitle: "Use the mic button to speak and keep the transcript in the card.",
95
+ hint: "Tap the mic button to start or pause voice capture.",
96
+ placeholder: "Voice mode listens through the mic."
97
+ },
98
+ translate: {
99
+ badge: "Translate",
100
+ kicker: "Translate Mode",
101
+ title: "Translate both ways",
102
+ subtitle: "Pick left and right languages, then translate in the chat card.",
103
+ hint: "Use the left button for the source side and the right button for the target side.",
104
+ placeholder: "Enter the text you want to translate..."
105
+ },
106
+ settings: {
107
+ badge: "Settings",
108
+ kicker: "Voice Settings",
109
+ title: "Tune voice conversation",
110
+ subtitle: "Choose language, voice, speed, and tone for spoken replies.",
111
+ hint: "These settings apply to voice chat and spoken responses.",
112
+ placeholder: ""
113
+ },
114
+ music: {
115
+ badge: "Music",
116
+ kicker: "Music Mode",
117
+ title: "Generate audio",
118
+ subtitle: "Music clips are rendered and returned in the same conversation.",
119
+ hint: "Describe the mood, instruments, or energy for the track.",
120
+ placeholder: "Describe the music you want..."
121
+ },
122
+ app: {
123
+ badge: "Apps",
124
+ kicker: "Shortcut Mode",
125
+ title: "Browse shortcuts",
126
+ subtitle: "Open saved shortcuts and run lightweight app flows from here.",
127
+ hint: "Search the shortcut list or open the app panel from the side rail.",
128
+ placeholder: "Search shortcuts..."
129
+ }
130
+ };
131
+
132
+ Object.keys(SERVER_I18N.modeMeta || {}).forEach((modeKey) => {
133
+ MODE_META[modeKey] = Object.assign({}, MODE_META[modeKey] || {}, SERVER_I18N.modeMeta[modeKey]);
134
+ });
135
+
136
+ const TTS_TEXT = Object.assign({
137
+ previewLabel: "Preview Voice",
138
+ previewHelper: "Use your browser voice to preview settings.",
139
+ failedToast: "Voice preview failed",
140
+ errorPrefix: "TTS failed:"
141
+ }, SERVER_I18N.ttsText || {});
142
+
143
+ const VOICE_SETTINGS_STORAGE_KEY = "ISAI_VOICE_SETTINGS";
144
+ const ASSISTANT_SETTINGS_STORAGE_KEY = "ISAI_ASSISTANT_SETTINGS";
145
+ const RECENT_MODE_STORAGE_KEY = "ISAI_RECENT_MODES";
146
+ const RECENT_MODE_LIMIT = 3;
147
+ const TRACKED_RECENT_MODES = ["chat", "search", "image", "video", "community", "code", "blog", "voice", "translate", "music"];
148
+ const DEFAULT_TTS_ENGINE = "browser";
149
+ const DEFAULT_TTS_VOICE = "";
150
+ const DEFAULT_TTS_STEPS = 1;
151
+ const DEFAULT_TTS_SPEED = 1;
152
+ const DEFAULT_TTS_VOLUME = 1;
153
+ const DEFAULT_ASSISTANT_TONE = "friendly";
154
+ const TTS_ENGINES = [
155
+ { id: "browser", label: "Browser Voice", chip: "SYS", icon: "ri-volume-up-line", description: "Use the browser built-in voice." }
156
+ ];
157
+
158
+ const SUPER_TTS_LANGUAGES = [
159
+ { id: "ko", label: "Korean", chip: "KO", icon: "ri-global-line" },
160
+ { id: "en", label: "English", chip: "EN", icon: "ri-global-line" },
161
+ { id: "ja", label: "Japanese", chip: "JA", icon: "ri-global-line" },
162
+ { id: "zh", label: "Chinese", chip: "ZH", icon: "ri-global-line" },
163
+ { id: "es", label: "Spanish", chip: "ES", icon: "ri-global-line" },
164
+ { id: "pt", label: "Portuguese", chip: "PT", icon: "ri-global-line" },
165
+ { id: "fr", label: "French", chip: "FR", icon: "ri-global-line" },
166
+ { id: "de", label: "German", chip: "DE", icon: "ri-global-line" },
167
+ { id: "ru", label: "Russian", chip: "RU", icon: "ri-global-line" }
168
+ ];
169
+
170
+ const ASSISTANT_TONES = [
171
+ { id: "friendly", label: "Friendly", chip: "FR", icon: "ri-emotion-happy-line", instruction: "Respond in a warm, friendly, and supportive tone." },
172
+ { id: "natural", label: "Natural", chip: "NA", icon: "ri-leaf-line", instruction: "Respond in a natural, balanced, and clear conversational tone." },
173
+ { id: "concise", label: "Concise", chip: "CO", icon: "ri-focus-3-line", instruction: "Respond concisely, directly, and with minimal filler." },
174
+ { id: "professional", label: "Professional", chip: "PR", icon: "ri-briefcase-4-line", instruction: "Respond in a polished, professional, and structured tone." },
175
+ { id: "playful", label: "Playful", chip: "PL", icon: "ri-sparkling-line", instruction: "Respond with a light, lively, and playful tone while staying clear." }
176
+ ];
177
+
178
+ let activeTtsLanguage = detectBrowserLanguage();
179
+ let activeTtsEngine = DEFAULT_TTS_ENGINE;
180
+ let activeTtsVoiceId = DEFAULT_TTS_VOICE;
181
+ let activeAssistantToneId = DEFAULT_ASSISTANT_TONE;
182
+ let activePreviewUtterance = null;
183
+
184
+ function escapeHtml(value) {
185
+ return String(value ?? "")
186
+ .replace(/&/g, "&")
187
+ .replace(/</g, "&lt;")
188
+ .replace(/>/g, "&gt;")
189
+ .replace(/"/g, "&quot;")
190
+ .replace(/'/g, "&#39;");
191
+ }
192
+
193
+ function formatTemplate(template, replacements = {}) {
194
+ return Object.keys(replacements).reduce((message, key) => {
195
+ return message.replace(new RegExp(`\\{${key}\\}`, "g"), String(replacements[key]));
196
+ }, String(template ?? ""));
197
+ }
198
+
199
+ function isImageErrorMessage(value) {
200
+ return /^Image Error:/i.test(String(value ?? "").trim());
201
+ }
202
+
203
+ function imageErrorIconHtml(message) {
204
+ const safeMessage = escapeHtml(String(message ?? "").trim() || "Image generation failed");
205
+ 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>`;
206
+ }
207
+
208
+ function loadRecentModes() {
209
+ try {
210
+ const parsed = JSON.parse(localStorage.getItem(RECENT_MODE_STORAGE_KEY) || "[]");
211
+ if (!Array.isArray(parsed)) return [];
212
+ return parsed.filter((mode) => TRACKED_RECENT_MODES.includes(mode));
213
+ } catch (error) {
214
+ return [];
215
+ }
216
+ }
217
+
218
+ function saveRecentModes(modes) {
219
+ try {
220
+ localStorage.setItem(RECENT_MODE_STORAGE_KEY, JSON.stringify(modes.slice(0, 8)));
221
+ } catch (error) {}
222
+ }
223
+
224
+ function rememberMode(mode) {
225
+ if (!TRACKED_RECENT_MODES.includes(mode)) return;
226
+ const merged = [mode].concat(loadRecentModes().filter((item) => item !== mode));
227
+ saveRecentModes(merged);
228
+ }
229
+
230
+ function syncRecentModePadding(count) {
231
+ const inputField = document.getElementById("chat-input-field");
232
+ if (!inputField) return;
233
+ inputField.style.setProperty("padding-right", "0px", "important");
234
+ }
235
+
236
+ function renderRecentModeActions(currentModeValue = getCurrentMode()) {
237
+ const container = document.getElementById("recent-mode-actions");
238
+ if (!container) return;
239
+
240
+ const modes = loadRecentModes()
241
+ .filter((mode) => mode !== currentModeValue)
242
+ .slice(0, RECENT_MODE_LIMIT);
243
+
244
+ if (modes.length === 0) {
245
+ container.innerHTML = "";
246
+ container.classList.add("hidden");
247
+ syncRecentModePadding(0);
248
+ return;
249
+ }
250
+
251
+ container.innerHTML = modes.map((mode) => {
252
+ const sourceButton = document.getElementById(`btn-${mode}`);
253
+ const iconHtml = sourceButton ? sourceButton.innerHTML : `<span class="text-[11px] font-bold uppercase">${escapeHtml(mode.slice(0, 2))}</span>`;
254
+ const label = sourceButton?.getAttribute("title")
255
+ || sourceButton?.getAttribute("aria-label")
256
+ || mode;
257
+ return `<button type="button" class="input-action-btn recent-mode-btn" data-mode="${escapeHtml(mode)}" title="${escapeHtml(label)}" aria-label="${escapeHtml(label)}">${iconHtml}</button>`;
258
+ }).join("");
259
+
260
+ container.querySelectorAll("[data-mode]").forEach((button) => {
261
+ button.addEventListener("click", (event) => {
262
+ event.preventDefault();
263
+ event.stopPropagation();
264
+ const mode = button.dataset.mode;
265
+ if (mode && typeof setMode === "function") {
266
+ setMode(mode);
267
+ }
268
+ });
269
+ });
270
+
271
+ container.classList.remove("hidden");
272
+ syncRecentModePadding(modes.length);
273
+ }
274
+
275
+ function currentAppRef() {
276
+ if (typeof activeApp !== "undefined" && activeApp) return activeApp;
277
+ if (window.activeApp) return window.activeApp;
278
+ return null;
279
+ }
280
+
281
+ function getCurrentMode() {
282
+ if (typeof currentMode !== "undefined" && currentMode) return currentMode;
283
+ if (typeof selectedMode !== "undefined" && selectedMode) return selectedMode;
284
+ if (window.currentMode) return window.currentMode;
285
+ if (window.selectedMode) return window.selectedMode;
286
+ return "chat";
287
+ }
288
+
289
+ function detectBrowserLanguage() {
290
+ const serverLocale = String(SERVER_I18N.locale || "").toLowerCase();
291
+ const serverCode = serverLocale.split("-")[0];
292
+ if (SUPER_TTS_LANGUAGES.some((item) => item.id === serverCode)) return serverCode;
293
+ if (serverCode === "ja" || serverCode === "hi") return "en";
294
+ const browserLang = navigator.language || navigator.userLanguage || "ko";
295
+ const langCode = browserLang.split("-")[0].toLowerCase();
296
+ return SUPER_TTS_LANGUAGES.some((item) => item.id === langCode) ? langCode : "en";
297
+ }
298
+
299
+ function detectUiLanguage() {
300
+ const raw = String(SERVER_I18N.locale || document.documentElement?.lang || navigator.language || navigator.userLanguage || "ko").toLowerCase();
301
+ if (DEFAULT_WELCOME_MESSAGES[raw]) return raw;
302
+ const primary = raw.split("-")[0];
303
+ if (primary === "zh") {
304
+ return raw.includes("tw") || raw.includes("hk") ? "zh-tw" : "zh";
305
+ }
306
+ return DEFAULT_WELCOME_MESSAGES[primary] ? primary : "en";
307
+ }
308
+
309
+ function normalizeWelcomeMessage(message, localeHint) {
310
+ const raw = String(message || "").trim();
311
+ if (!raw) return "";
312
+ const cleaned = raw.replace(/\?삃/g, "😊").replace(/\s+/g, " ").trim();
313
+ if ((String(localeHint || "").toLowerCase().startsWith("ko") || /[ㄱ-ㅎ가-힣]/.test(cleaned)) && /도와드릴까요|뭐 도와드릴까요/.test(cleaned)) {
314
+ return "안녕하세요. 무엇을 도와드릴까요? 😊";
315
+ }
316
+ return cleaned;
317
+ }
318
+
319
+ function getLocalizedWelcomeMessage() {
320
+ const uiLanguage = detectUiLanguage();
321
+ if (SERVER_I18N.welcomeMessage) {
322
+ const normalized = normalizeWelcomeMessage(SERVER_I18N.welcomeMessage, uiLanguage);
323
+ if (normalized) return normalized;
324
+ }
325
+ const fallbackMessage = DEFAULT_WELCOME_MESSAGES[uiLanguage] || DEFAULT_WELCOME_MESSAGES.en;
326
+ return normalizeWelcomeMessage(fallbackMessage, uiLanguage);
327
+ }
328
+
329
+ function getAssistantInfo() {
330
+ const appRef = currentAppRef();
331
+ if (appRef) {
332
+ return {
333
+ name: appRef.title || DEFAULT_ASSISTANT.name,
334
+ icon: ASSISTANT_ICON,
335
+ subtitle: appRef.description || appRef.summary || ""
336
+ };
337
+ }
338
+ return DEFAULT_ASSISTANT;
339
+ }
340
+
341
+ function buildAvatarContent(info, fallbackClass) {
342
+ const name = (info && info.name) || DEFAULT_ASSISTANT.name;
343
+ const icon = info && info.icon;
344
+ if (icon) {
345
+ return `
346
+ <img src="${escapeHtml(icon)}" alt="${escapeHtml(name)}" onerror="this.remove();this.nextElementSibling.style.display='flex';">
347
+ <span class="${fallbackClass}" style="display:none;">${escapeHtml(name.charAt(0).toUpperCase())}</span>
348
+ `;
349
+ }
350
+ return `<span class="${fallbackClass}">${escapeHtml(name.charAt(0).toUpperCase())}</span>`;
351
+ }
352
+
353
+ function updateHeaderAvatar(info) {
354
+ const wrapper = document.querySelector("#chat-profile-card .chat-profile-avatar");
355
+ if (!wrapper) return;
356
+ wrapper.innerHTML = buildAvatarContent(info, "chat-profile-fallback");
357
+ }
358
+
359
+ function updateComposerMeta(mode = getCurrentMode()) {
360
+ const meta = MODE_META[mode] || MODE_META.chat;
361
+ const assistant = getAssistantInfo();
362
+ const appRef = currentAppRef();
363
+
364
+ const kicker = document.getElementById("composer-profile-kicker");
365
+ const title = document.getElementById("composer-profile-title");
366
+ const subtitle = document.getElementById("composer-profile-subtitle");
367
+ const badge = document.getElementById("chat-mode-badge");
368
+ const hint = document.getElementById("composer-mode-hint");
369
+ const input = document.getElementById("prompt-input");
370
+ const icon = document.getElementById("icon-submit");
371
+ const submitButton = document.getElementById("btn-submit");
372
+
373
+ if (kicker) kicker.textContent = appRef ? "App Mode" : meta.kicker;
374
+ if (title) title.textContent = appRef ? assistant.name : meta.title;
375
+ if (subtitle) {
376
+ const subtitleText = appRef && assistant.subtitle ? assistant.subtitle : meta.subtitle;
377
+ subtitle.textContent = subtitleText.length > 140 ? `${subtitleText.slice(0, 137)}...` : subtitleText;
378
+ }
379
+ if (badge) badge.textContent = meta.badge;
380
+ if (hint) hint.textContent = meta.hint;
381
+ if (input) input.placeholder = "";
382
+ if (submitButton) {
383
+ submitButton.style.display = "inline-flex";
384
+ }
385
+ if (icon) icon.className = "ri-arrow-up-s-line text-[14px]";
386
+ updateHeaderAvatar(assistant);
387
+ }
388
+
389
+ function readRecentModes() {
390
+ try {
391
+ const raw = localStorage.getItem(RECENT_MODE_STORAGE_KEY);
392
+ const parsed = JSON.parse(raw || "[]");
393
+ if (!Array.isArray(parsed)) return [];
394
+ return parsed.filter((mode, index, list) => (
395
+ typeof mode === "string" &&
396
+ TRACKED_RECENT_MODES.includes(mode) &&
397
+ list.indexOf(mode) === index
398
+ ));
399
+ } catch (error) {
400
+ return [];
401
+ }
402
+ }
403
+
404
+ function writeRecentModes(modes) {
405
+ try {
406
+ localStorage.setItem(
407
+ RECENT_MODE_STORAGE_KEY,
408
+ JSON.stringify((Array.isArray(modes) ? modes : []).slice(0, RECENT_MODE_LIMIT + 1))
409
+ );
410
+ } catch (error) {
411
+ return;
412
+ }
413
+ }
414
+
415
+ function rememberRecentMode(mode) {
416
+ if (!TRACKED_RECENT_MODES.includes(mode)) return;
417
+ const nextModes = [mode].concat(readRecentModes().filter((item) => item !== mode));
418
+ writeRecentModes(nextModes);
419
+ }
420
+
421
+ function getModeButton(mode) {
422
+ return document.getElementById(`btn-${mode}`);
423
+ }
424
+
425
+ function getModeButtonMarkup(mode) {
426
+ const button = getModeButton(mode);
427
+ return button ? button.innerHTML : `<span>${escapeHtml(String(mode || "?").charAt(0).toUpperCase())}</span>`;
428
+ }
429
+
430
+ function getModeButtonLabel(mode) {
431
+ const button = getModeButton(mode);
432
+ if (!button) return mode;
433
+ return button.getAttribute("aria-label") || button.getAttribute("title") || mode;
434
+ }
435
+
436
+ function syncRecentModePadding(count) {
437
+ const field = document.getElementById("chat-input-field");
438
+ if (!field) return;
439
+ field.style.setProperty("padding-right", "0px", "important");
440
+ }
441
+
442
+ function renderRecentModeActions(currentModeValue = getCurrentMode()) {
443
+ const container = document.getElementById("recent-mode-actions");
444
+ if (!container) return;
445
+
446
+ const recentModes = readRecentModes()
447
+ .filter((mode) => mode !== currentModeValue)
448
+ .filter((mode) => !!getModeButton(mode))
449
+ .slice(0, RECENT_MODE_LIMIT);
450
+
451
+ container.innerHTML = "";
452
+
453
+ if (!recentModes.length) {
454
+ container.classList.add("hidden");
455
+ syncRecentModePadding(0);
456
+ return;
457
+ }
458
+
459
+ recentModes.forEach((mode) => {
460
+ const button = document.createElement("button");
461
+ const label = getModeButtonLabel(mode);
462
+ button.type = "button";
463
+ button.className = "input-action-btn recent-mode-btn";
464
+ button.dataset.mode = mode;
465
+ button.setAttribute("aria-label", label);
466
+ button.setAttribute("title", label);
467
+ button.innerHTML = getModeButtonMarkup(mode);
468
+ button.addEventListener("click", (event) => {
469
+ event.preventDefault();
470
+ event.stopPropagation();
471
+ if (typeof setMode === "function") setMode(mode);
472
+ });
473
+ container.appendChild(button);
474
+ });
475
+
476
+ container.classList.remove("hidden");
477
+ syncRecentModePadding(recentModes.length);
478
+ }
479
+
480
+ function decodeHtmlEntities(value) {
481
+ const textarea = document.createElement("textarea");
482
+ textarea.innerHTML = String(value ?? "");
483
+ return textarea.value;
484
+ }
485
+
486
+ function sanitizeAssistantHtml(value) {
487
+ return String(value ?? "")
488
+ .replace(/<script[\s\S]*?>[\s\S]*?<\/script>/gi, "")
489
+ .replace(/<img\b[^>]*>/gi, "")
490
+ .replace(/\son\w+=(["']).*?\1/gi, "")
491
+ .replace(/\son\w+=([^\s>]+)/gi, "")
492
+ .replace(/\s(href|src)\s*=\s*(["'])\s*javascript:[\s\S]*?\2/gi, ' $1="#"');
493
+ }
494
+
495
+ function normalizeMessageHtml(role, content) {
496
+ const text = String(content ?? "");
497
+ if (role === "user") return escapeHtml(text).replace(/\n/g, "<br>");
498
+
499
+ const decoded = decodeHtmlEntities(text);
500
+ const sanitizedDecoded = sanitizeAssistantHtml(decoded);
501
+ const sanitizedRaw = sanitizeAssistantHtml(text);
502
+ const htmlCandidate = /<\/?[a-z][^>]*>/i.test(sanitizedDecoded) ? sanitizedDecoded : sanitizedRaw;
503
+ return htmlCandidate
504
+ .replace(/&lt;br\s*\/?&gt;/gi, "<br>")
505
+ .replace(/\n/g, "<br>");
506
+ }
507
+
508
+ function ensureChatSpacer(chatBox) {
509
+ if (!chatBox) return null;
510
+ const first = chatBox.firstElementChild;
511
+ if (first && first.classList && first.classList.contains("chat-spacer")) return first;
512
+
513
+ let spacer = chatBox.querySelector(".chat-spacer");
514
+ if (!spacer) {
515
+ spacer = document.createElement("div");
516
+ spacer.className = "chat-spacer";
517
+ spacer.setAttribute("aria-hidden", "true");
518
+ }
519
+ chatBox.prepend(spacer);
520
+ return spacer;
521
+ }
522
+
523
+ function overwriteAppendMsg() {
524
+ if (typeof appendMsg !== "function") return;
525
+
526
+ appendMsg = function (role, content) {
527
+ const chatBox = document.getElementById("chat-box");
528
+ if (!chatBox) return null;
529
+
530
+ ensureChatSpacer(chatBox);
531
+
532
+ const entries = chatBox.querySelectorAll(".chat-entry");
533
+ if (entries.length > 60) {
534
+ const oldest = entries[0];
535
+ if (oldest && oldest.parentNode) {
536
+ oldest.parentNode.removeChild(oldest);
537
+ }
538
+ }
539
+
540
+ const wrapper = document.createElement("div");
541
+ const body = document.createElement("div");
542
+ const bubble = document.createElement("div");
543
+
544
+ if (role === "user") {
545
+ wrapper.className = "chat-entry user-entry";
546
+
547
+ body.className = "chat-entry-body";
548
+ bubble.className = "chat-bubble-user";
549
+ bubble.innerHTML = normalizeMessageHtml("user", content);
550
+ body.appendChild(bubble);
551
+ wrapper.appendChild(body);
552
+ } else {
553
+ wrapper.className = "chat-entry ai-entry";
554
+
555
+ body.className = "chat-entry-body";
556
+
557
+ const imageError = role === "error" && isImageErrorMessage(content);
558
+ bubble.className = imageError ? "chat-bubble-image-error" : "chat-bubble-ai";
559
+ bubble.innerHTML = imageError ? imageErrorIconHtml(content) : normalizeMessageHtml(role, content);
560
+ if (role === "error" && !imageError) {
561
+ bubble.style.background = "rgba(127, 29, 29, 0.82)";
562
+ bubble.style.color = "#fee2e2";
563
+ bubble.style.borderColor = "rgba(248, 113, 113, 0.28)";
564
+ }
565
+
566
+ body.appendChild(bubble);
567
+ wrapper.appendChild(body);
568
+ }
569
+
570
+ chatBox.appendChild(wrapper);
571
+ if (typeof scrollBottom === "function") scrollBottom();
572
+ return bubble;
573
+ };
574
+
575
+ window.appendMsg = appendMsg;
576
+ }
577
+
578
+ function ensureWelcomeMessage(force = false) {
579
+ const chatBox = document.getElementById("chat-box");
580
+ if (!chatBox) return;
581
+ if (force) chatBox.innerHTML = "";
582
+ ensureChatSpacer(chatBox);
583
+ if (chatBox.querySelector(".chat-entry")) return;
584
+ const welcomeMessage = getLocalizedWelcomeMessage();
585
+ if (typeof appendMsg === "function") {
586
+ appendMsg("ai", welcomeMessage);
587
+ return;
588
+ }
589
+
590
+ const wrapper = document.createElement("div");
591
+ const body = document.createElement("div");
592
+ const bubble = document.createElement("div");
593
+
594
+ wrapper.className = "chat-entry ai-entry";
595
+ body.className = "chat-entry-body";
596
+ bubble.className = "chat-bubble-ai";
597
+ bubble.innerHTML = normalizeMessageHtml("ai", welcomeMessage);
598
+ body.appendChild(bubble);
599
+ wrapper.appendChild(body);
600
+ chatBox.appendChild(wrapper);
601
+ }
602
+
603
+ function wrapResetChat() {
604
+ if (typeof resetChat !== "function") return;
605
+ const originalResetChat = resetChat;
606
+ resetChat = function () {
607
+ const result = originalResetChat.apply(this, arguments);
608
+ setTimeout(() => {
609
+ ensureWelcomeMessage(true);
610
+ updateComposerMeta("chat");
611
+ }, 0);
612
+ return result;
613
+ };
614
+ window.resetChat = resetChat;
615
+ }
616
+
617
+ function wrapAppModeHooks() {
618
+ if (typeof activateAppMode === "function") {
619
+ const originalActivateAppMode = activateAppMode;
620
+ activateAppMode = function () {
621
+ const result = originalActivateAppMode.apply(this, arguments);
622
+ setTimeout(() => updateComposerMeta(getCurrentMode()), 0);
623
+ return result;
624
+ };
625
+ window.activateAppMode = activateAppMode;
626
+ }
627
+
628
+ if (typeof exitAppMode === "function") {
629
+ const originalExitAppMode = exitAppMode;
630
+ exitAppMode = function () {
631
+ const result = originalExitAppMode.apply(this, arguments);
632
+ setTimeout(() => updateComposerMeta(getCurrentMode()), 0);
633
+ return result;
634
+ };
635
+ window.exitAppMode = exitAppMode;
636
+ }
637
+ }
638
+
639
+ function selectedTtsLanguage() {
640
+ return SUPER_TTS_LANGUAGES.find((item) => item.id === activeTtsLanguage) || SUPER_TTS_LANGUAGES[0];
641
+ }
642
+
643
+ function selectedTtsEngine() {
644
+ return TTS_ENGINES.find((item) => item.id === activeTtsEngine) || TTS_ENGINES[0];
645
+ }
646
+
647
+ function speechLangCode(languageId) {
648
+ const map = {
649
+ ko: "ko-KR",
650
+ en: "en-US",
651
+ ja: "ja-JP",
652
+ zh: "zh-CN",
653
+ es: "es-ES",
654
+ pt: "pt-PT",
655
+ fr: "fr-FR",
656
+ de: "de-DE",
657
+ ru: "ru-RU"
658
+ };
659
+ return map[languageId] || (navigator.language || "en-US");
660
+ }
661
+
662
+ function getBrowserVoices(languageId = activeTtsLanguage) {
663
+ const synth = window.speechSynthesis;
664
+ const voices = synth && typeof synth.getVoices === "function" ? synth.getVoices() : [];
665
+ if (!voices || !voices.length) {
666
+ return [{
667
+ id: "",
668
+ label: "System Default",
669
+ chip: "AUTO",
670
+ icon: "ri-volume-up-line",
671
+ description: "Use the browser default voice."
672
+ }];
673
+ }
674
+
675
+ const langCode = speechLangCode(languageId).toLowerCase();
676
+ const langPrefix = langCode.split("-")[0];
677
+ const filtered = voices.filter((voice) => {
678
+ const voiceLang = String(voice.lang || "").toLowerCase();
679
+ return voiceLang === langCode || voiceLang.startsWith(`${langPrefix}-`);
680
+ });
681
+ const source = filtered.length ? filtered : voices;
682
+
683
+ return source.slice(0, 12).map((voice, index) => ({
684
+ id: voice.name,
685
+ label: voice.name,
686
+ chip: `V${index + 1}`,
687
+ icon: voice.localService ? "ri-user-voice-line" : "ri-volume-up-line",
688
+ description: `${voice.lang || "auto"} / ${voice.localService ? "local" : "remote"}`
689
+ }));
690
+ }
691
+
692
+ function getAvailableTtsVoices(languageId = activeTtsLanguage) {
693
+ return getBrowserVoices(languageId);
694
+ }
695
+
696
+ function ensureValidTtsVoiceSelection() {
697
+ const voices = getAvailableTtsVoices(activeTtsLanguage);
698
+ if (!voices.length) {
699
+ activeTtsVoiceId = "";
700
+ return voices;
701
+ }
702
+ if (!voices.some((item) => item.id === activeTtsVoiceId)) {
703
+ activeTtsVoiceId = voices[0].id;
704
+ }
705
+ return voices;
706
+ }
707
+
708
+ function selectedTtsVoice() {
709
+ const voices = ensureValidTtsVoiceSelection();
710
+ return voices.find((item) => item.id === activeTtsVoiceId) || voices[0];
711
+ }
712
+
713
+ function stopPreviewPlayback() {
714
+ activePreviewUtterance = null;
715
+ if (window.speechSynthesis) {
716
+ window.speechSynthesis.cancel();
717
+ }
718
+ }
719
+
720
+ window.stopIsaiTtsPreview = stopPreviewPlayback;
721
+
722
+ function selectedSpeed() {
723
+ const range = document.getElementById("tts-speed-range");
724
+ const parsed = parseFloat(range?.value || String(DEFAULT_TTS_SPEED));
725
+ return Number.isFinite(parsed) ? parsed : DEFAULT_TTS_SPEED;
726
+ }
727
+
728
+ function selectedSteps() {
729
+ const range = document.getElementById("tts-steps-range");
730
+ const parsed = parseFloat(range?.value || String(DEFAULT_TTS_STEPS));
731
+ return Number.isFinite(parsed) ? parsed : DEFAULT_TTS_STEPS;
732
+ }
733
+
734
+ function loadVoiceSettings() {
735
+ try {
736
+ return JSON.parse(localStorage.getItem(VOICE_SETTINGS_STORAGE_KEY) || "{}");
737
+ } catch (error) {
738
+ return {};
739
+ }
740
+ }
741
+
742
+ function loadAssistantSettings() {
743
+ try {
744
+ const parsed = JSON.parse(localStorage.getItem(ASSISTANT_SETTINGS_STORAGE_KEY) || "{}");
745
+ const toneId = typeof parsed.tone === "string" && ASSISTANT_TONES.some((item) => item.id === parsed.tone)
746
+ ? parsed.tone
747
+ : DEFAULT_ASSISTANT_TONE;
748
+ return {
749
+ systemPrompt: typeof parsed.systemPrompt === "string" ? parsed.systemPrompt : "",
750
+ tone: toneId
751
+ };
752
+ } catch (error) {
753
+ return {
754
+ systemPrompt: "",
755
+ tone: DEFAULT_ASSISTANT_TONE
756
+ };
757
+ }
758
+ }
759
+
760
+ function selectedAssistantTone() {
761
+ return ASSISTANT_TONES.find((item) => item.id === activeAssistantToneId) || ASSISTANT_TONES[0];
762
+ }
763
+
764
+ function syncAssistantSettings() {
765
+ const promptInput = document.getElementById("assistant-system-prompt");
766
+ const state = {
767
+ systemPrompt: String(promptInput?.value || "").trim(),
768
+ tone: selectedAssistantTone().id
769
+ };
770
+
771
+ window.ISAI_ASSISTANT_SETTINGS = state;
772
+ window.getIsaiAssistantSettings = function () {
773
+ return Object.assign({}, state);
774
+ };
775
+
776
+ try {
777
+ localStorage.setItem(ASSISTANT_SETTINGS_STORAGE_KEY, JSON.stringify(state));
778
+ } catch (error) {}
779
+
780
+ return state;
781
+ }
782
+
783
+ function buildIsaiSystemPrompt(basePrompt = "") {
784
+ const settings = typeof window.getIsaiAssistantSettings === "function"
785
+ ? (window.getIsaiAssistantSettings() || {})
786
+ : loadAssistantSettings();
787
+ const assistantTone = ASSISTANT_TONES.find((item) => item.id === settings.tone) || ASSISTANT_TONES[0];
788
+ return [
789
+ String(basePrompt || "").trim(),
790
+ String(settings.systemPrompt || "").trim(),
791
+ assistantTone.instruction
792
+ ].filter(Boolean).join("\n\n");
793
+ }
794
+
795
+ window.buildIsaiSystemPrompt = buildIsaiSystemPrompt;
796
+
797
+ function renderAssistantToneChips() {
798
+ const container = document.getElementById("assistant-tone-list");
799
+ if (!container) return;
800
+
801
+ container.innerHTML = ASSISTANT_TONES.map((tone) => (
802
+ renderSettingsChip(tone, "tone", tone.id, tone.instruction)
803
+ )).join("");
804
+
805
+ container.querySelectorAll("[data-tone]").forEach((button) => {
806
+ button.addEventListener("click", () => {
807
+ activeAssistantToneId = button.dataset.tone || activeAssistantToneId;
808
+ updateAssistantSettingsUi();
809
+ });
810
+ });
811
+ }
812
+
813
+ function updateAssistantSettingsUi() {
814
+ const state = syncAssistantSettings();
815
+ updateChipState("assistant-tone-list", "[data-tone]", state.tone, "tone");
816
+ }
817
+
818
+ function setupAssistantSettingsControls() {
819
+ const saved = loadAssistantSettings();
820
+ activeAssistantToneId = saved.tone || DEFAULT_ASSISTANT_TONE;
821
+
822
+ const promptInput = document.getElementById("assistant-system-prompt");
823
+ if (promptInput) {
824
+ promptInput.value = saved.systemPrompt || "";
825
+ promptInput.addEventListener("input", () => {
826
+ syncAssistantSettings();
827
+ });
828
+ }
829
+
830
+ renderAssistantToneChips();
831
+ updateAssistantSettingsUi();
832
+ }
833
+
834
+ function syncVoiceSettings() {
835
+ const selectedVoice = selectedTtsVoice();
836
+ const state = {
837
+ ttsEngine: "browser",
838
+ recognitionLang: speechLangCode(activeTtsLanguage),
839
+ speechLang: speechLangCode(activeTtsLanguage),
840
+ voiceName: selectedVoice?.id || "",
841
+ rate: selectedSpeed(),
842
+ pitch: selectedSteps(),
843
+ volume: DEFAULT_TTS_VOLUME
844
+ };
845
+ window.ISAI_VOICE_SETTINGS = state;
846
+ window.getIsaiVoiceSettings = function () {
847
+ return Object.assign({}, state);
848
+ };
849
+ try {
850
+ localStorage.setItem(VOICE_SETTINGS_STORAGE_KEY, JSON.stringify(state));
851
+ } catch (error) {}
852
+ return state;
853
+ }
854
+
855
+ function stripLanguageTags(text) {
856
+ return String(text ?? "")
857
+ .trim()
858
+ .replace(/^<(en|ko|es|pt|fr)>/i, "")
859
+ .replace(/<\/(en|ko|es|pt|fr)>$/i, "")
860
+ .trim();
861
+ }
862
+
863
+ function updateChipState(containerId, selector, value, dataKey) {
864
+ const container = document.getElementById(containerId);
865
+ if (!container) return;
866
+ container.querySelectorAll(selector).forEach((element) => {
867
+ element.classList.toggle("active", element.dataset[dataKey] === value);
868
+ });
869
+ }
870
+
871
+ function renderSettingsChip(item, dataAttr, value, titleText = "") {
872
+ const iconClass = item.icon || "ri-settings-3-line";
873
+ const chipText = item.chip || item.id || "";
874
+ const compactText = item.shortLabel || chipText || item.label || value;
875
+ const title = titleText || item.description || item.instruction || item.label || "";
876
+ return `
877
+ <button type="button" class="tts-chip" data-${dataAttr}="${escapeHtml(value)}" title="${escapeHtml(title)}">
878
+ <span class="tts-chip-icon">
879
+ <i class="${escapeHtml(iconClass)}"></i>
880
+ </span>
881
+ <span class="tts-chip-main">
882
+ <small>${escapeHtml(chipText)}</small>
883
+ <strong>${escapeHtml(compactText)}</strong>
884
+ </span>
885
+ </button>
886
+ `;
887
+ }
888
+
889
+ function ensureTtsEngineControls() {
890
+ let group = document.getElementById("tts-engine-group");
891
+ if (group) return group;
892
+
893
+ const languageList = document.getElementById("tts-language-list");
894
+ const languageGroup = languageList ? languageList.closest(".tts-control-group") : null;
895
+ if (!languageGroup || !languageGroup.parentNode) return null;
896
+
897
+ group = document.createElement("div");
898
+ group.id = "tts-engine-group";
899
+ group.className = "tts-control-group";
900
+ group.innerHTML = `
901
+ <div class="tts-control-heading"><i class="ri-cpu-line"></i><span>Speech Engine</span></div>
902
+ <div id="tts-engine-list" class="tts-chip-list no-scrollbar"></div>
903
+ `;
904
+ languageGroup.parentNode.insertBefore(group, languageGroup);
905
+ return group;
906
+ }
907
+
908
+ function renderTtsEngineChips() {
909
+ ensureTtsEngineControls();
910
+ const container = document.getElementById("tts-engine-list");
911
+ if (!container) return;
912
+
913
+ container.innerHTML = TTS_ENGINES.map((engine) => (
914
+ renderSettingsChip(engine, "engine", engine.id, engine.description)
915
+ )).join("");
916
+
917
+ container.querySelectorAll("[data-engine]").forEach((button) => {
918
+ button.addEventListener("click", () => {
919
+ const nextEngine = button.dataset.engine || DEFAULT_TTS_ENGINE;
920
+ if (nextEngine === activeTtsEngine) return;
921
+
922
+ activeTtsEngine = nextEngine;
923
+ activeTtsVoiceId = "";
924
+ renderTtsVoiceChips();
925
+ updateTtsUi();
926
+ });
927
+ });
928
+ }
929
+
930
+ function renderTtsLanguageChips() {
931
+ const container = document.getElementById("tts-language-list");
932
+ if (!container) return;
933
+
934
+ container.innerHTML = SUPER_TTS_LANGUAGES.map((language) => (
935
+ renderSettingsChip(language, "language", language.id, language.label)
936
+ )).join("");
937
+
938
+ container.querySelectorAll("[data-language]").forEach((button) => {
939
+ button.addEventListener("click", () => {
940
+ activeTtsLanguage = button.dataset.language || activeTtsLanguage;
941
+ activeTtsVoiceId = "";
942
+ renderTtsVoiceChips();
943
+ updateTtsUi();
944
+ });
945
+ });
946
+ }
947
+
948
+ function renderTtsVoiceChips() {
949
+ const container = document.getElementById("tts-voice-list");
950
+ if (!container) return;
951
+
952
+ container.innerHTML = getAvailableTtsVoices(activeTtsLanguage).map((voice) => (
953
+ renderSettingsChip(Object.assign({ icon: "ri-user-voice-line" }, voice), "voice", voice.id, voice.description)
954
+ )).join("");
955
+
956
+ container.querySelectorAll("[data-voice]").forEach((button) => {
957
+ button.addEventListener("click", () => {
958
+ activeTtsVoiceId = button.dataset.voice || activeTtsVoiceId;
959
+ updateTtsUi();
960
+ });
961
+ });
962
+ }
963
+
964
+ function updateTtsUi(options = {}) {
965
+ const meta = MODE_META.settings || MODE_META.voice;
966
+ const engine = selectedTtsEngine();
967
+ const language = selectedTtsLanguage();
968
+ const voice = selectedTtsVoice();
969
+ const assistantTone = selectedAssistantTone();
970
+ const downloadButton = document.getElementById("tts-download-button");
971
+ const downloadLabel = document.getElementById("tts-download-label");
972
+ const helper = document.getElementById("tts-helper-text");
973
+ const languageValue = document.getElementById("tts-language-value");
974
+ const voiceValue = document.getElementById("tts-voice-value");
975
+ const speedValue = document.getElementById("tts-speed-value");
976
+ const stepsValue = document.getElementById("tts-steps-value");
977
+ const speedValueInline = document.getElementById("tts-speed-value-inline");
978
+ const stepsValueInline = document.getElementById("tts-steps-value-inline");
979
+ const kicker = document.querySelector(".tts-panel-kicker");
980
+ const title = document.querySelector(".tts-panel-title");
981
+ const toneMetaLabel = document.querySelector(".tts-meta-grid .tts-meta-item:nth-child(4) .tts-meta-copy span");
982
+ const toneRangeLabel = document.querySelector('label[for="tts-steps-range"] .tts-slider-label-main span:last-child');
983
+ const stepLabel = "Pitch";
984
+
985
+ if (languageValue) languageValue.textContent = language.label;
986
+ if (voiceValue) voiceValue.textContent = voice.label;
987
+ if (speedValue) speedValue.textContent = `${selectedSpeed().toFixed(2)}x`;
988
+ if (stepsValue) stepsValue.textContent = engine.label;
989
+ if (speedValueInline) speedValueInline.textContent = `${selectedSpeed().toFixed(2)}x`;
990
+ if (stepsValueInline) stepsValueInline.textContent = selectedSteps().toFixed(2);
991
+ if (kicker) kicker.textContent = meta.kicker || "Voice Settings";
992
+ if (title) title.textContent = meta.title || "Voice Settings";
993
+ if (toneMetaLabel) toneMetaLabel.textContent = "Engine";
994
+ if (toneRangeLabel) toneRangeLabel.textContent = stepLabel;
995
+
996
+ updateChipState("tts-engine-list", "[data-engine]", engine.id, "engine");
997
+ updateChipState("tts-language-list", "[data-language]", language.id, "language");
998
+ updateChipState("tts-voice-list", "[data-voice]", voice.id, "voice");
999
+ updateChipState("assistant-tone-list", "[data-tone]", assistantTone.id, "tone");
1000
+
1001
+ if (downloadLabel) {
1002
+ downloadLabel.textContent = options.label || TTS_TEXT.previewLabel || "Preview Voice";
1003
+ }
1004
+
1005
+ if (helper) {
1006
+ helper.textContent = options.helper || `${TTS_TEXT.previewHelper || meta.hint || "Voice settings apply to spoken responses."} / ${assistantTone.label} / ${language.label} / ${voice.label}`;
1007
+ }
1008
+
1009
+ if (downloadButton) {
1010
+ downloadButton.disabled = Boolean(options.disabled);
1011
+ }
1012
+
1013
+ syncVoiceSettings();
1014
+ }
1015
+
1016
+ function setupTtsControls() {
1017
+ const saved = loadVoiceSettings();
1018
+ const savedEngine = String(saved.ttsEngine || DEFAULT_TTS_ENGINE).toLowerCase();
1019
+ const savedLang = String(saved.speechLang || saved.recognitionLang || "").toLowerCase().split("-")[0];
1020
+ if (TTS_ENGINES.some((item) => item.id === savedEngine)) {
1021
+ activeTtsEngine = savedEngine;
1022
+ }
1023
+ if (savedLang && SUPER_TTS_LANGUAGES.some((item) => item.id === savedLang)) {
1024
+ activeTtsLanguage = savedLang;
1025
+ }
1026
+ if (typeof saved.voiceName === "string") {
1027
+ activeTtsVoiceId = saved.voiceName;
1028
+ }
1029
+
1030
+ setupAssistantSettingsControls();
1031
+ renderTtsEngineChips();
1032
+ renderTtsLanguageChips();
1033
+ renderTtsVoiceChips();
1034
+
1035
+ const stepsRange = document.getElementById("tts-steps-range");
1036
+ const speedRange = document.getElementById("tts-speed-range");
1037
+ const downloadButton = document.getElementById("tts-download-button");
1038
+
1039
+ if (stepsRange) {
1040
+ stepsRange.value = String(saved.pitch || DEFAULT_TTS_STEPS);
1041
+ stepsRange.addEventListener("input", () => updateTtsUi());
1042
+ }
1043
+
1044
+ if (speedRange) {
1045
+ speedRange.value = String(saved.rate || DEFAULT_TTS_SPEED);
1046
+ speedRange.addEventListener("input", () => updateTtsUi());
1047
+ }
1048
+
1049
+ if (downloadButton) {
1050
+ downloadButton.addEventListener("click", async () => {
1051
+ const settings = syncVoiceSettings();
1052
+ const previewText = stripLanguageTags(getLocalizedWelcomeMessage()).replace(/<br\s*\/?>/gi, " ");
1053
+ stopPreviewPlayback();
1054
+
1055
+ const synth = window.speechSynthesis;
1056
+ if (!synth) return;
1057
+ const preview = new SpeechSynthesisUtterance(previewText);
1058
+ const browserVoice = synth.getVoices().find((voice) => voice.name === settings.voiceName);
1059
+ preview.lang = settings.speechLang;
1060
+ preview.rate = settings.rate;
1061
+ preview.pitch = settings.pitch;
1062
+ preview.volume = settings.volume;
1063
+ if (browserVoice) preview.voice = browserVoice;
1064
+ synth.cancel();
1065
+ activePreviewUtterance = preview;
1066
+ synth.speak(preview);
1067
+ });
1068
+ }
1069
+
1070
+ if (window.speechSynthesis && !window.__isaiVoiceSettingsBound) {
1071
+ window.speechSynthesis.addEventListener("voiceschanged", () => {
1072
+ renderTtsVoiceChips();
1073
+ updateTtsUi();
1074
+ });
1075
+ window.__isaiVoiceSettingsBound = true;
1076
+ }
1077
+
1078
+ updateTtsUi();
1079
+ }
1080
+
1081
+ window.generateIsaiSupertonicSpeech = null;
1082
+
1083
+ async function generateTtsFromInput() {
1084
+ const input = document.getElementById("prompt-input");
1085
+ if (!input) return;
1086
+ const rawText = input.value.trim();
1087
+ if (!rawText) return;
1088
+
1089
+ try {
1090
+ if (typeof startExperience === "function") startExperience();
1091
+ if (typeof showLoader === "function") showLoader(true);
1092
+ appendMsg("user", rawText);
1093
+ input.value = "";
1094
+ input.style.height = "auto";
1095
+
1096
+ const settings = syncVoiceSettings();
1097
+ const synth = window.speechSynthesis;
1098
+ if (!synth) throw new Error("Browser speech synthesis is not available.");
1099
+
1100
+ const utterance = new SpeechSynthesisUtterance(rawText);
1101
+ const browserVoice = synth.getVoices().find((voice) => voice.name === settings.voiceName);
1102
+ utterance.lang = settings.speechLang;
1103
+ utterance.rate = settings.rate;
1104
+ utterance.pitch = settings.pitch;
1105
+ utterance.volume = settings.volume;
1106
+ if (browserVoice) utterance.voice = browserVoice;
1107
+ activePreviewUtterance = utterance;
1108
+ synth.cancel();
1109
+ synth.speak(utterance);
1110
+ } catch (error) {
1111
+ appendMsg("error", `${TTS_TEXT.errorPrefix} ${error?.message || error}`);
1112
+ } finally {
1113
+ if (typeof showLoader === "function") showLoader(false);
1114
+ updateTtsUi();
1115
+ input.focus();
1116
+ }
1117
+ }
1118
+
1119
+ function syncCodeWorkspace(mode) {
1120
+ if (typeof window.syncInlineCodePanel === "function") {
1121
+ window.syncInlineCodePanel(mode);
1122
+ return;
1123
+ }
1124
+
1125
+ mode = mode || "chat";
1126
+ if (document.body) document.body.setAttribute("data-ui-mode", mode);
1127
+
1128
+ const rightPanel = document.getElementById("right-panel");
1129
+ const codeTabs = document.getElementById("code-tabs");
1130
+ const codeEditor = document.getElementById("code-editor");
1131
+ const topZone = document.getElementById("top-zone");
1132
+ const chatBox = document.getElementById("chat-box");
1133
+ const chatInputShell = document.getElementById("chat-input-shell");
1134
+ const rightPanelOriginAnchor = document.getElementById("right-panel-origin-anchor");
1135
+ const desktopCodePanelHost = document.getElementById("desktop-code-panel-host");
1136
+ const isCodeMode = mode === "code";
1137
+ const vw = window.innerWidth || document.documentElement.clientWidth || 0;
1138
+ const fullscreenElement = document.fullscreenElement || document.webkitFullscreenElement || null;
1139
+ const fullscreenActive = !!(fullscreenElement && fullscreenElement.classList && fullscreenElement.classList.contains("island-box"));
1140
+ const mobileCodeMode = isCodeMode && vw <= 900;
1141
+ const desktopCodeMode = isCodeMode && vw > 900;
1142
+ const codePanelGridColumn = vw <= 1180 ? "1 / span 2" : "1 / span 4";
1143
+ const codePanelGridRow = vw <= 900 ? "2" : (vw <= 1180 ? "3" : "2");
1144
+ const codePanelMinHeight = vw <= 900 ? "240px" : "320px";
1145
+ const codePanelMaxHeight = vw <= 900 ? "360px" : "520px";
1146
+
1147
+ if (!rightPanel) return;
1148
+
1149
+ if (rightPanelOriginAnchor && rightPanelOriginAnchor.parentElement && desktopCodePanelHost) {
1150
+ if (desktopCodeMode && !fullscreenActive) {
1151
+ desktopCodePanelHost.style.display = "block";
1152
+ desktopCodePanelHost.style.width = "100%";
1153
+ desktopCodePanelHost.style.minWidth = "0";
1154
+ desktopCodePanelHost.style.minHeight = codePanelMinHeight;
1155
+ desktopCodePanelHost.style.visibility = "visible";
1156
+ desktopCodePanelHost.style.opacity = "1";
1157
+ if (rightPanel.parentElement !== desktopCodePanelHost) {
1158
+ desktopCodePanelHost.appendChild(rightPanel);
1159
+ }
1160
+ } else {
1161
+ desktopCodePanelHost.style.display = "none";
1162
+ desktopCodePanelHost.style.removeProperty("min-height");
1163
+ desktopCodePanelHost.style.removeProperty("visibility");
1164
+ desktopCodePanelHost.style.removeProperty("opacity");
1165
+ desktopCodePanelHost.style.removeProperty("overflow");
1166
+ if (rightPanel.parentElement !== rightPanelOriginAnchor.parentElement) {
1167
+ rightPanelOriginAnchor.parentElement.insertBefore(rightPanel, rightPanelOriginAnchor.nextSibling);
1168
+ }
1169
+ }
1170
+ }
1171
+
1172
+ document.body.classList.toggle("mode-code", mobileCodeMode);
1173
+ document.body.classList.toggle("desktop-code-open", desktopCodeMode);
1174
+ document.body.classList.toggle("desktop-code-panel-mounted", desktopCodeMode && !fullscreenActive);
1175
+ document.body.classList.remove("desktop-code-stage");
1176
+ rightPanel.classList.toggle("mobile-active", mobileCodeMode);
1177
+
1178
+ if (isCodeMode) {
1179
+ if (codeTabs && !codeTabs.querySelector(".code-tab-btn")) {
1180
+ codeTabs.innerHTML = '<div class="code-panel-empty"><span class="code-panel-empty-icon"><i class="ri-ai-generate-text text-sm"></i></span><span class="code-panel-empty-dots" aria-hidden="true"><span></span><span></span><span></span></span></div>';
1181
+ }
1182
+
1183
+ if (codeEditor && !String(codeEditor.value || "").trim()) {
1184
+ codeEditor.value = "// Describe the code you want and generated files will appear here.";
1185
+ }
1186
+
1187
+ rightPanel.classList.remove("hidden");
1188
+ rightPanel.style.setProperty("display", "flex", "important");
1189
+ rightPanel.style.setProperty("visibility", "visible", "important");
1190
+ rightPanel.style.setProperty("opacity", "1", "important");
1191
+ rightPanel.style.setProperty("width", "100%", "important");
1192
+ rightPanel.style.setProperty("min-width", "0", "important");
1193
+ rightPanel.style.setProperty("max-width", "100%", "important");
1194
+ if (mobileCodeMode) {
1195
+ rightPanel.style.setProperty("grid-row", codePanelGridRow, "important");
1196
+ rightPanel.style.setProperty("grid-column", codePanelGridColumn, "important");
1197
+ } else {
1198
+ rightPanel.style.removeProperty("grid-row");
1199
+ rightPanel.style.removeProperty("grid-column");
1200
+ }
1201
+ rightPanel.style.setProperty("height", "auto", "important");
1202
+ rightPanel.style.setProperty("min-height", codePanelMinHeight, "important");
1203
+ rightPanel.style.setProperty("max-height", codePanelMaxHeight, "important");
1204
+ rightPanel.style.setProperty("overflow", "hidden", "important");
1205
+ rightPanel.style.setProperty("border-top", "1px solid rgba(255, 255, 255, 0.06)", "important");
1206
+ if (topZone) {
1207
+ topZone.style.setProperty("display", "flex", "important");
1208
+ topZone.style.setProperty("visibility", "visible", "important");
1209
+ topZone.style.setProperty("opacity", "1", "important");
1210
+ }
1211
+ if (chatBox) {
1212
+ chatBox.classList.remove("hidden");
1213
+ chatBox.style.setProperty("display", "flex", "important");
1214
+ chatBox.style.setProperty("visibility", "visible", "important");
1215
+ chatBox.style.setProperty("opacity", "1", "important");
1216
+ }
1217
+ if (chatInputShell) {
1218
+ chatInputShell.classList.remove("hidden");
1219
+ chatInputShell.style.setProperty("display", "flex", "important");
1220
+ chatInputShell.style.setProperty("visibility", "visible", "important");
1221
+ chatInputShell.style.setProperty("opacity", "1", "important");
1222
+ }
1223
+ setTimeout(() => {
1224
+ try {
1225
+ (desktopCodeMode && desktopCodePanelHost ? desktopCodePanelHost : rightPanel).scrollIntoView({ behavior: "smooth", block: "start" });
1226
+ } catch (error) {}
1227
+ }, vw <= 900 ? 90 : 20);
1228
+ } else {
1229
+ document.body.classList.remove("desktop-code-stage");
1230
+ document.body.classList.remove("desktop-code-open");
1231
+ document.body.classList.remove("desktop-code-panel-mounted");
1232
+ rightPanel.classList.remove("mobile-active");
1233
+ rightPanel.classList.add("hidden");
1234
+ if (desktopCodePanelHost) {
1235
+ desktopCodePanelHost.style.display = "none";
1236
+ desktopCodePanelHost.style.removeProperty("min-height");
1237
+ desktopCodePanelHost.style.removeProperty("visibility");
1238
+ desktopCodePanelHost.style.removeProperty("opacity");
1239
+ desktopCodePanelHost.style.removeProperty("overflow");
1240
+ }
1241
+ if (rightPanelOriginAnchor && rightPanelOriginAnchor.parentElement && rightPanel.parentElement !== rightPanelOriginAnchor.parentElement) {
1242
+ rightPanelOriginAnchor.parentElement.insertBefore(rightPanel, rightPanelOriginAnchor.nextSibling);
1243
+ }
1244
+ rightPanel.style.removeProperty("display");
1245
+ rightPanel.style.removeProperty("visibility");
1246
+ rightPanel.style.removeProperty("opacity");
1247
+ rightPanel.style.removeProperty("width");
1248
+ rightPanel.style.removeProperty("min-width");
1249
+ rightPanel.style.removeProperty("max-width");
1250
+ rightPanel.style.removeProperty("grid-row");
1251
+ rightPanel.style.removeProperty("grid-column");
1252
+ rightPanel.style.removeProperty("height");
1253
+ rightPanel.style.removeProperty("max-height");
1254
+ rightPanel.style.removeProperty("min-height");
1255
+ rightPanel.style.removeProperty("overflow");
1256
+ rightPanel.style.removeProperty("border-top");
1257
+ if (topZone) {
1258
+ topZone.style.removeProperty("display");
1259
+ topZone.style.removeProperty("visibility");
1260
+ topZone.style.removeProperty("opacity");
1261
+ }
1262
+ if (chatBox) {
1263
+ chatBox.style.removeProperty("display");
1264
+ chatBox.style.removeProperty("visibility");
1265
+ chatBox.style.removeProperty("opacity");
1266
+ }
1267
+ if (chatInputShell) {
1268
+ chatInputShell.style.removeProperty("display");
1269
+ chatInputShell.style.removeProperty("visibility");
1270
+ chatInputShell.style.removeProperty("opacity");
1271
+ }
1272
+ }
1273
+
1274
+ if (typeof window.__applyMobileChatRailSafety === "function") {
1275
+ [0, 60, 160, 360].forEach((delay) => {
1276
+ setTimeout(window.__applyMobileChatRailSafety, delay);
1277
+ });
1278
+ }
1279
+ }
1280
+ window.syncCodeWorkspace = syncCodeWorkspace;
1281
+
1282
+ function wrapModeSetters() {
1283
+ if (typeof setMode !== "function") return;
1284
+ const originalSetMode = setMode;
1285
+ setMode = function (mode) {
1286
+ if (mode !== "voice" && typeof stopVoiceMode === "function") {
1287
+ try { stopVoiceMode(true); } catch (error) {}
1288
+ }
1289
+ const result = originalSetMode.apply(this, arguments);
1290
+ try { currentMode = mode; } catch (error) {}
1291
+ try { selectedMode = mode; } catch (error) {}
1292
+ window.currentMode = mode;
1293
+ window.selectedMode = mode;
1294
+ rememberRecentMode(mode);
1295
+ const ttsControls = document.getElementById("tts-controls");
1296
+ const chatBox = document.getElementById("chat-box");
1297
+ const chatStack = document.getElementById("chat-main-stack");
1298
+ const topZone = document.getElementById("top-zone");
1299
+ const leftVoiceButton = document.getElementById("btn-submit-left");
1300
+ const chatInputShell = document.getElementById("chat-input-shell");
1301
+ if (ttsControls) ttsControls.classList.toggle("hidden", mode !== "settings");
1302
+ if (chatBox) chatBox.classList.toggle("hidden", mode === "settings");
1303
+ if (chatStack) chatStack.classList.toggle("settings-mode", mode === "settings");
1304
+ if (topZone) topZone.classList.toggle("settings-mode", mode === "settings");
1305
+ if (chatInputShell) chatInputShell.classList.toggle("hidden", mode === "settings");
1306
+ syncCodeWorkspace(mode);
1307
+ if (leftVoiceButton) {
1308
+ leftVoiceButton.classList.remove("hidden");
1309
+ leftVoiceButton.style.display = "flex";
1310
+ }
1311
+ if (typeof setVoiceState === "function") {
1312
+ setTimeout(() => setVoiceState("left", "idle"), 0);
1313
+ }
1314
+ if (mode === "chat") {
1315
+ setTimeout(() => ensureWelcomeMessage(false), 0);
1316
+ }
1317
+ updateComposerMeta(mode);
1318
+ updateTtsUi();
1319
+ renderRecentModeActions(mode);
1320
+ return result;
1321
+ };
1322
+ window.setMode = setMode;
1323
+ }
1324
+
1325
+ function wrapExecuteAction() {
1326
+ if (typeof executeAction !== "function") return;
1327
+ const originalExecuteAction = executeAction;
1328
+ executeAction = async function () {
1329
+ return originalExecuteAction.apply(this, arguments);
1330
+ };
1331
+ window.executeAction = executeAction;
1332
+ }
1333
+
1334
+ function loadSideBanner() {
1335
+ const slot = document.getElementById("chat-ad-slot");
1336
+ const fallback = document.getElementById("chat-ad-fallback");
1337
+ if (!slot) return;
1338
+
1339
+ const renderBanner = () => {
1340
+ if (typeof loadBanner !== "function") return;
1341
+ try {
1342
+ const slotRect = slot.getBoundingClientRect();
1343
+ const island = slot.closest(".island-box");
1344
+ const islandRect = island ? island.getBoundingClientRect() : null;
1345
+ const measuredHeight = Math.round(slotRect.height || 0);
1346
+ const fallbackHeight = Math.round((islandRect ? islandRect.height : 0) - 4);
1347
+ const bannerHeight = Math.max(120, measuredHeight || fallbackHeight || 220);
1348
+ loadBanner("chat-ad-slot", "fluke103", "200", bannerHeight);
1349
+ setTimeout(() => {
1350
+ if (slot.querySelector(".snapp-banner-container") && fallback) {
1351
+ fallback.classList.add("hidden");
1352
+ }
1353
+ }, 1200);
1354
+ } catch (error) {
1355
+ if (fallback) fallback.classList.remove("hidden");
1356
+ }
1357
+ };
1358
+
1359
+ if (typeof loadBanner === "function") {
1360
+ renderBanner();
1361
+ return;
1362
+ }
1363
+
1364
+ const script = document.createElement("script");
1365
+ script.src = "https://cdn.jsdelivr.net/gh/sllkx/addly/cdnbanner_radius.js";
1366
+ script.onload = renderBanner;
1367
+ script.onerror = () => {
1368
+ if (fallback) fallback.classList.remove("hidden");
1369
+ };
1370
+ document.head.appendChild(script);
1371
+ }
1372
+
1373
+ function initChatTtsUi() {
1374
+ overwriteAppendMsg();
1375
+ wrapResetChat();
1376
+ wrapAppModeHooks();
1377
+ wrapModeSetters();
1378
+ wrapExecuteAction();
1379
+ setupTtsControls();
1380
+ ensureWelcomeMessage(true);
1381
+ updateComposerMeta(getCurrentMode());
1382
+ updateTtsUi();
1383
+ renderRecentModeActions(getCurrentMode());
1384
+ loadSideBanner();
1385
+
1386
+ const ttsControls = document.getElementById("tts-controls");
1387
+ const chatBox = document.getElementById("chat-box");
1388
+ const chatStack = document.getElementById("chat-main-stack");
1389
+ const topZone = document.getElementById("top-zone");
1390
+ const leftVoiceButton = document.getElementById("btn-submit-left");
1391
+ const chatInputShell = document.getElementById("chat-input-shell");
1392
+ if (ttsControls) {
1393
+ ttsControls.classList.toggle("hidden", getCurrentMode() !== "settings");
1394
+ }
1395
+ if (chatBox) {
1396
+ chatBox.classList.toggle("hidden", getCurrentMode() === "settings");
1397
+ }
1398
+ if (chatStack) {
1399
+ chatStack.classList.toggle("settings-mode", getCurrentMode() === "settings");
1400
+ }
1401
+ if (topZone) {
1402
+ topZone.classList.toggle("settings-mode", getCurrentMode() === "settings");
1403
+ }
1404
+ if (chatInputShell) {
1405
+ chatInputShell.classList.toggle("hidden", getCurrentMode() === "settings");
1406
+ }
1407
+ syncCodeWorkspace(getCurrentMode());
1408
+ if (leftVoiceButton) {
1409
+ leftVoiceButton.classList.remove("hidden");
1410
+ leftVoiceButton.style.display = "flex";
1411
+ }
1412
+ }
1413
+
1414
+ if (document.readyState === "loading") {
1415
+ document.addEventListener("DOMContentLoaded", initChatTtsUi);
1416
+ } else {
1417
+ initChatTtsUi();
1418
+ }
1419
+ })();