Skip to content

Commit 93d3174

Browse files
committed
Add diagram/wireframe quick-analysis prompts for attached images
Vision models already understand any attached image - this just saves retyping a good prompt for the common case of attaching a UI mockup, wireframe, or diagram. An "Analyze as" menu next to image attachments fills the composer with a preset prompt: describe the UI, convert to a Mermaid diagram, generate React+Tailwind code from it, list components, or review it for usability/accessibility issues.
1 parent 157867d commit 93d3174

3 files changed

Lines changed: 79 additions & 1 deletion

File tree

‎README.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ Beyond chat, Modelforge includes an **agentic mode** — the model can read/writ
2828

2929
**Chat & providers**
3030
- Local Ollama models, OpenAI, and Anthropic in one interface, with token-by-token streaming.
31-
- Vision support — attach images (or extract frames from a video) for models that can see them.
31+
- Vision support — attach images (or extract frames from a video) for models that can see them. When an image is attached, an **"Analyze as..."** menu fills the composer with a ready-made prompt for common diagram/wireframe tasks — describe the UI, convert it to a Mermaid diagram, generate React + Tailwind code from it, list its components, or review it for usability/accessibility issues.
3232
- Live token usage and estimated cost per message and per session (Ollama is free/local; cloud providers show a running estimate).
3333

3434
**Organization**

