docs: sync from backend 8fc2309 — M43/M44 missing FKs + H37 dispute enums

This commit is contained in:
Siavash Sameni
2026-06-07 07:16:02 +04:00
parent a2967ec594
commit 0bb60dbc98
24 changed files with 3428 additions and 906 deletions

479
DEPLOYMENT.md Normal file
View File

@@ -0,0 +1,479 @@
# 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.gg` or `amn.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
1. **Cloudflare Account** with Workers + Pages enabled
2. **Amanat Backend** running at `dev.amn.gg` (already deployed ✅)
3. **LLM API Keys** (at least one):
- Mistral: `sk_...`
- Kimi: `sk_...`
- DeepSeek: `sk_...`
4. **Domain** configured in Cloudflare: `assist.amn.gg` (or subdomain)
---
## 📦 Step 1: Deploy Static Frontend (Cloudflare Pages)
### 1.1 Create Cloudflare Pages Project
```bash
# 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
1. Go to: [https://dash.cloudflare.com](https://dash.cloudflare.com)
2. Select your account → **Workers & Pages****Create application****Pages**
3. **Connect Git repository** (if using Git) OR **Upload files**
4. **Project name:** `amanat-assist`
5. **Production branch:** `main` (or your deployment branch)
6. **Build command:** `npm run build`
7. **Build output directory:** `dist`
8. **Environment variables:** (see Section 3)
### 1.3 Configure Custom Domain
1. In Pages project → **Custom domains****Set up custom domain**
2. Enter: `assist.amn.gg`
3. Cloudflare will issue SSL certificate automatically
4. Wait for DNS propagation (~5-10 minutes)
---
## ☁️ Step 2: Deploy LLM Edge Function (Cloudflare Workers)
### 2.1 Create Worker
1. Go to: [https://dash.cloudflare.com](https://dash.cloudflare.com)
2. Select your account → **Workers & Pages****Create service****Worker**
3. **Service name:** `amanat-assist-llm`
4. **Starter:** `Fetch handler`
### 2.2 Worker Code
Create `index.ts`:
```typescript
// 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
1. **Routes:** `assist.amn.gg/api/llm/*` (or `assist.amn.gg/api/llm`)
2. **Environment variables:** Add your LLM API keys
3. **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
1. Open [@BotFather](https://t.me/BotFather) in Telegram
2. Send `/newbot`
3. Follow prompts to create bot
4. **Save the bot token** (needed for backend Telegram webhook)
### 4.2 Enable Mini App
1. In [@BotFather](https://t.me/BotFather), send `/mybots`
2. Select your bot
3. Go to **Bot Settings****Mini App**
4. Set **URL:** `https://assist.amn.gg`
5. Enable **Inline mode** (optional)
### 4.3 Configure Bot Menu (Optional)
1. In [@BotFather](https://t.me/BotFather), send `/setcommands`
2. Set commands:
```
start - Open Amanat Assist
help - Show help
auth - Re-authenticate
```
---
## 🧪 Step 5: Test Deployment
### 5.1 Test Frontend
```bash
# Local test
npm run dev
# Open: http://localhost:3000
# Production test
# Open: https://assist.amn.gg
```
### 5.2 Test Edge Function
```bash
# 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
1. Open your bot in Telegram
2. Click the Mini App button
3. 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
1. Go to: [https://dash.cloudflare.com](https://dash.cloudflare.com)
2. Workers & Pages → Your Worker → **Logs**
3. View real-time requests and errors
### Cloudflare Pages
1. Workers & Pages → Your Pages project → **Deployments**
2. 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 `allowedOrigins` array 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`:
```toml
[triggers]
crons = ["*/5 * * * *"] # Optional: cleanup
# Rate limiting via Cloudflare
# Configure in Cloudflare Dashboard → Workers → Rate Limiting
```
---
## 🔄 Update Process
### Frontend Updates
```bash
# 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
```bash
# 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`:
```bash
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/](https://developers.cloudflare.com/workers/)
- **Cloudflare Pages Docs:** [https://developers.cloudflare.com/pages/](https://developers.cloudflare.com/pages/)
- **Telegram Mini App Docs:** [https://core.telegram.org/bots/webapps](https://core.telegram.org/bots/webapps)
---
*Document version: 1.0 — 2026-06-05*
*Owner: Deployment Team*