# 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*
