Add image upload and vision analysis to Ask Simba chat
Users can now attach images in the web chat for Simba to analyze using Ollama's gemma3 vision model. Images are stored in Garage (S3-compatible) and displayed in chat history. Also fixes aerich migration config by extracting TORTOISE_CONFIG into a standalone config/db.py module, removing the stale aerich_config.py, and adding missing MODELS_STATE to migration 3. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -13,6 +13,7 @@ interface Message {
|
||||
text: string;
|
||||
speaker: "user" | "simba";
|
||||
created_at: string;
|
||||
image_key?: string | null;
|
||||
}
|
||||
|
||||
interface Conversation {
|
||||
@@ -121,17 +122,52 @@ class ConversationService {
|
||||
return await response.json();
|
||||
}
|
||||
|
||||
async uploadImage(
|
||||
file: File,
|
||||
conversationId: string,
|
||||
): Promise<{ image_key: string; image_url: string }> {
|
||||
const formData = new FormData();
|
||||
formData.append("file", file);
|
||||
formData.append("conversation_id", conversationId);
|
||||
|
||||
const response = await userService.fetchWithRefreshToken(
|
||||
`${this.conversationBaseUrl}/upload-image`,
|
||||
{
|
||||
method: "POST",
|
||||
body: formData,
|
||||
},
|
||||
{ skipContentType: true },
|
||||
);
|
||||
|
||||
if (!response.ok) {
|
||||
const data = await response.json();
|
||||
throw new Error(data.error || "Failed to upload image");
|
||||
}
|
||||
|
||||
return await response.json();
|
||||
}
|
||||
|
||||
getImageUrl(imageKey: string): string {
|
||||
return `/api/conversation/image/${imageKey}`;
|
||||
}
|
||||
|
||||
async streamQuery(
|
||||
query: string,
|
||||
conversation_id: string,
|
||||
onEvent: SSEEventCallback,
|
||||
signal?: AbortSignal,
|
||||
imageKey?: string,
|
||||
): Promise<void> {
|
||||
const body: Record<string, string> = { query, conversation_id };
|
||||
if (imageKey) {
|
||||
body.image_key = imageKey;
|
||||
}
|
||||
|
||||
const response = await userService.fetchWithRefreshToken(
|
||||
`${this.conversationBaseUrl}/stream-query`,
|
||||
{
|
||||
method: "POST",
|
||||
body: JSON.stringify({ query, conversation_id }),
|
||||
body: JSON.stringify(body),
|
||||
signal,
|
||||
},
|
||||
);
|
||||
|
||||
@@ -106,14 +106,15 @@ class UserService {
|
||||
async fetchWithRefreshToken(
|
||||
url: string,
|
||||
options: RequestInit = {},
|
||||
{ skipContentType = false }: { skipContentType?: boolean } = {},
|
||||
): Promise<Response> {
|
||||
const refreshToken = localStorage.getItem("refresh_token");
|
||||
|
||||
// Add authorization header
|
||||
const headers = {
|
||||
"Content-Type": "application/json",
|
||||
...(options.headers || {}),
|
||||
...(refreshToken && { Authorization: `Bearer ${refreshToken}` }),
|
||||
const headers: Record<string, string> = {
|
||||
...(skipContentType ? {} : { "Content-Type": "application/json" }),
|
||||
...((options.headers as Record<string, string>) || {}),
|
||||
...(refreshToken ? { Authorization: `Bearer ${refreshToken}` } : {}),
|
||||
};
|
||||
|
||||
let response = await fetch(url, { ...options, headers });
|
||||
|
||||
Reference in New Issue
Block a user