From 0d50a179aa5af11d4cafe955e6b2a345eb592dfc Mon Sep 17 00:00:00 2001 From: Your Name Date: Fri, 19 Dec 2025 12:07:06 +0800 Subject: [PATCH 1/3] fix: skip image parts for DeepSeek to avoid unknown variant "image_url" --- app/api/chat/route.ts | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/app/api/chat/route.ts b/app/api/chat/route.ts index cc844658..e2374d34 100644 --- a/app/api/chat/route.ts +++ b/app/api/chat/route.ts @@ -355,12 +355,17 @@ ${userInputText} ] // Add image parts back - for (const filePart of fileParts) { - contentParts.push({ - type: "image", - image: filePart.url, - mimeType: filePart.mediaType, - }) + const allowImages = + typeof modelId === "string" && + !modelId.toLowerCase().includes("deepseek") + if (allowImages) { + for (const filePart of fileParts) { + contentParts.push({ + type: "image", + image: filePart.url, + mimeType: filePart.mediaType, + }) + } } enhancedMessages = [ From 691c7e34bf67c30fad77c4ba242be7ae0ad94515 Mon Sep 17 00:00:00 2001 From: Your Name Date: Fri, 19 Dec 2025 14:12:31 +0800 Subject: [PATCH 2/3] feat: image capability detection and user-friendly errors; warn and filter images for unsupported models --- components/chat-input.tsx | 21 ++++++++-- components/chat-panel.tsx | 80 +++++++++++++++++++++++++++++---------- lib/model-capabilities.ts | 76 +++++++++++++++++++++++++++++++++++++ 3 files changed, 155 insertions(+), 22 deletions(-) create mode 100644 lib/model-capabilities.ts diff --git a/components/chat-input.tsx b/components/chat-input.tsx index b63bbd12..95d22193 100644 --- a/components/chat-input.tsx +++ b/components/chat-input.tsx @@ -58,6 +58,7 @@ interface ValidationResult { function validateFiles( newFiles: File[], existingCount: number, + imageSupported: boolean, ): ValidationResult { const errors: string[] = [] const validFiles: File[] = [] @@ -74,7 +75,10 @@ function validateFiles( errors.push(`Only ${availableSlots} more file(s) allowed`) break } - if (!isValidFileType(file)) { + if ( + !isValidFileType(file) || + (!imageSupported && file.type.startsWith("image/")) + ) { errors.push(`"${file.name}" is not a supported file type`) continue } @@ -137,6 +141,7 @@ interface ChatInputProps { error?: Error | null minimalStyle?: boolean onMinimalStyleChange?: (value: boolean) => void + imageSupported?: boolean } export function ChatInput({ @@ -154,6 +159,7 @@ export function ChatInput({ error = null, minimalStyle = false, onMinimalStyleChange = () => {}, + imageSupported = true, }: ChatInputProps) { const { diagramHistory, @@ -224,6 +230,7 @@ export function ChatInput({ const { validFiles, errors } = validateFiles( imageFiles, files.length, + imageSupported, ) showValidationErrors(errors) if (validFiles.length > 0) { @@ -234,7 +241,11 @@ export function ChatInput({ const handleFileChange = (e: React.ChangeEvent) => { const newFiles = Array.from(e.target.files || []) - const { validFiles, errors } = validateFiles(newFiles, files.length) + const { validFiles, errors } = validateFiles( + newFiles, + files.length, + imageSupported, + ) showValidationErrors(errors) if (validFiles.length > 0) { onFileChange([...files, ...validFiles]) @@ -428,7 +439,11 @@ export function ChatInput({ size="sm" onClick={triggerFileInput} disabled={isDisabled} - tooltipContent="Upload file (image, PDF, text)" + tooltipContent={ + imageSupported + ? "Upload file (image, PDF, text)" + : "Upload file (PDF, text)" + } className="h-8 w-8 p-0 text-muted-foreground hover:text-foreground" > diff --git a/components/chat-panel.tsx b/components/chat-panel.tsx index b7408b0b..ccd0fa00 100644 --- a/components/chat-panel.tsx +++ b/components/chat-panel.tsx @@ -23,6 +23,10 @@ import { SettingsDialog } from "@/components/settings-dialog" import { useDiagram } from "@/contexts/diagram-context" import { getAIConfig } from "@/lib/ai-config" import { findCachedResponse } from "@/lib/cached-responses" +import { + resolveImageSupport, + setCachedImageCapability, +} from "@/lib/model-capabilities" import { isPdfFile, isTextFile } from "@/lib/pdf-utils" import { type FileData, useFileProcessor } from "@/lib/use-file-processor" import { useQuotaManager } from "@/lib/use-quota-manager" @@ -581,9 +585,15 @@ Continue from EXACTLY where you stopped.`, "Output was truncated before the diagram could be generated. Try a simpler request or increase the maxOutputLength." } - // Translate image not supported error - if (friendlyMessage.includes("image content block")) { + if ( + friendlyMessage.includes("image content block") || + friendlyMessage.toLowerCase().includes("image_url") || + friendlyMessage.toLowerCase().includes("unknown variant") + ) { friendlyMessage = "This model doesn't support image input." + // Cache capability as unsupported for current provider/model + const cfg = getAIConfig() + setCachedImageCapability(cfg.aiProvider, cfg.aiModel, false) } // Add system message for error so it can be cleared @@ -959,7 +969,29 @@ Continue from EXACTLY where you stopped.`, // Check all quota limits if (!checkAllQuotaLimits()) return - sendChatMessage(parts, chartXml, previousXml, sessionId) + { + const config = getAIConfig() + const provider = config.aiProvider + const modelId = config.aiModel + const canUseImages = resolveImageSupport(provider, modelId) + const hasImage = + files.some((f) => !isPdfFile(f) && !isTextFile(f)) && + parts.some((p) => p.type === "file") + if (hasImage && !canUseImages) { + const filtered = parts.filter((p) => p.type !== "file") + sendChatMessage( + filtered, + chartXml, + previousXml, + sessionId, + ) + toast.warning( + "当前模型不支持图片输入,已忽略上传的图片", + ) + } else { + sendChatMessage(parts, chartXml, previousXml, sessionId) + } + } // Token count is tracked in onFinish with actual server usage setInput("") @@ -1387,22 +1419,32 @@ Continue from EXACTLY where you stopped.`,
- + {(() => { + const cfg = getAIConfig() + const imgCap = resolveImageSupport( + cfg.aiProvider, + cfg.aiModel, + ) + return ( + + ) + })()}
{ + if (typeof window === "undefined") return {} + try { + const raw = localStorage.getItem(LS_KEY) + return raw ? (JSON.parse(raw) as Record) : {} + } catch { + return {} + } +} + +function writeCache(cache: Record) { + if (typeof window === "undefined") return + try { + localStorage.setItem(LS_KEY, JSON.stringify(cache)) + } catch { + // ignore + } +} + +function capabilityKey(provider?: string, modelId?: string): string { + return `${(provider || "").toLowerCase()}::${(modelId || "").toLowerCase()}` +} + +export function getCachedImageCapability( + provider?: string, + modelId?: string, +): boolean | undefined { + const cache = readCache() + const key = capabilityKey(provider, modelId) + return key in cache ? cache[key] : undefined +} + +export function setCachedImageCapability( + provider?: string, + modelId?: string, + supported?: boolean, +) { + if (supported === undefined) return + const cache = readCache() + cache[capabilityKey(provider, modelId)] = supported + writeCache(cache) +} + +export function resolveImageSupport( + provider?: string, + modelId?: string, +): boolean { + const cached = getCachedImageCapability(provider, modelId) + if (cached !== undefined) return cached + return supportsImages(provider, modelId) +} From 5f71b2783f69a18c25412888c36ff845e4b7c20b Mon Sep 17 00:00:00 2001 From: Your Name Date: Fri, 19 Dec 2025 15:11:42 +0800 Subject: [PATCH 3/3] style: enhance UI with modern glassmorphism, animations and refined spacing --- app/globals.css | 29 +++++++++++++++++++--------- components/chat-input.tsx | 30 ++++++++++++++++++----------- components/chat-message-display.tsx | 10 +++++----- components/chat-panel.tsx | 9 +++++---- 4 files changed, 49 insertions(+), 29 deletions(-) diff --git a/app/globals.css b/app/globals.css index feaea6da..8262cdff 100644 --- a/app/globals.css +++ b/app/globals.css @@ -208,11 +208,11 @@ } .animate-fade-in { - animation: fadeIn 0.3s ease-out forwards; + animation: fadeIn 0.4s cubic-bezier(0.16, 1, 0.3, 1) forwards; } .animate-slide-in-right { - animation: slideInRight 0.3s ease-out forwards; + animation: slideInRight 0.4s cubic-bezier(0.16, 1, 0.3, 1) forwards; } /* Message bubble animations */ @@ -228,22 +228,33 @@ } .animate-message-in { - animation: messageIn 0.25s ease-out forwards; + animation: messageIn 0.35s cubic-bezier(0.16, 1, 0.3, 1) forwards; } /* Subtle floating shadow for cards */ .shadow-soft { box-shadow: - 0 1px 2px oklch(0.23 0.02 260 / 0.04), - 0 4px 12px oklch(0.23 0.02 260 / 0.06), - 0 8px 24px oklch(0.23 0.02 260 / 0.04); + 0 2px 4px oklch(0.23 0.02 260 / 0.02), + 0 6px 12px oklch(0.23 0.02 260 / 0.03), + 0 12px 24px oklch(0.23 0.02 260 / 0.03); } .shadow-soft-lg { box-shadow: - 0 2px 4px oklch(0.23 0.02 260 / 0.04), - 0 8px 20px oklch(0.23 0.02 260 / 0.08), - 0 16px 40px oklch(0.23 0.02 260 / 0.06); + 0 4px 8px oklch(0.23 0.02 260 / 0.03), + 0 12px 24px oklch(0.23 0.02 260 / 0.05), + 0 24px 48px oklch(0.23 0.02 260 / 0.04); +} + +/* Gradient borders */ +.border-gradient-primary { + position: relative; + background: + linear-gradient(var(--background), var(--background)) padding-box, + linear-gradient(135deg, oklch(0.55 0.18 265), oklch(0.6 0.2 290)) + border-box; + border: 2px solid transparent; + border-radius: var(--radius); } /* Gradient text utility */ diff --git a/components/chat-input.tsx b/components/chat-input.tsx index 95d22193..48d05e5a 100644 --- a/components/chat-input.tsx +++ b/components/chat-input.tsx @@ -294,6 +294,7 @@ export function ChatInput({ const { validFiles, errors } = validateFiles( supportedFiles, files.length, + imageSupported, ) showValidationErrors(errors) if (validFiles.length > 0) { @@ -344,7 +345,7 @@ export function ChatInput({ /> {/* Action bar */} -
+
{/* Left actions */}
-
+
- + Use minimal for faster generation (no colors) @@ -405,7 +406,7 @@ export function ChatInput({ onClick={() => onToggleHistory(true)} disabled={isDisabled || diagramHistory.length === 0} tooltipContent="Diagram history" - className="h-8 w-8 p-0 text-muted-foreground hover:text-foreground" + className="h-8 w-8 p-0 text-muted-foreground hover:text-foreground transition-colors" > @@ -417,7 +418,7 @@ export function ChatInput({ onClick={() => setShowSaveDialog(true)} disabled={isDisabled} tooltipContent="Save diagram" - className="h-8 w-8 p-0 text-muted-foreground hover:text-foreground" + className="h-8 w-8 p-0 text-muted-foreground hover:text-foreground transition-colors" > @@ -444,7 +445,7 @@ export function ChatInput({ ? "Upload file (image, PDF, text)" : "Upload file (PDF, text)" } - className="h-8 w-8 p-0 text-muted-foreground hover:text-foreground" + className="h-8 w-8 p-0 text-muted-foreground hover:text-foreground transition-colors" > @@ -463,9 +464,16 @@ export function ChatInput({