From b41c10d2e6d198c375faa14e2235eca1e795094e Mon Sep 17 00:00:00 2001 From: Russell Ballestrini Date: Sun, 26 Oct 2025 11:29:52 -0400 Subject: [PATCH] modified: .gitignore deleted: add_remote_url_keys.js deleted: build-all.log deleted: modal_tests.js deleted: simple_tests.js deleted: uncloseai.js.orig --- .gitignore | 2 + add_remote_url_keys.js | 172 --- build-all.log | 1455 --------------------- modal_tests.js | 376 ------ simple_tests.js | 266 ---- uncloseai.js.orig | 2711 ---------------------------------------- 6 files changed, 2 insertions(+), 4980 deletions(-) delete mode 100644 add_remote_url_keys.js delete mode 100644 build-all.log delete mode 100644 modal_tests.js delete mode 100644 simple_tests.js delete mode 100644 uncloseai.js.orig diff --git a/.gitignore b/.gitignore index dab8e44..d14f1d7 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,8 @@ node_modules/ webwords/ book/ +*.log + # Generated audio files in language examples public/languages/**/*.mp3 *.mp3 diff --git a/add_remote_url_keys.js b/add_remote_url_keys.js deleted file mode 100644 index 211e1da..0000000 --- a/add_remote_url_keys.js +++ /dev/null @@ -1,172 +0,0 @@ -#!/usr/bin/env node - -// Script to add missing remote URL translation keys to all language files - -import fs from 'fs'; -import path from 'path'; - -const languagesDir = './src/languages'; - -// The new keys to add in English -const newKeys = { - remoteUrlTab: "🌐 Remote URL", - remoteUrlInfo: "Enter a URL to fetch and translate:", - remoteUrlPlaceholder: "https://example.com", - fetchAndTranslate: "🌐 Fetch & Translate" -}; - -// Translations for each language -const translations = { - ar: { - remoteUrlTab: "🌐 رابط بعيد", - remoteUrlInfo: "أدخل رابط لجلبه وترجمته:", - remoteUrlPlaceholder: "https://example.com", - fetchAndTranslate: "🌐 جلب وترجمة" - }, - bn: { - remoteUrlTab: "🌐 দূরবর্তী URL", - remoteUrlInfo: "একটি URL প্রবেশ করান যা আনতে এবং অনুবাদ করতে:", - remoteUrlPlaceholder: "https://example.com", - fetchAndTranslate: "🌐 আনুন এবং অনুবাদ করুন" - }, - de: { - remoteUrlTab: "🌐 Externe URL", - remoteUrlInfo: "Geben Sie eine URL ein, um sie abzurufen und zu übersetzen:", - remoteUrlPlaceholder: "https://example.com", - fetchAndTranslate: "🌐 Abrufen & Übersetzen" - }, - es: { - remoteUrlTab: "🌐 URL Remota", - remoteUrlInfo: "Ingresa una URL para obtener y traducir:", - remoteUrlPlaceholder: "https://ejemplo.com", - fetchAndTranslate: "🌐 Obtener y Traducir" - }, - fr: { - remoteUrlTab: "🌐 URL Distante", - remoteUrlInfo: "Entrez une URL à récupérer et traduire:", - remoteUrlPlaceholder: "https://exemple.com", - fetchAndTranslate: "🌐 Récupérer et Traduire" - }, - hi: { - remoteUrlTab: "🌐 दूरस्थ URL", - remoteUrlInfo: "लाने और अनुवाद करने के लिए एक URL दर्ज करें:", - remoteUrlPlaceholder: "https://example.com", - fetchAndTranslate: "🌐 लाएं और अनुवाद करें" - }, - id: { - remoteUrlTab: "🌐 URL Jarak Jauh", - remoteUrlInfo: "Masukkan URL untuk diambil dan diterjemahkan:", - remoteUrlPlaceholder: "https://example.com", - fetchAndTranslate: "🌐 Ambil & Terjemahkan" - }, - ja: { - remoteUrlTab: "🌐 リモートURL", - remoteUrlInfo: "取得して翻訳するURLを入力してください:", - remoteUrlPlaceholder: "https://example.com", - fetchAndTranslate: "🌐 取得して翻訳" - }, - ko: { - remoteUrlTab: "🌐 원격 URL", - remoteUrlInfo: "가져와서 번역할 URL을 입력하세요:", - remoteUrlPlaceholder: "https://example.com", - fetchAndTranslate: "🌐 가져와서 번역" - }, - mr: { - remoteUrlTab: "🌐 दूरस्थ URL", - remoteUrlInfo: "आणण्यासाठी आणि भाषांतर करण्यासाठी URL प्रविष्ट करा:", - remoteUrlPlaceholder: "https://example.com", - fetchAndTranslate: "🌐 आणा आणि भाषांतर करा" - }, - pt: { - remoteUrlTab: "🌐 URL Remota", - remoteUrlInfo: "Digite uma URL para buscar e traduzir:", - remoteUrlPlaceholder: "https://exemplo.com", - fetchAndTranslate: "🌐 Buscar e Traduzir" - }, - ru: { - remoteUrlTab: "🌐 Удаленный URL", - remoteUrlInfo: "Введите URL для загрузки и перевода:", - remoteUrlPlaceholder: "https://example.com", - fetchAndTranslate: "🌐 Загрузить и Перевести" - }, - sw: { - remoteUrlTab: "🌐 URL ya Mbali", - remoteUrlInfo: "Ingiza URL ya kuleta na kutafsiri:", - remoteUrlPlaceholder: "https://example.com", - fetchAndTranslate: "🌐 Leta na Tafsiri" - }, - te: { - remoteUrlTab: "🌐 రిమోట్ URL", - remoteUrlInfo: "తీసుకురావడానికి మరియు అనువదించడానికి URL ని నమోదు చేయండి:", - remoteUrlPlaceholder: "https://example.com", - fetchAndTranslate: "🌐 తీసుకురండి & అనువదించండి" - }, - tr: { - remoteUrlTab: "🌐 Uzak URL", - remoteUrlInfo: "Getirmek ve çevirmek için bir URL girin:", - remoteUrlPlaceholder: "https://example.com", - fetchAndTranslate: "🌐 Getir ve Çevir" - }, - ur: { - remoteUrlTab: "🌐 ریموٹ URL", - remoteUrlInfo: "لانے اور ترجمہ کرنے کے لیے URL داخل کریں:", - remoteUrlPlaceholder: "https://example.com", - fetchAndTranslate: "🌐 لائیں اور ترجمہ کریں" - }, - "zh-tw": { - remoteUrlTab: "🌐 遠端網址", - remoteUrlInfo: "輸入要擷取和翻譯的網址:", - remoteUrlPlaceholder: "https://example.com", - fetchAndTranslate: "🌐 擷取並翻譯" - }, - zh: { - remoteUrlTab: "🌐 远程网址", - remoteUrlInfo: "输入要获取和翻译的网址:", - remoteUrlPlaceholder: "https://example.com", - fetchAndTranslate: "🌐 获取并翻译" - } -}; - -// Process each language file -for (const [langCode, langTranslations] of Object.entries(translations)) { - const filePath = path.join(languagesDir, `${langCode}.js`); - - if (fs.existsSync(filePath)) { - let content = fs.readFileSync(filePath, 'utf8'); - - // Find the position to insert new keys (before the closing }) - const insertPosition = content.lastIndexOf('};'); - - if (insertPosition === -1) { - console.error(`Could not find export structure in ${filePath}`); - continue; - } - - // Find the last non-empty line before the closing } - const beforeClosing = content.substring(0, insertPosition).trimEnd(); - - // Check if any of the new keys already exist - const hasNewKeys = Object.keys(langTranslations).some(key => - content.includes(`${key}:`)); - - if (hasNewKeys) { - console.log(`✅ ${langCode}: Keys already present, skipping`); - continue; - } - - // Build the new keys string - const newKeysString = Object.entries(langTranslations) - .map(([key, value]) => `\t${key}: "${value}",`) - .join('\n'); - - // Insert the new keys - const newContent = beforeClosing + ',\n' + newKeysString + '\n};'; - - fs.writeFileSync(filePath, newContent, 'utf8'); - console.log(`✅ ${langCode}: Added remote URL translation keys`); - } else { - console.error(`File not found: ${filePath}`); - } -} - -console.log('\n🎉 Finished adding remote URL translation keys to all language files!'); \ No newline at end of file diff --git a/build-all.log b/build-all.log deleted file mode 100644 index d2c5c45..0000000 --- a/build-all.log +++ /dev/null @@ -1,1455 +0,0 @@ -Building all language implementations... - -======================================== -[1] Building awk (languages/awk) -======================================== -#5 transferring context: 9.60kB done -#5 DONE 0.0s - -#6 [2/5] RUN apk add --no-cache gawk curl ca-certificates -#6 CACHED - -#7 [3/5] WORKDIR /app -#7 CACHED - -#8 [4/5] COPY uncloseai.awk . -#8 DONE 0.1s - -#9 [5/5] RUN chmod +x uncloseai.awk -#9 DONE 0.3s - -#10 exporting to image -#10 exporting layers 0.1s done -#10 writing image sha256:01d5c197f31cea2db8e850a3e9ebfc30e4a6bd75c003f847782e820d55f4fda6 done -#10 naming to docker.io/library/ai-unturf-awk done -#10 DONE 0.1s -✅ awk built successfully - -======================================== -[2] Building bash (languages/bash) -======================================== -#5 transferring context: 9.05kB done -#5 DONE 0.0s - -#6 [2/5] RUN apk add --no-cache bash curl jq ca-certificates -#6 CACHED - -#7 [3/5] WORKDIR /app -#7 CACHED - -#8 [4/5] COPY uncloseai.sh . -#8 DONE 0.1s - -#9 [5/5] RUN chmod +x uncloseai.sh -#9 DONE 0.4s - -#10 exporting to image -#10 exporting layers 0.1s done -#10 writing image sha256:bc4ced4134e29317557f58e9b0df11cfe45bd888aaec76a1faeafe6a8104b3a5 done -#10 naming to docker.io/library/ai-unturf-bash done -#10 DONE 0.1s -✅ bash built successfully - -======================================== -[3] Building c-curl (languages/c/curl) -======================================== -#10 0.549 uncloseai.c:297:5: note: 'snprintf' output between 18 and 529 bytes into a destination of size 512 -#10 0.549 297 | snprintf(url, sizeof(url), "%s/chat/completions", -#10 0.549 | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -#10 0.549 298 | client->models[model_idx].endpoint); -#10 0.549 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -#10 0.565 uncloseai.c: In function 'uncloseai_tts': -#10 0.566 uncloseai.c:343:35: warning: '/audio/speech' directive output may be truncated writing 13 bytes into a region of size between 1 and 512 [-Wformat-truncation=] -#10 0.566 343 | snprintf(url, sizeof(url), "%s/audio/speech", client->tts_endpoints[0]); -#10 0.566 | ^~~~~~~~~~~~~ -#10 0.566 uncloseai.c:343:5: note: 'snprintf' output between 14 and 525 bytes into a destination of size 512 -#10 0.566 343 | snprintf(url, sizeof(url), "%s/audio/speech", client->tts_endpoints[0]); -#10 0.566 | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -#10 DONE 0.8s - -#11 exporting to image -#11 exporting layers -#11 exporting layers 1.7s done -#11 writing image sha256:c937df2cc6c3f04e0b7dc73638467b8b38707272bb2f6c0f51c2dada81b2e19a done -#11 naming to docker.io/library/ai-unturf-c-curl done -#11 DONE 1.7s -✅ c-curl built successfully - -======================================== -[4] Building c-libsoup (languages/c/libsoup) -======================================== -#6 [4/6] COPY uncloseai.c . -#6 CACHED - -#7 [5/6] COPY Makefile . -#7 CACHED - -#8 [2/6] RUN apk --no-cache add gcc musl-dev libsoup-dev glib-dev make ca-certificates -#8 CACHED - -#9 [3/6] WORKDIR /app -#9 CACHED - -#10 [6/6] RUN make -#10 CACHED - -#11 exporting to image -#11 exporting layers done -#11 writing image sha256:f1d0804717b35866d852c1e56fa29726ea11072e54bb97ec53b242f635fff10f done -#11 naming to docker.io/library/ai-unturf-c-libsoup done -#11 DONE 0.0s -✅ c-libsoup built successfully - -======================================== -[5] Building clojure (languages/clojure) -======================================== -#10 5.443 Downloading: commons-io/commons-io/2.16.1/commons-io-2.16.1.jar from central -#10 5.582 Downloading: org/apache/httpcomponents/httpclient-cache/4.5.14/httpclient-cache-4.5.14.jar from central -#10 5.586 Downloading: org/apache/httpcomponents/httpcore/4.4.16/httpcore-4.4.16.jar from central -#10 5.616 Downloading: com/fasterxml/jackson/core/jackson-core/2.17.0/jackson-core-2.17.0.jar from central -#10 5.656 Downloading: riddley/riddley/0.1.12/riddley-0.1.12.jar from clojars -#10 5.717 Downloading: clj-http/clj-http/3.13.0/clj-http-3.13.0.jar from clojars -#10 5.732 Downloading: org/apache/httpcomponents/httpclient/4.5.14/httpclient-4.5.14.jar from central -#10 5.769 Downloading: commons-logging/commons-logging/1.2/commons-logging-1.2.jar from central -#10 5.900 Downloading: tigris/tigris/0.1.2/tigris-0.1.2.jar from clojars -#10 5.975 Downloading: potemkin/potemkin/0.4.7/potemkin-0.4.7.jar from clojars -#10 5.997 Downloading: com/fasterxml/jackson/dataformat/jackson-dataformat-smile/2.17.0/jackson-dataformat-smile-2.17.0.jar from central -#10 6.017 Downloading: org/apache/httpcomponents/httpmime/4.5.14/httpmime-4.5.14.jar from central -#10 6.149 Downloading: cheshire/cheshire/5.13.0/cheshire-5.13.0.jar from clojars -#10 DONE 6.5s - -#11 exporting to image -#11 exporting layers 0.1s done -#11 writing image sha256:a8d77e7401df76f22fc61ce68546d6f249c75c4d4b416cce9e6599576bf04d1c done -#11 naming to docker.io/library/ai-unturf-clojure done -#11 DONE 0.1s -✅ clojure built successfully - -======================================== -[6] Building c-nghttp2 (languages/c/nghttp2) -======================================== -#6 [5/6] COPY Makefile . -#6 CACHED - -#7 [2/6] RUN apk --no-cache add gcc musl-dev nghttp2-dev make ca-certificates -#7 CACHED - -#8 [4/6] COPY uncloseai.c . -#8 CACHED - -#9 [3/6] WORKDIR /app -#9 CACHED - -#10 [6/6] RUN make -#10 CACHED - -#11 exporting to image -#11 exporting layers done -#11 writing image sha256:ce696ab6210ed584a1fb1365f1549f74c7a08069c3fdea1d4e352fd3e03d10e2 done -#11 naming to docker.io/library/ai-unturf-c-nghttp2 done -#11 DONE 0.0s -✅ c-nghttp2 built successfully - -======================================== -[7] Building cobol (languages/cobol) -======================================== -#8 [stage-1 2/4] RUN apt-get update && apt-get install -y gnucobol4 libcob4 ca-certificates curl jq bash && rm -rf /var/lib/apt/lists/* -#8 CACHED - -#9 [stage-1 3/4] WORKDIR /app -#9 CACHED - -#10 [builder 2/5] RUN apt-get update && apt-get install -y gnucobol4 && rm -rf /var/lib/apt/lists/* -#10 CACHED - -#11 [builder 5/5] RUN cobc -x -free uncloseai.cob -o uncloseai -#11 CACHED - -#12 [stage-1 4/4] COPY --from=builder /app/uncloseai . -#12 CACHED - -#13 exporting to image -#13 exporting layers done -#13 writing image sha256:d17be52115e1fb61eef5fbc310e9eb2759221317ea08aed0e85dd40608f9ca60 done -#13 naming to docker.io/library/ai-unturf-cobol done -#13 DONE 0.0s -✅ cobol built successfully - -======================================== -[8] Building commonlisp (languages/commonlisp) -======================================== -#9 [4/9] WORKDIR /app -#9 CACHED - -#10 [5/9] COPY hermes-nonstreaming.lisp . -#10 CACHED - -#11 [3/9] RUN sbcl --eval '(ql:quickload :dexador)' --eval '(ql:quickload :jonathan)' --quit -#11 CACHED - -#12 [8/9] COPY qwen-streaming.lisp . -#12 CACHED - -#13 [9/9] COPY tts.lisp . -#13 DONE 0.1s - -#14 exporting to image -#14 exporting layers 0.0s done -#14 writing image sha256:fe2f726936e2d69c4ab0dfb75e4d446a944cd4b21af7aecc108649f9ccf5b7b8 done -#14 naming to docker.io/library/ai-unturf-commonlisp done -#14 DONE 0.1s -✅ commonlisp built successfully - -======================================== -[9] Building cpp-boost-beast (languages/cpp/boost-beast) -======================================== -#6 [2/6] RUN apk --no-cache add g++ make boost1.84-dev openssl-dev ca-certificates -#6 CACHED - -#7 [4/6] COPY uncloseai.cpp . -#7 CACHED - -#8 [5/6] COPY Makefile . -#8 CACHED - -#9 [3/6] WORKDIR /app -#9 CACHED - -#10 [6/6] RUN make -#10 CACHED - -#11 exporting to image -#11 exporting layers done -#11 writing image sha256:67c12d024da4d904c2976aa474879c1373180420f4503d023615a945a5b9b5f3 done -#11 naming to docker.io/library/ai-unturf-cpp-boost-beast done -#11 DONE 0.0s -✅ cpp-boost-beast built successfully - -======================================== -[10] Building cpp-cpp-httplib (languages/cpp/cpp-httplib) -======================================== -#7 CACHED - -#8 [3/7] WORKDIR /app -#8 CACHED - -#9 [5/7] COPY uncloseai.cpp . -#9 CACHED - -#10 [6/7] COPY Makefile . -#10 CACHED - -#11 [7/7] RUN make -#11 CACHED - -#12 exporting to image -#12 exporting layers done -#12 writing image sha256:585fbd6a4b5d8fabfbc1c6a1275519de80d253dfdaf057aaae8bec47ae81461f done -#12 naming to docker.io/library/ai-unturf-cpp-cpp-httplib -#12 naming to docker.io/library/ai-unturf-cpp-cpp-httplib done -#12 DONE 0.0s -✅ cpp-cpp-httplib built successfully - -======================================== -[11] Building cpp-libcurl (languages/cpp/libcurl) -======================================== - -#7 [3/6] WORKDIR /app -#7 DONE 0.1s - -#8 [4/6] COPY uncloseai.cpp . -#8 DONE 0.1s - -#9 [5/6] COPY Makefile . -#9 DONE 0.1s - -#10 [6/6] RUN make -#10 0.226 g++ -std=c++11 -Wall -Wextra -O2 -o uncloseai uncloseai.cpp -lcurl -#10 DONE 2.7s - -#11 exporting to image -#11 exporting layers -#11 exporting layers 2.5s done -#11 writing image sha256:31853963c4074a3bcf3af5175ea8a4350a4da280f251e1d730173c3c9be047b0 done -#11 naming to docker.io/library/ai-unturf-cpp-libcurl done -#11 DONE 2.5s -✅ cpp-libcurl built successfully - -======================================== -[12] Building crystal (languages/crystal) -======================================== -#9 [builder 3/4] COPY uncloseai.cr . -#9 DONE 0.1s - -#10 [builder 4/4] RUN crystal build --release uncloseai.cr -#10 DONE 43.9s - -#11 [stage-1 2/4] RUN apk add --no-cache ca-certificates gc-dev pcre2-dev libevent-dev -#11 CACHED - -#12 [stage-1 3/4] WORKDIR /app -#12 CACHED - -#13 [stage-1 4/4] COPY --from=builder /app/uncloseai . -#13 DONE 0.1s - -#14 exporting to image -#14 exporting layers 0.1s done -#14 writing image sha256:f23a4f18bebddf56759ce9abcd6d20cdddc29b89abe59e0830dbf4b7740fdafa done -#14 naming to docker.io/library/ai-unturf-crystal done -#14 DONE 0.1s -✅ crystal built successfully - -======================================== -[13] Building csharp-http (languages/csharp/http) -======================================== -#13 [builder 5/5] RUN dotnet build -c Release -o out -#13 1.349 Determining projects to restore... -#13 2.012 Restored /app/csharp.csproj (in 140 ms). -#13 6.670 csharp -> /app/out/csharp.dll -#13 6.689 -#13 6.691 Build succeeded. -#13 6.691 0 Warning(s) -#13 6.691 0 Error(s) -#13 6.694 -#13 6.694 Time Elapsed 00:00:05.95 -#13 DONE 6.8s - -#14 [stage-1 4/4] COPY --from=builder /app/out . -#14 DONE 0.2s - -#15 exporting to image -#15 exporting layers 0.1s done -#15 writing image sha256:ed7ce024bddb717a45b9f4155a84b26ec172c745946927a6164583dab366fffe done -#15 naming to docker.io/library/ai-unturf-csharp-http done -#15 DONE 0.1s -✅ csharp-http built successfully - -======================================== -[14] Building csharp-openai (languages/csharp/openai) -======================================== -5.546 -5.546 /app/uncloseai.cs(10,7): warning CS8981: The type name 'uncloseai' only contains lower-cased ascii characters. Such names may become reserved for the language. [/app/uncloseai.csproj] -5.546 /app/uncloseai.cs(47,13): error CS1739: The best overload for 'ChatClient' does not have a parameter named 'apiKey' [/app/uncloseai.csproj] -5.546 /app/uncloseai.cs(94,13): error CS1739: The best overload for 'ChatClient' does not have a parameter named 'apiKey' [/app/uncloseai.csproj] -5.547 /app/uncloseai.cs(141,13): error CS1739: The best overload for 'AudioClient' does not have a parameter named 'apiKey' [/app/uncloseai.csproj] -5.547 /app/uncloseai.cs(153,17): error CS0117: 'SpeechGenerationOptions' does not contain a definition for 'Speed' [/app/uncloseai.csproj] -5.547 1 Warning(s) -5.547 4 Error(s) -5.550 -5.550 Time Elapsed 00:00:04.97 ------- -Dockerfile:12 --------------------- - 10 | # Copy source and build - 11 | COPY uncloseai.cs ./ - 12 | >>> RUN dotnet build -c Release -o /app/build - 13 | - 14 | # Run stage --------------------- -ERROR: failed to solve: process "/bin/sh -c dotnet build -c Release -o /app/build" did not complete successfully: exit code: 1 -✅ csharp-openai built successfully - -======================================== -[15] Building dart (languages/dart) -======================================== -#9 [build 6/7] RUN dart pub get --offline -#9 CACHED - -#10 [build 7/7] RUN dart compile exe bin/uncloseai.dart -o bin/uncloseai -#10 CACHED - -#11 [build 2/7] WORKDIR /app -#11 CACHED - -#12 [build 3/7] COPY pubspec.* . -#12 CACHED - -#13 [stage-1 2/2] COPY --from=build /app/bin/uncloseai /app/bin/ -#13 CACHED - -#14 exporting to image -#14 exporting layers done -#14 writing image sha256:86328cf548eb37727bd1d1cfbbf86e4581b5750751ee55d5c614dc66968e9f26 done -#14 naming to docker.io/library/ai-unturf-dart done -#14 DONE 0.0s -✅ dart built successfully - -======================================== -[16] Building d (languages/d) -======================================== -#12 DONE 0.1s - -#13 [9/9] RUN dmd -of=hermes_nonstreaming hermes_nonstreaming.d && dmd -of=hermes_streaming hermes_streaming.d && dmd -of=qwen_nonstreaming qwen_nonstreaming.d && dmd -of=qwen_streaming qwen_streaming.d && dmd -of=tts tts.d -#13 0.436 hermes_nonstreaming.d(21): Error: not an associative array initializer -#13 ERROR: process "/bin/sh -c dmd -of=hermes_nonstreaming hermes_nonstreaming.d && dmd -of=hermes_streaming hermes_streaming.d && dmd -of=qwen_nonstreaming qwen_nonstreaming.d && dmd -of=qwen_streaming qwen_streaming.d && dmd -of=tts tts.d" did not complete successfully: exit code: 1 ------- - > [9/9] RUN dmd -of=hermes_nonstreaming hermes_nonstreaming.d && dmd -of=hermes_streaming hermes_streaming.d && dmd -of=qwen_nonstreaming qwen_nonstreaming.d && dmd -of=qwen_streaming qwen_streaming.d && dmd -of=tts tts.d: -0.436 hermes_nonstreaming.d(21): Error: not an associative array initializer ------- -Dockerfile:19 --------------------- - 18 | # Compile all examples - 19 | >>> RUN dmd -of=hermes_nonstreaming hermes_nonstreaming.d && \ - 20 | >>> dmd -of=hermes_streaming hermes_streaming.d && \ - 21 | >>> dmd -of=qwen_nonstreaming qwen_nonstreaming.d && \ - 22 | >>> dmd -of=qwen_streaming qwen_streaming.d && \ - 23 | >>> dmd -of=tts tts.d - 24 | --------------------- -ERROR: failed to solve: process "/bin/sh -c dmd -of=hermes_nonstreaming hermes_nonstreaming.d && dmd -of=hermes_streaming hermes_streaming.d && dmd -of=qwen_nonstreaming qwen_nonstreaming.d && dmd -of=qwen_streaming qwen_streaming.d && dmd -of=tts tts.d" did not complete successfully: exit code: 1 -✅ d built successfully - -======================================== -[17] Building deno (languages/deno) -======================================== -#3 DONE 0.0s - -#4 [1/3] FROM docker.io/denoland/deno:2.1.4@sha256:3bf75873714baa410dcf7fabaf76d806d20f0ac8a7579df11577b4ed97416e34 -#4 DONE 0.0s - -#5 [internal] load build context -#5 transferring context: 93B done -#5 DONE 0.0s - -#6 [2/3] WORKDIR /app -#6 CACHED - -#7 [3/3] COPY uncloseai.ts . -#7 CACHED - -#8 exporting to image -#8 exporting layers done -#8 writing image sha256:986244b89b1e93310d23f57e54d904a189784b73bfd41186463d75764c611784 done -#8 naming to docker.io/library/ai-unturf-deno done -#8 DONE 0.0s -✅ deno built successfully - -======================================== -[18] Building elixir-httpoison (languages/elixir/httpoison) -======================================== -#13 [stage-1 7/8] COPY --from=build /app/run.exs /app/run.exs -#13 CACHED - -#14 [build 3/8] RUN mix local.hex --force && mix local.rebar --force -#14 CACHED - -#15 [build 4/8] COPY mix.exs mix.lock* ./ -#15 CACHED - -#16 [stage-1 4/8] COPY --from=build /app/_build /app/_build -#16 CACHED - -#17 [stage-1 8/8] COPY --from=build /app/mix.exs /app/mix.exs -#17 CACHED - -#18 exporting to image -#18 exporting layers done -#18 writing image sha256:85d6d4eb47ea1b4bf86b5df606799e0fd1616b78f43e66211c95469d3ce4f3a3 done -#18 naming to docker.io/library/ai-unturf-elixir-httpoison done -#18 DONE 0.0s -✅ elixir-httpoison built successfully - -======================================== -[19] Building elixir-openai (languages/elixir/openai) -======================================== -#9 29.82 Compiling 40 files (.ex) -#9 31.17 Generated openai_ex app -#9 32.07 ===> Analyzing applications... -#9 32.12 ===> Compiling metrics -#9 33.55 ===> Analyzing applications... -#9 33.77 ===> Compiling hackney -#9 36.55 ==> httpoison -#9 36.55 Compiling 3 files (.ex) -#9 37.33 Generated httpoison app -#9 DONE 37.5s - -#10 [6/6] COPY uncloseai.exs ./ -#10 DONE 0.2s - -#11 exporting to image -#11 exporting layers -#11 exporting layers 0.3s done -#11 writing image sha256:d7dc3a70eadd3d30ed99bf17923f848b4e5f5d3119a5d3a8f70189273cc68622 done -#11 naming to docker.io/library/ai-unturf-elixir-openai 0.0s done -#11 DONE 0.4s -✅ elixir-openai built successfully - -======================================== -[20] Building erlang (languages/erlang) -======================================== -#11 [builder 7/7] RUN ./rebar3 compile -#11 CACHED - -#12 [stage-1 3/5] RUN apk add --no-cache ca-certificates -#12 CACHED - -#13 [builder 3/7] RUN apk add --no-cache git && wget https://s3.amazonaws.com/rebar3/rebar3 && chmod +x rebar3 -#13 CACHED - -#14 [stage-1 5/5] COPY --from=builder /app/src /app/src -#14 CACHED - -#15 exporting to image -#15 exporting layers done -#15 writing image sha256:7c0d118d6828e2e29bd7ea888f074160e16b1203d844d9aaa09adfffee8a2e31 done -#15 naming to docker.io/library/ai-unturf-erlang done -#15 DONE 0.0s - - 1 warning found (use docker --debug to expand): - - FromAsCasing: 'as' and 'FROM' keywords' casing do not match (line 1) -✅ erlang built successfully - -======================================== -[21] Building fortran (languages/fortran) -======================================== -#5 transferring context: 94B done -#5 DONE 0.1s - -#6 [2/5] RUN apt-get update && apt-get install -y gfortran-14 curl ca-certificates && rm -rf /var/lib/apt/lists/* -#6 CACHED - -#7 [3/5] WORKDIR /app -#7 CACHED - -#8 [4/5] COPY uncloseai.f90 . -#8 CACHED - -#9 [5/5] RUN gfortran-14 -o uncloseai uncloseai.f90 -#9 CACHED - -#10 exporting to image -#10 exporting layers done -#10 writing image sha256:c2e559eba5e6881b47ba1535a551b65378e808c73fa864dbca82f7662068af50 done -#10 naming to docker.io/library/ai-unturf-fortran done -#10 DONE 0.0s -✅ fortran built successfully - -======================================== -[22] Building fsharp (languages/fsharp) -======================================== -#11 11.50 0 Error(s) -#11 11.50 -#11 11.50 Time Elapsed 00:00:10.38 -#11 DONE 11.7s - -#12 [stage-1 2/4] RUN apk add --no-cache ca-certificates -#12 CACHED - -#13 [stage-1 3/4] WORKDIR /app -#13 CACHED - -#14 [stage-1 4/4] COPY --from=builder /app/out . -#14 DONE 0.2s - -#15 exporting to image -#15 exporting layers 0.1s done -#15 writing image sha256:d583439e0b7d49dba078f59f15887c555377896713c36cd53186f9bc48513748 -#15 writing image sha256:d583439e0b7d49dba078f59f15887c555377896713c36cd53186f9bc48513748 done -#15 naming to docker.io/library/ai-unturf-fsharp 0.0s done -#15 DONE 0.2s -✅ fsharp built successfully - -======================================== -[23] Building go-http (languages/go/http) -======================================== -#11 [builder 5/7] COPY uncloseai/ ./uncloseai/ -#11 ERROR: failed to calculate checksum of ref 466bb300-1953-4d46-a0ff-c9ba0efad3a2::tzkop5au2bw8yxjjck4sy95h3: "/uncloseai": not found - -#12 [builder 6/7] COPY examples/ ./examples/ -#12 ERROR: failed to calculate checksum of ref 466bb300-1953-4d46-a0ff-c9ba0efad3a2::tzkop5au2bw8yxjjck4sy95h3: "/examples": not found ------- - > [builder 5/7] COPY uncloseai/ ./uncloseai/: ------- ------- - > [builder 6/7] COPY examples/ ./examples/: ------- -Dockerfile:11 --------------------- - 9 | # Copy module files - 10 | COPY go.mod . - 11 | >>> COPY uncloseai/ ./uncloseai/ - 12 | COPY examples/ ./examples/ - 13 | --------------------- -ERROR: failed to solve: failed to compute cache key: failed to calculate checksum of ref 466bb300-1953-4d46-a0ff-c9ba0efad3a2::tzkop5au2bw8yxjjck4sy95h3: "/uncloseai": not found -✅ go-http built successfully - -======================================== -[24] Building go-openai (languages/go/openai) -======================================== -#13 0.519 go mod download github.com/openai/openai-go -#13 0.519 main.go:12:2: missing go.sum entry for module providing package github.com/openai/openai-go/option; to add: -#13 0.519 go mod download github.com/openai/openai-go -#13 ERROR: process "/bin/sh -c CGO_ENABLED=0 GOOS=linux go build -o /uncloseai main.go" did not complete successfully: exit code: 1 ------- - > [builder 6/6] RUN CGO_ENABLED=0 GOOS=linux go build -o /uncloseai main.go: -0.519 main.go:11:2: missing go.sum entry for module providing package github.com/openai/openai-go; to add: -0.519 go mod download github.com/openai/openai-go -0.519 main.go:12:2: missing go.sum entry for module providing package github.com/openai/openai-go/option; to add: -0.519 go mod download github.com/openai/openai-go ------- -Dockerfile:14 --------------------- - 12 | - 13 | # Build the application - 14 | >>> RUN CGO_ENABLED=0 GOOS=linux go build -o /uncloseai main.go - 15 | - 16 | # Final stage --------------------- -ERROR: failed to solve: process "/bin/sh -c CGO_ENABLED=0 GOOS=linux go build -o /uncloseai main.go" did not complete successfully: exit code: 1 -✅ go-openai built successfully - -======================================== -[25] Building groovy-openai-client (languages/groovy/openai-client) -======================================== -#7 DONE 0.3s - -#8 [4/7] COPY HermesStreaming.groovy . -#8 DONE 0.2s - -#9 [5/7] COPY QwenNonStreaming.groovy . -#9 DONE 0.2s - -#10 [6/7] COPY QwenStreaming.groovy . -#10 DONE 0.3s - -#11 [7/7] COPY TTS.groovy . -#11 DONE 0.2s - -#12 exporting to image -#12 exporting layers -#12 exporting layers 0.3s done -#12 writing image sha256:30c6a907695bd15615fd45c937aa92bee2e11deb124cb57e27b74876a9459503 done -#12 naming to docker.io/library/ai-unturf-groovy-openai-client 0.0s done -#12 DONE 0.4s -✅ groovy-openai-client built successfully - -======================================== -[26] Building haskell (languages/haskell) -======================================== -1.869 Module -1.869 ‘Network.HTTP.Client.Conduit’ -1.869 does not export -1.869 ‘streamResponseBody’ -1.869 | -1.869 11 | import Network.HTTP.Client.Conduit (streamResponseBody) -1.869 | ^^^^^^^^^^^^^^^^^^ ------- - - 1 warning found (use docker --debug to expand): - - FromAsCasing: 'as' and 'FROM' keywords' casing do not match (line 1) -Dockerfile:8 --------------------- - 6 | - 7 | COPY UncloseAI.hs . - 8 | >>> RUN cabal build - 9 | - 10 | FROM debian:bookworm-slim --------------------- -ERROR: failed to solve: process "/bin/sh -c cabal build" did not complete successfully: exit code: 1 -✅ haskell built successfully - -======================================== -[27] Building java-http (languages/java/http) -======================================== - -#5 [internal] load build context -#5 transferring context: 14.37kB done -#5 DONE 0.0s - -#6 [2/4] WORKDIR /app -#6 CACHED - -#7 [3/4] COPY UncloseAI.java . -#7 DONE 0.2s - -#8 [4/4] RUN javac UncloseAI.java -#8 DONE 2.4s - -#9 exporting to image -#9 exporting layers -#9 exporting layers 0.2s done -#9 writing image sha256:2bad21080f1cf9422e953a6f6d5ea0c5e5d508dc0d80e6eb42229b8bdc428b32 done -#9 naming to docker.io/library/ai-unturf-java-http 0.0s done -#9 DONE 0.2s -✅ java-http built successfully - -======================================== -[28] Building java-openai (languages/java/openai) -======================================== -#12 18.63 [INFO] ------------------------------------------------------------------------ -#12 18.63 [INFO] Total time: 14.482 s -#12 18.63 [INFO] Finished at: 2025-10-15T20:48:55Z -#12 18.63 [INFO] ------------------------------------------------------------------------ -#12 DONE 18.8s - -#13 [stage-1 3/6] COPY --from=builder /app/target/classes ./classes -#13 ERROR: failed to calculate checksum of ref 466bb300-1953-4d46-a0ff-c9ba0efad3a2::ajkt24m3vxz5a2qda5yce6fgm: "/app/target/classes": not found ------- - > [stage-1 3/6] COPY --from=builder /app/target/classes ./classes: ------- -Dockerfile:19 --------------------- - 17 | - 18 | # Copy compiled classes and dependencies from builder - 19 | >>> COPY --from=builder /app/target/classes ./classes - 20 | COPY --from=builder /root/.m2/repository /root/.m2/repository - 21 | COPY --from=builder /app/pom.xml ./ --------------------- -ERROR: failed to solve: failed to compute cache key: failed to calculate checksum of ref 466bb300-1953-4d46-a0ff-c9ba0efad3a2::ajkt24m3vxz5a2qda5yce6fgm: "/app/target/classes": not found -✅ java-openai built successfully - -======================================== -[29] Building javascript-bun (languages/javascript/bun) -======================================== -#3 DONE 0.0s - -#4 [1/3] FROM docker.io/oven/bun:1@sha256:00cccad6e9c66bbacc250851f689168606aaea551ac473e908bbcf00a5645025 -#4 DONE 0.0s - -#5 [2/3] WORKDIR /app -#5 CACHED - -#6 [internal] load build context -#6 transferring context: 7.11kB done -#6 DONE 0.1s - -#7 [3/3] COPY uncloseai.ts . -#7 DONE 0.2s - -#8 exporting to image -#8 exporting layers 0.1s done -#8 writing image sha256:7678ae2232d77f86fde3da0ca03a3cd5a19571deead14c56c2353dd58c190910 done -#8 naming to docker.io/library/ai-unturf-javascript-bun 0.0s done -#8 DONE 0.2s -✅ javascript-bun built successfully - -======================================== -[30] Building javascript-nodejs (languages/javascript/nodejs) -======================================== -#4 DONE 0.0s - -#5 [internal] load build context -#5 transferring context: 10.13kB done -#5 DONE 0.1s - -#6 [2/4] WORKDIR /app -#6 CACHED - -#7 [3/4] COPY uncloseai.js . -#7 CACHED - -#8 [4/4] COPY package.json . -#8 CACHED - -#9 exporting to image -#9 exporting layers done -#9 writing image sha256:e4e151ae1f9fe2cbc520baf46621979624d59c93a41e7eea71d9145feff6070c done -#9 naming to docker.io/library/ai-unturf-javascript-nodejs 0.0s done -#9 DONE 0.1s -✅ javascript-nodejs built successfully - -======================================== -[31] Building javascript-openai-nodejs (languages/javascript/openai/nodejs) -======================================== -#5 transferring context: 4.22kB done -#5 DONE 0.1s - -#6 [2/5] WORKDIR /app -#6 CACHED - -#7 [3/5] COPY package.json ./ -#7 CACHED - -#8 [4/5] RUN npm install -#8 CACHED - -#9 [5/5] COPY uncloseai.js ./ -#9 CACHED - -#10 exporting to image -#10 exporting layers done -#10 writing image sha256:8f8b90b16e8725ebbe8aac5b7d64d85e504125287e790b10186e1aaf048124c4 done -#10 naming to docker.io/library/ai-unturf-javascript-openai-nodejs done -#10 DONE 0.1s -✅ javascript-openai-nodejs built successfully - -======================================== -[32] Building javascript-typescript (languages/javascript/typescript) -======================================== -#8 4.991 added 3 packages, and audited 4 packages in 4s -#8 4.993 -#8 4.993 found 0 vulnerabilities -#8 4.996 npm notice -#8 4.996 npm notice New major version of npm available! 10.9.2 -> 11.6.2 -#8 4.996 npm notice Changelog: https://github.com/npm/cli/releases/tag/v11.6.2 -#8 4.996 npm notice To update run: npm install -g npm@11.6.2 -#8 4.996 npm notice -#8 5.481 -#8 5.481 > uncloseai-typescript-examples@1.0.0 build -#8 5.481 > tsc -#8 5.481 -#8 DONE 9.0s - -#9 exporting to image -#9 exporting layers -#9 exporting layers 0.8s done -#9 writing image sha256:a1564d45580396be1fcc563f8608653388fb760092682fe188e6b1fd2c873677 done -#9 naming to docker.io/library/ai-unturf-javascript-typescript 0.0s done -#9 DONE 0.9s -✅ javascript-typescript built successfully - -======================================== -[33] Building javascript-vanilla (languages/javascript/vanilla) -======================================== -#3 DONE 0.0s - -#4 [1/3] FROM docker.io/library/python:3.13-alpine@sha256:b423d3e1a4e39e34193dd728852bf7d74f2f5bef7ab3e87bf7c459e7482fac44 -#4 DONE 0.0s - -#5 [2/3] WORKDIR /app -#5 CACHED - -#6 [internal] load build context -#6 transferring context: 10.41kB done -#6 DONE 0.1s - -#7 [3/3] COPY uncloseai.html index.html -#7 DONE 0.2s - -#8 exporting to image -#8 exporting layers 0.1s done -#8 writing image sha256:107c8bf1ce4ca7950fa22bd4cc086977b7fe41d52d33b13a412b8035ef17e3e0 done -#8 naming to docker.io/library/ai-unturf-javascript-vanilla 0.0s done -#8 DONE 0.2s -✅ javascript-vanilla built successfully - -======================================== -[34] Building julia (languages/julia) -======================================== -#5 resolve docker.io/library/julia:1.11@sha256:b1a53d67d0390aa9f94d93171dd86821775dd869db827e7e2c3bef353a2497a9 0.1s done -#5 DONE 0.1s - -#6 [2/5] WORKDIR /app -#6 CACHED - -#7 [3/5] COPY Project.toml . -#7 CACHED - -#8 [4/5] RUN julia -e 'using Pkg; Pkg.activate("."); Pkg.instantiate(); Pkg.precompile()' -#8 CACHED - -#9 [5/5] COPY src ./src -#9 CACHED - -#10 exporting to image -#10 exporting layers done -#10 writing image sha256:c00c8eacf6d51d08b8d92e469f3ecd11ccb405cf97503373ebda25fbd9d79aa7 done -#10 naming to docker.io/library/ai-unturf-julia done -#10 DONE 0.0s -✅ julia built successfully - -======================================== -[35] Building kotlin (languages/kotlin) -======================================== -#12 108.6 > Task :jar -#12 109.7 > Task :startScripts -#12 109.8 > Task :installDist -#12 109.8 -#12 109.8 BUILD SUCCESSFUL in 1m 49s -#12 109.8 5 actionable tasks: 5 executed -#12 DONE 110.5s - -#13 [stage-1 2/3] WORKDIR /app -#13 CACHED - -#14 [stage-1 3/3] COPY --from=builder /app/build/install/app /app -#14 DONE 0.2s - -#15 exporting to image -#15 exporting layers -#15 exporting layers 0.2s done -#15 writing image sha256:6a3144c8e2e3927d0afd29e4650cf3719a9d3f41656eb4fe562b82febd9f445f done -#15 naming to docker.io/library/ai-unturf-kotlin 0.0s done -#15 DONE 0.2s -✅ kotlin built successfully - -======================================== -[36] Building lua (languages/lua) -======================================== -#5 transferring context: 11.33kB done -#5 DONE 0.0s - -#6 [3/5] RUN luarocks-5.4 install luasocket && luarocks-5.4 install luasec && luarocks-5.4 install lua-cjson -#6 CACHED - -#7 [2/5] RUN apk add --no-cache lua5.4 lua5.4-dev luarocks5.4 ca-certificates openssl-dev gcc musl-dev -#7 CACHED - -#8 [4/5] WORKDIR /app -#8 CACHED - -#9 [5/5] COPY uncloseai.lua . -#9 DONE 0.2s - -#10 exporting to image -#10 exporting layers 0.1s done -#10 writing image sha256:36949ae65e58ee5a01959efb2268d7731f74963c8db9b6233ed7978edf40f8c3 done -#10 naming to docker.io/library/ai-unturf-lua 0.0s done -#10 DONE 0.2s -✅ lua built successfully - -======================================== -[37] Building mojo (languages/mojo) -======================================== -#0 building with "default" instance using docker driver - -#1 [internal] load build definition from Dockerfile -#1 transferring dockerfile: 505B done -#1 DONE 0.1s - -#2 [internal] load metadata for docker.io/modular/max:latest -#2 ERROR: pull access denied, repository does not exist or may require authorization: server message: insufficient_scope: authorization failed ------- - > [internal] load metadata for docker.io/modular/max:latest: ------- -Dockerfile:3 --------------------- - 1 | # Use Modular MAX container (checked 2025-10-15: latest stable Mojo runtime) - 2 | # Note: This requires Modular license acceptance - 3 | >>> FROM modular/max:latest - 4 | - 5 | WORKDIR /app --------------------- -ERROR: failed to solve: modular/max:latest: failed to resolve source metadata for docker.io/modular/max:latest: pull access denied, repository does not exist or may require authorization: server message: insufficient_scope: authorization failed -✅ mojo built successfully - -======================================== -[38] Building nim (languages/nim) -======================================== -#9 4.786 CC: ../nim/lib/pure/streams.nim -#9 4.997 CC: ../nim/lib/pure/asyncfutures.nim -#9 5.047 CC: ../nim/lib/pure/asyncdispatch.nim -#9 5.071 CC: ../nim/lib/pure/httpclient.nim -#9 5.101 CC: ../nim/lib/pure/lexbase.nim -#9 5.159 CC: ../nim/lib/pure/parsejson.nim -#9 5.180 CC: ../nim/lib/pure/json.nim -#9 5.337 CC: ../nim/lib/pure/strformat.nim -#9 5.480 CC: uncloseai.nim -#9 5.904 Hint: [Link] -#9 5.967 Hint: mm: orc; threads: on; opt: none (DEBUG BUILD, `-d:release` generates faster code) -#9 5.967 79322 lines; 5.395s; 140.758MiB peakmem; proj: /app/uncloseai.nim; out: /app/uncloseai [SuccessX] -#9 DONE 6.2s - -#10 exporting to image -#10 exporting layers -#10 exporting layers 0.2s done -#10 writing image sha256:befc067e888cca33e1aedf363c80b0634726a1995af380f4f3299e01f5861280 done -#10 naming to docker.io/library/ai-unturf-nim 0.0s done -#10 DONE 0.2s -✅ nim built successfully - -======================================== -[39] Building ocaml (languages/ocaml) -======================================== -#7 CACHED - -#8 [4/6] RUN opam install -y lwt cohttp-lwt-unix yojson dune -#8 CACHED - -#9 [5/6] COPY --chown=opam:opam . . -#9 DONE 0.2s - -#10 [6/6] RUN eval $(opam env) && dune build -#10 DONE 1.8s - -#11 exporting to image -#11 exporting layers -#11 exporting layers 0.2s done -#11 writing image sha256:56a713a68317b9a489e45ebd2e587caa2c96a99e311c62842bfe659a816c6adf done -#11 naming to docker.io/library/ai-unturf-ocaml 0.0s done -#11 DONE 0.3s - - 1 warning found (use docker --debug to expand): - - JSONArgsRecommended: JSON arguments recommended for CMD to prevent unintended behavior related to OS signals (line 20) -✅ ocaml built successfully - -======================================== -[40] Building odin (languages/odin) -======================================== -#5 transferring context: 9.75kB done -#5 DONE 0.0s - -#6 [3/5] RUN wget https://github.com/odin-lang/Odin/releases/download/dev-2025-10/odin-linux-amd64-dev-2025-10-05.tar.gz && tar -xzf odin-linux-amd64-dev-2025-10-05.tar.gz && mv odin-linux-amd64-nightly+2025-10-05 /opt/odin && rm odin-linux-amd64-dev-2025-10-05.tar.gz && rm -rf /var/lib/apt/lists/* -#6 CACHED - -#7 [2/5] RUN apt-get update && apt-get install -y wget llvm-18 clang-18 build-essential ca-certificates && rm -rf /var/lib/apt/lists/* -#7 CACHED - -#8 [4/5] WORKDIR /app -#8 CACHED - -#9 [5/5] COPY uncloseai.odin . -#9 DONE 0.2s - -#10 exporting to image -#10 exporting layers 0.1s done -#10 writing image sha256:c96da793fb3a4d37c287fbdc06b3db6e7402a8f5d23ecfc747d59455f82c75e4 done -#10 naming to docker.io/library/ai-unturf-odin 0.0s done -#10 DONE 0.2s -✅ odin built successfully - -======================================== -[41] Building perl (languages/perl) -======================================== -#5 DONE 0.0s - -#6 [2/5] RUN apt-get update && apt-get install -y ca-certificates make gcc libc-dev libssl-dev && rm -rf /var/lib/apt/lists/* && cpanm --notest LWP::UserAgent LWP::Protocol::https JSON HTTP::Request && apt-get purge -y make gcc libc-dev && apt-get autoremove -y -#6 CACHED - -#7 [3/5] WORKDIR /app -#7 CACHED - -#8 [4/5] COPY uncloseai.pl . -#8 DONE 0.2s - -#9 [5/5] RUN chmod +x uncloseai.pl -#9 DONE 0.5s - -#10 exporting to image -#10 exporting layers -#10 exporting layers 0.2s done -#10 writing image sha256:6303e622152fb1407d98d01344fc78e05d7e2764653194fa79de4aebc3874c5e done -#10 naming to docker.io/library/ai-unturf-perl 0.0s done -#10 DONE 0.2s -✅ perl built successfully - -======================================== -[42] Building php (languages/php) -======================================== -#5 DONE 0.0s - -#6 [2/5] RUN apk add --no-cache ca-certificates -#6 CACHED - -#7 [3/5] WORKDIR /app -#7 CACHED - -#8 [4/5] COPY uncloseai.php . -#8 DONE 0.2s - -#9 [5/5] RUN chmod +x uncloseai.php -#9 DONE 0.6s - -#10 exporting to image -#10 exporting layers -#10 exporting layers 0.2s done -#10 writing image sha256:b01fc9abdab97f0e2796d89383fee47affe451e61f8f1352728b37582fce9159 done -#10 naming to docker.io/library/ai-unturf-php 0.0s done -#10 DONE 0.3s -✅ php built successfully - -======================================== -[43] Building powershell (languages/powershell) -======================================== -#5 DONE 0.1s - -#6 [2/5] RUN apk add --no-cache ca-certificates -#6 CACHED - -#7 [3/5] WORKDIR /app -#7 CACHED - -#8 [4/5] COPY uncloseai.ps1 . -#8 DONE 0.2s - -#9 [5/5] RUN chmod +x uncloseai.ps1 -#9 DONE 0.6s - -#10 exporting to image -#10 exporting layers -#10 exporting layers 0.2s done -#10 writing image sha256:c62d090199e11dbdb458ab68d9d71c69bed8fe4c352ecb2c52319a20737f67af done -#10 naming to docker.io/library/ai-unturf-powershell 0.0s done -#10 DONE 0.2s -✅ powershell built successfully - -======================================== -[44] Building prolog (languages/prolog) -======================================== -#5 DONE 0.1s - -#6 [2/5] RUN apt-get update && apt-get install -y ca-certificates && rm -rf /var/lib/apt/lists/* -#6 CACHED - -#7 [3/5] WORKDIR /app -#7 CACHED - -#8 [4/5] COPY uncloseai.pl . -#8 DONE 0.2s - -#9 [5/5] RUN chmod +x uncloseai.pl -#9 DONE 0.5s - -#10 exporting to image -#10 exporting layers -#10 exporting layers 0.2s done -#10 writing image sha256:aba9f229f5161ccc6cad6ef167bc49e18130992ffd860b465a1f5f5549a378a3 done -#10 naming to docker.io/library/ai-unturf-prolog 0.0s done -#10 DONE 0.2s -✅ prolog built successfully - -======================================== -[45] Building python-aiohttp (languages/python/aiohttp) -======================================== -#6 [2/6] WORKDIR /app -#6 CACHED - -#7 [3/6] COPY requirements.txt /app/requirements.txt -#7 CACHED - -#8 [4/6] COPY uncloseai.py /app/uncloseai.py -#8 CACHED - -#9 [5/6] RUN chmod +x /app/uncloseai.py -#9 CACHED - -#10 [6/6] RUN pip3 install --no-cache-dir -r /app/requirements.txt -#10 CACHED - -#11 exporting to image -#11 exporting layers done -#11 writing image sha256:40561f33dc84e22806283699c70a14095174813ee17e9ce9d28142ebcfe711b7 done -#11 naming to docker.io/library/ai-unturf-python-aiohttp done -#11 DONE 0.0s -✅ python-aiohttp built successfully - -======================================== -[46] Building python-httpx-async (languages/python/httpx-async) -======================================== -#6 [5/6] RUN chmod +x /app/uncloseai.py -#6 CACHED - -#7 [2/6] WORKDIR /app -#7 CACHED - -#8 [3/6] COPY requirements.txt /app/requirements.txt -#8 CACHED - -#9 [4/6] COPY uncloseai.py /app/uncloseai.py -#9 CACHED - -#10 [6/6] RUN pip3 install --no-cache-dir -r /app/requirements.txt -#10 CACHED - -#11 exporting to image -#11 exporting layers done -#11 writing image sha256:ba2eda73911f88cc39ff2b5c092b2a9d9b0a544354bedfe7e155e8b92f70ed86 done -#11 naming to docker.io/library/ai-unturf-python-httpx-async 0.0s done -#11 DONE 0.0s -✅ python-httpx-async built successfully - -======================================== -[47] Building python-openai (languages/python/openai) -======================================== -#6 [2/6] WORKDIR /app -#6 CACHED - -#7 [3/6] COPY requirements.txt /app/requirements.txt -#7 CACHED - -#8 [4/6] COPY uncloseai.py /app/uncloseai.py -#8 CACHED - -#9 [5/6] RUN chmod +x /app/uncloseai.py -#9 CACHED - -#10 [6/6] RUN pip3 install --no-cache-dir -r /app/requirements.txt -#10 CACHED - -#11 exporting to image -#11 exporting layers done -#11 writing image sha256:dee85dbeb9b2bc897611caabd4851f489410826e392c622b06e279305426dc71 done -#11 naming to docker.io/library/ai-unturf-python-openai done -#11 DONE 0.0s -✅ python-openai built successfully - -======================================== -[48] Building python-requests (languages/python/requests) -======================================== -#6 [2/6] WORKDIR /app -#6 CACHED - -#7 [3/6] COPY requirements.txt /app/requirements.txt -#7 CACHED - -#8 [4/6] COPY uncloseai.py /app/uncloseai.py -#8 CACHED - -#9 [5/6] RUN chmod +x /app/uncloseai.py -#9 CACHED - -#10 [6/6] RUN pip3 install --no-cache-dir -r /app/requirements.txt -#10 CACHED - -#11 exporting to image -#11 exporting layers done -#11 writing image sha256:57f05217ad6ba799d3e6c99227ff57dcd2c86c06cf5d2bf306329c9e04267b0a done -#11 naming to docker.io/library/ai-unturf-python-requests done -#11 DONE 0.0s -✅ python-requests built successfully - -======================================== -[49] Building r (languages/r) -======================================== -#5 DONE 0.0s - -#6 [2/5] RUN apt-get update && apt-get install -y libcurl4-openssl-dev libssl-dev ca-certificates && R -e "install.packages(c('httr', 'jsonlite', 'R6'), repos='https://cloud.r-project.org/')" && rm -rf /var/lib/apt/lists/* -#6 CACHED - -#7 [3/5] WORKDIR /app -#7 CACHED - -#8 [4/5] COPY uncloseai.R . -#8 DONE 0.4s - -#9 [5/5] RUN chmod +x uncloseai.R -#9 DONE 0.6s - -#10 exporting to image -#10 exporting layers -#10 exporting layers 0.3s done -#10 writing image sha256:64646b72fb21939b4aa41267a27a1eee93f7aca6ae1562bb987a9e25664c3d1e 0.0s done -#10 naming to docker.io/library/ai-unturf-r 0.0s done -#10 DONE 0.3s -✅ r built successfully - -======================================== -[50] Building ruby-http (languages/ruby/http) -======================================== -#4 transferring context: 6.34kB done -#4 DONE 0.1s - -#6 [4/5] COPY uncloseai.rb . -#6 CACHED - -#7 [2/5] RUN apk add --no-cache ca-certificates -#7 CACHED - -#8 [3/5] WORKDIR /app -#8 CACHED - -#9 [5/5] RUN chmod +x uncloseai.rb -#9 CACHED - -#10 exporting to image -#10 exporting layers done -#10 writing image sha256:8f82bcf24665cf7963e9b0fba9d96cc25f53e9881745de2c1660cb14a886d7d4 done -#10 naming to docker.io/library/ai-unturf-ruby-http done -#10 DONE 0.0s -✅ ruby-http built successfully - -======================================== -[51] Building ruby-openai (languages/ruby/openai) -======================================== -#6 [2/6] WORKDIR /app -#6 CACHED - -#7 [3/6] COPY Gemfile ./ -#7 CACHED - -#8 [4/6] RUN bundle install -#8 CACHED - -#9 [5/6] COPY uncloseai.rb ./ -#9 CACHED - -#10 [6/6] RUN chmod +x uncloseai.rb -#10 CACHED - -#11 exporting to image -#11 exporting layers done -#11 writing image sha256:ddaa873fb65bd7de3153e115ca64ce7db3140274c1eca5d581ac984b8fb3f04a done -#11 naming to docker.io/library/ai-unturf-ruby-openai done -#11 DONE 0.0s -✅ ruby-openai built successfully - -======================================== -[52] Building rust (languages/rust) -======================================== -#13 [stage-1 2/5] RUN apt-get update && apt-get install -y ca-certificates && rm -rf /var/lib/apt/lists/* -#13 CACHED - -#14 [stage-1 3/5] WORKDIR /app -#14 CACHED - -#15 [stage-1 4/5] COPY --from=builder /app/target/release/uncloseai . -#15 ERROR: failed to calculate checksum of ref 466bb300-1953-4d46-a0ff-c9ba0efad3a2::ran0wfk0i5h7bk1hadbc29ccb: "/app/target/release/uncloseai": not found ------- - > [stage-1 4/5] COPY --from=builder /app/target/release/uncloseai .: ------- -Dockerfile:16 --------------------- - 14 | - 15 | WORKDIR /app - 16 | >>> COPY --from=builder /app/target/release/uncloseai . - 17 | COPY --from=builder /app/target/release/examples/basic ./basic - 18 | --------------------- -ERROR: failed to solve: failed to compute cache key: failed to calculate checksum of ref 466bb300-1953-4d46-a0ff-c9ba0efad3a2::ran0wfk0i5h7bk1hadbc29ccb: "/app/target/release/uncloseai": not found -✅ rust built successfully - -======================================== -[53] Building scala (languages/scala) -======================================== -#12 31.46 [info] done compiling -#12 32.39 [success] Total time: 21 s, completed Oct 15, 2025, 8:52:16 PM -#12 32.83 [info] Main Scala API documentation to /app/target/scala-2.13/api... -#12 33.01 [info] Wrote /app/target/scala-2.13/uncloseai_2.13-0.1.0.pom -#12 39.78 [info] Main Scala API documentation successful. -#12 40.26 [success] Total time: 8 s, completed Oct 15, 2025, 8:52:24 PM -#12 DONE 41.0s - -#13 [stage-1 2/3] WORKDIR /app -#13 CACHED - -#14 [stage-1 3/3] COPY --from=builder /app/target/universal/stage . -#14 DONE 0.2s - -#15 exporting to image -#15 exporting layers -#15 exporting layers 0.3s done -#15 writing image sha256:5ecde580fac40a1404f16ff1f800f248b678accab625e3721e5f2ce32b6d71f1 done -#15 naming to docker.io/library/ai-unturf-scala 0.0s done -#15 DONE 0.4s -✅ scala built successfully - -======================================== -[54] Building scheme (languages/scheme) -======================================== -#9 DONE 0.2s - -#10 [6/9] COPY qwen-nonstreaming.scm . -#10 DONE 0.2s - -#11 [7/9] COPY qwen-streaming.scm . -#11 DONE 0.2s - -#12 [8/9] COPY tts.scm . -#12 DONE 0.2s - -#13 [9/9] RUN chmod +x *.scm -#13 DONE 0.5s - -#14 exporting to image -#14 exporting layers -#14 exporting layers 4.1s done -#14 writing image sha256:1d0cba4b49c586660f31f75a492607d2b5031f847fd5c8bb6a03b4373bcf77a7 done -#14 naming to docker.io/library/ai-unturf-scheme 0.0s done -#14 DONE 4.2s -✅ scheme built successfully - -======================================== -[55] Building tcl (languages/tcl) -======================================== -#5 DONE 0.1s - -#6 [2/5] RUN apk add --no-cache tcl ca-certificates curl jq -#6 CACHED - -#7 [3/5] WORKDIR /app -#7 CACHED - -#8 [4/5] COPY uncloseai.tcl . -#8 DONE 0.2s - -#9 [5/5] RUN chmod +x uncloseai.tcl -#9 DONE 0.6s - -#10 exporting to image -#10 exporting layers -#10 exporting layers 0.2s done -#10 writing image sha256:bc9234d423a400ce7b4aad26e25de10f439b3e737e17ac3f9c2d5486547e1acb done -#10 naming to docker.io/library/ai-unturf-tcl 0.0s done -#10 DONE 0.2s -✅ tcl built successfully - -======================================== -[56] Building vbnet (languages/vbnet) -======================================== -#15 5.438 -#15 5.438 Time Elapsed 00:00:04.53 -#15 DONE 5.6s - -#16 [publish 1/1] RUN dotnet publish "UncloseAI.vbproj" -c Release -o /app/publish -#16 1.504 Determining projects to restore... -#16 2.127 All projects are up-to-date for restore. -#16 2.663 UncloseAI -> /src/bin/Release/net8.0/UncloseAI.dll -#16 2.741 UncloseAI -> /app/publish/ -#16 DONE 2.9s - -#17 [final 2/2] COPY --from=publish /app/publish . -#17 DONE 0.2s - -#18 exporting to image -#18 exporting layers -#18 exporting layers 0.2s done -#18 writing image sha256:6c6077652772a5f08c362863a2c2db6ce3e24b618175d57a6b08cc97b468b94a done -#18 naming to docker.io/library/ai-unturf-vbnet 0.0s done -#18 DONE 0.4s -✅ vbnet built successfully - -======================================== -[57] Building v (languages/v) -======================================== -#8 [stage-1 2/3] RUN apk add --no-cache ca-certificates -#8 CACHED - -#9 [builder 2/4] WORKDIR /app -#9 CACHED - -#10 [builder 3/4] COPY v.mod uncloseai.v ./ -#10 CACHED - -#11 [builder 4/4] RUN v -prod uncloseai.v -#11 CACHED - -#12 [stage-1 3/3] COPY --from=builder /app/uncloseai /usr/local/bin/uncloseai -#12 CACHED - -#13 exporting to image -#13 exporting layers done -#13 writing image sha256:b10b618e2d958833856c33260bf3a4c782008b8f17aa6bece0082db6b03a5c92 done -#13 naming to docker.io/library/ai-unturf-v done -#13 DONE 0.0s -✅ v built successfully - -======================================== -[58] Building zig (languages/zig) -======================================== -#7 [builder 3/5] WORKDIR /app -#7 CACHED - -#8 [builder 4/5] COPY . . -#8 DONE 0.2s - -#9 [builder 5/5] RUN /opt/zig/zig build -Doptimize=ReleaseSafe -#9 DONE 101.8s - -#10 [stage-1 2/3] RUN apk add --no-cache ca-certificates -#10 CACHED - -#11 [stage-1 3/3] COPY --from=builder /app/zig-out/bin/uncloseai /usr/local/bin/uncloseai -#11 CACHED - -#12 exporting to image -#12 exporting layers done -#12 writing image sha256:c829bcad3e50c562b087532c60170b498c07704ce811db45ac9589ae31c1afc8 done -#12 naming to docker.io/library/ai-unturf-zig done -#12 DONE 0.0s -✅ zig built successfully - -======================================== -Build Summary: 58/58 successful -✅ All builds successful! diff --git a/modal_tests.js b/modal_tests.js deleted file mode 100644 index b33ded9..0000000 --- a/modal_tests.js +++ /dev/null @@ -1,376 +0,0 @@ -// Custom Modal Test Suite -// Tests modal functionality using JSDOM without Jest complexity -// Run with: node modal_tests.js - -import { JSDOM } from 'jsdom'; -import { fileURLToPath } from 'url'; -import { dirname, join } from 'path'; -import fs from 'fs'; - -const __filename = fileURLToPath(import.meta.url); -const __dirname = dirname(__filename); - -// Test suite state -let testCount = 0; -let passedCount = 0; -let failedCount = 0; - -// Simple test framework -function test(description, testFn) { - testCount++; - console.log(`\n🧪 Test: ${description}`); - try { - testFn(); - console.log(`✅ PASS`); - passedCount++; - } catch (error) { - console.log(`❌ FAIL: ${error.message}`); - failedCount++; - } -} - -function assert(condition, message) { - if (!condition) { - throw new Error(message); - } -} - -function assertEqual(actual, expected, message) { - if (actual !== expected) { - throw new Error(`${message} - Expected: ${expected}, Got: ${actual}`); - } -} - -// Setup mock browser environment -function setupBrowserMocks() { - const dom = new JSDOM(` - - - Modal Test Page - - - -
- - `, { - url: "https://ai.unturf.com", - pretendToBeVisual: true, - resources: "usable" - }); - - // Set up globals safely for Node.js v22 - Object.defineProperty(global, 'window', { value: dom.window, configurable: true }); - Object.defineProperty(global, 'document', { value: dom.window.document, configurable: true }); - Object.defineProperty(global, 'navigator', { value: dom.window.navigator, configurable: true }); - Object.defineProperty(global, 'HTMLElement', { value: dom.window.HTMLElement, configurable: true }); - Object.defineProperty(global, 'Element', { value: dom.window.Element, configurable: true }); - // Polyfill HTMLDialogElement if not available - if (!dom.window.HTMLDialogElement) { - dom.window.HTMLDialogElement = class HTMLDialogElement extends dom.window.HTMLElement { - constructor() { - super(); - this.open = false; - } - - showModal() { - this.open = true; - } - - show() { - this.open = true; - } - - close() { - this.open = false; - } - }; - } - Object.defineProperty(global, 'HTMLDialogElement', { value: dom.window.HTMLDialogElement, configurable: true }); - - // Add Event constructor polyfill - if (!global.Event) { - global.Event = dom.window.Event; - } - if (!global.MouseEvent) { - global.MouseEvent = dom.window.MouseEvent; - } - - // Mock fetch - global.fetch = async (url, options) => { - // Mock model API responses - if (url.includes('/v1/models')) { - return { - ok: true, - json: async () => ({ - data: [ - { id: 'gpt-4o', object: 'model' }, - { id: 'claude-3-sonnet', object: 'model' } - ] - }) - }; - } - - // Mock chat completion responses - if (url.includes('/v1/chat/completions')) { - return { - ok: true, - json: async () => ({ - choices: [{ message: { content: 'Test response' } }] - }) - }; - } - - return { ok: false, status: 404 }; - }; - - // Mock localStorage - const localStorageMock = { - getItem: (key) => localStorageMock.data[key] || null, - setItem: (key, value) => { localStorageMock.data[key] = value; }, - removeItem: (key) => { delete localStorageMock.data[key]; }, - clear: () => { localStorageMock.data = {}; }, - data: {} - }; - global.localStorage = localStorageMock; - - // Mock other browser APIs - global.Audio = class MockAudio { - constructor(src) { this.src = src; } - play() { return Promise.resolve(); } - pause() {} - }; - - global.speechSynthesis = { - speak: () => {}, - cancel: () => {}, - getVoices: () => [] - }; - - return dom; -} - -// Mock external CDN dependencies -function mockCDNDependencies() { - // Mock marked - global.marked = { - parse: (text) => `

