diff --git a/.github/workflows/static.yml b/.github/workflows/static.yml index 5a5a01a..fc9e92e 100644 --- a/.github/workflows/static.yml +++ b/.github/workflows/static.yml @@ -24,18 +24,53 @@ jobs: - name: Checkout uses: actions/checkout@v4 - # Injects an inline " - if grep -qi "window.POLLINATIONS_TOKEN" index.html; then - sed -i "s||$INJ|I" index.html - else - awk -v inj="$INJ" 'BEGIN{IGNORECASE=1} /<\/head>/{print inj} {print}' index.html > index.html.tmp - mv index.html.tmp index.html - fi + python - <<'PY' + import json + import os + import re + import sys + + path = "index.html" + secrets = { + "POLLINATIONS_TOKEN": os.environ.get("POLLINATIONS_TOKEN", "").strip(), + "TWILIO_ACCOUNT_SID": os.environ.get("TWILIO_ACCOUNT_SID", "").strip(), + "TWILIO_AUTH_TOKEN": os.environ.get("TWILIO_AUTH_TOKEN", "").strip(), + "TWILIO_PHONE_NUMBER": os.environ.get("TWILIO_PHONE_NUMBER", "").strip(), + } + + payload = {key: value for key, value in secrets.items() if value} + if not payload: + sys.exit(0) + + script = " " + + with open(path, encoding="utf-8") as handle: + html = handle.read() + + script_pattern = re.compile(r"", re.IGNORECASE | re.DOTALL) + legacy_pattern = re.compile(r"", re.IGNORECASE | re.DOTALL) + + if script_pattern.search(html): + html = script_pattern.sub(script, html) + elif legacy_pattern.search(html): + html = legacy_pattern.sub(script, html) + else: + html = re.sub(r"", script + "\n ", html, count=1, flags=re.IGNORECASE) + + with open(path, "w", encoding="utf-8") as handle: + handle.write(html) + PY - name: Setup Pages uses: actions/configure-pages@v5 diff --git a/chat-core.js b/chat-core.js index c601132..98b0ef8 100644 --- a/chat-core.js +++ b/chat-core.js @@ -1,17 +1,29 @@ // ===== network.js ===== -async function pollinationsFetch(url, options = {}, { timeoutMs = 45000 } = {}) { - const controller = new AbortController(); - const timer = setTimeout(() => controller.abort(new DOMException('timeout', 'AbortError')), timeoutMs); - try { +async function pollinationsFetch(url, options = {}, { timeoutMs = 45000 } = {}) { + const controller = new AbortController(); + const timer = setTimeout(() => controller.abort(new DOMException('timeout', 'AbortError')), timeoutMs); + try { + const init = { ...options, signal: controller.signal, cache: 'no-store' }; + const token = typeof window !== 'undefined' && typeof window.POLLINATIONS_TOKEN === 'string' + ? window.POLLINATIONS_TOKEN.trim() + : ''; + if (token) { + const headers = new Headers(init.headers || {}); + if (!headers.has('Authorization')) { + headers.set('Authorization', `Bearer ${token}`); + } + init.headers = headers; + } + const res = await fetch( url, - { ...options, signal: controller.signal, cache: 'no-store' } // fixed: spread options + init ); - if (!res.ok) throw new Error(`HTTP ${res.status}`); - return res; - } finally { - clearTimeout(timer); - } + if (!res.ok) throw new Error(`HTTP ${res.status}`); + return res; + } finally { + clearTimeout(timer); + } } window.pollinationsFetch = pollinationsFetch; diff --git a/index.html b/index.html index 1838e1b..60004bd 100644 --- a/index.html +++ b/index.html @@ -226,6 +226,24 @@

Configure your Twilio voice bridge server and start a phone call where Unity speaks using Pollinations.

+
{ const twilioVoiceSelect = document.getElementById("twilio-voice-select"); const twilioCallBtn = document.getElementById("twilio-start-call-btn"); const twilioStatusEl = document.getElementById("twilio-call-status"); + const twilioSecretsContainer = document.getElementById("twilio-secret-credentials"); + const twilioAccountSidDisplay = document.getElementById("twilio-account-sid-display"); + const twilioAuthTokenDisplay = document.getElementById("twilio-auth-token-display"); + const twilioPhoneDisplay = document.getElementById("twilio-phone-display"); const twilioStorageKeys = { server: "unityTwilioServerUrl", @@ -44,6 +48,40 @@ document.addEventListener("DOMContentLoaded", () => { voice: "unityTwilioVoice" }; + const deploymentSecrets = { + accountSid: typeof window.TWILIO_ACCOUNT_SID === "string" ? window.TWILIO_ACCOUNT_SID.trim() : "", + authToken: typeof window.TWILIO_AUTH_TOKEN === "string" ? window.TWILIO_AUTH_TOKEN.trim() : "", + phoneNumber: typeof window.TWILIO_PHONE_NUMBER === "string" ? window.TWILIO_PHONE_NUMBER.trim() : "" + }; + + function applySecretToField(field, value) { + if (!field) return false; + const normalized = typeof value === "string" ? value.trim() : ""; + const wrapper = field.closest(".form-group") || field.parentElement; + if (normalized) { + field.value = normalized; + if (wrapper && wrapper.classList.contains("hidden")) { + wrapper.classList.remove("hidden"); + } + return true; + } + field.value = ""; + if (wrapper && !wrapper.classList.contains("hidden")) { + wrapper.classList.add("hidden"); + } + return false; + } + + if (twilioSecretsContainer) { + const hasSecrets = [ + applySecretToField(twilioAccountSidDisplay, deploymentSecrets.accountSid), + applySecretToField(twilioAuthTokenDisplay, deploymentSecrets.authToken), + applySecretToField(twilioPhoneDisplay, deploymentSecrets.phoneNumber) + ].some(Boolean); + + twilioSecretsContainer.classList.toggle("hidden", !hasSecrets); + } + function sanitizeServerUrl(value) { if (!value) return ""; return value.trim().replace(/\/+$/, ""); @@ -86,6 +124,10 @@ document.addEventListener("DOMContentLoaded", () => { if (storedPhone) { twilioPhoneInput.value = storedPhone; } + if (!twilioPhoneInput.value && deploymentSecrets.phoneNumber) { + twilioPhoneInput.value = deploymentSecrets.phoneNumber; + persistValue(twilioStorageKeys.phone, deploymentSecrets.phoneNumber); + } twilioPhoneInput.addEventListener("change", () => { persistValue(twilioStorageKeys.phone, twilioPhoneInput.value); });