‎frontend/src/lib/translations.ts‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,12 @@ export interface Dictionary {
121121
modelsDirApplied: string;
122122
modelsDirFailed: string;
123123
modelsDirExternalWarning: string;
124+
analyzeAs: string;
125+
analyzeDescribeUI: string;
126+
analyzeToMermaid: string;
127+
analyzeToCode: string;
128+
analyzeListComponents: string;
129+
analyzeFindIssues: string;
124130
resetToDefault: string;
125131
usingCustomPrompt: string;
126132
dataManagement: string;
@@ -280,6 +286,12 @@ export const en: Dictionary = {
280286
modelsDirApplied: "Applied — Ollama restarted with the new location.",
281287
modelsDirFailed: "Couldn't restart Ollama with the new location. Try starting it manually.",
282288
modelsDirExternalWarning: "Saved, but Ollama is running outside this app — restart it manually (with OLLAMA_MODELS set to this folder) for the change to take effect.",
289+
analyzeAs: "Analyze as...",
290+
analyzeDescribeUI: "Describe UI/wireframe",
291+
analyzeToMermaid: "Convert to Mermaid diagram",
292+
analyzeToCode: "Convert to React + Tailwind code",
293+
analyzeListComponents: "List UI components",
294+
analyzeFindIssues: "Find usability/accessibility issues",
283295
resetToDefault: "Reset to default",
284296
usingCustomPrompt: "Custom prompt for this chat",
285297
dataManagement: "Data management",
@@ -447,6 +459,12 @@ export const tr: Dictionary = {
447459
modelsDirApplied: "Uygulandı — Ollama yeni konumla yeniden başlatıldı.",
448460
modelsDirFailed: "Ollama yeni konumla yeniden başlatılamadı. Elle başlatmayı deneyin.",
449461
modelsDirExternalWarning: "Kaydedildi, ancak Ollama bu uygulamanın dışında çalışıyor — değişikliğin etkili olması için Ollama'yı bu klasör OLLAMA_MODELS olarak ayarlanmış şekilde elle yeniden başlatın.",
462+
analyzeAs: "Şu şekilde analiz et...",
463+
analyzeDescribeUI: "UI/wireframe'i betimle",
464+
analyzeToMermaid: "Mermaid diyagramına dönüştür",
465+
analyzeToCode: "React + Tailwind koduna dönüştür",
466+
analyzeListComponents: "UI bileşenlerini listele",
467+
analyzeFindIssues: "Kullanılabilirlik/erişilebilirlik sorunlarını bul",
450468
resetToDefault: "Varsayılana dön",
451469
usingCustomPrompt: "Bu sohbet için özel istem",
452470
dataManagement: "Veri yönetimi",

‎frontend/src/pages/Chat.tsx‎

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,42 @@ const RAG_THRESHOLD_CHARS = 20_000;
9090
// click each time — write_file and run_command always require explicit
9191
// per-call approval since they have real, potentially irreversible effects.
9292
const READ_ONLY_TOOLS = new Set(["read_file", "list_dir", "search_files"]);
93+
94+
// Vision models can already reason over any attached image — these just save
95+
// re-typing a good prompt for the common "I attached a diagram/wireframe"
96+
// case. Selecting one fills the composer; the user can still edit before sending.
97+
interface DiagramPromptPreset {
98+
id: string;
99+
labelKey: "analyzeDescribeUI" | "analyzeToMermaid" | "analyzeToCode" | "analyzeListComponents" | "analyzeFindIssues";
100+
prompt: string;
101+
}
102+
const DIAGRAM_PROMPT_PRESETS: DiagramPromptPreset[] = [
103+
{
104+
id: "describe",
105+
labelKey: "analyzeDescribeUI",
106+
prompt: "Describe this UI/wireframe in detail: overall layout, every visible component, and how they're organized/hierarchical.",
107+
},
108+
{
109+
id: "mermaid",
110+
labelKey: "analyzeToMermaid",
111+
prompt: "Convert this diagram into a Mermaid diagram definition (pick whichever Mermaid diagram type — flowchart, sequence, class, etc. — best fits what's shown).",
112+
},
113+
{
114+
id: "code",
115+
labelKey: "analyzeToCode",
116+
prompt: "Generate React + Tailwind CSS code that reproduces this UI mockup/wireframe as closely as possible.",
117+
},
118+
{
119+
id: "components",
120+
labelKey: "analyzeListComponents",
121+
prompt: "List every distinct UI component visible in this image, grouped by type (buttons, inputs, navigation, cards, etc.).",
122+
},
123+
{
124+
id: "issues",
125+
labelKey: "analyzeFindIssues",
126+
prompt: "Review this UI/wireframe for usability or accessibility issues and suggest concrete improvements.",
127+
},
128+
];
93129
// Caps how many automatic tool-result -> model-continuation round trips can
94130
// happen for a single user turn, so a model that keeps calling tools without
95131
// ever producing a final answer can't loop indefinitely.
@@ -551,6 +587,11 @@ export default function Chat() {
551587
setImageAttachments((prev) => prev.filter((f) => f.path !== path));
552588
}
553589

590+
function applyDiagramPreset(prompt: string) {
591+
setInput(prompt);
592+
textareaRef.current?.focus();
593+
}
594+
554595
async function handleAttachFolder() {
555596
const result = await window.api.files.openFolderAndRead();
556597
if (!result || result.files.length === 0) return;
@@ -1528,6 +1569,25 @@ export default function Chat() {
15281569
</button>
15291570
</div>
15301571
))}
1572+
{imageAttachments.length > 0 && (
1573+
<DropdownMenu>
1574+
<DropdownMenuTrigger
1575+
render={
1576+
<button className="flex items-center gap-1 rounded-md border border-dashed border-border px-2 py-1 text-xs text-muted-foreground hover:bg-muted">
1577+
<Sparkles className="size-3" />
1578+
{t.analyzeAs}
1579+
</button>
1580+
}
1581+
/>
1582+
<DropdownMenuContent>
1583+
{DIAGRAM_PROMPT_PRESETS.map((preset) => (
1584+
<DropdownMenuItem key={preset.id} onClick={() => applyDiagramPreset(preset.prompt)}>
1585+
{t[preset.labelKey]}
1586+
</DropdownMenuItem>
1587+
))}
1588+
</DropdownMenuContent>
1589+
</DropdownMenu>
1590+
)}
15311591
{indexingFolder && (
15321592
<div className="flex items-center gap-1.5 rounded-md bg-muted px-2 py-1 text-xs text-muted-foreground">
15331593
<Loader2 className="size-3 animate-spin" />

0 commit comments

Comments
 (0)