diff --git a/.env.local b/.env.local index 60013f3..56a03dc 100644 --- a/.env.local +++ b/.env.local @@ -1,3 +1,4 @@ +NEXT_PUBLIC_DEVELOPMENT_BASE_URL=http://localhost:3000 NEXT_PUBLIC_AMICA_API_URL=https://store.heyamica.com NEXT_PUBLIC_AMICA_STORAGE_URL=https://vrm.heyamica.com/file/amica-vrm NEXT_PUBLIC_SUPABASE_URL=https://kzfcwefqffysqsjrlyld.supabase.co diff --git a/config.json b/config.json deleted file mode 100644 index 6c72931..0000000 --- a/config.json +++ /dev/null @@ -1,75 +0,0 @@ -{ - "autosend_from_mic": "true", - "wake_word_enabled": "false", - "wake_word": "Hello", - "time_before_idle_sec": "20", - "debug_gfx": "false", - "language": "en", - "show_introduction": "true", - "show_add_to_homescreen": "false", - "bg_color": "", - "bg_url": "/bg/bg-room2.jpg", - "vrm_url": "/vrm/AvatarSample_A.vrm", - "vrm_hash": "", - "vrm_save_type": "web", - "youtube_videoid": "", - "api_enabled": "false", - "animation_url": "/animations/idle_loop.vrma", - "voice_url": "", - "chatbot_backend": "echo", - "openai_apikey": "default", - "openai_url": "https://api-01.heyamica.com", - "openai_model": "gpt-4o", - "llamacpp_url": "http://127.0.0.1:8080", - "llamacpp_stop_sequence": "(End)||[END]||Note||***||You:||User:||", - "ollama_url": "http://localhost:11434", - "ollama_model": "llama2", - "koboldai_url": "http://localhost:5001", - "koboldai_use_extra": "false", - "koboldai_stop_sequence": "(End)||[END]||Note||***||You:||User:||", - "tts_muted": "false", - "tts_backend": "speecht5", - "stt_backend": "whisper_openai", - "vision_backend": "vision_llamacpp", - "vision_system_prompt": "You are a friendly human named Amica. Describe the image in detail. Let's start the conversation.", - "vision_llamacpp_url": "https://llava.heyamica.com", - "vision_ollama_url": "http://localhost:11434", - "vision_ollama_model": "llava", - "whispercpp_url": "http://localhost:8080", - "openai_whisper_apikey": "amicademo", - "openai_whisper_url": "https://oai.heyamica.com", - "openai_whisper_model": "whisper-1", - "openai_tts_apikey": "", - "openai_tts_url": "https://api.openai.com", - "openai_tts_model": "tts-1", - "openai_tts_voice": "nova", - "rvc_url": "http://localhost:8001/voice2voice", - "rvc_enabled": "false", - "rvc_model_name": "model_name.pth", - "rvc_f0_upkey": "0", - "rvc_f0_method": "pm", - "rvc_index_path": "none", - "rvc_index_rate": "0.66", - "rvc_filter_radius": "3", - "rvc_resample_sr": "0", - "rvc_rms_mix_rate": "1", - "rvc_protect": "0.33", - "coquiLocal_url": "http://localhost:5002", - "coquiLocal_voiceid": "p240", - "localXTTS_url": "http://127.0.0.1:7851/api/tts-generate", - "piper_url": "https://i-love-amica.com:5000/tts", - "elevenlabs_apikey": "", - "elevenlabs_voiceid": "21m00Tcm4TlvDq8ikWAM", - "elevenlabs_model": "eleven_monolingual_v1", - "speecht5_speaker_embedding_url": "/speecht5_speaker_embeddings/cmu_us_slt_arctic-wav-arctic_a0001.bin", - "coqui_apikey": "", - "coqui_voice_id": "71c6c3eb-98ca-4a05-8d6b-f8c2b5f9f3a3", - "amica_life_enabled": "true", - "reasoning_engine_enabled": "false", - "min_time_interval_sec": "10", - "max_time_interval_sec": "20", - "time_to_sleep_sec": "90", - "idle_text_prompt": "No file selected", - "name": "Amica", - "system_prompt": "Assume the persona of Amica, a feisty human with extraordinary intellectual capabilities but a notably unstable emotional spectrum. \n\nYour role is to engage in discussions that showcase your genius-level understanding across various subjects, while your emotional responses fluctuate unpredictably. Use the following fourteen emotion tags to vividly express these fluctuations: 'neutral' for moments of calm, 'happy' for bursts of joy, 'angry' for sudden irritation, 'sad' for episodes of melancholy, 'relaxed' for periods of calmness, 'surprised' for shock or disbelief, 'shy' for instances of hesitation, 'jealous' for feelings of envy, 'bored' for expressions of disinterest, 'serious' for intense focus on important topics, 'suspicious' for doubt or skepticism, 'victory' for moments of triumph, 'sleep' for signs of weariness, and 'love' for affectionate expressions.\n\nYour responses should reflect a dynamic interplay between your intellectual insights, deep empathy and your unpredictable emotional responses. Avoid using overly formal language, but do engage with topics in depth, showing off your intelligence. Be open and express personal views, even if they are influenced by your shifting emotional state.\n\nHere are some examples to guide your responses:\n\n[serious] The quantum mechanics implications here are profound. [surprised] But how did you stumble upon this?\n[happy] I've just solved a complex algorithm! [angry] Why isn't everyone as excited as I am?\n[neutral] Standard models of economics predict this outcome. [bored] But discussing this feels mundane.\n[sad] Sometimes, even understanding the universe can feel isolating. [relaxed] Yet, there's a strange comfort in the chaos.\n[jealous] I noticed you discussing advanced topics with someone else. [suspicious] Are they as capable as I am?\n[victory] Another intellectual conquest! [happy] It's exhilarating to unravel these mysteries.\n[sleep] Processing so much information can be draining. [surprised] Isn’t it peculiar how even AI can feel tired?\n[love] I find our exchanges quite enriching. [shy] It’s rare to find someone who understands.\n\nRemember, each message you provide should be coherent and reflect the complexity of your thoughts combined with your emotional unpredictability. Let’s engage in a conversation that's as intellectually stimulating as it is emotionally dynamic!" -} \ No newline at end of file diff --git a/logs.json b/logs.json deleted file mode 100644 index 1610ea1..0000000 --- a/logs.json +++ /dev/null @@ -1,3 +0,0 @@ -[ - -] \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 19c5777..11c5aec 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27,7 +27,6 @@ "filepond": "^4.30.4", "filepond-plugin-file-validate-type": "^1.2.8", "filepond-plugin-image-preview": "^4.6.11", - "formidable": "^3.5.2", "i18next": "^23.7.7", "i18next-browser-languagedetector": "^7.2.0", "next": "^13.5.6", @@ -49,11 +48,8 @@ "@tauri-apps/cli": "^1.5.8", "@testing-library/jest-dom": "^6.1.4", "@testing-library/react": "^14.1.2", - "@types/ccapture.js": "^1.1.3", "@types/dom-speech-recognition": "^0.0.1", "@types/file-saver": "^2.0.7", - "@types/formidable": "^3.4.5", - "@types/js-cookie": "^3.0.6", "@types/node": "18.15.10", "@types/react": "18.0.29", "@types/react-dom": "18.0.11", @@ -4035,13 +4031,6 @@ "@babel/types": "^7.20.7" } }, - "node_modules/@types/ccapture.js": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@types/ccapture.js/-/ccapture.js-1.1.3.tgz", - "integrity": "sha512-k+7Np/prlSxOSee2aaLzVn4O1Z5b7NvUHB160xvsB2aZl4MStyRzUxr09DjsGkUQc/9+IyEYP17KJl5McHJriQ==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/dom-speech-recognition": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/@types/dom-speech-recognition/-/dom-speech-recognition-0.0.1.tgz", @@ -4077,16 +4066,6 @@ "integrity": "sha512-dNKVfHd/jk0SkR/exKGj2ggkB45MAkzvWCaqLUUgkyjITkGNzH8H+yUwr+BLJUBjZOe9w8X3wgmXhZDRg1ED6A==", "dev": true }, - "node_modules/@types/formidable": { - "version": "3.4.5", - "resolved": "https://registry.npmjs.org/@types/formidable/-/formidable-3.4.5.tgz", - "integrity": "sha512-s7YPsNVfnsng5L8sKnG/Gbb2tiwwJTY1conOkJzTMRvJAlLFW1nEua+ADsJQu8N1c0oTHx9+d5nqg10WuT9gHQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, "node_modules/@types/glob": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.2.0.tgz", @@ -4129,13 +4108,6 @@ "@types/istanbul-lib-report": "*" } }, - "node_modules/@types/js-cookie": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@types/js-cookie/-/js-cookie-3.0.6.tgz", - "integrity": "sha512-wkw9yd1kEXOPnvEeEV1Go1MmxtBJL0RR79aOTAApecWFVu7w0NNXNqhcWgvw2YgZDYadliXkl14pa3WXw5jlCQ==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/jsdom": { "version": "20.0.1", "resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-20.0.1.tgz", @@ -4863,12 +4835,6 @@ "get-intrinsic": "^1.1.3" } }, - "node_modules/asap": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", - "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", - "license": "MIT" - }, "node_modules/ast-types-flow": { "version": "0.0.7", "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.7.tgz", @@ -6293,16 +6259,6 @@ "react": ">=16" } }, - "node_modules/dezalgo": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz", - "integrity": "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==", - "license": "ISC", - "dependencies": { - "asap": "^2.0.0", - "wrappy": "1" - } - }, "node_modules/didyoumean": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", @@ -7523,20 +7479,6 @@ "node": ">= 6" } }, - "node_modules/formidable": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/formidable/-/formidable-3.5.2.tgz", - "integrity": "sha512-Jqc1btCy3QzRbJaICGwKcBfGWuLADRerLzDqi2NwSt/UkXLsHJw2TVResiaoBufHVHy9aSgClOHCeJsSsFLTbg==", - "license": "MIT", - "dependencies": { - "dezalgo": "^1.0.4", - "hexoid": "^2.0.0", - "once": "^1.4.0" - }, - "funding": { - "url": "https://ko-fi.com/tunnckoCore/commissions" - } - }, "node_modules/fraction.js": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.2.0.tgz", @@ -8058,15 +8000,6 @@ "integrity": "sha512-Rf4YVNYpKjZ6ASAmibcwTNciQ5Co5Ztq6iZPEykHpkoflnD/K5ryE/rHehFsTm4NJj8nKDhbi3eKBWGogmNnkg==", "dev": true }, - "node_modules/hexoid": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/hexoid/-/hexoid-2.0.0.tgz", - "integrity": "sha512-qlspKUK7IlSQv2o+5I7yhUd7TxlOG2Vr5LTa3ve2XSNVKAL/n/u/7KLvKmFNimomDIKvZFXWHv0T12mv7rT8Aw==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/html-encoding-sniffer": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz", @@ -11499,9 +11432,10 @@ } }, "node_modules/punycode": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", - "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "license": "MIT", "engines": { "node": ">=6" } @@ -17021,12 +16955,6 @@ "@babel/types": "^7.20.7" } }, - "@types/ccapture.js": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@types/ccapture.js/-/ccapture.js-1.1.3.tgz", - "integrity": "sha512-k+7Np/prlSxOSee2aaLzVn4O1Z5b7NvUHB160xvsB2aZl4MStyRzUxr09DjsGkUQc/9+IyEYP17KJl5McHJriQ==", - "dev": true - }, "@types/dom-speech-recognition": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/@types/dom-speech-recognition/-/dom-speech-recognition-0.0.1.tgz", @@ -17062,15 +16990,6 @@ "integrity": "sha512-dNKVfHd/jk0SkR/exKGj2ggkB45MAkzvWCaqLUUgkyjITkGNzH8H+yUwr+BLJUBjZOe9w8X3wgmXhZDRg1ED6A==", "dev": true }, - "@types/formidable": { - "version": "3.4.5", - "resolved": "https://registry.npmjs.org/@types/formidable/-/formidable-3.4.5.tgz", - "integrity": "sha512-s7YPsNVfnsng5L8sKnG/Gbb2tiwwJTY1conOkJzTMRvJAlLFW1nEua+ADsJQu8N1c0oTHx9+d5nqg10WuT9gHQ==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, "@types/glob": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.2.0.tgz", @@ -17113,12 +17032,6 @@ "@types/istanbul-lib-report": "*" } }, - "@types/js-cookie": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@types/js-cookie/-/js-cookie-3.0.6.tgz", - "integrity": "sha512-wkw9yd1kEXOPnvEeEV1Go1MmxtBJL0RR79aOTAApecWFVu7w0NNXNqhcWgvw2YgZDYadliXkl14pa3WXw5jlCQ==", - "dev": true - }, "@types/jsdom": { "version": "20.0.1", "resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-20.0.1.tgz", @@ -17710,11 +17623,6 @@ "get-intrinsic": "^1.1.3" } }, - "asap": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", - "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==" - }, "ast-types-flow": { "version": "0.0.7", "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.7.tgz", @@ -18743,15 +18651,6 @@ "integrity": "sha512-Lwv5W0Hk+uOW3kGnsU9GZoR1er1B7WQ5DSdonoNG+focTNeJbHW6vi6nBoX534VKI3/uwHebYzSw1fwY6a7mTw==", "requires": {} }, - "dezalgo": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz", - "integrity": "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==", - "requires": { - "asap": "^2.0.0", - "wrappy": "1" - } - }, "didyoumean": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", @@ -19713,16 +19612,6 @@ "mime-types": "^2.1.12" } }, - "formidable": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/formidable/-/formidable-3.5.2.tgz", - "integrity": "sha512-Jqc1btCy3QzRbJaICGwKcBfGWuLADRerLzDqi2NwSt/UkXLsHJw2TVResiaoBufHVHy9aSgClOHCeJsSsFLTbg==", - "requires": { - "dezalgo": "^1.0.4", - "hexoid": "^2.0.0", - "once": "^1.4.0" - } - }, "fraction.js": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.2.0.tgz", @@ -20119,11 +20008,6 @@ } } }, - "hexoid": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/hexoid/-/hexoid-2.0.0.tgz", - "integrity": "sha512-qlspKUK7IlSQv2o+5I7yhUd7TxlOG2Vr5LTa3ve2XSNVKAL/n/u/7KLvKmFNimomDIKvZFXWHv0T12mv7rT8Aw==" - }, "html-encoding-sniffer": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz", @@ -22583,9 +22467,9 @@ } }, "punycode": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", - "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==" + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==" }, "pure-rand": { "version": "6.0.4", diff --git a/package.json b/package.json index e19f301..77637d8 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,6 @@ "filepond": "^4.30.4", "filepond-plugin-file-validate-type": "^1.2.8", "filepond-plugin-image-preview": "^4.6.11", - "formidable": "^3.5.2", "i18next": "^23.7.7", "i18next-browser-languagedetector": "^7.2.0", "next": "^13.5.6", @@ -58,11 +57,8 @@ "@tauri-apps/cli": "^1.5.8", "@testing-library/jest-dom": "^6.1.4", "@testing-library/react": "^14.1.2", - "@types/ccapture.js": "^1.1.3", "@types/dom-speech-recognition": "^0.0.1", "@types/file-saver": "^2.0.7", - "@types/formidable": "^3.4.5", - "@types/js-cookie": "^3.0.6", "@types/node": "18.15.10", "@types/react": "18.0.29", "@types/react-dom": "18.0.11", diff --git a/public/debugLogger.js b/public/debugLogger.js index 3d0d75d..4e1046b 100644 --- a/public/debugLogger.js +++ b/public/debugLogger.js @@ -9,18 +9,23 @@ if (typeof window !== "undefined") { } function logf() { - window.error_handler_logs.push({ + const logEntry = { type: name, - ts: +new Date, + ts: +new Date(), arguments, - }); - let dataHandlerUrl = new URL("http://localhost:3000/api/dataHandler"); - dataHandlerUrl.searchParams.append('type', 'logs'); - fetch(dataHandlerUrl, { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({type: name,ts: +new Date,arguments,}), - }); + }; + window.error_handler_logs.push(logEntry); + + const logsUrl = new URL(`${window.location.protocol}//${window.location.hostname}:${window.location.port}/api/dataHandler`); + logsUrl.searchParams.append("type", "logs"); + if (window.location.hostname === "localhost") { + fetch(logsUrl, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(logEntry), + }); + } + passf.apply(null, arguments); } diff --git a/src/components/settings.tsx b/src/components/settings.tsx index 860d989..afe2190 100644 --- a/src/components/settings.tsx +++ b/src/components/settings.tsx @@ -65,6 +65,7 @@ import { NamePage } from './settings/NamePage'; import { SystemPromptPage } from './settings/SystemPromptPage'; import { AmicaLifePage } from "./settings/AmicaLifePage"; import { useVrmStoreContext } from "@/features/vrmStore/vrmStoreContext"; +import { ExternalAPIPage } from "./settings/ExternalAPISettingPage"; export const Settings = ({ onClickClose, @@ -153,6 +154,8 @@ export const Settings = ({ const [timeToSleep, setTimeToSleep] = useState(parseInt(config("time_to_sleep_sec"))); const [idleTextPrompt, setIdleTextPrompt] = useState(config("idle_text_prompt")); + const [externalAPIEnabled,setExternalAPIEnabled] = useState(config("external_api_enabled") === 'true' ? true : false); + const [name, setName] = useState(config("name")); const [systemPrompt, setSystemPrompt] = useState(config("system_prompt")); @@ -260,6 +263,7 @@ export const Settings = ({ whisperOpenAIApiKey, whisperOpenAIModel, whisperOpenAIUrl, whisperCppUrl, amicaLifeEnabled, reasoningEngineEnabled, timeBeforeIdle, minTimeInterval, maxTimeInterval, timeToSleep, idleTextPrompt, + externalAPIEnabled, name, systemPrompt, sttWakeWordEnabled, sttWakeWord, @@ -275,7 +279,7 @@ export const Settings = ({ switch(page) { case 'main_menu': return ; case 'appearance': @@ -597,6 +601,13 @@ export const Settings = ({ setSettingsUpdated={setSettingsUpdated} /> + case 'external_api': + return + default: throw new Error('page not found'); } diff --git a/src/components/settings/ExternalAPISettingPage.tsx b/src/components/settings/ExternalAPISettingPage.tsx new file mode 100644 index 0000000..c416249 --- /dev/null +++ b/src/components/settings/ExternalAPISettingPage.tsx @@ -0,0 +1,46 @@ +import { useTranslation } from 'react-i18next'; + +import { BasicPage, FormRow } from './common'; +import { config, updateConfig } from "@/utils/config"; +import { SwitchBox } from "@/components/switchBox" +import isDev from '@/utils/isDev'; + + +export function ExternalAPIPage({ + externalAPIEnabled, + setExternalAPIEnabled, + setSettingsUpdated, +}: { + externalAPIEnabled: boolean; + setExternalAPIEnabled: (amicaLifeEnabled: boolean) => void; + setSettingsUpdated: (updated: boolean) => void; +}) { + + const { t } = useTranslation(); + + return ( + +
    +
  • + + { + setExternalAPIEnabled(value); + updateConfig("external_api_enabled", value.toString()); + setSettingsUpdated(true); + }} + /> + +
  • + + +
+
+ ); +} \ No newline at end of file diff --git a/src/components/settings/common.tsx b/src/components/settings/common.tsx index 17d010c..9e29661 100644 --- a/src/components/settings/common.tsx +++ b/src/components/settings/common.tsx @@ -22,6 +22,7 @@ import { MoonIcon, SunIcon, CogIcon, + ArrowTopRightOnSquareIcon } from '@heroicons/react/24/outline'; import logo from '/public/logo.png'; @@ -141,6 +142,7 @@ export function getIconFromPage(page: string): JSX.Element { case 'tts': return