← Home System Overview Architecture Data Pipeline Health System Usage System Workflows MCP Tools

Architecture — ManyAI Mobile

Blueprint · Last updated 2026-05-02

Directory Structure

/
├── app/                         # Expo Router — screens and navigation
│   ├── _layout.tsx              # Root: Stack navigator, OTA update check, ThemeProvider
│   ├── modal.tsx                # Modal screen
│   └── (tabs)/
│       ├── _layout.tsx          # Tab bar definition (Home, Saved, Settings)
│       ├── index.tsx            # Chat screen (primary)
│       ├── saved.tsx            # Saved responses
│       └── explore.tsx          # Settings / provider configuration
│
├── lib/
│   ├── providers/               # All AI provider logic
│   │   ├── providers.ts         # PROVIDERS registry, ROUTING_ORDER, pickProvider()
│   │   ├── callProvider.ts      # API adapter (text + vision)
│   │   ├── imageGen.ts          # Image generation — detection + API calls
│   │   ├── keyStore.ts          # AsyncStorage CRUD for API keys
│   │   ├── providerPrefs.ts     # AsyncStorage for order, enabled state, selected models
│   │   └── remoteConfig.ts      # Runtime model patching from remote JSON
│   └── saved/
│       ├── savedResponses.ts    # Save/load/delete responses by category
│       ├── shareUtils.ts        # Native share sheet + save-to-device
│       └── refineSeed.ts        # Module-level variable for cross-tab refine handoff
│
├── components/                  # Pure UI — no business logic
│   ├── haptic-tab.tsx           # Tab bar button with haptic feedback
│   ├── parallax-scroll-view.tsx
│   ├── themed-text.tsx
│   ├── themed-view.tsx
│   └── ui/
│       ├── collapsible.tsx
│       ├── icon-symbol.tsx      # SF Symbol wrapper (iOS) + fallback
│       └── icon-symbol.ios.tsx
│
├── constants/
│   └── theme.ts                 # Color palette and theme tokens
│
├── hooks/
│   └── use-color-scheme.ts      # Detects light/dark mode
│
└── assets/images/               # App icons, splash screens

Routing (Expo Router)

Provider Layer (lib/providers/)

providers.ts

The single source of truth for all provider metadata.

callProvider.ts

Unified API adapter. Handles 5 different API shapes:

ShapeProvidersNotes
PollinationspollinationsGET text.pollinations.ai/{encoded} — no auth
GeminigeminiPOST /generateContentparts array, inline_data for images
AnthropicanthropicPOST /messagesx-api-key header, anthropic-version header
CloudflarecloudflareKey split accountId:apiToken, account ID embedded in URL
OpenAI-compatall othersPOST /chat/completions, Authorization: Bearer

Timeout per call: 30 000 ms (AbortController). History capped at MAX_HISTORY = 10 messages.

imageGen.ts

keyStore.ts

AsyncStorage-backed key CRUD. Keys stored per ProviderKey. loadAllKeys() returns Partial<Record<ProviderKey, string>>.

providerPrefs.ts

remoteConfig.ts

Fetches stevepleasants.com/manyai/config.json to patch model lists at runtime without a build. Applied to the PROVIDERS object on app load.

Saved Layer (lib/saved/)

savedResponses.ts

Responses stored in AsyncStorage keyed by category. Supports: save (with optional title derivation from prompt), load, delete, move, rename category. Saved item shape: { id, prompt, response, provider, category, title, generatedImageUri?, createdAt }.

shareUtils.ts

refineSeed.ts

Module-level variable (not React state or AsyncStorage) holding { prompt, response, title, provider }. setRefineSeed(seed) is called from the Saved tab when user taps "Refine". consumeRefineSeed() is called in Chat tab's useFocusEffect — reads and clears the variable atomically. Achieves cross-tab communication without navigation params or global state.

Chat Screen Key State (app/(tabs)/index.tsx)

StateTypeNotes
messagesMessage[]Conversation array including system greeting
inputstringCurrent text field value
pendingImagePendingImage|nullAttached image (uri + base64 + mime)
loadingbooleanTrue while any API call is in flight
loadingLabelstring"Trying ProviderName · model..." shown in typing indicator
keysPartial<Record<ProviderKey, string>>Reloaded on each tab focus
providerOrderProviderKey[]Reloaded on each tab focus
enabledProvidersPartial<Record<ProviderKey, boolean>>Reloaded on each tab focus
selectedModelsPartial<Record<ProviderKey, string>>Reloaded on each tab focus
failedProvidersuseRef<Set<ProviderKey>>Not state — persists across renders, reset on tab focus
saveTargetMessage|nullMessage awaiting category selection
refineTitlestring|nullSet when refine seed is consumed
showHelpbooleanHelp modal visibility

Send Flow

send()
  │
  ├─ /help → show help modal, return
  │
  ├─ IMAGE GENERATION PATH (text + no attached image + isImageGenRequest())
  │   └─ for each IMAGE_PROVIDER:
  │       skip if needs key and key missing
  │       callImageProvider() → success: display generatedImage bubble, return
  │       failure: try next provider
  │   └─ all failed → show error bubble
  │
  └─ TEXT GENERATION PATH (default)
      build available = keys + 'pollinations'
      if pendingImage: filter to VISION_PROVIDERS ∩ available
      build history (last 10 non-error non-system messages)
      tried = session failedProviders
      while attempts < MAX_RETRIES (8):
          providerKey = pickProvider(pool, 'general', tried, order, enabled)
          if null → break (all exhausted)
          inject selectedModel override
          setLoadingLabel("Trying Name · model...")
          result = callProvider(provider, text, key, imageBase64, mime, history)
          if success → add assistant bubble, return
          else: tried.add(key), failedProviders.current.add(key)
      all failed → show error bubble with instructions

Render Optimization

Color Palette (Dark Theme)

TokenValueUse
Background#1a1a2eScreen background
Surface#16213eHeader, AI bubbles, input bar
Accent#0f3460User bubbles, buttons
Teal#4ECDC4Active tint, provider labels, send button
Error#FF6B6BError messages
Text#eeeeeePrimary text
Muted#888Secondary text, timestamps