Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 46 additions & 11 deletions .github/workflows/static.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,18 +24,53 @@ jobs:
- name: Checkout
uses: actions/checkout@v4

# Injects an inline <script> that sets window.POLLINATIONS_TOKEN
- name: Inject Pollinations token into index.html
if: ${{ secrets.POLLINATIONS_TOKEN != '' }}
# Inject deployment secrets as window-scoped variables
- name: Inject deployment secrets into index.html
if: ${{ secrets.POLLINATIONS_TOKEN != '' || secrets.TWILIO_ACCOUNT_SID != '' || secrets.TWILIO_AUTH_TOKEN != '' || secrets.TWILIO_PHONE_NUMBER != '' }}
env:
POLLINATIONS_TOKEN: ${{ secrets.POLLINATIONS_TOKEN }}
TWILIO_ACCOUNT_SID: ${{ secrets.TWILIO_ACCOUNT_SID }}
TWILIO_AUTH_TOKEN: ${{ secrets.TWILIO_AUTH_TOKEN }}
TWILIO_PHONE_NUMBER: ${{ secrets.TWILIO_PHONE_NUMBER }}
run: |
set -e
INJ="<script>window.POLLINATIONS_TOKEN='${{ secrets.POLLINATIONS_TOKEN }}';</script>"
if grep -qi "window.POLLINATIONS_TOKEN" index.html; then
sed -i "s|<script>window.POLLINATIONS_TOKEN.*</script>|$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 = " <script id=\"deployment-secrets\">" + "".join(
f"window.{key}={json.dumps(value)};" for key, value in payload.items()
) + "</script>"

with open(path, encoding="utf-8") as handle:
html = handle.read()

script_pattern = re.compile(r"<script id=\"deployment-secrets\">.*?</script>", re.IGNORECASE | re.DOTALL)
legacy_pattern = re.compile(r"<script>window\.POLLINATIONS_TOKEN.*?</script>", 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"</head>", script + "\n </head>", 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
Expand Down
32 changes: 22 additions & 10 deletions chat-core.js
Original file line number Diff line number Diff line change
@@ -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;

Expand Down
18 changes: 18 additions & 0 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,24 @@ <h4 class="twilio-call-heading">
<p class="twilio-call-description">
Configure your Twilio voice bridge server and start a phone call where Unity speaks using Pollinations.
</p>
<div id="twilio-secret-credentials" class="twilio-secret-credentials hidden mb-3">
<div class="alert alert-secondary d-flex align-items-center gap-2 mb-3" role="alert">
<i class="fas fa-key"></i>
<span>These Twilio credentials were loaded from repository secrets.</span>
</div>
<div class="form-group mb-3">
<label for="twilio-account-sid-display" class="form-label">Twilio Account SID</label>
<input id="twilio-account-sid-display" type="text" class="form-control" readonly />
</div>
<div class="form-group mb-3">
<label for="twilio-auth-token-display" class="form-label">Twilio Auth Token</label>
<input id="twilio-auth-token-display" type="text" class="form-control" readonly />
</div>
<div class="form-group mb-0">
<label for="twilio-phone-display" class="form-label">Twilio Phone Number</label>
<input id="twilio-phone-display" type="text" class="form-control" readonly />
</div>
</div>
<div class="form-group mb-3">
<label for="twilio-server-url" class="form-label">Voice bridge URL</label>
<input
Expand Down
42 changes: 42 additions & 0 deletions ui.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ document.addEventListener("DOMContentLoaded", () => {
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",
Expand All @@ -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(/\/+$/, "");
Expand Down Expand Up @@ -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);
});
Expand Down