Skip to Content
AIChat

Chat

The Chat tab lets you choose a model and have a conversation with streaming replies. It uses the chat edge function.

Change the chat system prompt in this project. Edit the chat edge function, and tell me what else reads that prompt.
A chat thread showing a markdown-formatted assistant reply, with the thread's model shown as a badge in the header

Change the prompt or reply length

To changeEditThen
The system promptsupabase/functions/chat/index.tsRedeploy
Reply lengthMAX_OUTPUT_TOKENS in the same file (currently 2048)Redeploy
Which models appearsupabase/functions/_utils/ai.config.json, shared by the app and serverSee Models
The UIsrc/features/chat/components/Reload the app during development

No system prompt is set by default. Add system to the existing streamText call:

supabase/functions/chat/index.ts
const result = streamText({ model: openrouter.chat(model), system: 'You are a helpful assistant for a meal-planning app.', messages: await convertToModelMessages(await inlineFileParts(history, admin)), maxOutputTokens: MAX_OUTPUT_TOKENS, ...(caps.imageOutput ? { providerOptions: IMAGE_MODALITY_OPTIONS } : {}), onFinish: (turn) => persistAssistantTurn(admin, user.id, threadId, turn), });
supabase functions deploy chat

Send a new message to check that the reply follows your prompt.

Raising MAX_OUTPUT_TOKENS allows the model to generate longer replies.

Gotchas

Keep expo/fetch if you replace the transport. React Native’s global fetch buffers the response, so the reply appears all at once instead of streaming.

The app disables ModelPicker once a thread exists, so a thread’s history stays consistent with the model that produced it. To switch models, start a new chat.

Attachments are resized to a maximum long edge of 1024px and re-encoded as JPEG at quality 0.7. Smaller images keep their dimensions. The picker’s own quality option compresses images without limiting their dimensions.

Error codes

Requests rejected before streaming starts return a JSON body containing a code:

CodeHTTPMeaning
invalid_request400The request body does not match the expected shape.
invalid_model400Slug not in the function’s allow-list. See Models.
model_not_vision400An image was attached to a model that does not accept image input.
invalid_file_url400A file part’s url is not a valid storage path under the caller’s user ID.
unauthorized401The Supabase access token is missing or invalid.
pro_required403An imageOutput model requested by a non-Pro user.
not_found404The threadId does not exist or belongs to another user.
quota_exceeded402Free allowance used up. See Quota & Paywall.
provider_not_configured500OPENROUTER_API_KEY isn’t set. See Secrets.
internal_error500An unexpected server error. Check the function logs for details.

Files

        • chats.tsx - Thread list route
        • [threadId].tsx - Conversation route
      • chatTransport.ts - Streaming transport
      • ChatsScreen.tsx - Thread list screen
      • ChatScreen.tsx - Conversation screen
      • repository.ts - Thread and message queries
      • chatMedia.ts - Attachments
    • index.ts - Chat edge function

How it works

Threads. The client sends the newest message and, for an existing conversation, its threadId. The function reads earlier messages from the database. For a new conversation, it creates the thread and returns its ID in the x-thread-id header. The title uses the first 40 characters of the message, or New chat for an image-only message.

Attachments. Images are uploaded to the private chat-media bucket under {userId}/<uuid>.jpg. The client sends that storage path in the file part’s url field, and the function checks that it matches the caller’s user ID before storing the message. resolveChatMediaUrl() signs the path on demand to display the image.

Server-side inlining. OpenRouter cannot fetch images from a local Supabase instance. Before calling the model, inlineFileParts() in supabase/functions/_utils/media.ts downloads images with the service-role client and converts them to base64 data: URLs. The bucket stays private. The OpenRouter provider is configured in supabase/functions/_utils/provider.ts.

Image replies. A model flagged imageOutput can answer with images. These models are Pro-only, and their replies upload separately to chat-media/{userId}/gen/ so they survive in history.

Last updated on