Skip to content
Closed
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
48 changes: 48 additions & 0 deletions -scripts/render-mermaid.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
#!/usr/bin/env bash
#
# Re-renders all .mmd Mermaid sources to .svg in the on-premises images folder.
#
# Usage (from repo root):
# ./-scripts/render-mermaid.sh
#
# Requirements:
# Node.js (npx downloads @mermaid-js/mermaid-cli automatically)
#
set -euo pipefail

DIAGRAM_DIR="modules/ROOT/images/tinymceai-on-premises"
CONFIG_FILE=$(mktemp)

cat > "$CONFIG_FILE" << 'JSON'
{
"htmlLabels": false,
"flowchart": { "htmlLabels": false, "useMaxWidth": true },
"sequence": { "useMaxWidth": true },
"theme": "default"
}
JSON

trap 'rm -f "$CONFIG_FILE"' EXIT

count=0
for mmd in "$DIAGRAM_DIR"/*.mmd; do
[ -f "$mmd" ] || continue
svg="${mmd%.mmd}.svg"
name=$(basename "$mmd")
printf " Rendering %s\n" "$name"
npx -y @mermaid-js/mermaid-cli -i "$mmd" -o "$svg" \
-c "$CONFIG_FILE" --backgroundColor white 2>/dev/null

# Mermaid outputs width="100%" which has no intrinsic size in <img> tags.
# Replace with the actual pixel width from the viewBox so browsers can
# calculate the correct aspect ratio when the page scales the image.
vb_width=$(grep -o 'viewBox="[^"]*"' "$svg" | head -1 | awk -F'[ "]' '{print $4}')
if [ -n "$vb_width" ]; then
vb_int=$(printf "%.0f" "$vb_width")
perl -i -pe "s/width=\"100%\"/width=\"${vb_int}\"/" "$svg"
fi

count=$((count + 1))
done

printf "\nRendered %d diagrams in %s\n" "$count" "$DIAGRAM_DIR"
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
flowchart LR
Editor[TinyMCE editor] <-->|chat / quick actions| AI[AI Service]
AI <-->|MCP tools/call| MCP[MCP Server<br>knowledge-hub]
MCP <-->|read| KB[Confluence ·<br>Notion ·<br>GitBook ·<br>internal wiki]
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
flowchart LR
subgraph Tenants[Your SaaS customers]
CA[Customer A users]
CB[Customer B users]
CC[Customer C users]
end
subgraph AISvc[Single AI service deployment]
EA[Environment A<br>access keys A<br>isolated conversations]
EB[Environment B<br>access keys B<br>isolated conversations]
EC[Environment C<br>access keys C<br>isolated conversations]
end
CA --> EA --> OpenAI[OpenAI]
CB --> EB --> Anthropic[Anthropic]
CC --> EC --> Azure[Azure OpenAI]
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
flowchart LR
Lawyer[TinyMCE editor<br>used by lawyer] <--> AI[AI Service]
AI -->|tools/call| MCP1[MCP: contract-db]
AI -->|tools/call| MCP2[MCP: compliance-checker]
AI -->|tools/call| MCP3[MCP: precedent-search]
MCP1 --> ContractDB[(Contract clause<br>repository)]
MCP2 --> ComplianceRules[(Regulatory<br>rule sets)]
MCP3 --> PrecedentIdx[(Precedent<br>search index)]
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
flowchart TB
Browser["Browser<br>TinyMCE editor + tinymceai plugin"]
TokenEP["Your token endpoint<br>signs HS256 JWTs"]
Browser -->|"fetch JWT"| TokenEP
Browser -->|"HTTPS + Bearer JWT"| LB

subgraph App["Application layer (stateless, N replicas)"]
LB["Reverse proxy / Load balancer<br>nginx · ALB · K8s Ingress<br>TLS termination · SSE pass-through"]
AIN["ai-service replica N"]
AI2["ai-service replica 2"]
AI1["ai-service replica 1"]
LB --> AIN
LB --> AI2
LB --> AI1
end

subgraph Data["Shared data layer"]
DB[("SQL database<br>MySQL 8.0+ / PostgreSQL 13+")]
Cache[("Redis 3.2.6+")]
Storage[("File storage<br>S3 · Azure Blob · filesystem")]
end

AI1 --> Data

AI1 -->|"HTTPS"| LLM["LLM provider<br>OpenAI · Anthropic · Google ·<br>Azure · Bedrock · Vertex ·<br>self-hosted"]

AI1 -.->|"telemetry"| Obs["OpenTelemetry · Langfuse"]
AI1 -.->|"tool calls"| MCP["MCP servers"]
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
flowchart TD
Start([New deployment]) --> Q1{Evaluating or<br>going to production?}
Q1 -->|Evaluating locally| Compose[Docker Compose<br>all services on one host<br>Getting started guide]
Q1 -->|Production| Q2{Orchestrator?}
Q2 -->|Kubernetes| K8s[Kubernetes deployment<br>Production guide]
Q2 -->|AWS ECS / Fargate| ECS[ECS task definition<br>Production guide]
Q2 -->|Docker / Podman on VMs| VMs[Docker or Podman compose<br>Database guide]
Q2 -->|Bare metal / no containers| Bare[Native install for<br>data layer; container<br>for AI service<br>Database guide]
Compose --> DB{Database?}
K8s --> DB
ECS --> DB
VMs --> DB
Bare --> DB
DB -->|Managed cloud DB| Managed[RDS · Cloud SQL ·<br>Azure Database]
DB -->|Self-managed| Self[Containers or native install]
Managed --> Done([Continue with<br>LLM providers guide])
Self --> Done
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
flowchart TB
Internet([Internet]) --> Ingress[Ingress controller<br>nginx-ingress · ALB controller<br>proxy-buffering off]
Ingress --> SvcAI[Service: ai-service]
SvcAI --> Pod1[Pod: ai-service replica 1]
SvcAI --> Pod2[Pod: ai-service replica 2]
SvcAI --> PodN[Pod: ai-service replica N]
Pod1 --> SvcDB[Service: database<br>or external RDS]
Pod2 --> SvcDB
PodN --> SvcDB
Pod1 --> SvcRedis[Service: redis<br>or external ElastiCache]
Pod2 --> SvcRedis
PodN --> SvcRedis
Pod1 --> S3[(S3 / Azure Blob)]
Pod2 --> S3
PodN --> S3
HPA[HorizontalPodAutoscaler] -. scales .-> Pod1
HPA -. scales .-> Pod2
HPA -. scales .-> PodN
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
flowchart LR
subgraph PR[PROVIDERS env var]
P1["my-openai-key<br>type: openai<br>apiKeys: [sk-...]"]
P2["my-bedrock<br>type: bedrock<br>credentials: {...}"]
P3["my-ollama<br>type: openai-compatible<br>baseUrl: .../v1"]
end
subgraph MD[MODELS env var]
M1["id: gpt-4.1<br>provider: my-openai-key<br>features: [...]"]
M2["id: us.anthropic.claude-sonnet-4-...<br>provider: my-bedrock<br>features: [...]"]
M3["id: qwen3:0.6b<br>provider: my-ollama<br>features: [...]"]
end
subgraph JWT[JWT auth.ai.permissions]
K1["ai:models:my-openai-key:gpt-4.1"]
K2["ai:models:my-bedrock:us.anthropic.claude-sonnet-4-..."]
K3["ai:models:my-ollama:qwen3:0.6b"]
end
M1 -.provider key.-> P1
M2 -.provider key.-> P2
M3 -.provider key.-> P3
K1 -.gates access.-> M1
K2 -.gates access.-> M2
K3 -.gates access.-> M3
M1 ==>|forwarded| LLM1[OpenAI API]
M2 ==>|forwarded| LLM2[AWS Bedrock]
M3 ==>|forwarded| LLM3[Local Ollama]
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
sequenceDiagram
autonumber
actor User
participant Editor as TinyMCE editor<br>tinymceai plugin
participant App as Your backend<br>token endpoint
participant AI as AI service
participant LLM as LLM provider

User->>Editor: Triggers an AI feature
Editor->>App: POST /api/ai-token<br>session cookie or Bearer
App->>App: Authenticate the user
Note over App: Sign HS256 JWT with API Secret<br>aud = environment ID<br>sub = user ID<br>auth.ai.permissions = [...]
App-->>Editor: { token: eyJ... }
Editor->>AI: POST /v1/conversations/id/messages<br>Authorization: Bearer eyJ...
AI->>AI: Verify HS256 signature<br>check aud, exp, permissions

alt Token valid and permissions allow
AI->>LLM: Forward prompt
LLM-->>AI: Stream response chunks
AI-->>Editor: SSE text-delta events
else Signature does not match
AI-->>Editor: 401 invalid-jwt-signature
else aud not registered with AI runtime
AI-->>Editor: 401 invalid-jwt-payload
else Past expiry plus 60s leeway
AI-->>Editor: 401 invalid-jwt
else Permissions do not cover action
AI-->>Editor: 200 with allowed false
end
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
sequenceDiagram
autonumber
actor User
participant Editor as TinyMCE + tinymceai plugin
participant Provider as tinymceai_token_provider<br>your function
participant App as Your backend<br>token endpoint
participant AI as AI service

Note over Editor: tinymce.init runs once<br>plugin registers toolbar buttons
User->>Editor: Click AI button or open chat
Editor->>Provider: invoke
Provider->>App: fetch /api/ai-token<br>credentials include
App-->>Provider: { token eyJ... }
Provider-->>Editor: { token }
Editor->>AI: HTTPS request<br>Authorization Bearer eyJ...
AI-->>Editor: SSE stream
loop For each chunk
Editor->>Editor: Render streaming text
end
Note over Editor,Provider: Plugin re-invokes the provider<br>before token expiry<br>do not cache the JWT yourself
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
flowchart LR
Editor[TinyMCE editor] <-->|"chat / quick actions"| AI[AI Service]
AI -->|"MCP tools/call"| MCP[MCP Server<br>knowledge-hub]
MCP -->|"read"| KB[(Confluence ·<br>Notion ·<br>GitBook ·<br>internal wiki)]
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
flowchart LR
subgraph Tenants[Your SaaS customers]
CA[Customer A users]
CB[Customer B users]
CC[Customer C users]
end
subgraph AISvc[Single AI service deployment]
EA[Environment A<br>access keys A<br>isolated conversations]
EB[Environment B<br>access keys B<br>isolated conversations]
EC[Environment C<br>access keys C<br>isolated conversations]
end
CA --> EA --> OpenAI[OpenAI]
CB --> EB --> Anthropic[Anthropic]
CC --> EC --> Azure[Azure OpenAI]
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
flowchart TD
Comment thread
kemister85 marked this conversation as resolved.
Start([Something is wrong]) --> Q1{Container is<br>running?<br>docker ps shows it}
Q1 -->|No - exited or wont pull| S1[Container startup failures]
Comment thread
kemister85 marked this conversation as resolved.
Q1 -->|Yes| Q2{curl /health<br>returns 200?}
Q2 -->|No - times out or 5xx| S1
Q2 -->|Yes| Q3{API call returns<br>auth error?}
Q3 -->|Yes - 401 allowed false<br>invalid-jwt-...| S2[API and JWT authentication]
Q3 -->|No| Q4{SSE stream<br>carries event error<br>from LLM?}
Q4 -->|Yes| S3[LLM provider errors]
Q4 -->|No| Q5{Editor side<br>broken?<br>no toolbar token 401<br>hanging stream}
Q5 -->|Yes| S4[Editor and front-end]
Q5 -->|No| Q6{Slow timing out<br>or failing under load?}
Q6 -->|Yes| S5[Performance and capacity]
Q6 -->|No| S6[Diagnostic recipes]
S1 --> Recipe([If none fit<br>see Diagnostic recipes<br>then escalate])
S2 --> Recipe
S3 --> Recipe
S4 --> Recipe
S5 --> Recipe
S6 --> Recipe
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
flowchart TD
Start([Where will MySQL/Postgres and Redis run?]) --> Q1{Evaluating or<br>deploying to prod?}
Q1 -->|Evaluating locally| Compose[Docker Compose<br>image: mysql:8.0 or postgres:16<br>+ redis:7]
Q1 -->|Deploying| Q2{Cloud or self-managed?}
Q2 -->|Cloud / managed services| Managed[AWS RDS · Cloud SQL ·<br>Azure Database<br>+ ElastiCache · Memorystore ·<br>Azure Cache for Redis]
Q2 -->|Self-managed| Q3{Container runtime<br>available?}
Q3 -->|Docker or Podman| Containers[Containers on the same<br>network or pod as ai-service]
Q3 -->|None - bare metal or VM| Native[Native install<br>brew · apt · yum · dnf<br>service runs on host]
Compose --> Verify([Verify: nc -zv host port<br>then start ai-service])
Managed --> Verify
Containers --> Verify
Native --> Verify
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
sequenceDiagram
autonumber
actor User
participant Editor as TinyMCE + tinymceai plugin
participant Provider as tinymceai_token_provider<br>(your function)
participant App as Your backend<br>(token endpoint)
participant AI as AI service

Note over Editor: tinymce.init() runs once<br>plugin registers toolbar buttons
User->>Editor: Click AI button or open chat
Editor->>Provider: invoke()
Provider->>App: fetch('/api/ai-token', { credentials: 'include' })
App-->>Provider: { token: "eyJ..." }
Provider-->>Editor: { token }
Editor->>AI: HTTPS request<br>Authorization: Bearer eyJ...
AI-->>Editor: SSE stream
loop For each chunk
Editor->>Editor: Render streaming text
end
Note over Editor,Provider: Plugin re-invokes the provider<br>before token expiry — do not<br>cache the JWT yourself
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
sequenceDiagram
autonumber
actor User
participant Editor as TinyMCE editor<br>(tinymceai plugin)
participant App as Your application backend<br>(token endpoint)
participant AI as AI service
participant LLM as LLM provider

User->>Editor: Triggers an AI feature
Editor->>App: POST /api/ai-token<br>session cookie or Bearer
App->>App: Authenticate the user
Note over App: Sign HS256 JWT with API Secret<br>aud = environment ID<br>sub = user ID<br>auth.ai.permissions = [...]
App-->>Editor: { "token": "eyJ..." }
Editor->>AI: POST /v1/conversations/{id}/messages<br>Authorization: Bearer eyJ...
AI->>AI: Verify HS256 signature<br>check aud, exp, permissions

alt Token valid and permissions allow the action
AI->>LLM: Forward prompt
LLM-->>AI: Stream response chunks
AI-->>Editor: SSE: text-delta events
else Signature does not match
AI-->>Editor: 401 invalid-jwt-signature
else aud is not registered with AI runtime
AI-->>Editor: 401 invalid-jwt-payload
else Past expiry plus 60s leeway
AI-->>Editor: 401 invalid-jwt
else Permissions do not cover the action
AI-->>Editor: 200 with allowed:false
end
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
flowchart LR
Client["Client application"] -->|"1. fetch JWT"| Token["Token endpoint"]
Client -->|"2. prompt + JWT"| AI["AI service<br>(container)"]
AI -->|"3. forward prompt"| LLM["LLM provider"]
LLM -->|"4. stream response"| AI
AI -->|"4. SSE stream"| Client
AI --- DB[("Database<br>+ Redis")]
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
flowchart TB
Browser([TinyMCE in browser])
Browser -->|HTTPS + JWT| LB[Reverse Proxy / Load Balancer<br>nginx · ALB · Ingress<br>TLS termination<br>proxy_buffering off]
LB -->|HTTP :8000| AI1[ai-service replica 1]
LB -->|HTTP :8000| AI2[ai-service replica 2]
LB -->|HTTP :8000| AIN[ai-service replica N]

subgraph DataLayer["Shared data layer"]
DB[("MySQL 8.0+ /<br>Postgres 13+<br>Multi-AZ in prod")]
Cache[("Redis 7<br>cluster or managed")]
Storage[("S3 · Azure Blob ·<br>filesystem · DB")]
end

AI1 --> DB
AI1 --> Cache
AI1 --> Storage
AI2 --> DB
AI2 --> Cache
AI2 --> Storage
AIN --> DB
AIN --> Cache
AIN --> Storage

AI1 --> LLM[LLM Provider<br>OpenAI · Anthropic · Google ·<br>Azure · Bedrock · Vertex · self-hosted]
AI2 --> LLM
AIN --> LLM

AI1 -.->|optional| Obs[OpenTelemetry · Langfuse ·<br>log aggregator]
AI2 -.-> Obs
AIN -.-> Obs
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
flowchart LR
subgraph PR[PROVIDERS env var · JSON object]
P1["my-openai-key<br>type: openai<br>apiKeys: [sk-...]"]
P2["my-bedrock<br>type: bedrock<br>credentials: {...}"]
P3["my-ollama<br>type: openai-compatible<br>baseUrl: .../v1"]
end
subgraph MD[MODELS env var · JSON array]
M1["id: gpt-4.1<br>provider: my-openai-key<br>features: [...]"]
M2["id: us.anthropic.claude-sonnet-4-...<br>provider: my-bedrock<br>features: [...]"]
M3["id: qwen3:0.6b<br>provider: my-ollama<br>features: [...]"]
end
subgraph JWT[JWT auth.ai.permissions]
K1["ai:models:my-openai-key:gpt-4.1"]
K2["ai:models:my-bedrock:us.anthropic.claude-sonnet-4-..."]
K3["ai:models:my-ollama:qwen3:0.6b"]
end
M1 -.references provider key.-> P1
M2 -.references provider key.-> P2
M3 -.references provider key.-> P3
K1 -.gates per-user access.-> M1
K2 -.gates per-user access.-> M2
K3 -.gates per-user access.-> M3
M1 ==>|forwarded to upstream| LLM1[OpenAI API]
M2 ==>|forwarded to upstream| LLM2[AWS Bedrock]
M3 ==>|forwarded to upstream| LLM3[Local Ollama]
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading