Example page body
" } +---- + +==== Scraper 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 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); +---- + +NOTE: A model must include `capabilities.webSearch: true` in its `MODELS` entry to expose the web search toggle. + +*Example prompts:* + +* "Research the latest trends in AI governance and write a summary" +* "Read this URL and rewrite the key points for the target audience: pass:[https://…]" + + + +For production deployment guidance including Kubernetes manifests, scaling, security hardening, rate limiting, and observability, see xref:tinymceai-on-premises-production.adoc[Production deployment]. + +For common errors and debugging steps, see xref:tinymceai-on-premises-troubleshooting.adoc[Troubleshooting]. diff --git a/modules/ROOT/pages/tinymceai-on-premises-database.adoc b/modules/ROOT/pages/tinymceai-on-premises-database.adoc new file mode 100644 index 0000000000..215dd1759e --- /dev/null +++ b/modules/ROOT/pages/tinymceai-on-premises-database.adoc @@ -0,0 +1,668 @@ += Database, Redis, and infrastructure setup +:navtitle: Database, Redis, and storage +:description: Database, Redis, and file storage setup for the TinyMCE AI On-Premises service +:keywords: AI, on-premises, database, MySQL, PostgreSQL, Redis, Docker, Podman, file storage, S3, Azure Blob + +This page covers the data layer: the SQL database, Redis, and file storage. +For container runtimes, reverse proxies, Transport Layer Security (TLS), Kubernetes, and ECS deployment, see the xref:tinymceai-on-premises-production.adoc[Production deployment guide]. + +== Supported versions + +[cols="1,1,1,2",options="header"] +|=== +|Component |Minimum |Recommended |Notes + +|MySQL +|8.0 +|8.0.x (latest patch) +|Pin to `mysql:8.0`. See <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. + +[source,bash] +---- +# 1. Health check +curl http://localhost:8000/health +---- + +Expected: + +[source,json] +---- +{"serviceName":"on-premises-http","uptime":12.345} +---- + +[source,bash] +---- +# 2. Generate a token +curl -s -X POST http://localhost:3000/api/ai-token | python3 -m json.tool +---- + +Expected: + +[source,json] +---- +{ + "token": "eyJhbGciOiJIUzI1NiIs..." +} +---- + +[source,bash] +---- +# 3. Create a conversation and send a message +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"}' + +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: `docker compose restart` after `.env` changes silently keeps the old environment values. The restart preserves the container and does not re-read `.env`. Always use `docker compose up -d --force-recreate` instead. + +[source,bash] +---- +docker compose up -d --force-recreate +# Or recreate only the AI service: +docker compose up -d --force-recreate ai-service +---- + +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 +---- 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..7c88f42535 --- /dev/null +++ b/modules/ROOT/pages/tinymceai-on-premises-jwt.adoc @@ -0,0 +1,911 @@ += 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 + +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: Bearer