mirror of
https://github.com/DayuanJiang/next-ai-draw-io.git
synced 2026-10-11 03:59:58 +08:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
97a1c0b1d7 |
@@ -1,10 +1,10 @@
|
||||
import {
|
||||
ChevronDown,
|
||||
Download,
|
||||
FilePlus,
|
||||
MessageSquareDashed,
|
||||
PanelRightClose,
|
||||
Settings2,
|
||||
SquarePen,
|
||||
Settings,
|
||||
} from "lucide-react"
|
||||
import { useRef } from "react"
|
||||
import { DevXmlSimulator } from "@/components/dev-xml-simulator"
|
||||
@@ -139,14 +139,14 @@ export function ChatPanel({
|
||||
disabled={engine.isBusy}
|
||||
data-testid="new-chat-button"
|
||||
>
|
||||
<SquarePen />
|
||||
<FilePlus />
|
||||
</IconButton>
|
||||
<IconButton
|
||||
label={dict.nav.settings}
|
||||
onClick={() => openSettings()}
|
||||
data-testid="settings-button"
|
||||
>
|
||||
<Settings2 />
|
||||
<Settings />
|
||||
</IconButton>
|
||||
{!mobile && (
|
||||
<IconButton
|
||||
@@ -173,7 +173,7 @@ export function ChatPanel({
|
||||
/>
|
||||
)}
|
||||
|
||||
<div className="shrink-0 p-2.5 pt-1">
|
||||
<div className="shrink-0 px-4 pt-1 pb-3">
|
||||
<Composer
|
||||
placeholder={
|
||||
engine.messages.length > 0 &&
|
||||
|
||||
@@ -18,14 +18,18 @@ interface Example {
|
||||
file?: { path: string; name: string; type: string }
|
||||
}
|
||||
|
||||
/** Secondary start buttons: outlined, so they read as clickable */
|
||||
const startButtonClass =
|
||||
"inline-flex h-8 items-center gap-1.5 rounded-lg border border-border bg-card px-3 text-[13px] text-foreground transition-colors hover:bg-accent [&_svg]:text-muted-foreground"
|
||||
|
||||
async function loadExampleFile(file: NonNullable<Example["file"]>) {
|
||||
const response = await fetch(getAssetUrl(file.path))
|
||||
const blob = await response.blob()
|
||||
return new File([blob], file.name, { type: file.type })
|
||||
}
|
||||
|
||||
/** One start-screen choice: a small picture, a title and one line */
|
||||
function ExampleCard({
|
||||
/** One start-screen example: a small picture over its title */
|
||||
function ExampleTile({
|
||||
onClick,
|
||||
title,
|
||||
description,
|
||||
@@ -35,6 +39,7 @@ function ExampleCard({
|
||||
}: {
|
||||
onClick: () => void
|
||||
title: string
|
||||
/** Shown as the tooltip: the title alone names the example */
|
||||
description: string
|
||||
picture: React.ReactNode
|
||||
/** An icon on a tinted tile instead of a diagram on paper */
|
||||
@@ -45,24 +50,20 @@ function ExampleCard({
|
||||
<button
|
||||
type="button"
|
||||
onClick={onClick}
|
||||
className="group/example flex min-w-0 items-center gap-2.5 rounded-xl border border-border bg-card p-1.5 pr-2.5 text-left transition-colors hover:border-foreground/25"
|
||||
title={description}
|
||||
className="group/example flex min-w-0 flex-col gap-2 rounded-xl border border-border bg-card p-1.5 pb-2.5 text-left transition-colors hover:border-foreground/25"
|
||||
data-testid={testId}
|
||||
>
|
||||
<span
|
||||
className={cn(
|
||||
"flex h-12 w-16 shrink-0 items-center justify-center overflow-hidden rounded-lg",
|
||||
"flex h-16 w-full items-center justify-center overflow-hidden rounded-lg",
|
||||
plain ? "bg-muted" : "sheet-light bg-white",
|
||||
)}
|
||||
>
|
||||
{picture}
|
||||
</span>
|
||||
<span className="min-w-0 flex-1">
|
||||
<span className="line-clamp-2 text-[13px] leading-snug font-medium text-foreground">
|
||||
{title}
|
||||
</span>
|
||||
<span className="mt-0.5 block truncate text-xs text-muted-foreground">
|
||||
{description}
|
||||
</span>
|
||||
<span className="px-1 text-[13px] leading-snug font-medium text-foreground">
|
||||
{title}
|
||||
</span>
|
||||
</button>
|
||||
)
|
||||
@@ -92,6 +93,7 @@ export function LobbyHero({
|
||||
key: "paper",
|
||||
title: dict.examples.paperToDiagram,
|
||||
description: dict.examples.paperDescription,
|
||||
image: "/paper_demo.svg",
|
||||
prompt: "Summarize this paper as a diagram",
|
||||
file: {
|
||||
path: "/chain-of-thought.txt",
|
||||
@@ -185,10 +187,12 @@ export function LobbyHero({
|
||||
placeholder={t.placeholder}
|
||||
/>
|
||||
|
||||
{/* Examples: one row of tiles; the ways to bring your own
|
||||
diagram follow as a separate group */}
|
||||
<div
|
||||
className={cn(
|
||||
"mt-5 grid gap-2",
|
||||
compact ? "grid-cols-1" : "grid-cols-3",
|
||||
compact ? "grid-cols-1" : "grid-cols-5",
|
||||
)}
|
||||
>
|
||||
{examples.map((example) =>
|
||||
@@ -209,7 +213,7 @@ export function LobbyHero({
|
||||
</span>
|
||||
</button>
|
||||
) : (
|
||||
<ExampleCard
|
||||
<ExampleTile
|
||||
key={example.key}
|
||||
onClick={() => pickExample(example)}
|
||||
title={example.title}
|
||||
@@ -221,7 +225,7 @@ export function LobbyHero({
|
||||
<img
|
||||
src={getAssetUrl(example.image)}
|
||||
alt=""
|
||||
className="max-h-10 max-w-[56px] object-contain transition-transform duration-300 group-hover/example:scale-[1.06]"
|
||||
className="max-h-14 max-w-[88%] object-contain transition-transform duration-300 group-hover/example:scale-[1.06]"
|
||||
/>
|
||||
) : (
|
||||
<FileText className="size-5 text-muted-foreground" />
|
||||
@@ -231,48 +235,34 @@ export function LobbyHero({
|
||||
/>
|
||||
),
|
||||
)}
|
||||
{!compact && (
|
||||
<ExampleCard
|
||||
onClick={onOpenFile}
|
||||
title={dict.workspace.openFile}
|
||||
description={t.openFileHint}
|
||||
picture={
|
||||
<FileUp className="size-5 text-muted-foreground" />
|
||||
}
|
||||
plain
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<p className="mt-3 text-center text-xs text-faint">
|
||||
<p className="mt-2.5 text-center text-xs text-faint">
|
||||
{dict.examples.cachedNote}
|
||||
</p>
|
||||
|
||||
<div className="mt-6 flex flex-wrap items-center justify-center gap-x-5 gap-y-2 text-[13px] text-muted-foreground">
|
||||
{/* Ways to start without an example, as plain buttons */}
|
||||
<div className="mt-6 flex flex-wrap items-center justify-center gap-2">
|
||||
{engine.sessions.length > 0 && (
|
||||
<SessionMenu align="center" onOpenFile={onOpenFile}>
|
||||
<button
|
||||
type="button"
|
||||
className="inline-flex items-center gap-1.5 hover:text-foreground"
|
||||
>
|
||||
<button type="button" className={startButtonClass}>
|
||||
<History className="size-4" />
|
||||
{t.recent}
|
||||
</button>
|
||||
</SessionMenu>
|
||||
)}
|
||||
{compact && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={onOpenFile}
|
||||
className="inline-flex items-center gap-1.5 hover:text-foreground"
|
||||
>
|
||||
<FileUp className="size-4" />
|
||||
{dict.workspace.openFile}
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
type="button"
|
||||
onClick={onOpenFile}
|
||||
className={startButtonClass}
|
||||
data-testid="open-file"
|
||||
>
|
||||
<FileUp className="size-4" />
|
||||
{dict.workspace.openFile}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={onDrawYourself}
|
||||
className="inline-flex items-center gap-1.5 hover:text-foreground"
|
||||
className={startButtonClass}
|
||||
data-testid="draw-yourself"
|
||||
>
|
||||
<Pencil className="size-4" />
|
||||
|
||||
@@ -2,7 +2,6 @@ import {
|
||||
AlertCircle,
|
||||
Check,
|
||||
ChevronDown,
|
||||
Code2,
|
||||
Copy,
|
||||
Library,
|
||||
PenTool,
|
||||
@@ -180,17 +179,23 @@ function VersionCard({
|
||||
</button>
|
||||
)
|
||||
|
||||
// What the AI wrote, on every card (main showed it on every tool call)
|
||||
const codeButton = (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowCode((v) => !v)}
|
||||
aria-label={showCode ? t.hideCode : t.showCode}
|
||||
title={showCode ? t.hideCode : t.showCode}
|
||||
className="inline-flex size-7 shrink-0 items-center justify-center rounded-lg text-muted-foreground hover:bg-accent hover:text-foreground"
|
||||
>
|
||||
<Code2 className="size-4" />
|
||||
</button>
|
||||
// What the AI wrote: a text link in the summary line, so the card's
|
||||
// only button is the action (undo or restore)
|
||||
const summaryLine = (
|
||||
<div className="text-xs text-muted-foreground">
|
||||
{summary}
|
||||
{/* The link wraps as one unit with its separator */}
|
||||
<span className="whitespace-nowrap">
|
||||
<span aria-hidden> · </span>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowCode((v) => !v)}
|
||||
className="underline decoration-foreground/30 underline-offset-2 hover:text-foreground hover:decoration-foreground"
|
||||
>
|
||||
{showCode ? t.hideCode : t.showCode}
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
)
|
||||
const codePanel = showCode && (
|
||||
<div className="relative border-t border-border bg-surface-1 px-3 py-2.5">
|
||||
@@ -231,15 +236,12 @@ function VersionCard({
|
||||
/>
|
||||
</button>
|
||||
{badge}
|
||||
<div className="min-w-0 flex-1" title={summary}>
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="truncate text-[13px] text-foreground">
|
||||
{title}
|
||||
</div>
|
||||
<div className="truncate text-xs text-muted-foreground">
|
||||
{summary}
|
||||
</div>
|
||||
{summaryLine}
|
||||
</div>
|
||||
{codeButton}
|
||||
{restoreButton}
|
||||
</div>
|
||||
{codePanel}
|
||||
@@ -271,17 +273,12 @@ function VersionCard({
|
||||
{t.compare}
|
||||
</span>
|
||||
</button>
|
||||
<div className="flex items-center gap-2.5 px-3 py-2.5">
|
||||
{/* Title row with the action; the summary runs under both */}
|
||||
<div className="grid grid-cols-[auto_minmax(0,1fr)_auto] items-center gap-x-2.5 px-3 py-2.5">
|
||||
{badge}
|
||||
<div className="min-w-0 flex-1" title={summary}>
|
||||
<div className="truncate text-[13px] font-medium text-foreground">
|
||||
{title}
|
||||
</div>
|
||||
<div className="line-clamp-2 text-xs text-muted-foreground">
|
||||
{summary}
|
||||
</div>
|
||||
<div className="truncate text-[13px] font-medium text-foreground">
|
||||
{title}
|
||||
</div>
|
||||
{codeButton}
|
||||
{canUndo ? (
|
||||
<button
|
||||
type="button"
|
||||
@@ -300,6 +297,9 @@ function VersionCard({
|
||||
) : (
|
||||
restoreButton
|
||||
)}
|
||||
<div className="col-span-2 col-start-2 mt-0.5">
|
||||
{summaryLine}
|
||||
</div>
|
||||
</div>
|
||||
{codePanel}
|
||||
</div>
|
||||
|
||||
@@ -220,14 +220,14 @@ export function ModelSelector({
|
||||
<span className="min-w-0 truncate text-xs">
|
||||
{selectedModel
|
||||
? shortModelName(selectedModel.modelId)
|
||||
: dict.modelConfig.default}
|
||||
: dict.modelConfig.serverDefault}
|
||||
</span>
|
||||
) : (
|
||||
// Keep an sr-only label for screen readers when hidden
|
||||
<span className="sr-only">
|
||||
{selectedModel
|
||||
? selectedModel.modelId
|
||||
: dict.modelConfig.default}
|
||||
: dict.modelConfig.serverDefault}
|
||||
</span>
|
||||
)}
|
||||
<ChevronDown className="h-3 w-3 flex-shrink-0 text-muted-foreground" />
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"use client"
|
||||
|
||||
import { MessageSquare, PanelRightOpen, Settings2, Shapes } from "lucide-react"
|
||||
import { MessageSquare, PanelRightOpen, Settings, Shapes } from "lucide-react"
|
||||
import { usePathname, useRouter } from "next/navigation"
|
||||
import { useEffect, useRef, useState } from "react"
|
||||
import { CanvasStage } from "@/components/canvas/canvas-stage"
|
||||
@@ -100,7 +100,7 @@ function StartBar({
|
||||
onClick={() => openSettings()}
|
||||
data-testid="settings-button"
|
||||
>
|
||||
<Settings2 />
|
||||
<Settings />
|
||||
</IconButton>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -72,7 +72,7 @@
|
||||
"replicateDescription": "Upload and replicate an existing flowchart",
|
||||
"creativeDrawing": "Creative drawing",
|
||||
"creativeDescription": "Draw something fun and creative",
|
||||
"cachedNote": "Examples open instantly and don't use your model.",
|
||||
"cachedNote": "Examples use saved answers: they appear instantly and don't call the AI.",
|
||||
"mcpServer": "MCP Server",
|
||||
"mcpDescription": "Use in Claude Desktop, VS Code & Cursor"
|
||||
},
|
||||
@@ -96,7 +96,7 @@
|
||||
"language": "Language",
|
||||
"languageDescription": "Choose your interface language.",
|
||||
"theme": "Theme",
|
||||
"themeDescription": "Light or dark interface. The diagram always stays on a white sheet, the way it exports.",
|
||||
"themeDescription": "Light or dark interface. The canvas follows the same theme.",
|
||||
"diagramStyle": "Diagram style",
|
||||
"diagramStyleDescription": "How the AI draws new diagrams: Styled uses colors and icons, Minimal keeps shapes plain.",
|
||||
"sendShortcut": "Send shortcut",
|
||||
@@ -400,7 +400,7 @@
|
||||
"searchModels": "Search models...",
|
||||
"noModelsFound": "No models found.",
|
||||
"default": "Default",
|
||||
"serverDefault": "Server Default",
|
||||
"serverDefault": "Server model",
|
||||
"serverModels": "Server Models",
|
||||
"userModels": "User Models",
|
||||
"configureModels": "Configure Models...",
|
||||
@@ -695,7 +695,7 @@
|
||||
},
|
||||
"lobby": {
|
||||
"title": "What would you like to draw?",
|
||||
"lede": "Describe a diagram, or upload a screenshot or PDF. You get a regular draw.io diagram that you can keep editing and export.",
|
||||
"lede": "Describe a diagram or upload a screenshot or PDF. You get a draw.io diagram you can keep editing and export.",
|
||||
"placeholder": "For example: a login sequence diagram with SMS codes and retries",
|
||||
"recent": "Recent diagrams",
|
||||
"drawYourself": "Start with a blank canvas",
|
||||
|
||||
@@ -72,7 +72,7 @@
|
||||
"replicateDescription": "既存のフローチャートをアップロードして複製",
|
||||
"creativeDrawing": "自由なイラスト",
|
||||
"creativeDescription": "楽しくてクリエイティブなものを描く",
|
||||
"cachedNote": "例はすぐに開き、モデルは使いません。",
|
||||
"cachedNote": "例は保存済みの回答を使うので、すぐに表示され、AI を呼び出しません。",
|
||||
"mcpServer": "MCP サーバー",
|
||||
"mcpDescription": "Claude Desktop、VS Code、Cursor で使用"
|
||||
},
|
||||
@@ -96,7 +96,7 @@
|
||||
"language": "言語",
|
||||
"languageDescription": "インターフェース言語を選択します。",
|
||||
"theme": "テーマ",
|
||||
"themeDescription": "画面をライトにするかダークにするか。図はエクスポートしたときと同じく、常に白い用紙の上に表示されます。",
|
||||
"themeDescription": "画面をライトにするかダークにするか。キャンバスも同じテーマになります。",
|
||||
"diagramStyle": "図のスタイル",
|
||||
"diagramStyleDescription": "AI が新しい図を描くときのスタイル。スタイル付きは色やアイコンを使い、ミニマルは素朴な図形だけにします。",
|
||||
"sendShortcut": "送信のショートカット",
|
||||
@@ -355,7 +355,7 @@
|
||||
"searchModels": "モデルを検索...",
|
||||
"noModelsFound": "モデルが見つかりません。",
|
||||
"default": "デフォルト",
|
||||
"serverDefault": "サーバーデフォルト",
|
||||
"serverDefault": "サーバーのモデル",
|
||||
"serverModels": "サーバーモデル",
|
||||
"userModels": "ユーザーモデル",
|
||||
"configureModels": "モデルを設定...",
|
||||
@@ -695,7 +695,7 @@
|
||||
},
|
||||
"lobby": {
|
||||
"title": "どんな図を描きますか?",
|
||||
"lede": "図の内容を説明するか、スクリーンショットや PDF をアップロードしてください。できあがるのは通常の draw.io の図なので、続けて編集もエクスポートもできます。",
|
||||
"lede": "図を説明するか、スクリーンショットや PDF をアップロードしてください。できあがるのは編集もエクスポートもできる draw.io の図です。",
|
||||
"placeholder": "例:SMS 認証とリトライを含むログインのシーケンス図",
|
||||
"recent": "最近の図",
|
||||
"drawYourself": "白紙のキャンバスから始める",
|
||||
|
||||
@@ -72,7 +72,7 @@
|
||||
"replicateDescription": "上傳並複製現有流程圖",
|
||||
"creativeDrawing": "創意繪畫",
|
||||
"creativeDescription": "繪製有趣且富有創意的內容",
|
||||
"cachedNote": "範例會立即開啟,不呼叫模型。",
|
||||
"cachedNote": "範例使用保存好的答案,立即顯示,不呼叫 AI。",
|
||||
"mcpServer": "MCP 伺服器",
|
||||
"mcpDescription": "在 Claude Desktop、VS Code 和 Cursor 中使用"
|
||||
},
|
||||
@@ -96,7 +96,7 @@
|
||||
"language": "語言",
|
||||
"languageDescription": "選擇介面語言。",
|
||||
"theme": "主題",
|
||||
"themeDescription": "介面用淺色還是深色。圖紙始終是白底,和匯出後的樣子一致。",
|
||||
"themeDescription": "介面用淺色還是深色,畫布跟隨同一主題。",
|
||||
"diagramStyle": "圖形風格",
|
||||
"diagramStyleDescription": "AI 畫新圖時的風格:精緻會用顏色和圖示,簡約只用樸素的圖形。",
|
||||
"sendShortcut": "傳送快速鍵",
|
||||
@@ -400,7 +400,7 @@
|
||||
"searchModels": "搜尋模型...",
|
||||
"noModelsFound": "未找到模型。",
|
||||
"default": "預設",
|
||||
"serverDefault": "伺服器預設",
|
||||
"serverDefault": "伺服器模型",
|
||||
"serverModels": "伺服器模型",
|
||||
"userModels": "使用者模型",
|
||||
"configureModels": "配置模型...",
|
||||
@@ -695,7 +695,7 @@
|
||||
},
|
||||
"lobby": {
|
||||
"title": "說說你要畫什麼",
|
||||
"lede": "描述一張圖,或上傳截圖、PDF。畫出來的是標準的 draw.io 圖表,可以接著改,也可以匯出。",
|
||||
"lede": "描述一張圖,或上傳截圖、PDF。得到的是可以繼續編輯和匯出的 draw.io 圖。",
|
||||
"placeholder": "例如:使用者登入的時序圖,包含簡訊驗證碼和失敗重試",
|
||||
"recent": "最近的圖紙",
|
||||
"drawYourself": "從空白畫布開始",
|
||||
|
||||
@@ -72,7 +72,7 @@
|
||||
"replicateDescription": "上传并复制现有流程图",
|
||||
"creativeDrawing": "创意绘画",
|
||||
"creativeDescription": "绘制有趣且富有创意的内容",
|
||||
"cachedNote": "示例会立即打开,不调用模型。",
|
||||
"cachedNote": "示例使用保存好的答案,立即显示,不调用 AI。",
|
||||
"mcpServer": "MCP 服务器",
|
||||
"mcpDescription": "在 Claude Desktop、VS Code 和 Cursor 中使用"
|
||||
},
|
||||
@@ -96,7 +96,7 @@
|
||||
"language": "语言",
|
||||
"languageDescription": "选择界面语言。",
|
||||
"theme": "主题",
|
||||
"themeDescription": "界面用浅色还是深色。图纸始终是白底,和导出后的样子一致。",
|
||||
"themeDescription": "界面用浅色还是深色,画布跟随同一主题。",
|
||||
"diagramStyle": "图形风格",
|
||||
"diagramStyleDescription": "AI 画新图时的风格:精致会用颜色和图标,简约只用朴素的图形。",
|
||||
"sendShortcut": "发送快捷键",
|
||||
@@ -400,7 +400,7 @@
|
||||
"searchModels": "搜索模型...",
|
||||
"noModelsFound": "未找到模型。",
|
||||
"default": "默认",
|
||||
"serverDefault": "服务器默认",
|
||||
"serverDefault": "服务器模型",
|
||||
"serverModels": "服务器模型",
|
||||
"userModels": "用户模型",
|
||||
"configureModels": "配置模型...",
|
||||
@@ -695,7 +695,7 @@
|
||||
},
|
||||
"lobby": {
|
||||
"title": "说说你要画什么",
|
||||
"lede": "描述一张图,或者上传截图、PDF。画出来的是标准的 draw.io 图表,可以接着改,也可以导出。",
|
||||
"lede": "描述一张图,或上传截图、PDF。得到的是可以继续编辑和导出的 draw.io 图。",
|
||||
"placeholder": "比如:用户登录的时序图,包含短信验证码和失败重试",
|
||||
"recent": "最近的图纸",
|
||||
"drawYourself": "从空白画布开始",
|
||||
|
||||
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 67 KiB |
Reference in New Issue
Block a user