${text}

`, - setOptions: () => {} - }; - - // Mock highlight.js - global.hljs = { - highlightAuto: (code) => ({ value: code }), - configure: () => {} - }; -} - -// Load source files for testing -async function loadSourceFile(filename) { - const filePath = join(__dirname, 'src', filename); - const content = fs.readFileSync(filePath, 'utf-8'); - - // Replace CDN imports with our mocks - const modifiedContent = content - .replace(/import\s+\{[^}]+\}\s+from\s+["']https:\/\/cdn\.jsdelivr\.net\/npm\/marked\/lib\/marked\.esm\.js["']/g, - '// Mock marked import') - .replace(/import\s+\{[^}]+\}\s+from\s+["']https:\/\/cdnjs\.cloudflare\.com\/ajax\/libs\/highlight\.js\/[^"']+["']/g, - '// Mock hljs import'); - - return modifiedContent; -} - -// Test modal creation and basic functionality -async function testModalCreation() { - test('Modal dialog element can be created', () => { - // Create a regular div and enhance it with dialog-like properties - const modal = document.createElement('div'); - modal.id = 'test-modal'; - modal.className = 'modal-dialog'; - - // Add dialog-like methods - modal.open = false; - modal.showModal = function() { this.open = true; this.style.display = 'block'; }; - modal.show = function() { this.open = true; this.style.display = 'block'; }; - modal.close = function() { this.open = false; this.style.display = 'none'; }; - - document.body.appendChild(modal); - - assert(typeof modal.showModal === 'function', 'Modal should have showModal method'); - assert(modal.id === 'test-modal', 'Modal should have correct ID'); - assert(document.getElementById('test-modal'), 'Modal should be in DOM'); - }); - - test('Modal can be opened and closed', () => { - const modal = document.getElementById('test-modal'); - - modal.showModal(); - assert(modal.open === true, 'Modal should be open after showModal()'); - - modal.close(); - assert(modal.open === false, 'Modal should be closed after close()'); - }); -} - -// Test modal content injection -async function testModalContent() { - test('Modal content can be injected', () => { - const modal = document.getElementById('test-modal'); - - modal.innerHTML = ` - - - `; - - assert(modal.querySelector('.modal-header'), 'Modal should have header'); - assert(modal.querySelector('.modal-content'), 'Modal should have content'); - assert(modal.querySelector('.close-btn'), 'Modal should have close button'); - }); - - test('Modal close button functionality', () => { - const modal = document.getElementById('test-modal'); - const closeBtn = modal.querySelector('.close-btn'); - - modal.showModal(); - assert(modal.open === true, 'Modal should be open'); - - // Simulate click on close button - closeBtn.click(); - // Note: In real implementation, this would trigger close event - modal.close(); // Manually close for test - - assert(modal.open === false, 'Modal should close when close button clicked'); - }); -} - -// Test modal form handling -async function testModalForms() { - test('Modal can contain form elements', () => { - const modal = document.getElementById('test-modal'); - - modal.innerHTML = ` - - `; - - const form = modal.querySelector('.modal-form'); - const input = modal.querySelector('#test-input'); - const select = modal.querySelector('#test-select'); - - assert(form, 'Modal should contain form'); - assert(input, 'Modal should contain input field'); - assert(select, 'Modal should contain select field'); - }); - - test('Modal form inputs can be manipulated', () => { - const modal = document.getElementById('test-modal'); - const input = modal.querySelector('#test-input'); - const select = modal.querySelector('#test-select'); - - input.value = 'Test value'; - select.value = 'option2'; - - assertEqual(input.value, 'Test value', 'Input value should be set'); - assertEqual(select.value, 'option2', 'Select value should be set'); - }); -} - -// Test modal event handling -async function testModalEvents() { - test('Modal can handle custom events', () => { - const modal = document.getElementById('test-modal'); - let eventFired = false; - - modal.addEventListener('custom-event', () => { - eventFired = true; - }); - - const customEvent = new window.Event('custom-event'); - modal.dispatchEvent(customEvent); - - assert(eventFired, 'Custom event should fire'); - }); - - test('Modal click outside should work', () => { - const modal = document.getElementById('test-modal'); - modal.showModal(); - - // Simulate click on backdrop (this would need proper implementation) - const clickEvent = new MouseEvent('click', { - clientX: 0, - clientY: 0 - }); - - modal.dispatchEvent(clickEvent); - // In real implementation, this would check if click was outside content - assert(true, 'Click outside event handling works'); - }); -} - -// Test localStorage integration -async function testLocalStorageIntegration() { - test('Modal can save state to localStorage', () => { - const testData = { modalState: 'open', userPrefs: { theme: 'dark' } }; - - localStorage.setItem('modal-test', JSON.stringify(testData)); - const retrieved = JSON.parse(localStorage.getItem('modal-test')); - - assertEqual(retrieved.modalState, 'open', 'localStorage should save modal state'); - assertEqual(retrieved.userPrefs.theme, 'dark', 'localStorage should save user preferences'); - }); - - test('Modal can load state from localStorage', () => { - const savedData = localStorage.getItem('modal-test'); - assert(savedData, 'Data should be available in localStorage'); - - const parsed = JSON.parse(savedData); - assert(parsed.modalState === 'open', 'Modal state should be restored'); - }); -} - -// Main test runner -async function runModalTests() { - console.log('🚀 Starting Custom Modal Test Suite\n'); - console.log('=' .repeat(50)); - - // Setup environment - setupBrowserMocks(); - mockCDNDependencies(); - - // Run test suites - await testModalCreation(); - await testModalContent(); - await testModalForms(); - await testModalEvents(); - await testLocalStorageIntegration(); - - // Print results - console.log('\n' + '=' .repeat(50)); - console.log('📊 Modal Test Results:'); - console.log(`Total tests: ${testCount}`); - console.log(`Passed: ${passedCount} ✅`); - console.log(`Failed: ${failedCount} ❌`); - - if (failedCount === 0) { - console.log('\n🎉 All modal tests passed!'); - } else { - console.log('\n💥 Some modal tests failed.'); - process.exit(1); - } -} - -// Run the tests -runModalTests().catch(error => { - console.error('Test runner error:', error); - process.exit(1); -}); \ No newline at end of file diff --git a/simple_tests.js b/simple_tests.js deleted file mode 100644 index 8690596..0000000 --- a/simple_tests.js +++ /dev/null @@ -1,266 +0,0 @@ -// Simple tests that don't require DOM or external dependencies -import { promises as fs } from 'fs'; -import path from 'path'; - -const srcDir = './src'; - -async function runSimpleTests() { - console.log('🧪 Running Simple Tests\n'); - - let tests = 0; - let passed = 0; - let failed = 0; - - function test(description, testFn) { - tests++; - try { - testFn(); - console.log(`✅ ${description}`); - passed++; - } catch (error) { - console.error(`❌ ${description}`); - console.error(` Error: ${error.message}`); - failed++; - } - } - - function assert(condition, message) { - if (!condition) { - throw new Error(message); - } - } - - // Test 1: Core files exist - test('Core JavaScript files exist', async () => { - const coreFiles = [ - 'ui.js', 'translation.js', 'tts.js', 'chat.js', 'config.js', - 'widget-library.js', 'tts-modal.js', 'translate-modal.js', - 'uncloseai-embed-modal.js' - ]; - - for (const file of coreFiles) { - const filePath = path.join(srcDir, file); - try { - await fs.access(filePath); - } catch (error) { - throw new Error(`File ${file} does not exist`); - } - } - }); - - // Test 2: Language files exist - test('Language files structure is correct', async () => { - const langDir = path.join(srcDir, 'languages'); - const files = await fs.readdir(langDir); - const jsFiles = files.filter(f => f.endsWith('.js')); - - assert(jsFiles.length >= 18, `Expected at least 18 language files, found ${jsFiles.length}`); - assert(jsFiles.includes('index.js'), 'Missing languages/index.js'); - assert(jsFiles.includes('en.js'), 'Missing languages/en.js'); - }); - - // Test 3: File sizes are reasonable - test('UI.js size is reasonable after refactoring', async () => { - const content = await fs.readFile(path.join(srcDir, 'ui.js'), 'utf-8'); - const lines = content.split('\n').length; - - assert(lines < 600, `UI.js is too large: ${lines} lines (expected < 600)`); - assert(lines > 100, `UI.js is too small: ${lines} lines (expected > 100)`); - }); - - // Test 4: No obvious syntax errors in main files - test('Main files have balanced braces', async () => { - const mainFiles = ['ui.js', 'translation.js', 'tts.js', 'chat.js']; - - for (const file of mainFiles) { - const content = await fs.readFile(path.join(srcDir, file), 'utf-8'); - const openBraces = (content.match(/{/g) || []).length; - const closeBraces = (content.match(/}/g) || []).length; - - assert(openBraces === closeBraces, - `${file} has mismatched braces: ${openBraces} open, ${closeBraces} close`); - } - }); - - // Test 5: Export statements exist - test('Key files have export statements', async () => { - const filesWithExports = ['ui.js', 'translation.js', 'tts.js', 'widget-library.js']; - - for (const file of filesWithExports) { - const content = await fs.readFile(path.join(srcDir, file), 'utf-8'); - assert(content.includes('export'), `${file} has no export statements`); - } - }); - - // Test 6: Import statements are valid - test('Import statements use correct syntax', async () => { - const allFiles = await fs.readdir(srcDir); - const jsFiles = allFiles.filter(f => f.endsWith('.js')); - - for (const file of jsFiles) { - const content = await fs.readFile(path.join(srcDir, file), 'utf-8'); - - // Check for balanced braces in import statements (basic check) - const importMatches = content.match(/import\s*{[^}]*}/g) || []; - for (const importMatch of importMatches) { - const openBraces = (importMatch.match(/{/g) || []).length; - const closeBraces = (importMatch.match(/}/g) || []).length; - assert(openBraces === closeBraces, - `${file}: Unbalanced braces in import: ${importMatch.substring(0, 50)}...`); - } - - // Check that imports with 'from' have quotes - const fromMatches = content.match(/import.*from\s+[^'"]/g) || []; - if (fromMatches.length > 0) { - console.warn(`⚠️ ${file} may have unquoted import sources`); - } - } - }); - - // Test 7: Function definitions look correct - test('Function definitions have basic structure', async () => { - const mainFiles = ['ui.js', 'translation.js', 'tts.js', 'chat.js']; - - for (const file of mainFiles) { - const content = await fs.readFile(path.join(srcDir, file), 'utf-8'); - - // Check for function definitions - const functionCount = (content.match(/function\s+\w+/g) || []).length; - const arrowFunctionCount = (content.match(/=\s*\([^)]*\)\s*=>/g) || []).length; - const asyncFunctionCount = (content.match(/async\s+function/g) || []).length; - - const totalFunctions = functionCount + arrowFunctionCount + asyncFunctionCount; - assert(totalFunctions > 0, `${file} has no function definitions`); - } - }); - - // Test 8: No obvious console.log statements in production code - test('No debug console.log statements in main files', async () => { - const mainFiles = ['ui.js', 'translation.js', 'tts.js']; - - for (const file of mainFiles) { - const content = await fs.readFile(path.join(srcDir, file), 'utf-8'); - const consoleLogCount = (content.match(/console\.log\s*\(/g) || []).length; - - // Allow some console logs but warn if too many - if (consoleLogCount > 3) { - console.warn(`⚠️ ${file} has ${consoleLogCount} console.log statements (consider removing for production)`); - } - } - }); - - // Test 9: Language files have proper structure - test('Language files export correct structure', async () => { - const langDir = path.join(srcDir, 'languages'); - const files = await fs.readdir(langDir); - const langFiles = files.filter(f => f.endsWith('.js') && f !== 'index.js' && !f.includes('verify') && !f.includes('add-missing')); - - for (const file of langFiles) { - const content = await fs.readFile(path.join(langDir, file), 'utf-8'); - - // Check for export statement - assert(content.includes('export const'), `${file} missing export const statement`); - - // Check for object structure - const objectPattern = /export\s+const\s+\w+\s*=\s*{/; - assert(objectPattern.test(content), `${file} export is not a proper object`); - - // Check for translation keys (basic validation) - const keyCount = (content.match(/\w+:\s*['"]/g) || []).length; - assert(keyCount > 10, `${file} has too few translation keys: ${keyCount}`); - } - }); - - // Test 10: Config files have required sections - test('Config file has required structure', async () => { - const content = await fs.readFile(path.join(srcDir, 'config.js'), 'utf-8'); - - // Check for key config sections - assert(content.includes('API_URL') || content.includes('CONFIG') || content.includes('SYSTEM_MESSAGE'), - 'config.js missing main configuration exports'); - assert(content.includes('url') || content.includes('URL') || content.includes('endpoint'), - 'config.js missing URL/endpoint configuration'); - }); - - // Test 11: Modal files have required functions - test('Modal files export required functions', async () => { - const modalFiles = ['tts-modal.js', 'translate-modal.js', 'uncloseai-embed-modal.js']; - - for (const file of modalFiles) { - const content = await fs.readFile(path.join(srcDir, file), 'utf-8'); - - // Check for main function export - assert(content.includes('export'), `${file} has no exports`); - - // Check for modal-specific patterns - if (file.includes('modal')) { - assert(content.includes('modal') || content.includes('Modal'), - `${file} doesn't contain modal-related code`); - } - } - }); - - // Test 12: Check for potential security issues - test('No obvious security vulnerabilities', async () => { - const allFiles = await fs.readdir(srcDir); - const jsFiles = allFiles.filter(f => f.endsWith('.js')); - - for (const file of jsFiles) { - const content = await fs.readFile(path.join(srcDir, file), 'utf-8'); - - // Check for innerHTML usage (potential XSS) - const innerHTMLCount = (content.match(/\.innerHTML\s*=/g) || []).length; - if (innerHTMLCount > 0) { - console.warn(`⚠️ ${file} uses innerHTML (${innerHTMLCount} times) - ensure proper sanitization`); - } - - // Check for eval usage - assert(!content.includes('eval('), `${file} contains eval() - security risk`); - - // Check for document.write (but allow in specific cases like new windows) - const documentWriteMatches = content.match(/document\.write/g) || []; - if (documentWriteMatches.length > 0 && !content.includes('previewWindow') && !content.includes('newWindow')) { - assert(false, `${file} contains document.write - security/performance risk`); - } - } - }); - - // Test 13: File encoding and line endings - test('Files have consistent encoding', async () => { - const allFiles = await fs.readdir(srcDir); - const jsFiles = allFiles.filter(f => f.endsWith('.js')); - - for (const file of jsFiles) { - const buffer = await fs.readFile(path.join(srcDir, file)); - const content = buffer.toString('utf-8'); - - // Check for BOM (Byte Order Mark) - assert(!content.startsWith('\uFEFF'), `${file} contains BOM - should be removed`); - - // Check for non-ASCII characters that might cause issues - const hasNonASCII = /[^\x00-\x7F]/.test(content); - if (hasNonASCII && !file.includes('languages/')) { - console.warn(`⚠️ ${file} contains non-ASCII characters (may be intentional)`); - } - } - }); - - // Wait for async tests to complete - await new Promise(resolve => setTimeout(resolve, 100)); - - console.log('\n📊 Simple Test Results:'); - console.log('='.repeat(30)); - console.log(`Total tests: ${tests}`); - console.log(`Passed: ${passed} ✅`); - console.log(`Failed: ${failed} ❌`); - - if (failed === 0) { - console.log('\n🎉 All simple tests passed!'); - process.exit(0); - } else { - console.log('\n💥 Some tests failed.'); - process.exit(1); - } -} - -runSimpleTests().catch(console.error); \ No newline at end of file diff --git a/uncloseai.js.orig b/uncloseai.js.orig deleted file mode 100644 index 545d1a0..0000000 --- a/uncloseai.js.orig +++ /dev/null @@ -1,2711 +0,0 @@ -/* how to use in HTML */ - -/* - - - - -
-
-
-
-
- - -
-*/ - -import { marked } from "https://cdn.jsdelivr.net/npm/marked/lib/marked.esm.js"; -import hljs from 'https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/es/highlight.min.js'; - -// ------------------------- -// Configuration and Endpoints -// ------------------------- - -// Original API endpoints for other functionalities -const TTS_API_URL = "https://speech.ai.unturf.com/v1/audio/speech"; -const MEGAPARCE_API_URL = "https://megaparce.ai.unturf.com/v1/file"; -const API_KEY = "dummy-api-key"; -const MODEL = "adamo1139/Hermes-3-Llama-3.1-8B-FP8-Dynamic"; // default model (always available) - -let lastTTSInput = ''; -let lastTTSResult = null; - -// Dynamic Endpoints Configuration for Chat API -// You can list as many endpoints as you need. -// { id: 'hermes.ai.unturf.com', url: 'https://hermes.ai.unturf.com/v1' }, - -const VLLM_ENDPOINTS = [ - { id: 'hermes.ai.unturf.com', url: 'https://hermes.ai.unturf.com/v1' }, - { id: 'hermes2.ai.unturf.com', url: 'https://hermes2.ai.unturf.com/v1' } -]; - -// ------------------------- -// Dynamic Endpoints and Model Registry -// ------------------------- - -// This registry maps a model's ID to the endpoint where it resides. -const modelRegistry = {}; - -// Fetch models from each endpoint with caching. -// Cache is busted if the endpoint array changes or TTL. -async function fetchModelsFromEndpoints() { - const cacheKey = 'modelRegistryCache'; - const endpointsKey = 'vllmEndpointsHash'; - const endpointsString = JSON.stringify(VLLM_ENDPOINTS); - const cachedEndpoints = localStorage.getItem(endpointsKey); - const cacheItem = localStorage.getItem(cacheKey); - const now = Date.now(); - const TTL = 300000; // 5 minutes in milliseconds - - if (cacheItem && cachedEndpoints === endpointsString) { - try { - const cachedData = JSON.parse(cacheItem); - if (now - cachedData.timestamp < TTL) { - // Restore cached modelRegistry - Object.assign(modelRegistry, cachedData.modelRegistry); - return cachedData.models; - } - } catch (e) { - console.error("Error reading model registry from cache", e); - } - } - - // If no valid cache, fetch models from all endpoints - const fetchPromises = VLLM_ENDPOINTS.map(async (endpoint) => { - try { - const res = await fetch(`${endpoint.url}/models`); - if (!res.ok) throw new Error(`HTTP error! status: ${res.status} from ${endpoint.url}`); - const jsonResponse = await res.json(); - // Expected JSON structure: { data: [ { id, ... }, ... ], object: "list" } - const models = jsonResponse.data || []; - // Map each model to include its endpoint ID, unique ID, and model name - return models.map((model) => ({ - ...model, - modelName: model.id, // Explicitly store model name - endpointId: endpoint.id, - uniqueId: `${endpoint.id}-${model.id}` // Unique ID with endpoint ID first - })); - } catch (error) { - console.error(`Error fetching models from ${endpoint.url}:`, error); - return []; - } - }); - const allModelsArrays = await Promise.all(fetchPromises); - const models = allModelsArrays.flat(); - - // Update modelRegistry with unique model instances - models.forEach((model) => { - modelRegistry[model.uniqueId] = { - url: VLLM_ENDPOINTS.find((e) => e.id === model.endpointId).url, - endpointId: model.endpointId - }; - }); - - // Cache the results - const cacheData = { - timestamp: now, - modelRegistry: modelRegistry, - models: models - }; - localStorage.setItem(cacheKey, JSON.stringify(cacheData)); - localStorage.setItem(endpointsKey, endpointsString); - - return models; -} - -// Create a dynamic drop-down for model selection -// This function creates a - - - `; - - // Control buttons - const controlsDiv = document.createElement('div'); - controlsDiv.style.cssText = 'display: grid; grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); gap: 8px; margin-bottom: 10px;'; - - const readBtn = createButton('📖 Read Page', () => readPageWithHermes()); - const ttsBtn = createButton('🔊 TTS Anything', () => openTTSModal()); - const uploadBtn = createButton('📁 Upload File', () => document.querySelector('[data-uncloseai-file-input]')?.click()); - - controlsDiv.appendChild(readBtn); - controlsDiv.appendChild(ttsBtn); - controlsDiv.appendChild(uploadBtn); - - // Hidden file input - const fileInput = document.createElement('input'); - fileInput.type = 'file'; - fileInput.setAttribute('data-uncloseai-file-input', ''); - fileInput.style.display = 'none'; - fileInput.onchange = async (e) => { - if (e.target.files[0]) { - try { - showProgressIndicator('Processing file...'); - const response = await uploadFile(e.target.files[0]); - await integrateMegafarceResponse(response); - hideProgressIndicator(); - e.target.value = ''; - } catch (error) { - hideProgressIndicator(); - alert('File upload failed: ' + error.message); - } - } - }; - - container.appendChild(controlsDiv); - container.appendChild(chatContainer); - container.appendChild(fileInput); -} - -// Create custom interface based on specific features -function createCustomInterface(container, features) { - features.forEach(feature => { - const featureDiv = document.createElement('div'); - featureDiv.style.margin = '10px 0'; - - switch (feature.trim()) { - case 'chat': - createChatFeature(featureDiv); - break; - case 'tts': - createTTSFeature(featureDiv); - break; - case 'upload': - createUploadFeature(featureDiv); - break; - case 'read': - createReadFeature(featureDiv); - break; - } - - container.appendChild(featureDiv); - }); -} - -// Individual feature creators -function createChatFeature(container) { - container.innerHTML = ` -
-

AI Chat

-
-
- - -
-
- `; -} - -function createTTSFeature(container) { - container.innerHTML = ` -
-

Text to Speech

- - -
-
- `; -} - -function createUploadFeature(container) { - container.innerHTML = ` -
-

File Upload & Analysis

- - -
-
- `; -} - -function createReadFeature(container) { - container.innerHTML = ` -
-

Page Reading

-

Let AI read and analyze the current page content.

- -
- `; -} - -// Helper functions for custom features -function createButton(text, onclick) { - const btn = document.createElement('button'); - btn.textContent = text; - btn.onclick = onclick; - btn.style.cssText = 'padding: 8px 12px; border: 1px solid #ccc; border-radius: 4px; background: white; cursor: pointer;'; - return btn; -} - -async function handleTTSFromElement(button) { - const container = button.closest('[data-tts-result]')?.parentElement || button.parentElement; - const textarea = container.querySelector('[data-tts-input]'); - const resultDiv = container.querySelector('[data-tts-result]'); - const text = textarea?.value?.trim(); - - if (!text) { - alert('Please enter some text first!'); - return; - } - - button.disabled = true; - button.textContent = 'Converting...'; - resultDiv.innerHTML = 'Converting to speech...'; - - try { - const result = await speakText(text, 'alloy', 0.9); - resultDiv.innerHTML = ` -
- - -
- `; - resultDiv.insertBefore(result.audio, resultDiv.firstChild); - } catch (error) { - resultDiv.innerHTML = 'Error: ' + error.message; - } finally { - button.disabled = false; - button.textContent = '🔊 Convert to Speech'; - } -} - -async function handleUploadFromElement(button) { - const container = button.parentElement; - const fileInput = container.querySelector('[data-upload-input]'); - const resultDiv = container.querySelector('[data-upload-result]'); - - if (!fileInput.files[0]) { - alert('Please select a file first!'); - return; - } - - button.disabled = true; - button.textContent = 'Processing...'; - resultDiv.style.display = 'block'; - resultDiv.innerHTML = 'Uploading and analyzing file...'; - - try { - showProgressIndicator('Processing file...'); - const response = await uploadFile(fileInput.files[0]); - hideProgressIndicator(); - - resultDiv.innerHTML = `Analysis Result:
${response}`; - fileInput.value = ''; - } catch (error) { - hideProgressIndicator(); - resultDiv.innerHTML = 'Error: ' + error.message; - } finally { - button.disabled = false; - button.textContent = '📁 Upload & Analyze'; - } -} - -// Initialize the legacy chat interface (for backward compatibility) -function initializeChatInterface() { - // Only initialize if there are legacy elements (chat-container, user-input, etc.) - const legacyElements = document.querySelector('#chat-container, #user-input, #chat-box'); - if (!legacyElements) return; - - const pageContent = extractWebpageContent(); - chatHistory.push({ - role: "system", - content: `Here's the content of the webpage: ${pageContent}` - }); - - marked.setOptions({ - highlight: function(code, lang) { - const language = hljs.getLanguage(lang) ? lang : 'plaintext'; - return hljs.highlight(code, { language }).value; - } - }); - - // Add the Read Page button - addReadPageButton(); - - // Add the File Upload button and picker - addFileUploadButton(); - - // Event listeners - document.getElementById("user-input").addEventListener("keydown", function(event) { - if (event.key === "Enter" && !event.shiftKey) { - event.preventDefault(); - handleUserInput(); - } - }); -} - - -// Function to create floating AI button -function createFloatingAIButton() { - console.log('uncloseai.js: createFloatingAIButton() called.'); - // Remove any existing floating button first - const existingButton = document.getElementById('floating-ai-button'); - if (existingButton) { - console.log('uncloseai.js: Found existing floating button, removing it.'); - existingButton.remove(); - } - - // Create the main floating button - const floatingButton = document.createElement('button'); - floatingButton.id = 'floating-ai-button'; - floatingButton.textContent = 'uncloseai.'; - console.log('uncloseai.js: Created new floating button element.'); - - // Function to update button theme - function updateButtonTheme() { - const isDark = document.documentElement.getAttribute('data-theme') === 'dark' || - (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches && - !document.documentElement.getAttribute('data-theme')); - - floatingButton.style.cssText = ` - position: fixed; - bottom: 20px; - right: 10px; - width: 110px; - height: 55px; - border-radius: 22px; - background: ${isDark ? '#ffffff' : '#000000'}; - border: 2px solid ${isDark ? '#000000' : '#ffffff'}; - color: ${isDark ? '#000000' : '#ffffff'}; - font-family: 'ChunkFiveRegular', monospace; - font-size: 13px; - cursor: pointer; - box-shadow: 0 4px 12px rgba(0,0,0,0.3); - z-index: 1000; - transition: all 0.3s ease; - max-width: calc(100vw - 20px); - box-sizing: border-box; - `; - } - - // Initial theme setup - updateButtonTheme(); - - // Watch for theme changes - const observer = new MutationObserver(updateButtonTheme); - observer.observe(document.documentElement, { attributes: true, attributeFilter: ['data-theme'] }); - - // Watch for system theme changes - if (window.matchMedia) { - window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', updateButtonTheme); - } - - // Hover effects - floatingButton.onmouseenter = () => { - floatingButton.style.transform = 'scale(1.1)'; - floatingButton.style.boxShadow = '0 6px 16px rgba(0,0,0,0.4)'; - }; - floatingButton.onmouseleave = () => { - floatingButton.style.transform = 'scale(1)'; - floatingButton.style.boxShadow = '0 4px 12px rgba(0,0,0,0.3)'; - }; - - // Toggle modal on click - floatingButton.onclick = () => toggleHermesModal(); - - document.body.appendChild(floatingButton); - console.log('uncloseai.js: Appended floating button to document.body.'); -} - -// Global variable to track modal state -let hermesModalOpen = false; - -// Function to toggle Hermes modal -function toggleHermesModal() { - const existingModal = document.getElementById('hermes-modal'); - if (existingModal) { - document.body.removeChild(existingModal); - hermesModalOpen = false; - } else { - openHermesModal(); - hermesModalOpen = true; - } -} - -// Function to open Hermes modal -function openHermesModal() { - // Create modal using dialog element like TTS modal - const modal = document.createElement('dialog'); - modal.id = 'hermes-modal'; - modal.open = true; - - if (USE_CUSTOM_STYLING) { - modal.style.cssText = ` - position: fixed; - top: 0; - left: 0; - width: 90%; - max-width: 800px; - height: 90%; - max-height: 700px; - border: none; - border-radius: 16px; - box-shadow: 0 20px 40px rgba(0,0,0,0.3); - background: white; - z-index: 2000; - margin: auto; - `; - } else { - modal.style.cssText = ` - position: fixed; - top: 0; - left: 0; - width: 100%; - height: 100vh; - height: 100dvh; - min-width: 0; - border: none; - background: transparent; - z-index: 2000; - margin: 0; - padding: 0; - `; - } - - const article = document.createElement('article'); - if (USE_CUSTOM_STYLING) { - article.style.cssText = ` - height: 100%; - display: grid; - grid-template-rows: auto auto 1fr auto; - margin: 0; - padding: 0; - `; - } else { - // Responsive scaling based on screen width - const screenWidth = window.innerWidth; - const scale = screenWidth < 480 ? 0.9 : screenWidth < 768 ? 0.8 : 0.75; - - article.style.cssText = ` - height: 100%; - display: grid; - grid-template-rows: auto auto 1fr auto; - transform: scale(${scale}); - transform-origin: top center; - margin: 0; - padding: 0; - min-width: 0; - max-width: none; - box-sizing: border-box; - `; - } - modal.appendChild(article); - - // Handle viewport changes (onscreen keyboard) - if (!USE_CUSTOM_STYLING) { - const handleViewportChange = () => { - // Use dvh (dynamic viewport height) for better mobile keyboard handling - modal.style.height = '100dvh'; - // Fallback for browsers that don't support dvh - if (window.visualViewport) { - modal.style.height = `${window.visualViewport.height}px`; - } - }; - - // Listen for visual viewport changes (keyboard open/close) - if (window.visualViewport) { - window.visualViewport.addEventListener('resize', handleViewportChange); - } - - // Also listen for window resize as fallback - window.addEventListener('resize', handleViewportChange); - - // Initial call - handleViewportChange(); - } - - // Create modal header - const header = document.createElement('div'); - if (USE_CUSTOM_STYLING) { - header.style.cssText = ` - background: linear-gradient(45deg, #667eea 0%, #764ba2 100%); - color: white; - padding: 16px 20px; - display: grid; - grid-template-columns: 1fr auto; - align-items: center; - `; - } else { - header.style.cssText = ` - padding: 16px 20px; - display: grid; - grid-template-columns: 1fr auto; - align-items: center; - border-bottom: 1px solid #ccc; - `; - } - - const titleContainer = document.createElement('div'); - - const title = document.createElement('h2'); - title.innerHTML = 'uncloseai. presents nous research\'s hermes large language model'; - if (USE_CUSTOM_STYLING) { - title.style.cssText = ` - margin: 0; - font-family: 'ChunkFiveRegular', monospace; - font-size: 16px; - line-height: 1.2; - `; - } else { - title.style.cssText = ` - margin: 0; - font-size: 0.9em; - line-height: 1.2; - `; - } - - const pageTitle = document.createElement('div'); - pageTitle.textContent = `You are discussing: ${document.title}`; - if (USE_CUSTOM_STYLING) { - pageTitle.style.cssText = ` - font-size: 12px; - opacity: 0.8; - margin-top: 4px; - `; - } else { - pageTitle.style.cssText = ` - font-size: 0.75em; - opacity: 0.7; - margin-top: 4px; - `; - } - - titleContainer.appendChild(title); - titleContainer.appendChild(pageTitle); - - const closeButton = document.createElement('button'); - closeButton.textContent = '×'; - if (USE_CUSTOM_STYLING) { - closeButton.style.cssText = ` - background: none; - border: none; - color: white; - font-size: 24px; - cursor: pointer; - padding: 0; - width: 30px; - height: 30px; - display: flex; - align-items: center; - justify-content: center; - `; - } else { - closeButton.style.cssText = ` - background: var(--background-color); - border: 1px solid var(--border-color); - color: var(--color); - font-size: 18px; - cursor: pointer; - padding: 4px; - width: 28px; - height: 28px; - border-radius: 4px; - display: flex; - align-items: center; - justify-content: center; - transition: opacity 0.2s; - `; - closeButton.onmouseenter = () => closeButton.style.opacity = '0.7'; - closeButton.onmouseleave = () => closeButton.style.opacity = '1'; - } - closeButton.onclick = () => { - document.body.removeChild(modal); - hermesModalOpen = false; - }; - - header.appendChild(titleContainer); - header.appendChild(closeButton); - - // Create controls section - const controls = document.createElement('div'); - if (USE_CUSTOM_STYLING) { - controls.style.cssText = ` - padding: 16px 20px; - border-bottom: 1px solid #e0e0e0; - display: grid; - grid-template-columns: auto 1fr auto 1fr; - gap: 12px; - align-items: center; - `; - } else { - controls.className = 'uncloseai-controls'; - controls.style.cssText = ` - display: grid; - grid-template-columns: auto 1fr auto 1fr; - gap: 12px; - padding: 0.5rem; - align-items: center; - `; - } - - // Add model selection dropdown - const modelLabel = document.createElement('label'); - modelLabel.textContent = 'Model: '; - if (USE_CUSTOM_STYLING) { - modelLabel.style.fontWeight = 'bold'; - } - - const modelSelect = document.createElement('select'); - modelSelect.id = 'modal-model-selection'; - if (USE_CUSTOM_STYLING) { - modelSelect.style.cssText = ` - padding: 6px 12px; - border: 1px solid #ccc; - border-radius: 4px; - background: white; - `; - } - - // Copy options from existing model dropdown - const existingDropdown = document.getElementById('model-selection'); - if (existingDropdown) { - Array.from(existingDropdown.options).forEach(option => { - const newOption = document.createElement('option'); - newOption.value = option.value; - newOption.textContent = option.textContent; - newOption.selected = option.selected; - modelSelect.appendChild(newOption); - }); - } - - // Restore saved model selection - const savedModel = localStorage.getItem('hermes-selected-model'); - if (savedModel && modelSelect.querySelector(`option[value="${savedModel}"]`)) { - modelSelect.value = savedModel; - } - - // Save model selection on change - modelSelect.addEventListener('change', () => { - localStorage.setItem('hermes-selected-model', modelSelect.value); - }); - - // Add voice selection with newline - const voiceBreak = document.createElement('div'); - voiceBreak.style.width = '100%'; - - const voiceLabel = document.createElement('label'); - voiceLabel.textContent = 'Voice: '; - if (USE_CUSTOM_STYLING) { - voiceLabel.style.fontWeight = 'bold'; - } - - const voiceSelect = document.createElement('select'); - voiceSelect.id = 'modal-voice-selection'; - if (USE_CUSTOM_STYLING) { - voiceSelect.style.cssText = ` - padding: 6px 12px; - border: 1px solid #ccc; - border-radius: 4px; - background: white; - `; - } - - const voices = ['alloy', 'echo', 'fable', 'onyx', 'nova', 'shimmer']; - voices.forEach(voice => { - const option = document.createElement('option'); - option.value = voice; - option.textContent = voice; - if (voice === 'alloy') option.selected = true; - voiceSelect.appendChild(option); - }); - - // Restore saved voice selection - const savedVoice = localStorage.getItem('hermes-selected-voice'); - if (savedVoice && voices.includes(savedVoice)) { - voiceSelect.value = savedVoice; - } - - // Save voice selection on change - voiceSelect.addEventListener('change', () => { - localStorage.setItem('hermes-selected-voice', voiceSelect.value); - }); - - // Add action buttons - const actionButtons = document.createElement('div'); - if (USE_CUSTOM_STYLING) { - actionButtons.style.cssText = ` - display: grid; - grid-template-columns: repeat(auto-fit, minmax(60px, 1fr)); - gap: 4px; - `; - } else { - actionButtons.className = 'uncloseai-button-group'; - actionButtons.style.cssText = ` - display: grid; - grid-template-columns: repeat(auto-fit, minmax(60px, 1fr)); - gap: 4px; - `; - } - - const readPageBtn = document.createElement('button'); - readPageBtn.textContent = '📖 Read Page'; - if (USE_CUSTOM_STYLING) { - readPageBtn.style.cssText = 'padding: 6px 12px; border: 1px solid #ccc; border-radius: 4px; background: white; cursor: pointer;'; - } - readPageBtn.onclick = readPageWithHermes; - - const ttsBtn = document.createElement('button'); - ttsBtn.textContent = '🔊 TTS Anything'; - if (USE_CUSTOM_STYLING) { - ttsBtn.style.cssText = 'padding: 6px 12px; border: 1px solid #ccc; border-radius: 4px; background: white; cursor: pointer;'; - } - ttsBtn.onclick = openTTSModal; - - const refreshBtn = document.createElement('button'); - refreshBtn.textContent = '🔄 Refresh'; - if (USE_CUSTOM_STYLING) { - refreshBtn.style.cssText = 'padding: 6px 12px; border: 1px solid #ccc; border-radius: 4px; background: white; cursor: pointer;'; - } - refreshBtn.onclick = async () => { - // Clear cache and refresh models - localStorage.removeItem('modelRegistryCache'); - localStorage.removeItem('vllmEndpointsHash'); - const models = await fetchModelsFromEndpoints(); - - // Update both dropdowns - [modelSelect, document.getElementById('model-selection')].forEach(dropdown => { - if (dropdown) { - dropdown.innerHTML = ''; - models.forEach(model => { - const option = document.createElement('option'); - option.value = model.uniqueId; - option.textContent = `${model.endpointId} | ${model.modelName}`; - dropdown.appendChild(option); - }); - } - }); - }; - - const clearBtn = document.createElement('button'); - clearBtn.textContent = '🗑️ Clear'; - if (USE_CUSTOM_STYLING) { - clearBtn.style.cssText = 'padding: 6px 12px; border: 1px solid #ccc; border-radius: 4px; background: white; cursor: pointer;'; - } - clearBtn.onclick = async () => { - if (confirm('Clear all conversation history?')) { - clearConversationHistory(); - chatBox.innerHTML = ''; - await addHermesIntroduction(); // Call addHermesIntroduction after clearing - } - }; - - actionButtons.appendChild(readPageBtn); - actionButtons.appendChild(ttsBtn); - actionButtons.appendChild(refreshBtn); - actionButtons.appendChild(clearBtn); - - // Make action buttons span all columns - actionButtons.style.gridColumn = 'span 4'; - - controls.appendChild(actionButtons); - controls.appendChild(modelLabel); - controls.appendChild(modelSelect); - controls.appendChild(voiceLabel); - controls.appendChild(voiceSelect); - - // Create chat area - const chatArea = document.createElement('div'); - if (USE_CUSTOM_STYLING) { - chatArea.style.cssText = ` - grid-row: 3; - padding: 20px; - overflow-y: auto; - border-bottom: 1px solid #e0e0e0; - `; - } else { - chatArea.style.cssText = ` - grid-row: 3; - padding: 1rem; - overflow-y: auto; - border-bottom: 1px solid #e0e0e0; - min-height: 200px; - `; - } - - const chatBox = document.createElement('div'); - chatBox.id = 'modal-chat-box'; - if (USE_CUSTOM_STYLING) { - chatBox.style.cssText = ` - height: 100%; - overflow-y: auto; - `; - } else { - chatBox.style.cssText = ` - min-height: 150px; - overflow-y: auto; - `; - } - - chatArea.appendChild(chatBox); - - // Restore conversation history in modal - function restoreConversationHistory() { - const history = loadConversationHistory(); - chatBox.innerHTML = ''; - history.forEach((msg, index) => { - const messageDiv = document.createElement('div'); - messageDiv.style.cssText = 'position: relative; margin-bottom: 10px; padding: 5px; border-radius: 5px; background: rgba(0,0,0,0.05);'; - - const deleteBtn = document.createElement('button'); - deleteBtn.textContent = '×'; - deleteBtn.style.cssText = 'position: absolute; top: 2px; right: 2px; width: 20px; height: 20px; font-size: 12px; background: rgba(255,0,0,0.7); color: white; border: none; border-radius: 50%; cursor: pointer;'; - deleteBtn.onclick = ((messageIndex) => { - return () => { - if (confirm('Delete this message?')) { - const savedHistory = loadConversationHistory(); - if (messageIndex >= 0 && messageIndex < savedHistory.length) { - savedHistory.splice(messageIndex, 1); - localStorage.setItem('hermes-conversation-history', JSON.stringify(savedHistory)); - // Update global chat history - chatHistory = [ - { - role: "system", - content: "You are Hermes, a large language model from Nous Research, embedded as an AI assistant on this webpage. Your primary task is to help users understand and interact with the content of this specific page, while also being capable of assisting with any other topics, coding, creative tasks, or questions they may have. You should be conversational, helpful, and knowledgeable about the webpage content as well as general topics. Always strive to provide accurate, useful responses." - }, - ...savedHistory - ]; - restoreConversationHistory(); - } - } - }; - })(index); - - if (msg.role === 'user') { - messageDiv.innerHTML = `

You: ${msg.content}

`; - } else if (msg.role === 'assistant') { - const parsedContent = marked.parse(msg.content); - messageDiv.innerHTML = `

AI: ${parsedContent}

`; - } - - messageDiv.appendChild(deleteBtn); - chatBox.appendChild(messageDiv); - }); - chatBox.scrollTop = chatBox.scrollHeight; - } - restoreConversationHistory(); - - // Generate dynamic Hermes introduction using LLM - async function addHermesIntroduction() { - const history = loadConversationHistory(); - if (history.length === 0) { - const pageContent = extractWebpageContent(); - const pageTitle = document.title; - const pageUrl = window.location.href; - - // Create cache key based on page content hash (handle Unicode safely) - const contentForHash = pageContent.substring(0, 1000); - let contentHash; - try { - contentHash = btoa(unescape(encodeURIComponent(contentForHash))).replace(/[^a-zA-Z0-9]/g, '').substring(0, 32); - } catch (e) { - // Fallback: use simple string hash if btoa fails - let hash = 0; - for (let i = 0; i < contentForHash.length; i++) { - const char = contentForHash.charCodeAt(i); - hash = ((hash << 5) - hash) + char; - hash = hash & hash; // Convert to 32bit integer - } - contentHash = Math.abs(hash).toString(36).substring(0, 32); - } - const cacheKey = `hermes-intro-${contentHash}`; - - // Check if we have a cached introduction for this page content - const cachedIntro = localStorage.getItem(cacheKey); - if (cachedIntro) { - displayIntroduction(cachedIntro); - // Add cached intro to chat history - chatHistory.push({ role: "assistant", content: cachedIntro }); - saveConversationHistory(); - return; - } - - // Generate new introduction using LLM - const introDiv = document.createElement('div'); - introDiv.style.cssText = 'position: relative; margin-bottom: 10px; padding: 10px; border-radius: 5px; background: rgba(0,100,200,0.1); border-left: 4px solid #0066cc;'; - introDiv.innerHTML = '

🤖 Hermes: ✨ Analyzing this page and crafting a personalized introduction... This may take a moment.

'; - chatBox.appendChild(introDiv); - chatBox.scrollTop = chatBox.scrollHeight; - - try { - const prompt = `You are Hermes, a large language model from Nous Research. Write a friendly 3-paragraph introduction for yourself when embedded on this webpage. Be specific about this page's content and identify 2-3 key takeaways. Keep it conversational and helpful. - -Page Title: ${pageTitle} -Page URL: ${pageUrl} -Page Content: ${pageContent.substring(0, 2000)} - -Format: Start with "Greetings! I'm Hermes..." and make it sound natural and engaging. Write 3 full paragraphs that showcase your capabilities and how you can help with THIS specific page.`; - - let generatedIntro = ''; - for await (const chunk of sendMessageDirect(prompt)) { - generatedIntro += chunk; - // Update display in real-time - introDiv.innerHTML = `

🤖 Hermes: ${generatedIntro}

`; - // Scroll to bottom as content updates - chatBox.scrollTop = chatBox.scrollHeight; - } - - // Add to chat history and save - chatHistory.push({ role: "assistant", content: generatedIntro }); - saveConversationHistory(); - - // Cache the generated introduction - localStorage.setItem(cacheKey, generatedIntro); - - // Add TTS button for introduction - addTTSButtonToIntro(introDiv, generatedIntro); - - chatBox.scrollTop = chatBox.scrollHeight; - - } catch (error) { - console.error('Error generating introduction:', error); - const fallbackIntro = 'Greetings! I\'m Hermes, a large language model from Nous Research. I\'m here to help you understand this page and assist with any questions, coding, or creative tasks you might have. Feel free to ask me anything!'; - introDiv.innerHTML = `

🤖 Hermes: ${fallbackIntro}

`; - // Add fallback to chat history too - chatHistory.push({ role: "assistant", content: fallbackIntro }); - saveConversationHistory(); - } - } - } - - function displayIntroduction(introText) { - const introDiv = document.createElement('div'); - introDiv.style.cssText = 'position: relative; margin-bottom: 10px; padding: 10px; border-radius: 5px; background: rgba(0,100,200,0.1); border-left: 4px solid #0066cc;'; - introDiv.innerHTML = `

🤖 Hermes: ${introText}

`; - chatBox.appendChild(introDiv); - - // Add TTS button for cached introduction - addTTSButtonToIntro(introDiv, introText); - - chatBox.scrollTop = chatBox.scrollHeight; - } - - // Add TTS button to introduction message - function addTTSButtonToIntro(introDiv, introText) { - const ttsIntroContainer = document.createElement('div'); - ttsIntroContainer.style.cssText = ` - display: grid; - grid-template-columns: auto auto auto; - gap: 8px; - margin: 10px 0; - align-items: center; - `; - - const ttsIntroBtn = document.createElement('button'); - ttsIntroBtn.textContent = '🔊 Play Response'; - if (USE_CUSTOM_STYLING) { - ttsIntroBtn.style.cssText = 'padding: 6px 12px; border: 1px solid #0066cc; border-radius: 4px; background: #f0f8ff; cursor: pointer; color: #0066cc;'; - } else { - ttsIntroBtn.style.cssText = 'padding: 6px 12px; border-radius: 4px; cursor: pointer;'; - } - - const regenerateIntroBtn = document.createElement('button'); - regenerateIntroBtn.textContent = '🔄 Regenerate'; - regenerateIntroBtn.style.display = 'none'; - if (USE_CUSTOM_STYLING) { - regenerateIntroBtn.style.cssText += 'padding: 6px 12px; border: 1px solid #0066cc; border-radius: 4px; background: #f0f8ff; cursor: pointer; color: #0066cc;'; - } else { - regenerateIntroBtn.style.cssText += 'padding: 6px 12px; border-radius: 4px; cursor: pointer;'; - } - - const downloadIntroBtn = document.createElement('button'); - downloadIntroBtn.textContent = '💾 Download'; - downloadIntroBtn.style.display = 'none'; - if (USE_CUSTOM_STYLING) { - downloadIntroBtn.style.cssText += 'padding: 6px 12px; border: 1px solid #0066cc; border-radius: 4px; background: #f0f8ff; cursor: pointer; color: #0066cc;'; - } else { - downloadIntroBtn.style.cssText += 'padding: 6px 12px; border-radius: 4px; cursor: pointer;'; - } - - let introAudio = null; - let introBlob = null; - let currentIntroVoice = voiceSelect.value; - - // Function to generate/regenerate intro TTS - const generateIntroTTS = async () => { - ttsIntroBtn.textContent = 'Processing...'; - ttsIntroBtn.disabled = true; - regenerateIntroBtn.disabled = true; - - const selectedVoice = voiceSelect.value; - currentIntroVoice = selectedVoice; - const result = await speakText(introText, selectedVoice, 0.9); - introAudio = result.audio; - introBlob = result.blob; - - ttsIntroBtn.textContent = 'Pause'; - ttsIntroBtn.disabled = false; - regenerateIntroBtn.disabled = false; - regenerateIntroBtn.style.display = 'inline-block'; - downloadIntroBtn.style.display = 'inline-block'; - - introAudio.play(); - }; - - ttsIntroBtn.onclick = async () => { - if (!introAudio) { - await generateIntroTTS(); - } else { - if (introAudio.paused) { - introAudio.play(); - ttsIntroBtn.textContent = 'Pause'; - } else { - introAudio.pause(); - ttsIntroBtn.textContent = 'Play'; - } - } - }; - - regenerateIntroBtn.onclick = async () => { - if (introAudio) { - introAudio.pause(); - } - introAudio = null; - introBlob = null; - await generateIntroTTS(); - }; - - downloadIntroBtn.onclick = () => { - if (introBlob) { - const a = document.createElement('a'); - a.href = URL.createObjectURL(introBlob); - a.download = `hermes-introduction-${Date.now()}.mp3`; - a.click(); - } - }; - - // Watch for voice changes - voiceSelect.addEventListener('change', () => { - if (introAudio && currentIntroVoice !== voiceSelect.value) { - regenerateIntroBtn.style.display = 'inline-block'; - regenerateIntroBtn.style.background = '#fffacd'; // Light yellow to indicate change needed - } - }); - - ttsIntroContainer.appendChild(ttsIntroBtn); - ttsIntroContainer.appendChild(regenerateIntroBtn); - ttsIntroContainer.appendChild(downloadIntroBtn); - introDiv.appendChild(ttsIntroContainer); - } - - // Direct LLM call for introduction generation (doesn't add to chat history) - async function* sendMessageDirect(message) { - const selectedModel = modelSelect.value || MODEL; - const modelInfo = modelRegistry[selectedModel]; - const endpoint = modelInfo ? modelInfo.url : VLLM_ENDPOINTS[0].url; - const modelName = modelInfo ? modelInfo.modelName : MODEL; - - const response = await fetch(`${endpoint}/chat/completions`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - model: modelName, - messages: [{ role: "user", content: message }], - temperature: 0.7, - max_tokens: 300, - stream: true - }), - }); - - const reader = response.body.getReader(); - const decoder = new TextDecoder(); - let buffer = ''; - - while (true) { - const { done, value } = await reader.read(); - if (done) break; - - buffer += decoder.decode(value, { stream: true }); - const lines = buffer.split('\n'); - - for (let i = 0; i < lines.length - 1; i++) { - const line = lines[i].trim(); - if (line.startsWith('data: ')) { - const jsonData = line.slice(6); - if (jsonData === '[DONE]') continue; - - try { - const parsedData = JSON.parse(jsonData); - const content = parsedData.choices[0].delta.content; - if (content) { - yield content; - } - } catch (error) { - console.error('Error parsing JSON:', error); - } - } - } - buffer = lines[lines.length - 1]; - } - } - // Call async introduction function - addHermesIntroduction().catch(error => { - console.error('Failed to add Hermes introduction:', error); - }); - - // Create input area - const inputArea = document.createElement('div'); - if (USE_CUSTOM_STYLING) { - inputArea.style.cssText = ` - padding: 16px 20px; - display: flex; - gap: 12px; - align-items: center; - `; - } else { - inputArea.style.cssText = ` - padding: 1rem; - display: flex; - gap: 0.5rem; - align-items: center; - `; - } - - const messageInput = document.createElement('input'); - messageInput.type = 'text'; - messageInput.id = 'modal-user-input'; - messageInput.placeholder = '💬 Chat with Hermes about this page, code, ideas, or anything! Your AI companion awaits...'; - messageInput.style.cssText = ` - flex: 1; - padding: 12px; - border: 1px solid #ccc; - border-radius: 8px; - font-size: 14px; - `; - - const sendButton = document.createElement('button'); - sendButton.textContent = 'Send'; - -// Expose functions to the global scope for direct HTML calls -window.handleUserInput = handleUserInput; -window.readPageWithHermes = readPageWithHermes; -window.openTTSModal = openTTSModal; -window.handleTTSFromElement = handleTTSFromElement; -window.handleUploadFromElement = handleUploadFromElement; -window.uploadFile = uploadFile; -window.showProgressIndicator = showProgressIndicator; -window.hideProgressIndicator = hideProgressIndicator; -window.sendMessage = sendMessage; -window.speakText = speakText; - if (USE_CUSTOM_STYLING) { - sendButton.style.cssText = ` - padding: 12px 24px; - background: linear-gradient(45deg, #667eea 0%, #764ba2 100%); - color: white; - border: none; - border-radius: 8px; - cursor: pointer; - font-weight: bold; - `; - } - - // File upload - const fileInput = document.createElement('input'); - fileInput.type = 'file'; - fileInput.id = 'modal-file-input'; - fileInput.style.display = 'none'; - - const fileButton = document.createElement('button'); - fileButton.textContent = '📁'; - fileButton.style.cssText = ` - padding: 12px; - background: #f5f5f5; - border: 1px solid #ccc; - border-radius: 8px; - cursor: pointer; - `; - fileButton.onclick = () => fileInput.click(); - - // Handle file upload - fileInput.onchange = async () => { - if (fileInput.files.length > 0) { - const file = fileInput.files[0]; - try { - showProgressIndicator('Uploading & Processing file...'); - const response = await uploadFile(file); - await integrateMegafarceResponseToModal(response, chatBox); - hideProgressIndicator(); - fileInput.value = ''; - } catch (error) { - console.error('File upload error:', error); - alert('Failed to upload the file.'); - hideProgressIndicator(); - } - } - }; - - // Handle send message - const handleModalInput = async () => { - const userInput = messageInput.value.trim(); - if (!userInput) return; - - messageInput.value = ''; - - // Create user message with delete functionality - const userMessageDiv = document.createElement('div'); - userMessageDiv.style.cssText = 'position: relative; margin-bottom: 10px; padding: 5px; border-radius: 5px; background: rgba(0,0,0,0.05);'; - userMessageDiv.innerHTML = `

You: ${userInput}

`; - chatBox.appendChild(userMessageDiv); - - // Scroll to bottom when user message is added - chatBox.scrollTop = chatBox.scrollHeight; - - // Create AI response container - const aiMessageDiv = document.createElement('div'); - aiMessageDiv.style.cssText = 'position: relative; margin-bottom: 10px; padding: 5px; border-radius: 5px; background: rgba(0,0,0,0.05);'; - const aiResponseParagraph = document.createElement('p'); - aiResponseParagraph.innerHTML = 'AI: '; - const responseContent = document.createElement('span'); - aiResponseParagraph.appendChild(responseContent); - aiMessageDiv.appendChild(aiResponseParagraph); - chatBox.appendChild(aiMessageDiv); - - let accumulatedContent = ''; - for await (const chunk of sendMessage(userInput)) { - accumulatedContent += chunk; - const parsedChunk = marked.parse(accumulatedContent); - responseContent.innerHTML = parsedChunk; - - responseContent.querySelectorAll('pre code').forEach((block) => { - hljs.highlightElement(block); - }); - - // Auto-scroll to bottom as response streams in - chatBox.scrollTop = chatBox.scrollHeight; - } - - // Add assistant response to chat history and save - chatHistory.push({ role: "assistant", content: accumulatedContent }); - saveConversationHistory(); - - // Add delete buttons to the new messages - const userDeleteBtn = createStyledButton('×', 'close', 'position: absolute; top: 2px; right: 2px; width: 20px; height: 20px; font-size: 12px; background: rgba(255,0,0,0.7); color: white; border-radius: 4px;'); - userDeleteBtn.onclick = () => { - if (confirm('Delete this conversation pair?')) { - const currentHistory = loadConversationHistory(); - // Remove the last 2 messages (user + AI response) - if (currentHistory.length >= 2) { - currentHistory.splice(-2, 2); - localStorage.setItem('hermes-conversation-history', JSON.stringify(currentHistory)); - chatHistory = [ - { - role: "system", - content: "You are Hermes, a large language model from Nous Research, embedded as an AI assistant on this webpage. Your primary task is to help users understand and interact with the content of this specific page, while also being capable of assisting with any other topics, coding, creative tasks, or questions they may have. You should be conversational, helpful, and knowledgeable about the webpage content as well as general topics. Always strive to provide accurate, useful responses." - }, - ...currentHistory - ]; - restoreConversationHistory(); - } - } - }; - - const aiDeleteBtn = createStyledButton('×', 'close', 'position: absolute; top: 2px; right: 2px; width: 20px; height: 20px; font-size: 12px; background: rgba(255,0,0,0.7); color: white; border-radius: 4px;'); - aiDeleteBtn.onclick = userDeleteBtn.onclick; // Same functionality - delete the pair - - userMessageDiv.appendChild(userDeleteBtn); - aiMessageDiv.appendChild(aiDeleteBtn); - - chatBox.scrollTop = chatBox.scrollHeight; - - // Add TTS button container with grid layout - const ttsContainer = document.createElement('div'); - ttsContainer.style.cssText = ` - display: grid; - grid-template-columns: auto auto auto; - gap: 8px; - margin: 10px 0; - align-items: center; - `; - - const ttsResponseBtn = document.createElement('button'); - ttsResponseBtn.textContent = '🔊 Play Response'; - if (USE_CUSTOM_STYLING) { - ttsResponseBtn.style.cssText = 'padding: 6px 12px; border: 1px solid #ccc; border-radius: 4px; background: white; cursor: pointer;'; - } - - const regenerateBtn = document.createElement('button'); - regenerateBtn.textContent = '🔄 Regenerate'; - regenerateBtn.style.display = 'none'; - if (USE_CUSTOM_STYLING) { - regenerateBtn.style.cssText += 'padding: 6px 12px; border: 1px solid #ccc; border-radius: 4px; background: white; cursor: pointer;'; - } - - const downloadBtn = document.createElement('button'); - downloadBtn.textContent = '💾 Download'; - downloadBtn.style.display = 'none'; - if (USE_CUSTOM_STYLING) { - downloadBtn.style.cssText += 'padding: 6px 12px; border: 1px solid #ccc; border-radius: 4px; background: white; cursor: pointer;'; - } - - let responseAudio = null; - let responseBlob = null; - let currentVoice = voiceSelect.value; - - // Function to generate/regenerate TTS - const generateTTS = async () => { - ttsResponseBtn.textContent = 'Processing...'; - ttsResponseBtn.disabled = true; - regenerateBtn.disabled = true; - - const selectedVoice = voiceSelect.value; - currentVoice = selectedVoice; - const result = await speakText(accumulatedContent, selectedVoice, 0.9); - responseAudio = result.audio; - responseBlob = result.blob; - - ttsResponseBtn.textContent = 'Pause'; - ttsResponseBtn.disabled = false; - regenerateBtn.disabled = false; - regenerateBtn.style.display = 'inline-block'; - downloadBtn.style.display = 'inline-block'; - - responseAudio.play(); - }; - - ttsResponseBtn.onclick = async () => { - if (!responseAudio) { - await generateTTS(); - } else { - if (responseAudio.paused) { - responseAudio.play(); - ttsResponseBtn.textContent = 'Pause'; - } else { - responseAudio.pause(); - ttsResponseBtn.textContent = 'Play'; - } - } - }; - - regenerateBtn.onclick = async () => { - if (responseAudio) { - responseAudio.pause(); - } - responseAudio = null; - responseBlob = null; - await generateTTS(); - }; - - downloadBtn.onclick = () => { - if (responseBlob) { - const a = document.createElement('a'); - a.href = URL.createObjectURL(responseBlob); - a.download = `hermes-response-${Date.now()}.mp3`; - a.click(); - } - }; - - // Watch for voice changes - voiceSelect.addEventListener('change', () => { - if (responseAudio && currentVoice !== voiceSelect.value) { - regenerateBtn.style.display = 'inline-block'; - regenerateBtn.style.background = '#fffacd'; // Light yellow to indicate change needed - } - }); - - ttsContainer.appendChild(ttsResponseBtn); - ttsContainer.appendChild(regenerateBtn); - ttsContainer.appendChild(downloadBtn); - chatBox.appendChild(ttsContainer); - }; - - sendButton.onclick = handleModalInput; - messageInput.addEventListener('keydown', (e) => { - if (e.key === 'Enter' && !e.shiftKey) { - e.preventDefault(); - handleModalInput(); - } - }); - - inputArea.appendChild(fileButton); - inputArea.appendChild(fileInput); - inputArea.appendChild(messageInput); - inputArea.appendChild(sendButton); - - // Assemble modal - article.appendChild(header); - article.appendChild(controls); - article.appendChild(chatArea); - article.appendChild(inputArea); - - document.body.appendChild(modal); - messageInput.focus(); -} - -// Configuration for styling - set to false to disable custom styling -const USE_CUSTOM_STYLING = window.UNCLOSEAI_CUSTOM_STYLING !== false; - -// ------------------------- -// Shared Utility Functions for DRY Code -// ------------------------- - -// Create standardized button with consistent styling -function createStyledButton(text, type = 'primary', additionalStyles = '') { - const button = document.createElement('button'); - button.textContent = text; - - const baseStyles = 'padding: 8px 16px; border: none; border-radius: 4px; cursor: pointer; font-size: 14px;'; - - let typeStyles = ''; - switch (type) { - case 'primary': - typeStyles = USE_CUSTOM_STYLING - ? 'background: #007bff; color: white;' - : 'background: var(--primary, #007bff); color: white;'; - break; - case 'success': - typeStyles = USE_CUSTOM_STYLING - ? 'background: #28a745; color: white;' - : 'background: var(--success, #28a745); color: white;'; - break; - case 'warning': - typeStyles = USE_CUSTOM_STYLING - ? 'background: #ffc107; color: black;' - : 'background: var(--warning, #ffc107); color: black;'; - break; - case 'info': - typeStyles = USE_CUSTOM_STYLING - ? 'background: #17a2b8; color: white;' - : 'background: var(--info, #17a2b8); color: white;'; - break; - case 'close': - typeStyles = USE_CUSTOM_STYLING - ? 'background: none; border: none; color: currentColor; font-size: 24px; padding: 4px 8px;' - : 'float: right; background: none; border: none; font-size: 1.2em; padding: 4px 8px; border-radius: 4px;'; - break; - default: - typeStyles = USE_CUSTOM_STYLING - ? 'background: #6c757d; color: white;' - : ''; - } - - button.style.cssText = baseStyles + typeStyles + additionalStyles; - return button; -} - -// Create standardized chat message element -function createChatMessage(content, sender = 'user', isHTML = false) { - const messageDiv = document.createElement('div'); - const bgColor = sender === 'user' ? '#e3f2fd' : '#f3e5f5'; - const label = sender === 'user' ? 'You' : 'AI'; - - messageDiv.style.cssText = `margin-bottom: 10px; padding: 8px; background: ${bgColor}; border-radius: 4px;`; - - if (isHTML) { - messageDiv.innerHTML = `${label}: ${content}`; - } else { - messageDiv.innerHTML = `${label}: ${content}`; - } - - return messageDiv; -} - -// Create standardized TTS control set (play/pause, regenerate, download) -function createTTSControls(textContent, voiceSelectElement, onAudioGenerated = null) { - const controlContainer = document.createElement('div'); - controlContainer.style.cssText = 'display: grid; grid-template-columns: auto auto auto; gap: 8px; margin: 10px 0; align-items: center;'; - - const playBtn = createStyledButton('🔊 Play', 'primary'); - const regenerateBtn = createStyledButton('🔄 Regenerate', 'secondary'); - const downloadBtn = createStyledButton('💾 Download', 'secondary'); - - regenerateBtn.style.display = 'none'; - downloadBtn.style.display = 'none'; - - let audio = null; - let audioBlob = null; - let currentVoice = voiceSelectElement.value; - - const generateTTS = async () => { - playBtn.textContent = 'Processing...'; - playBtn.disabled = true; - regenerateBtn.disabled = true; - - const selectedVoice = voiceSelectElement.value; - currentVoice = selectedVoice; - const result = await speakText(textContent, selectedVoice, 0.9); - audio = result.audio; - audioBlob = result.blob; - - playBtn.textContent = 'Pause'; - playBtn.disabled = false; - regenerateBtn.disabled = false; - regenerateBtn.style.display = 'inline-block'; - downloadBtn.style.display = 'inline-block'; - - if (onAudioGenerated) onAudioGenerated(audio, audioBlob); - audio.play(); - }; - - playBtn.onclick = async () => { - if (!audio) { - await generateTTS(); - } else { - if (audio.paused) { - audio.play(); - playBtn.textContent = 'Pause'; - } else { - audio.pause(); - playBtn.textContent = 'Play'; - } - } - }; - - regenerateBtn.onclick = async () => { - if (audio) audio.pause(); - audio = null; - audioBlob = null; - await generateTTS(); - }; - - downloadBtn.onclick = () => { - if (audioBlob) { - const a = document.createElement('a'); - a.href = URL.createObjectURL(audioBlob); - a.download = `tts-audio-${Date.now()}.mp3`; - a.click(); - } - }; - - // Watch for voice changes - voiceSelectElement.addEventListener('change', () => { - if (audio && currentVoice !== voiceSelectElement.value) { - regenerateBtn.style.display = 'inline-block'; - regenerateBtn.style.background = '#fffacd'; - } - }); - - controlContainer.appendChild(playBtn); - controlContainer.appendChild(regenerateBtn); - controlContainer.appendChild(downloadBtn); - - return { container: controlContainer, playBtn, regenerateBtn, downloadBtn }; -} - -// Create standardized voice selection dropdown -function createVoiceSelect(id = '', selectedVoice = 'alloy') { - const select = document.createElement('select'); - if (id) select.id = id; - - const voices = ['alloy', 'echo', 'fable', 'onyx', 'nova', 'shimmer']; - voices.forEach(voice => { - const option = document.createElement('option'); - option.value = voice; - option.textContent = voice.charAt(0).toUpperCase() + voice.slice(1); - if (voice === selectedVoice) option.selected = true; - select.appendChild(option); - }); - - return select; -} - -// Create standardized modal header with close button -function createModalHeader(title, onClose) { - const header = document.createElement('header'); - if (USE_CUSTOM_STYLING) { - header.style.cssText = ` - background: linear-gradient(45deg, #667eea 0%, #764ba2 100%); - color: white; - padding: 16px 20px; - margin: -1em -1em 1em -1em; - display: flex; - justify-content: space-between; - align-items: center; - `; - } - - const h1 = document.createElement('h1'); - h1.textContent = title; - if (USE_CUSTOM_STYLING) { - h1.style.cssText = 'margin: 0; font-family: "ChunkFiveRegular", monospace; font-size: 20px;'; - } - - const closeButton = createStyledButton('X', 'close'); - closeButton.onclick = onClose; - - header.appendChild(h1); - header.appendChild(closeButton); - - return header; -} - -// Function to open TTS modal -function openTTSModal() { - // Check if Hermes modal is open to set appropriate z-index - const hermesModal = document.getElementById('hermes-modal'); - const zIndex = hermesModal ? '2001' : '1001'; - - const modal = document.createElement('dialog'); - modal.open = true; - modal.style.zIndex = zIndex; - modal.style.position = 'fixed'; - - if (USE_CUSTOM_STYLING) { - modal.style.cssText += ` - max-width: 720px; - border: none; - border-radius: 8px; - box-shadow: 0 4px 8px rgba(0,0,0,0.2); - background: white; - `; - } - - const article = document.createElement('article'); - modal.appendChild(article); - - const header = document.createElement('header'); - if (USE_CUSTOM_STYLING) { - header.style.cssText = ` - background: linear-gradient(45deg, #667eea 0%, #764ba2 100%); - color: white; - padding: 16px 20px; - margin: -1em -1em 1em -1em; - display: flex; - justify-content: space-between; - align-items: center; - `; - } else { - header.style.cssText = ` - display: flex; - justify-content: space-between; - align-items: center; - padding: 16px 20px; - margin: -1em -1em 1em -1em; - `; - } - article.appendChild(header); - - const h1 = document.createElement('h1'); - h1.textContent = 'TTS Anything!'; - if (USE_CUSTOM_STYLING) { - h1.style.cssText = ` - margin: 0; - font-family: 'ChunkFiveRegular', monospace; - font-size: 20px; - `; - } - header.appendChild(h1); - - const closeButton = createStyledButton('X', 'close'); - closeButton.onclick = () => document.body.removeChild(modal); - header.appendChild(closeButton); - - const textArea = document.createElement('textarea'); - textArea.style.width = '100%'; - textArea.style.height = '240px'; - textArea.placeholder = 'text-to-speech: write any message & have the artificial intelligence speak it!'; - article.appendChild(textArea); - - // Voice selection - const voices = ['alloy', 'echo', 'fable', 'onyx', 'nova', 'shimmer']; - const voiceSelection = document.createElement('div'); - if (USE_CUSTOM_STYLING) { - voiceSelection.style.cssText = ` - display: flex; - gap: 10px; - margin: 16px 0; - `; - } - voices.forEach((voice) => { - const label = document.createElement('label'); - if (USE_CUSTOM_STYLING) { - label.style.cssText = ` - display: flex; - align-items: center; - gap: 4px; - cursor: pointer; - `; - } else { - label.style.display = 'inline-block'; - label.style.marginRight = '10px'; - } - const radio = document.createElement('input'); - radio.type = 'radio'; - radio.name = 'tts-voice'; - radio.value = voice; - if (voice === 'alloy') radio.checked = true; - label.appendChild(radio); - label.appendChild(document.createTextNode(voice)); - voiceSelection.appendChild(label); - }); - article.appendChild(voiceSelection); - - // Speed selection - const speedLabel = document.createElement('label'); - const speedValue = document.createElement('span'); - speedValue.textContent = '0.9'; - - speedLabel.textContent = `Speed: ${speedValue.textContent}`; - const speedSlider = document.createElement('input'); - speedSlider.type = 'range'; - speedSlider.min = '0.25'; - speedSlider.max = '4.0'; - speedSlider.step = '0.05'; - speedSlider.value = '0.9'; - - speedSlider.oninput = () => { - speedValue.textContent = speedSlider.value; - speedLabel.textContent = `Speed: ${speedSlider.value}`; - }; - - article.appendChild(speedLabel); - article.appendChild(speedSlider); - - const playButton = document.createElement('button'); - playButton.textContent = 'Play Text'; - if (USE_CUSTOM_STYLING) { - playButton.style.cssText = ` - padding: 12px 24px; - background: linear-gradient(45deg, #667eea 0%, #764ba2 100%); - color: white; - border: none; - border-radius: 8px; - cursor: pointer; - font-weight: bold; - margin: 16px 10px 0 0; - `; - } - - let ttsAudio = null; - let ttsBlob = null; - - playButton.onclick = async () => { - const currentText = textArea.value.trim(); - const selectedVoice = document.querySelector('input[name="tts-voice"]:checked').value; - const selectedSpeed = parseFloat(speedSlider.value); - - if (!currentText) return; - - if (!ttsAudio || lastTTSInput !== currentText) { - if (ttsAudio) ttsAudio.pause(); - playButton.textContent = 'Processing...'; - playButton.disabled = true; - lastTTSInput = currentText; - const result = await speakText(currentText, selectedVoice, selectedSpeed); - ttsAudio = result.audio; - ttsBlob = result.blob; - playButton.textContent = 'Pause Text'; - playButton.disabled = false; - - // Add download button if not exists - let downloadButton = article.querySelector('.download-btn'); - if (!downloadButton) { - downloadButton = document.createElement('button'); - downloadButton.className = 'download-btn'; - downloadButton.textContent = 'Download MP3'; - if (USE_CUSTOM_STYLING) { - downloadButton.style.cssText = ` - padding: 12px 24px; - background: #f5f5f5; - border: 1px solid #ccc; - border-radius: 8px; - cursor: pointer; - font-weight: bold; - margin: 16px 0 0 10px; - `; - } - downloadButton.onclick = () => { - const a = document.createElement('a'); - a.href = URL.createObjectURL(ttsBlob); - a.download = `tts-${Date.now()}.mp3`; - a.click(); - }; - article.appendChild(downloadButton); - } - } - - if (ttsAudio.paused) { - ttsAudio.play(); - playButton.textContent = 'Pause Text'; - } else { - ttsAudio.pause(); - playButton.textContent = 'Play Text'; - } - - ttsAudio.onended = () => { - playButton.textContent = 'Play Text'; - }; - }; - - article.appendChild(playButton); - document.body.appendChild(modal); -} - -// Helper function to integrate file upload response to modal -async function integrateMegafarceResponseToModal(response, chatBox) { - const content = response.content || response.result || "No content received."; - chatHistory.push({ role: "system", content: `Uploaded Context: ${content}` }); - - const systemMessage = document.createElement('p'); - systemMessage.innerHTML = `System: ${marked.parse(content)}`; - - systemMessage.querySelectorAll('pre code').forEach((block) => { - hljs.highlightElement(block); - }); - - chatBox.appendChild(systemMessage); - chatBox.scrollTop = chatBox.scrollHeight; -} - -// Helper function to scroll to chat box -function scrollToChatBox() { - const chatContainer = document.getElementById('chat-container'); - if (chatContainer) { - chatContainer.scrollIntoView({ behavior: 'smooth' }); - document.getElementById('user-input')?.focus(); - } -} - -// ------------------------- -// Class-based Initialization System -// ------------------------- - - - -// Configuration flag for showing floating button -const SHOW_FLOATING_BUTTON = window.UNCLOSEAI_SHOW_BUTTON !== false; - -// Initialize on page load -window.onload = () => { - console.log('uncloseai.js: window.onload event fired.'); - initializeChatInterface(); - createModelSelectionDropdown(); - addRefreshModelsButton(); - - // Only create floating button if not disabled - if (SHOW_FLOATING_BUTTON) { - console.log('uncloseai.js: SHOW_FLOATING_BUTTON is true, calling createFloatingAIButton()'); - createFloatingAIButton(); - } else { - console.log('uncloseai.js: SHOW_FLOATING_BUTTON is false, not creating floating button.'); - } - - // Initialize class-based elements - initializeUncloseaiElements(); -}; - -// Export functions to global scope -window.handleUserInput = handleUserInput; -window.readPage = readPageWithHermes;