← back to Exo
Fix image models through dashboard (#1746)
ff4d20eed928a55bb268d8ce6276741ae12cd0ec · 2026-03-17 11:22:01 +0000 · ciaranbor
## Motivation
Image generation/editing from the dashboard was broken. ChatForm
bypassed the parent's model-launch logic, so image requests fell through
to text chat.
## Changes
- Moved image routing logic from ChatForm.svelte to +page.svelte
(routeMessage())
- ChatForm now always delegates to parent via onAutoSend (made required)
- Fixed missing updateActiveConversation() call on message retry
## Why It Works
All sends now go through the parent's launch-then-route path, so image
models get launched before the request is dispatched to the correct
endpoint.
Files touched
M dashboard/src/lib/components/ChatForm.svelteM dashboard/src/lib/stores/app.svelte.tsM dashboard/src/routes/+page.svelte
Diff
commit ff4d20eed928a55bb268d8ce6276741ae12cd0ec
Author: ciaranbor <81697641+ciaranbor@users.noreply.github.com>
Date: Tue Mar 17 11:22:01 2026 +0000
Fix image models through dashboard (#1746)
## Motivation
Image generation/editing from the dashboard was broken. ChatForm
bypassed the parent's model-launch logic, so image requests fell through
to text chat.
## Changes
- Moved image routing logic from ChatForm.svelte to +page.svelte
(routeMessage())
- ChatForm now always delegates to parent via onAutoSend (made required)
- Fixed missing updateActiveConversation() call on message retry
## Why It Works
All sends now go through the parent's launch-then-route path, so image
models get launched before the request is dispatched to the correct
endpoint.
---
dashboard/src/lib/components/ChatForm.svelte | 50 ++----------------------
dashboard/src/lib/stores/app.svelte.ts | 1 +
dashboard/src/routes/+page.svelte | 57 ++++++++++++++++++++++++++--
3 files changed, 58 insertions(+), 50 deletions(-)
diff --git a/dashboard/src/lib/components/ChatForm.svelte b/dashboard/src/lib/components/ChatForm.svelte
index b5cc7166..e768ce33 100644
--- a/dashboard/src/lib/components/ChatForm.svelte
+++ b/dashboard/src/lib/components/ChatForm.svelte
@@ -1,9 +1,6 @@
<script lang="ts">
import {
isLoading,
- sendMessage,
- generateImage,
- editImage,
editingImage,
clearEditingImage,
selectedChatModel,
@@ -28,7 +25,7 @@
modelTasks?: Record<string, string[]>;
modelCapabilities?: Record<string, string[]>;
onSend?: () => void;
- onAutoSend?: (
+ onAutoSend: (
content: string,
files?: {
id: string;
@@ -216,49 +213,10 @@
uploadedFiles = [];
resetTextareaHeight();
- // When onAutoSend is provided, the parent controls all send logic
- // (including launching non-running models before sending)
- if (onAutoSend) {
- onAutoSend(content, files);
- onSend?.();
- setTimeout(() => textareaRef?.focus(), 10);
- return;
- }
-
- // Use image editing if in edit mode
- if (isEditMode && currentEditingImage && content) {
- editImage(content, currentEditingImage.imageDataUrl);
- }
- // If user attached an image with an ImageToImage model, use edit endpoint
- else if (
- currentModel &&
- modelSupportsImageEditing(currentModel) &&
- files.length > 0 &&
- content
- ) {
- // Use the first attached image for editing
- const imageFile = files[0];
- if (imageFile.preview) {
- editImage(content, imageFile.preview);
- }
- } else if (
- currentModel &&
- modelSupportsTextToImage(currentModel) &&
- content
- ) {
- // Use image generation for text-to-image models
- generateImage(content);
- } else {
- sendMessage(
- content,
- files,
- modelSupportsThinking() ? thinkingEnabled : null,
- );
- }
-
+ // Parent controls all send logic (including image routing,
+ // launching non-running models before sending, etc.)
+ onAutoSend(content, files);
onSend?.();
-
- // Refocus the textarea after sending
setTimeout(() => textareaRef?.focus(), 10);
}
diff --git a/dashboard/src/lib/stores/app.svelte.ts b/dashboard/src/lib/stores/app.svelte.ts
index c31da1a7..44fd2962 100644
--- a/dashboard/src/lib/stores/app.svelte.ts
+++ b/dashboard/src/lib/stores/app.svelte.ts
@@ -1577,6 +1577,7 @@ class AppStore {
// Remove messages after user message (including the user message for image requests
// since generateImage/editImage will re-add it)
this.messages = this.messages.slice(0, lastUserIndex);
+ this.updateActiveConversation();
switch (requestType) {
case "image-generation":
diff --git a/dashboard/src/routes/+page.svelte b/dashboard/src/routes/+page.svelte
index 7f8a0382..262a8338 100644
--- a/dashboard/src/routes/+page.svelte
+++ b/dashboard/src/routes/+page.svelte
@@ -42,6 +42,9 @@
setSelectedChatModel,
selectedChatModel,
sendMessage,
+ generateImage,
+ editImage,
+ editingImage,
messages,
debugMode,
toggleDebugMode,
@@ -834,6 +837,52 @@
if (!model?.tasks) return false;
return model.tasks.includes("ImageToImage");
}
+
+ // Route a message to the correct endpoint based on model capabilities.
+ // Image models go to generateImage/editImage; text models go to sendMessage.
+ function routeMessage(
+ content: string,
+ files?: {
+ id: string;
+ name: string;
+ type: string;
+ textContent?: string;
+ preview?: string;
+ }[],
+ ) {
+ const model = selectedChatModel();
+ if (!model) {
+ sendMessage(content, files, null);
+ return;
+ }
+
+ const currentEditImage = editingImage();
+
+ // Image editing mode (explicit edit or attached image with ImageToImage model)
+ if (currentEditImage && content && modelSupportsImageEditing(model)) {
+ editImage(content, currentEditImage.imageDataUrl);
+ return;
+ }
+ if (
+ modelSupportsImageEditing(model) &&
+ files?.length &&
+ files[0].preview &&
+ content
+ ) {
+ editImage(content, files[0].preview);
+ return;
+ }
+
+ // Text-to-image generation
+ if (modelSupportsImageGeneration(model) && content) {
+ generateImage(content);
+ return;
+ }
+
+ // Default: text chat
+ sendMessage(content, files, null);
+ }
+
let selectedSharding = $state<"Pipeline" | "Tensor">("Pipeline");
type InstanceMeta = "MlxRing" | "MlxJaccl";
@@ -2851,7 +2900,7 @@
// Running model is same or better tier — use it directly
setSelectedChatModel(bestRunning.id);
if (!chatStarted) createConversation();
- sendMessage(content, files);
+ routeMessage(content, files);
return;
}
}
@@ -2868,7 +2917,7 @@
if (hasRunningInstance(autoModel.id)) {
setSelectedChatModel(autoModel.id);
if (!chatStarted) createConversation();
- sendMessage(content, files);
+ routeMessage(content, files);
return;
}
@@ -3021,7 +3070,7 @@
if (pendingAutoMessage) {
const msg = pendingAutoMessage;
pendingAutoMessage = null;
- sendMessage(msg.content, msg.files);
+ routeMessage(msg.content, msg.files);
}
return;
}
@@ -3100,7 +3149,7 @@
// Model is selected and running — send directly
if (model && hasRunningInstance(model)) {
chatLaunchState = "ready";
- sendMessage(content, files, null);
+ routeMessage(content, files);
return;
}
← 7ed46395 use structured concurrency in download coordinator (#1722)
·
back to Exo
·
Fix exo bench prefill and decode tps (#1749) 6ee67314 →