Prompt Input
Message input with voice transcription, model switching, and suggestions.
The ChatPromptInput component provides a rich message input with several integrated features.
Core Input
A textarea with keyboard shortcuts:
| Shortcut | Action |
|---|---|
Enter | Send message |
Shift+Enter | New line |
Escape | Clear input |
The textarea auto-resizes as the user types.
Suggestions
On an empty chat (no messages yet), quick-start suggestions appear above the input:
┌──────────────────────────────────────────┐
│ 💡 "Summarize this document" │
│ 💡 "What are the key findings?" │
│ 💡 "Compare these two topics" │
│ 💡 "Explain this concept simply" │
└──────────────────────────────────────────┘Clicking a suggestion populates the input and sends immediately.
Voice Input
The mic button enables speech-to-text:
- User clicks the mic button
- Browser's MediaRecorder API captures audio
- On stop, the audio blob is sent to the transcription endpoint:
POST /api/transcribe-audio/ Content-Type: multipart/form-data { audio_file, workspace_id, language } - Backend processes with OpenAI Whisper
- Transcribed text is inserted into the input
Language Selector
A popover dropdown lets the user select the transcription language (defaults to English). This is sent to the Whisper API as a hint for better accuracy.
Model Selector
Users can switch the AI model mid-conversation without opening settings. ModelPicker lists the models from GET /api/workspaces/{id}/ai-models/, grouped by provider with each one's tier; models the workspace's plan doesn't include are locked, naming the plan that does:
┌──────────────────────────────┐
│ Gemini 2.5 Flash ▾ │
├──────────────────────────────┤
│ OpenAI │
│ GPT-5 mini Fast │
│ GPT-5 🔒 Pro │
│ Anthropic │
│ Claude Haiku 4.5 Fast │
│ Claude Opus 4.6 🔒 Team │
│ Google │
│ Gemini 2.5 Flash Fast ✓ │
└──────────────────────────────┘Choosing a model sends a PATCH with the chat's new ai_model; if the backend refuses, its reason shows as a toast. The settings sheet uses the same picker, and the list is fetched once per workspace (useAiModels).
Message Submission
async function handleSubmit(content: string) {
setIsAiThinking(true);
const res = await fetch(
`/api/workspaces/${workspaceId}/chats/${chatId}/messages/`,
{
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ role: "user", content }),
}
);
const data = await res.json();
// Update message list with user message + AI response
setIsAiThinking(false);
}AI Thinking State
While the AI is processing, the UI shows:
- A typing indicator in the message list
- The send button is disabled
- The input is grayed out
Attachments
The prompt input supports file attachments via the AI elements PromptInputProvider:
<PromptInputProvider>
<PromptInputTextarea placeholder="Type a message..." />
<PromptInputActions>
<PromptInputAttachment />
<PromptInputSubmit />
</PromptInputActions>
</PromptInputProvider>Attached files are sent with the message as multipart form data (up to 5, 10 MB each). Images go to the model with that message; documents are read into text the model sees on later turns too. A model that can't read images refuses them with a message.
Stop
While a reply streams, the send button becomes Stop. It posts the stream's id to .../messages/stream/stop/; the reply ends shortly after, and the part written so far is saved.