GreenThreadDocs

AI Console is the customer-facing portal in front of a LiquidCompute install. It turns a private GreenThread cluster into something that looks and feels like OpenAI or ElevenLabs: customers sign in, discover models, mint API keys, try them in playgrounds, watch their usage — and integrate against an OpenAI-compatible /v1/* endpoint that proxies to the engine underneath.

It is optional — install it when you want to expose the platform to non-platform-team users (paying customers, partners, internal product teams) with proper auth, audit, and quota.

What's in the box

WorkloadRole
backendGo API. Two route families: /api/* for the React SPA (cookie auth) and /v1/* OpenAI-compatible endpoints (bearer token = user's API key, or session cookie). Embeds migrations; runs them on boot.
frontendnginx + React SPA. Login, dashboard, model catalog, chat / voice-chat / STT / TTS playgrounds, API Keys, Usage, Audit Log, Admin.
postgresExternal — the chart does not bundle one. Bring your own (CloudNativePG, RDS, the LiquidCompute Postgres, anything that speaks Postgres ≥ 14).

Key concepts

Users + admin

A single admin user is seeded on first boot from admin.defaultEmail / admin.defaultPassword (or an admin.existingSecret). All subsequent users are created via the Admin UI. Passwords are bcrypt-hashed; sessions live in Postgres so logins survive backend restarts.

API keys

Each user mints any number of API keys from the API Keys page. Keys are prefixed (sk-…) and shown once at creation. Requests to /v1/* authenticate with Authorization: Bearer sk-… and the key's owner is recorded in the audit log.

Playgrounds

Browser-side UIs that call the same /v1/* endpoints customers use. The chat playground hits /v1/chat/completions, voice-chat hits /v1/audio/{transcriptions,speech}, and so on. Streaming responses render token-by-token in the UI.

Usage + audit

Every /v1/* call is recorded (user, key, model, prompt + completion tokens, latency, status). The Usage page rolls these up by user / model / time. The Audit Log shows the raw event stream for incident review.

Install

AI Console needs an external Postgres URL and a LiquidCompute (or other OpenAI-compatible) upstream.

Pull credentials + Secrets

The chart and images live on licence.greenthread.ai (see Licensing › Pulling charts and images). If you already ran helm registry login for the engine install you can skip that step. The remaining setup:

kubectl create namespace ai-console

# 1. Image-pull Secret for the licence registry.
kubectl create secret docker-registry greenthread-registry \
  -n ai-console \
  --docker-server=licence.greenthread.ai \
  --docker-username=licence \
  --docker-password=<your-licence-token>

# 2. Postgres DSN.
kubectl create secret generic ai-console-db -n ai-console \
  --from-literal=url='postgres://aic:pw@postgres.db.svc.cluster.local:5432/ai_console?sslmode=disable'

# 3. Admin seed (read once on first boot, then ignored).
kubectl create secret generic ai-console-admin -n ai-console \
  --from-literal=email='admin@example.com' \
  --from-literal=password='<generate-a-password>'

Helm install

helm upgrade --install ai-console \
  oci://licence.greenthread.ai/greenthread/charts/ai-console \
  --namespace ai-console \
  --set 'imagePullSecrets[0].name=greenthread-registry' \
  --set 'image.registry=licence.greenthread.ai/greenthread' \
  --set 'image.repository=ai-console' \
  --set 'database.existingSecret.name=ai-console-db' \
  --set 'database.existingSecret.key=url' \
  --set 'admin.existingSecret.name=ai-console-admin' \
  --set 'upstream.baseUrl=http://liquidcompute-lc-proxy.liquidcompute-system:8080' \
  --set 'ingress.enabled=true' \
  --set 'ingress.className=nginx' \
  --set 'ingress.host=platform.example.com' \
  --set 'ingress.certManager.enabled=true' \
  --set 'ingress.certManager.clusterIssuer=letsencrypt-production' \
  --set 'apiIngress.enabled=true' \
  --set 'apiIngress.className=nginx' \
  --set 'apiIngress.host=api.example.com' \
  --set 'apiIngress.certManager.enabled=true' \
  --set 'apiIngress.certManager.clusterIssuer=letsencrypt-production'

The image.registry + image.repository overrides combine into licence.greenthread.ai/greenthread/ai-console/{backend,frontend}:tag — the AI Console chart appends /backend and /frontend for the two workloads.

ValuePurpose
database.existingSecret.{name,key}Secret + key whose value is the full postgres://… URL. Alternative: database.url inline (chart creates the Secret).
admin.existingSecret.{name,emailKey,passwordKey}First-boot admin seed. After the first row in users, this is ignored.
upstream.baseUrlURL the backend proxies /v1/* to. Usually lc-proxy in-cluster.
upstream.apiKeyOptional. Bearer key the backend sends upstream — leave empty when lc-proxy doesn't require auth.
ingress.hostConsole hostname (platform.example.com).
apiIngress.hostOptional second hostname that exposes only /v1/* on its own cert — gives external SDK users a clean URL (api.example.com).
Two hostnames, one backend

The split between ingress (the console) and apiIngress (just /v1/*) is for UX, not security — both hit the same backend pod. Customers point their SDKs at api.example.com and never see the console domain.

Verify

$ kubectl get pods -n ai-console
NAME                                   READY   STATUS    AGE
ai-console-backend-68ff688d6c-sjw98    1/1     Running   24h
ai-console-frontend-58c58645c6-zq8sw   1/1     Running   25h

$ kubectl get ingress -n ai-console
NAME                  CLASS   HOSTS                     ADDRESS         PORTS     AGE
ai-console            nginx   platform.example.com      203.0.113.10    80, 443   25h
ai-console-api        nginx   api.example.com           203.0.113.10    80, 443   25h

# Health check
curl -k https://platform.example.com/api/healthz
# → {"ok":true}

Using the API

Once a user mints an API key in the console, they can hit /v1/* exactly like OpenAI:

from openai import OpenAI

client = OpenAI(
    base_url="https://api.example.com/v1",
    api_key="sk-...",
)

response = client.chat.completions.create(
    model="meta-llama/Llama-3.1-8B-Instruct",
    messages=[{"role": "user", "content": "Hello!"}],
)
print(response.choices[0].message.content)

The model field is the upstream HuggingFace ID, exactly like OpenAI's API. Discover what's available with GET /v1/models. The backend authenticates the key, records the request, and proxies upstream to lc-proxy.

Audio

# Speech to text
with open("recording.wav", "rb") as f:
    transcript = client.audio.transcriptions.create(
        model="openai/whisper-large-v3-turbo",
        file=f,
    )

# Text to speech
audio = client.audio.speech.create(
    model="fishaudio/s2-pro",
    voice="alloy",
    input="Hello, world!",
)

Where to next