feat(chat): add optional photo upload support
Add vision/multimodal support to chat, allowing users to send images alongside or instead of text prompts. Images are transmitted and persisted as base64 data URLs. Backend: - Add Images []string to Message struct for persistence - Add Images []string to GenerateTextRequest with relaxed validation - Build multimodal user messages using OpenAI SDK content parts - Pass images through from handlers to client - Deep-copy Images slice in message cloning Frontend: - Add images?: string[] to Message and GenerateTextRequest types - Add image selection state and file input handler - Add camera icon button, hidden file input, and image preview strip - Render images in user message bubbles - Pass images through to GenerateTextRequest Tests: - Add TestSendMessageWithImage for vision model testing
This commit is contained in:
@@ -37,6 +37,7 @@ Alpine.data('chatManager', () => ({
|
||||
|
||||
selectedModel: '',
|
||||
inputMessage: '',
|
||||
selectedImages: [] as string[],
|
||||
error: '',
|
||||
|
||||
selectedChatID: null as string | null,
|
||||
@@ -88,10 +89,12 @@ Alpine.data('chatManager', () => ({
|
||||
|
||||
async sendMessage() {
|
||||
const message = this.inputMessage.trim();
|
||||
if (!message || this.loading) return;
|
||||
if ((!message && this.selectedImages.length === 0) || this.loading) return;
|
||||
|
||||
// Update State
|
||||
const images = [...this.selectedImages];
|
||||
this.inputMessage = '';
|
||||
this.selectedImages = [];
|
||||
this.loading = true;
|
||||
this.error = '';
|
||||
|
||||
@@ -121,6 +124,7 @@ Alpine.data('chatManager', () => ({
|
||||
role: 'user',
|
||||
thinking: '',
|
||||
content: message,
|
||||
images: images,
|
||||
created_at: new Date().toISOString(),
|
||||
});
|
||||
currentChat.message_count += 1;
|
||||
@@ -128,7 +132,7 @@ Alpine.data('chatManager', () => ({
|
||||
try {
|
||||
await sendMessage(
|
||||
this.selectedChatID === IN_PROGRESS_UUID ? '' : this.selectedChatID,
|
||||
{ model: this.selectedModel, prompt: message },
|
||||
{ model: this.selectedModel, prompt: message, images },
|
||||
(chunk: MessageChunk) => {
|
||||
if (chunk.chat) this.activeStreamChatID = chunk.chat.id;
|
||||
this.applyMessageChunk(chunk);
|
||||
|
||||
@@ -16,6 +16,7 @@ export interface Message {
|
||||
role: 'user' | 'assistant';
|
||||
thinking: string;
|
||||
content: string;
|
||||
images?: string[];
|
||||
status?: MessageStatus;
|
||||
stats?: MessageStats;
|
||||
}
|
||||
@@ -69,6 +70,7 @@ export interface GenerateImageRequest {
|
||||
export interface GenerateTextRequest {
|
||||
model: string;
|
||||
prompt: string;
|
||||
images?: string[];
|
||||
}
|
||||
|
||||
export interface ChatListResponse {
|
||||
|
||||
Reference in New Issue
Block a user