15 KiB
15 KiB
Amanat Assist Mini App - Deployment Guide
Codename: amanat-assist
Version: 1.0
Last Updated: 2026-06-05
Owner: Deployment
🎯 Overview
This document describes the deployment architecture for the Amanat Assist Telegram Mini App, using:
- Frontend: Vite + React (static hosting)
- LLM Edge Function: Cloudflare Workers (server-side LLM calls)
- Backend: Amanat API (
dev.amn.ggoramn.gg)
📁 Architecture Diagram
┌─────────────────────────────────────────────────────────────────┐
│ Telegram Client │
└─────────────────┬─────────────────────────────────┬───────────────┘
│ │
▼ ▼
┌─────────────────────────────┐ ┌─────────────────────────────┐
│ Mini App (Static) │ │ LLM Edge Function │
│ - React + Vite │ │ - Cloudflare Workers │
│ - Hosted on CF Pages │ │ - Route: /api/llm │
│ - URL: assist.amn.gg │ │ - Handles auth + LLM calls │
└─────────────────────────────┘ └──────────┬────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ LLM Providers │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Mistral │ │ Kimi │ │ DeepSeek │ │ OpenCode │ │
│ │ (Primary) │ │ (Fallback) │ │ (Fallback) │ │ (Local) │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘ │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ Amanat Backend │
│ - POST /api/auth/telegram (Telegram SSO) │
│ - GET /api/marketplace/categories │
│ - POST /api/files/upload (File upload) │
│ - POST /api/marketplace/purchase-requests (Submit w/ aiGenerated) │
└─────────────────────────────────────────────────────────────────┘
🚀 Quick Start
Prerequisites
- Cloudflare Account with Workers + Pages enabled
- Amanat Backend running at
dev.amn.gg(already deployed ✅) - LLM API Keys (at least one):
- Mistral:
sk_... - Kimi:
sk_... - DeepSeek:
sk_...
- Mistral:
- Domain configured in Cloudflare:
assist.amn.gg(or subdomain)
📦 Step 1: Deploy Static Frontend (Cloudflare Pages)
1.1 Create Cloudflare Pages Project
# Navigate to project
cd /Users/manwe/CascadeProjects/escrow/amanat-assist
# Install dependencies
npm install
# Build production bundle
npm run build
1.2 Cloudflare Dashboard Setup
- Go to: https://dash.cloudflare.com
- Select your account → Workers & Pages → Create application → Pages
- Connect Git repository (if using Git) OR Upload files
- Project name:
amanat-assist - Production branch:
main(or your deployment branch) - Build command:
npm run build - Build output directory:
dist - Environment variables: (see Section 3)
1.3 Configure Custom Domain
- In Pages project → Custom domains → Set up custom domain
- Enter:
assist.amn.gg - Cloudflare will issue SSL certificate automatically
- Wait for DNS propagation (~5-10 minutes)
☁️ Step 2: Deploy LLM Edge Function (Cloudflare Workers)
2.1 Create Worker
- Go to: https://dash.cloudflare.com
- Select your account → Workers & Pages → Create service → Worker
- Service name:
amanat-assist-llm - Starter:
Fetch handler
2.2 Worker Code
Create index.ts:
// src/index.ts for Cloudflare Worker
interface LLMRequest {
messages: Array<{ role: 'user' | 'assistant'; content: string }>;
provider?: 'mistral' | 'kimi' | 'deepseek' | 'opencode';
model?: string;
}
interface ProviderConfig {
baseUrl: string;
apiKeyEnv: string;
chatEndpoint: string;
model: string;
}
const PROVIDERS: Record<string, ProviderConfig> = {
mistral: {
baseUrl: 'https://api.mistral.ai',
apiKeyEnv: 'MISTRAL_API_KEY',
chatEndpoint: '/v1/chat/completions',
model: 'mistral-large-latest',
},
kimi: {
baseUrl: 'https://api.moonshot.cn',
apiKeyEnv: 'KIMI_API_KEY',
chatEndpoint: '/v1/chat/completions',
model: 'moonshot-v1-8k',
},
deepseek: {
baseUrl: 'https://api.deepseek.com',
apiKeyEnv: 'DEEPSEEK_API_KEY',
chatEndpoint: '/chat/completions',
model: 'deepseek-chat',
},
opencode: {
baseUrl: 'http://127.0.0.1:3456',
apiKeyEnv: '',
chatEndpoint: '/v1/messages',
model: 'claude-3-sonnet',
},
};
export default {
async fetch(request: Request, env: Env): Promise<Response> {
// Only allow POST
if (request.method !== 'POST') {
return new Response(JSON.stringify({ error: 'Method not allowed' }), {
status: 405,
headers: { 'Content-Type': 'application/json', 'Allow': 'POST' },
});
}
// Validate origin (optional - for production)
const origin = request.headers.get('origin');
const allowedOrigins = [
'https://assist.amn.gg',
'https://dev.amn.gg',
'https://amn.gg',
];
if (origin && !allowedOrigins.some(o => origin.startsWith(o))) {
return new Response(JSON.stringify({ error: 'Origin not allowed' }), {
status: 403,
headers: { 'Content-Type': 'application/json' },
});
}
try {
const body: LLMRequest = await request.json();
const provider = body.provider || 'mistral';
const config = PROVIDERS[provider];
if (!config) {
return new Response(JSON.stringify({ error: `Unknown provider: ${provider}` }), {
status: 400,
headers: { 'Content-Type': 'application/json' },
});
}
// Get API key from environment
const apiKey = config.apiKeyEnv ? env[config.apiKeyEnv] : '';
if (config.apiKeyEnv && !apiKey) {
return new Response(JSON.stringify({ error: `API key for ${provider} not configured` }), {
status: 400,
headers: { 'Content-Type': 'application/json' },
});
}
// Build request for the provider
const headers: Record<string, string> = { 'Content-Type': 'application/json' };
if (apiKey) {
headers['Authorization'] = `Bearer ${apiKey}`;
}
const model = body.model || config.model;
const messages = body.messages;
// Format request based on provider
let providerBody: any;
if (provider === 'opencode') {
providerBody = {
model,
messages: messages.map(m => ({
role: m.role,
content: m.content,
})),
max_tokens: 1024,
};
} else {
providerBody = {
model,
messages,
temperature: 0.7,
};
}
// Call the LLM provider
const providerResponse = await fetch(`${config.baseUrl}${config.chatEndpoint}`, {
method: 'POST',
headers,
body: JSON.stringify(providerBody),
});
if (!providerResponse.ok) {
const error = await providerResponse.text();
return new Response(JSON.stringify({
error: `LLM error: ${providerResponse.status} - ${error}`
}), {
status: providerResponse.status,
headers: { 'Content-Type': 'application/json' },
});
}
const data = await providerResponse.json();
// Extract content based on provider
let content: string;
if (provider === 'opencode') {
content = data.content?.[0]?.text || data.content || JSON.stringify(data);
} else {
content = data.choices?.[0]?.message?.content || JSON.stringify(data);
}
return new Response(JSON.stringify({ content, model: data.model || model }), {
status: 200,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': origin || '*',
'Access-Control-Allow-Methods': 'POST',
},
});
} catch (err) {
return new Response(JSON.stringify({
error: `Internal error: ${err instanceof Error ? err.message : String(err)}`
}), {
status: 500,
headers: { 'Content-Type': 'application/json' },
});
}
},
};
// Type for Cloudflare environment variables
export interface Env {
MISTRAL_API_KEY?: string;
KIMI_API_KEY?: string;
DEEPSEEK_API_KEY?: string;
}
2.3 Configure Worker Settings
- Routes:
assist.amn.gg/api/llm/*(orassist.amn.gg/api/llm) - Environment variables: Add your LLM API keys
- Enable CORS: Handled in code
⚙️ Step 3: Environment Variables
3.1 Frontend (Cloudflare Pages)
| Variable | Value | Required | Notes |
|---|---|---|---|
VITE_AMANAT_API_BASE |
https://dev.amn.gg |
✅ | Amanat backend |
VITE_LLM_PROVIDER |
mistral |
✅ | Primary LLM provider |
VITE_LLM_API_URL |
https://assist.amn.gg/api/llm |
✅ | Edge function URL |
Optional:
VITE_OPENCODE_PROXY_URL- If using local proxy
3.2 Edge Function (Cloudflare Worker)
| Variable | Value | Required |
|---|---|---|
MISTRAL_API_KEY |
Your Mistral key | ✅ (if using Mistral) |
KIMI_API_KEY |
Your Kimi key | ❌ |
DEEPSEEK_API_KEY |
Your DeepSeek key | ❌ |
🤖 Step 4: Configure Telegram Mini App
4.1 Create Telegram Bot
- Open @BotFather in Telegram
- Send
/newbot - Follow prompts to create bot
- Save the bot token (needed for backend Telegram webhook)
4.2 Enable Mini App
- In @BotFather, send
/mybots - Select your bot
- Go to Bot Settings → Mini App
- Set URL:
https://assist.amn.gg - Enable Inline mode (optional)
4.3 Configure Bot Menu (Optional)
- In @BotFather, send
/setcommands - Set commands:
start - Open Amanat Assist
help - Show help
auth - Re-authenticate
🧪 Step 5: Test Deployment
5.1 Test Frontend
# Local test
npm run dev
# Open: http://localhost:3000
# Production test
# Open: https://assist.amn.gg
5.2 Test Edge Function
# Direct test
curl -X POST https://assist.amn.gg/api/llm \
-H "Content-Type: application/json" \
-d '{
"messages": [{"role": "user", "content": "Hello"}],
"provider": "mistral",
"model": "mistral-large-latest"
}'
5.3 Test via Telegram
- Open your bot in Telegram
- Click the Mini App button
- Verify:
- ✅ Silent auth (no login prompt)
- ✅ Greeting message appears
- ✅ Chat works
- ✅ File upload works
- ✅ Submit creates request in Amanat
- ✅ AI badge appears in Amanat UI
📊 Monitoring & Logging
Cloudflare Workers
- Go to: https://dash.cloudflare.com
- Workers & Pages → Your Worker → Logs
- View real-time requests and errors
Cloudflare Pages
- Workers & Pages → Your Pages project → Deployments
- View build logs and deployment status
⚠️ Security Considerations
1. Origin Validation
The edge function validates the Origin header to prevent unauthorized access:
- Allowed:
https://assist.amn.gg,https://dev.amn.gg,https://amn.gg - Action: Update the
allowedOriginsarray if adding new domains
2. API Key Protection
- Never expose LLM API keys in frontend code
- All LLM calls go through the edge function
- API keys stored only in Worker environment variables
3. Rate Limiting (Recommended)
Add to Worker wrangler.toml:
[triggers]
crons = ["*/5 * * * *"] # Optional: cleanup
# Rate limiting via Cloudflare
# Configure in Cloudflare Dashboard → Workers → Rate Limiting
🔄 Update Process
Frontend Updates
# Make changes
npm run build
# Push to Git (if using Git integration)
git add . && git commit -m "..." && git push
# Cloudflare Pages auto-deploys
Worker Updates
# Make changes
# Deploy via Wrangler or Dashboard
npx wrangler deploy
📞 Support & Troubleshooting
Common Issues
| Issue | Solution |
|---|---|
| CORS errors | Verify Access-Control-Allow-Origin in Worker |
| 403 from LLM | Check API key in Worker environment |
| 404 on /api/llm | Verify Worker route is configured |
| Telegram auth fails | Verify backend /api/auth/telegram endpoint |
| No AI badge | Verify backend schema changes (PRD §12) |
Debug Mode
Add to frontend .env:
VITE_DEBUG=true
📚 References
- PRD:
/Users/manwe/CascadeProjects/escrow/nick-doc/PRD - AI Request Assistant Mini App.md - Backend PRD §12: Already implemented (commits
6da6e27,1ef9b95) - Cloudflare Workers Docs: https://developers.cloudflare.com/workers/
- Cloudflare Pages Docs: https://developers.cloudflare.com/pages/
- Telegram Mini App Docs: https://core.telegram.org/bots/webapps
Document version: 1.0 — 2026-06-05 Owner: Deployment Team