Select text and use the AI toolbar, or open the AI chat sidebar.
+ + + +`); +}); + +app.listen(PORT, () => { + console.log('Editor: http://localhost:' + PORT); + console.log('Token API: http://localhost:' + PORT + '/api/ai-token'); + console.log('AI Service: ' + AI_SERVICE_URL); +}); +---- + +=== Install and run + +[source,bash] +---- +npm install +npm start +---- + +=== Open the demo + +Open *http://localhost:3000* in a browser. The editor loads with the AI toolbar. Select text and try the AI features. Responses stream in real time from the chosen large language model (LLM) provider, processed entirely within the local infrastructure. + +The TinyMCE AI on-premises service is now running. + +== Verifying the installation + +After completing the quick start, exercise the pipeline end-to-end from the command line. + +=== Step 1: Health check + +Confirms the AI service container is running and connected to the database and Redis. + +[source,bash] +---- +curl http://localhost:8000/health +---- + +Expected: + +[source,json] +---- +{"serviceName":"on-premises-http","uptime":12.345} +---- + +=== Step 2: Generate a token + +Confirms the token server can sign a valid JWT using the API Secret and Environment ID. + +[source,bash] +---- +curl -s -X POST http://localhost:3000/api/ai-token | python3 -m json.tool +---- + +Expected: + +[source,json] +---- +{ + "token": "eyJhbGciOiJIUzI1NiIs..." +} +---- + +=== Step 3: Create a conversation and send a message + +Confirms the full chain: JWT verification, permissions, environment registration, LLM provider authentication, and SSE streaming. + +[source,bash] +---- +TOKEN=$(curl -s -X POST http://localhost:3000/api/ai-token | python3 -c "import sys,json;print(json.load(sys.stdin)['token'])") + +curl -s -X POST http://localhost:8000/v1/conversations \ + -H "Authorization: Bearer $TOKEN" \ + -H "Content-Type: application/json" \ + -d '{"id":"verify-1","title":"Verification"}' +---- + +NOTE: The command below uses the built-in `agent-1` model. If `MODELS` has been explicitly configured, replace `agent-1` with the `id` of one of the configured models. See xref:tinymceai-on-premises-providers.adoc#models-required[Defining the model list]. + +[source,bash] +---- +curl -s -N -X POST http://localhost:8000/v1/conversations/verify-1/messages \ + -H "Authorization: Bearer $TOKEN" \ + -H "Content-Type: application/json" \ + -d '{"prompt":"Say hello in five words.","model":"agent-1"}' +---- + +The message endpoint returns a Server-Sent Events stream: + +[source,text] +---- +event: message-metadata +data: {"messageId":"abc123"} + +event: text-delta +data: {"textDelta":"Hello "} + +event: text-delta +data: {"textDelta":"there, "} + +event: text-delta +data: {"textDelta":"friend!"} + +event: done +data: {} +---- + +If the stream emits `event: error`, inspect the `data` payload. Provider errors (invalid API key, IAM denial, model unavailable) ride inside the Server-Sent Events (SSE) response. The HTTP status stays 200. See the xref:tinymceai-on-premises-troubleshooting.adoc[LLM provider errors] section in the Troubleshooting guide for details. + +A successful round-trip confirms: container health, database connectivity, Redis connectivity, JWT signing, JWT verification, permissions checking, environment registration, LLM provider authentication, and SSE streaming. If problems persist after these checks, focus on the editor configuration next. + +== Updating configuration + +IMPORTANT: After changing `.env` values, containers must be recreated to pick up new environment variables. A simple restart (`docker restart` or `docker compose restart`) preserves the old values. + +[source,bash] +---- +# Recreate the data layer (MySQL, Redis): +docker compose up -d --force-recreate + +# Recreate the standalone AI service: +docker stop ai-service && docker rm ai-service +# Then re-run the launch script from "Launch the AI service" above. +---- + +For Kubernetes, update the Secret and trigger a rollout restart: + +[source,bash] +---- +kubectl rollout restart deployment/ai-service -n tinymce-ai +---- + +== Stopping and cleaning up + +[source,bash] +---- +# Stop the AI service (standalone Docker) +docker stop ai-service && docker rm ai-service + +# Stop the Docker Compose stack +docker compose down + +# Remove all data including volumes (destructive) +docker compose down -v +---- + +For Kubernetes, scale the deployment to zero or delete it. Persistent volumes for the database are retained unless explicitly deleted. + +[source,bash] +---- +kubectl delete deployment ai-service -n tinymce-ai +---- + +== Next steps + +The quick start validates the stack end-to-end on a single machine. To deploy for production, work through each guide in order: + +. xref:tinymceai-on-premises-database.adoc[Database, Redis, and storage]: provision managed databases, configure TLS, and set up production-grade file storage. +. xref:tinymceai-on-premises-providers.adoc[LLM providers]: configure explicit model catalogs and multi-provider routing. +. xref:tinymceai-on-premises-jwt.adoc[JWT authentication]: build the production token endpoint with proper permissions and multi-tenant isolation. +. xref:tinymceai-on-premises-frameworks.adoc[TinyMCE integration]: wire the editor to the production AI service with CORS and CSP. +. xref:tinymceai-on-premises-production.adoc[Production deployment]: deploy to Kubernetes or ECS with TLS, scaling, and observability. diff --git a/modules/ROOT/pages/tinymceai-on-premises-jwt.adoc b/modules/ROOT/pages/tinymceai-on-premises-jwt.adoc new file mode 100644 index 0000000000..48ab8390cb --- /dev/null +++ b/modules/ROOT/pages/tinymceai-on-premises-jwt.adoc @@ -0,0 +1,990 @@ += JWT authentication for the on-premises AI service +:navtitle: JWT authentication +:description: JWT authentication for the TinyMCE AI on-premises service using HS256 symmetric signing +:keywords: AI, on-premises, JWT, authentication, HS256, multi-tenant + +This page covers *authentication between the application back end and the AI service*. Every request from the editor to the AI service carries a signed JWT — this is how the service identifies users, enforces permissions, and isolates conversations. The token endpoint runs in the application back end; the editor calls it automatically through the `tinymceai_token_provider` callback configured in xref:tinymceai-on-premises-frameworks.adoc[TinyMCE integration]. + +Before configuring JWT, complete the xref:tinymceai-on-premises-database.adoc[data layer] and xref:tinymceai-on-premises-providers.adoc[LLM provider] setup. After JWT, proceed to xref:tinymceai-on-premises-frameworks.adoc[TinyMCE integration] to wire the editor to the token endpoint. + +The on-premises AI service uses *HS256* (HMAC-SHA256, symmetric shared secret) for JSON Web Token (JWT) authentication. This is different from the Tiny Cloud AI service, which uses RS256. + +[WARNING] +-- +Do not follow the xref:tinymceai-jwt-authentication-intro.adoc[Cloud JWT guide] for on-premises deployments. The on-premises verifier silently rejects RS256-signed tokens with `invalid-jwt-signature` and no indication that the algorithm is wrong. +-- + + + +== End-to-end flow + +[.text-center] +image::tinymceai-on-premises/jwt-authentication-fig-1.svg[alt="JWT token exchange sequence between user application back end and AI service with error branches",width=100%] + +The shared secret (API Secret) never leaves the application back end. The editor only ever sees signed tokens, and the AI service only ever sees signed tokens; neither has direct access to the secret. + + + +== Signing model + +[cols=",",options="header",] +|=== +|Property |Value +|Algorithm |`HS256` (HMAC-SHA256) +|Key type |Symmetric shared secret +|Key source |*API Secret* generated for an access key inside an environment through the Management Panel +|Header format |`Authorization: BearerExample page body
" } +---- + +==== Scraper implementation example (Playwright) + +[source,javascript] +---- +// scraper-service.js +const { chromium } = require('playwright'); +const express = require('express'); +const app = express(); +app.use(express.json()); + +app.post('/scrape', async (req, res) => { + const browser = await chromium.launch(); + const page = await browser.newPage(); + await page.goto(req.body.url, { waitUntil: 'networkidle' }); + const content = await page.content(); + await browser.close(); + res.json({ type: 'text/html', data: content }); +}); + +app.listen(4000); +---- + +=== Web search endpoint contract + +[cols="1,2",options="header"] +|=== +|Direction |Payload +|Request |JSON object with a `query` field (search string). +|Response |JSON object with a `results` array; each item includes `url`, `text`, `title`, and optional `author`, `publishedAt`, and `favicon`. +|=== + +.Request body +[source,json] +---- +{ "query": "search string" } +---- + +.Response body +[source,json] +---- +{ + "results": [ + { + "url": "https://example.com/article", + "text": "Content snippet", + "title": "Article Title", + "author": "Author", + "publishedAt": "2026-04-30T10:00:00Z", + "favicon": "https://example.com/favicon.ico" + } + ] +} +---- + +==== Search implementation example (SerpAPI) + +[source,javascript] +---- +// search-service.js +const express = require('express'); +const app = express(); +app.use(express.json()); + +app.post('/search', async (req, res) => { + const response = await fetch( + `https://serpapi.com/search.json?q=${encodeURIComponent(req.body.query)}&api_key=${process.env.SERP_API_KEY}` + ); + const data = await response.json(); + const results = (data.organic_results || []).slice(0, 5).map(r => ({ + url: r.link, + title: r.title, + text: r.snippet + })); + res.json({ results }); +}); + +app.listen(4001); +---- + + + +== See also + +* xref:tinymceai-on-premises-providers.adoc[LLM providers]: provider configuration and the `MODELS` catalog +* xref:tinymceai-on-premises-reference.adoc[Reference]: full environment variable reference including `MCP_SERVERS`, `WEBRESOURCES_*`, and `WEBSEARCH_*` +* xref:tinymceai-on-premises-troubleshooting.adoc[Troubleshooting]: general troubleshooting diff --git a/modules/ROOT/pages/tinymceai-on-premises-production.adoc b/modules/ROOT/pages/tinymceai-on-premises-production.adoc new file mode 100644 index 0000000000..c42db02149 --- /dev/null +++ b/modules/ROOT/pages/tinymceai-on-premises-production.adoc @@ -0,0 +1,618 @@ += TinyMCE AI on-premises: Production deployment guide +:navtitle: Production deployment +:description: Production deployment guide for the TinyMCE AI on-premises service +:keywords: AI, on-premises, production, Kubernetes, ECS, scaling + +This guide assumes a running Kubernetes cluster, ECS cluster, or Docker/Podman host with the relevant CLI tools (`kubectl`, `aws`, `docker`) configured. For cluster setup, refer to the platform documentation. + +== Production readiness checklist + +Before deploying to production, confirm each item: + +. xref:tinymceai-on-premises-database.adoc[Database and Redis]: provisioned, accessible from the AI service, schema created (PostgreSQL). +. xref:tinymceai-on-premises-providers.adoc[LLM providers]: `PROVIDERS` configured and verified; `MODELS` defined for the target provider(s). +. xref:tinymceai-on-premises-jwt.adoc[JWT authentication]: token endpoint deployed, signing with HS256 and the correct API Secret. +. xref:tinymceai-on-premises-frameworks.adoc[TinyMCE integration]: `tinymceai_service_url` and `tinymceai_token_provider` configured; `ALLOWED_ORIGINS` set on the AI service. +. Container image pulled and registry credentials stored as a secret. +. Reverse proxy with TLS termination and `proxy_buffering off` for SSE. +. Environment and access key created through the Management Panel. + +== Architecture overview + +[.text-center] +image::tinymceai-on-premises/complete-guide-fig-1.svg[alt="Enterprise architecture showing browser with TinyMCE token endpoint AI service replicas database Redis LLM providers and observability",width=100%] + +The AI service is stateless, persists all state to MySQL/PostgreSQL and Redis, and scales horizontally behind a load balancer. + + + +== TLS / HTTPS + +The AI service does not terminate Transport Layer Security (TLS). Place a reverse proxy in front. + +=== Nginx example + +[source,nginx] +---- +server { + listen 443 ssl; + server_name ai.example.com; + + ssl_certificate /etc/ssl/certs/ai.example.com.pem; + ssl_certificate_key /etc/ssl/private/ai.example.com.key; + + location / { + proxy_pass http://ai-service:8000; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + # SSE streaming support + proxy_buffering off; + proxy_cache off; + proxy_read_timeout 300s; + } +} +---- + +[IMPORTANT] +-- +Server-Sent Events (SSE) streaming requires https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_buffering[`proxy_buffering off`]. Without it, AI responses appear to hang until the entire response is generated. +-- + +=== AWS ALB + +* Target group: HTTP on port 8000 +* Health check path: `/health` +* Idle timeout: 300 seconds (for long AI responses) +* Stickiness: not required (service is stateless) + + + +== Horizontal scaling + +The AI service is stateless. All persistent state lives in the SQL database, Redis, and the file-storage back end. Any number of replicas can run behind a load balancer. All replicas must share identical environment variable configuration. On first boot or after an image upgrade, start a single replica and wait for it to become healthy before scaling up (see <