Files
nick-doc/10 - Services/amanat-assist.md

11 KiB

amanat-assist — AI Request Assistant

Status: Live at assist.amn.gg (v1.1.0) Repo: /amanat-assist (separate repo, no Amanat DB or internal-service access) Owner: Amanat Platform PRD: PRD — AI Request Assistant Mini App


1. Overview

amanat-assist is a Telegram Mini App and standalone web app that guides buyers through creating a purchase request on the Amanat escrow marketplace using a conversational LLM interface. The user describes what they want in plain language; the assistant asks clarifying questions, suggests price and delivery windows, then with one tap posts the structured request to the Amanat backend.

The user never sees a form. The LLM handles categorisation, field normalisation, and the API call.


2. Architecture

┌─────────────────────────────────────────────────────────┐
│  Telegram / Browser                                      │
│  assist.amn.gg (nginx, static React/Vite bundle)        │
│  → auth (Telegram SSO or web redirect to dev.amn.gg)    │
│  → UI: multi-turn chat, photo upload, review card       │
└──────────────────────┬──────────────────────────────────┘
                       │ POST /api/llm
                       ▼
┌─────────────────────────────────────────────────────────┐
│  amanat-llm-proxy (Node.js 18+, port 3001)              │
│  Providers: Mistral → fallback DeepSeek on 429          │
│  Also: Kimi, OpenCode proxy                             │
│  API keys server-side only, never in browser            │
└──────────────────────┬──────────────────────────────────┘
                       │ Bearer JWT
                       ▼
┌─────────────────────────────────────────────────────────┐
│  Amanat Backend (api.amn.gg / dev.amn.gg)               │
│  /api/auth/telegram  /api/categories  /api/requests     │
└─────────────────────────────────────────────────────────┘

Docker Compose

Service Image Container Notes
frontend nginx:alpine amanat-frontend Serves dist/ — static bundle
llm-proxy Built from ./llm-proxy/ amanat-llm-proxy Port 3001

Both services join the external escrow-dev_default docker network (alias escrow_net).


3. Tech Stack

Layer Tech
Frontend React 18, TypeScript, Vite 5
Styling CSS variables + Telegram theme tokens
LLM Proxy Plain Node.js 18+ (http module, native fetch) — zero npm deps
State React state machine + useSlotFilling hook
Persistence localStorage via useChatSessions hook
Auth (Telegram) window.Telegram.WebApp.initData/api/auth/telegram
Auth (Web) Redirect to dev.amn.gg?access_token=... callback
CI Woodpecker CI on ARM64 agent co-located with assist.amn.gg

4. State Machine

stateDiagram-v2
    [*] --> INIT
    INIT --> AUTH : Telegram initData present
    INIT --> GREETING : Dev mode (skip auth)
    INIT --> GREETING : Web — stored session valid
    INIT --> GREETING : Web — OAuth callback received
    AUTH --> GREETING : silentSSO success
    AUTH --> ERROR : silentSSO failure
    GREETING --> COLLECT : user sends first message
    COLLECT --> COLLECT : LLM asks follow-up
    COLLECT --> REVIEW : all required slots filled
    REVIEW --> SUBMITTING : user taps Submit
    REVIEW --> COLLECT : user taps Edit
    SUBMITTING --> DONE : POST /api/requests 200
    SUBMITTING --> ERROR : submit failed
    ERROR --> AUTH : retry (Telegram)
    ERROR --> GREETING : retry (dev/web)
    COLLECT --> HISTORY : user taps History
    HISTORY --> COLLECT : user loads session
    HISTORY --> GREETING : new chat

5. Auth

5.1 Telegram Mini App (primary)

User opens bot
  → window.Telegram.WebApp.initData (injected by Telegram)
  → POST https://dev.amn.gg/api/auth/telegram
      { initData: "<raw string>", role: "buyer" }
  ← { data: { tokens: { accessToken, refreshToken }, user, isNewUser } }
  → Store accessToken in memory (not localStorage — ephemeral session)

On any 401, the app transparently POSTs /api/auth/refresh-token and retries.

5.2 Web Browser

  1. Check for ?access_token=... in URL (OAuth callback redirect from dev.amn.gg)
  2. Check localStorage for a stored valid session (calls /api/auth/me to verify)
  3. If no session → redirect to dev.amn.gg?redirect_uri=<current-origin> for login

5.3 Development Mode

Skips all auth, uses mock tokens + mock user.


6. LLM Service

6.1 Providers

Provider Model Key env var Notes
mistral mistral-large-latest MISTRAL_API_KEY Primary
mistral (vision) pixtral-12b-2409 MISTRAL_API_KEY Image analysis
kimi moonshot-v1-8k KIMI_API_KEY Optional
deepseek deepseek-chat DEEPSEEK_API_KEY Auto-fallback on 429
opencode claude-3-sonnet OpenCode local proxy

6.2 Proxy API

POST /api/llm
Content-Type: application/json

{
  "messages": [{ "role": "user", "content": "..." }, ...],
  "provider": "mistral",          // optional, defaults to mistral
  "model": "mistral-large-latest" // optional
}

Response: { "content": "...", "model": "..." }
         | { "content": "...", "model": "...", "fallback": true }  // on auto-failover

6.3 Slot Filling

The system prompt instructs the LLM to:

  1. Extract ALL info from the user's message before asking anything

  2. Ask at most one question at a time

  3. When all required slots are filled, output a fenced JSON block:

    ```request
    { "title": "...", "description": "...", "categoryId": "...", ... }
    ```
    

Required fields: title, description, categoryId, urgency, deliveryInfo.deliveryType

Optional: productLink, attachments[], budget{min,max,currency}, quantity, size, color

The app detects the ```request fence, parses the JSON, and transitions to REVIEW.

6.4 Vision (Image Upload)

Uses pixtral-12b-2409. The user uploads a photo; the LLM returns structured JSON with name, category, color, description, quantity. Result is merged into slots; categoryId is never set from vision (names aren't valid ObjectIds).

6.5 Price Suggestion

If slots.budget is unset at REVIEW time, the app calls the LLM with a structured price-suggestion prompt. Result tagged high/medium/low confidence; only high and medium are auto-applied.


7. Request Slots Schema

interface RequestSlots {
  title?: string;
  description?: string;
  categoryId?: string;          // must be a valid ObjectId from /api/categories
  productLink?: string;
  attachments?: string[];       // image URLs or base64 (base64 stripped before storage)
  budget?: { min?: number; max?: number; currency: string };
  urgency?: 'low' | 'medium' | 'high' | 'urgent';
  quantity?: number;
  size?: string;
  color?: string;
  deliveryInfo?: {
    deliveryType: 'physical' | 'online';
    email?: string;
  };
}

8. Frontend Components

Component Description
App.tsx State machine root — renders one screen per state
ChatUI Scrollable message list + text/photo input + category chips
ChatHistory localStorage-persisted past sessions list
ReviewCard Final structured view of filled slots + Submit/Edit buttons
AuthScreen Loading spinner shown during SSO
ErrorScreen Error message + Retry button

Hooks

Hook Description
useSlotFilling Manages LLM conversation, slot extraction, greeting, session load
useChatSessions Read/write/delete chat sessions from localStorage

9. Amanat API Calls

Method Endpoint Auth Purpose
POST /api/auth/telegram Exchange Telegram initData for JWT
POST /api/auth/refresh-token Refresh expired access token
GET /api/auth/me Bearer Validate stored session
GET /api/categories Bearer Load category list for slot filling
POST /api/requests Bearer Submit completed purchase request

All requests from src/services/api.ts use amanatApi() from auth.ts, which auto-refreshes on 401.

Submitted requests include aiGenerated: true.


10. Deployment

CI Pipeline (.woodpecker/ci.yml)

trigger: push/manual to main
agent: linux/arm64 (same host as assist.amn.gg)

steps:
  1. build-frontend: npm ci + npm run build (Vite)
  2. deploy:
     - Copy dist/ to /opt/amanat-assist/dist/ (nginx bind-mount)
     - Rebuild amanat-llm-proxy Docker image in-place
     - docker compose up -d --no-deps llm-proxy
  3. notify: Telegram CI notification

Nginx picks up new static files from the bind-mount without restart. The proxy container is recreated with the new image.

Environment Variables

Variable Scope Description
VITE_AMANAT_API_BASE Frontend build-time Backend URL (e.g. https://dev.amn.gg)
VITE_LLM_PROVIDER Frontend build-time Default LLM provider (mistral)
VITE_LLM_API_URL Frontend build-time Proxy URL (e.g. https://assist.amn.gg/api/llm)
MISTRAL_API_KEY llm-proxy runtime Mistral API key (server-side only)
KIMI_API_KEY llm-proxy runtime Optional Kimi API key
DEEPSEEK_API_KEY llm-proxy runtime Optional DeepSeek API key (auto-fallback)
OPENCODE_PROXY_URL llm-proxy runtime OpenCode local proxy URL
ALLOWED_ORIGINS llm-proxy runtime CORS whitelist (comma-separated)
PORT llm-proxy runtime Port (default 3001)

11. Integration with dev.amn.gg Frontend

The dev.amn.gg frontend (Next.js) includes a native AI Assistant page at /dashboard/assist that:

  • Proxies /api/llm calls to amanat-llm-proxy via an internal Next.js API route
  • Uses the existing dev.amn.gg session (no re-auth needed)
  • Allows buyers to start an AI-assisted request flow from within the main dashboard
  • The "New Request" page includes a button to launch the AI assistant

See src/sections/assist/ in the frontend repo for the implementation.


12. Known Limitations / Open Items

  • No voice input — text and photo only (MVP)
  • Single-item only — one purchase request per conversation
  • No post-submit editing — requests posted via the assistant cannot be edited through the assistant
  • Session storage is local only — history lives in localStorage, not synced to backend
  • Vision model not streaming — responses may feel slow for image analysis
  • categoryId from vision disabled — vision returns category names, not ObjectIds; name→ID matching is left to the LLM in the follow-up turn