From 366d9ee4ff9c262e6dcb6161aa338529abf66ed7 Mon Sep 17 00:00:00 2001 From: "dayuan.jiang" Date: Tue, 6 Oct 2026 09:28:46 +0900 Subject: [PATCH] feat(ui): canvas-first redesign with a self-hosted draw.io The canvas now fills the window and the chat floats on its right. draw.io is served from our own origin (public/drawio, downloaded at build time and checked against the release's SHA-256), so the app can drive the editor directly: - every AI change becomes a version card with a thumbnail; compare, restore or undo it, and Ctrl+Z on the canvas takes an AI change back in one step - shapes the AI just changed are highlighted on the canvas - selected shapes appear as a chip and are sent to the model - the app's own canvas toolbar, shape library and page tabs replace draw.io's chrome - new start screen, session rename and switcher, open .drawio files - settings split into Models & keys, Appearance, Advanced and About - phones switch between canvas and chat - dark mode keeps the diagram on a white sheet and no longer flashes With an external draw.io (NEXT_PUBLIC_DRAWIO_BASE_URL) the app falls back to draw.io's own toolbar and the features above that need same origin are off. The chat logic moved from the 1500-line chat panel into a chat engine plus small UI components; zustand holds canvas, versions, settings and UI state. --- .github/workflows/electron-release.yml | 21 +- Dockerfile | 5 +- README.md | 5 +- app/[lang]/about/cn/page.tsx | 9 +- app/[lang]/about/ja/page.tsx | 10 +- app/[lang]/about/page.tsx | 12 +- app/[lang]/layout.tsx | 19 +- app/[lang]/page.tsx | 257 +-- app/api/chat/route.ts | 4 +- app/globals.css | 492 ++--- components/canvas/canvas-stage.tsx | 200 ++ components/canvas/canvas-toolbar.tsx | 405 ++++ components/canvas/drawio-frame.tsx | 233 +++ components/canvas/page-tabs.tsx | 117 ++ components/canvas/selection-ask.tsx | 68 + components/chat-example-panel.tsx | 227 --- components/chat-input.tsx | 669 ------- components/chat-message-display.tsx | 1382 -------------- components/chat-panel.tsx | 1641 ----------------- components/chat/ChatLobby.tsx | 360 ---- components/chat/TemplatePanel.tsx | 8 - components/chat/ToolCallCard.tsx | 267 --- components/chat/attachments.ts | 77 + components/chat/chat-engine.tsx | 1503 +++++++++++++++ components/chat/chat-panel.tsx | 192 ++ components/chat/compare-dialog.tsx | 152 ++ components/chat/composer.tsx | 525 ++++++ components/chat/lobby-hero.tsx | 284 +++ components/chat/message-list.tsx | 634 +++++++ components/chat/message-text.ts | 64 + components/chat/session-menu.tsx | 294 +++ components/chat/tool-activity.tsx | 487 +++++ components/chat/use-diagram-streaming.ts | 223 +++ components/chat/use-versions.ts | 169 ++ components/chat/version-strip.tsx | 91 + components/history-dialog.tsx | 114 -- components/model-config-dialog.tsx | 92 +- components/model-selector.tsx | 37 +- components/reset-warning-modal.tsx | 50 - components/save-dialog.tsx | 95 +- components/settings-dialog.tsx | 1138 +++++------- components/ui/resizable.tsx | 56 - components/workspace/app-toaster.tsx | 28 + components/workspace/brand-mark.tsx | 29 + components/workspace/icon-button.tsx | 80 + components/workspace/workspace.tsx | 422 +++++ contexts/diagram-context.tsx | 234 ++- docker-compose.yml | 12 +- docs/cn/FAQ.md | 6 +- docs/cn/README_CN.md | 5 +- docs/cn/offline-deployment.md | 24 +- docs/en/FAQ.md | 6 +- docs/en/offline-deployment.md | 24 +- docs/ja/FAQ.md | 6 +- docs/ja/README_JA.md | 5 +- docs/ja/offline-deployment.md | 24 +- env.example | 9 +- hooks/use-diagram-tool-handlers.ts | 73 +- hooks/use-panel-width.ts | 22 + hooks/use-session-manager.ts | 66 +- lib/chat-helpers.ts | 34 + lib/chat-request.ts | 37 + lib/clipboard.ts | 36 + lib/diagram-diff.ts | 273 +++ lib/drawio-themes.ts | 17 - lib/drawio/drawio-config.ts | 116 ++ lib/drawio/editor-bridge.ts | 745 ++++++++ lib/i18n/dictionaries/en.json | 312 ++-- lib/i18n/dictionaries/ja.json | 294 ++- lib/i18n/dictionaries/zh-Hant.json | 296 +-- lib/i18n/dictionaries/zh.json | 292 ++- lib/platform.ts | 7 + lib/relative-time.ts | 20 + lib/session-storage.ts | 19 +- lib/storage.ts | 31 +- lib/use-file-processor.tsx | 27 +- lib/version-text.ts | 35 + next.config.ts | 3 + package-lock.json | 53 +- package.json | 7 +- scripts/fetch-drawio.mjs | 148 ++ stores/canvas-store.ts | 54 + stores/settings-store.ts | 159 ++ stores/ui-store.ts | 62 + stores/versions-store.ts | 145 ++ tests/e2e/chat-errors.spec.ts | 14 +- tests/e2e/chat.spec.ts | 19 +- tests/e2e/diagram-content.spec.ts | 392 +++- tests/e2e/diagram-generation.spec.ts | 4 +- tests/e2e/error-handling.spec.ts | 7 +- tests/e2e/file-upload.spec.ts | 27 +- tests/e2e/history-restore.spec.ts | 71 +- tests/e2e/iframe-interaction.spec.ts | 8 +- tests/e2e/language.spec.ts | 42 +- tests/e2e/lib/fixtures.ts | 52 +- tests/e2e/model-test.spec.ts | 4 +- tests/e2e/provider-models.spec.ts | 72 +- tests/e2e/settings.spec.ts | 25 +- tests/e2e/workspace.spec.ts | 1055 +++++++++++ tests/unit/diagram-context.test.tsx | 2 +- tests/unit/diagram-diff.test.ts | 229 +++ tests/unit/redesign-helpers.test.ts | 168 ++ tests/unit/short-model-name.test.ts | 37 + ...l-card.test.tsx => tool-activity.test.tsx} | 35 +- ...est.tsx => use-diagram-streaming.test.tsx} | 25 +- tests/unit/use-diagram-tool-handlers.test.tsx | 59 +- 106 files changed, 12453 insertions(+), 6879 deletions(-) create mode 100644 components/canvas/canvas-stage.tsx create mode 100644 components/canvas/canvas-toolbar.tsx create mode 100644 components/canvas/drawio-frame.tsx create mode 100644 components/canvas/page-tabs.tsx create mode 100644 components/canvas/selection-ask.tsx delete mode 100644 components/chat-example-panel.tsx delete mode 100644 components/chat-input.tsx delete mode 100644 components/chat-message-display.tsx delete mode 100644 components/chat-panel.tsx delete mode 100644 components/chat/ChatLobby.tsx delete mode 100644 components/chat/ToolCallCard.tsx create mode 100644 components/chat/attachments.ts create mode 100644 components/chat/chat-engine.tsx create mode 100644 components/chat/chat-panel.tsx create mode 100644 components/chat/compare-dialog.tsx create mode 100644 components/chat/composer.tsx create mode 100644 components/chat/lobby-hero.tsx create mode 100644 components/chat/message-list.tsx create mode 100644 components/chat/message-text.ts create mode 100644 components/chat/session-menu.tsx create mode 100644 components/chat/tool-activity.tsx create mode 100644 components/chat/use-diagram-streaming.ts create mode 100644 components/chat/use-versions.ts create mode 100644 components/chat/version-strip.tsx delete mode 100644 components/history-dialog.tsx delete mode 100644 components/reset-warning-modal.tsx delete mode 100644 components/ui/resizable.tsx create mode 100644 components/workspace/app-toaster.tsx create mode 100644 components/workspace/brand-mark.tsx create mode 100644 components/workspace/icon-button.tsx create mode 100644 components/workspace/workspace.tsx create mode 100644 hooks/use-panel-width.ts create mode 100644 lib/chat-request.ts create mode 100644 lib/clipboard.ts create mode 100644 lib/diagram-diff.ts delete mode 100644 lib/drawio-themes.ts create mode 100644 lib/drawio/drawio-config.ts create mode 100644 lib/drawio/editor-bridge.ts create mode 100644 lib/platform.ts create mode 100644 lib/relative-time.ts create mode 100644 lib/version-text.ts create mode 100644 scripts/fetch-drawio.mjs create mode 100644 stores/canvas-store.ts create mode 100644 stores/settings-store.ts create mode 100644 stores/ui-store.ts create mode 100644 stores/versions-store.ts create mode 100644 tests/e2e/workspace.spec.ts create mode 100644 tests/unit/diagram-diff.test.ts create mode 100644 tests/unit/redesign-helpers.test.ts create mode 100644 tests/unit/short-model-name.test.ts rename tests/unit/{tool-call-card.test.tsx => tool-activity.test.tsx} (61%) rename tests/unit/{chat-message-display-preview.test.tsx => use-diagram-streaming.test.tsx} (74%) diff --git a/.github/workflows/electron-release.yml b/.github/workflows/electron-release.yml index 9e8ef191..9fed5980 100644 --- a/.github/workflows/electron-release.yml +++ b/.github/workflows/electron-release.yml @@ -34,15 +34,7 @@ jobs: node-version: 24 cache: "npm" - - name: Download draw.io static files for offline use - run: | - rm -rf public/drawio - git clone --depth 1 https://github.com/jgraph/drawio.git /tmp/drawio - mkdir -p public/drawio - cp -r /tmp/drawio/src/main/webapp/* public/drawio/ - rm -rf public/drawio/WEB-INF - rm -rf public/drawio/META-INF - + # npm run build downloads draw.io first (prebuild) - name: Install dependencies run: npm install @@ -66,16 +58,7 @@ jobs: node-version: 24 cache: "npm" - - name: Download draw.io static files for offline use - shell: bash - run: | - rm -rf public/drawio - git clone --depth 1 https://github.com/jgraph/drawio.git /tmp/drawio - mkdir -p public/drawio - cp -r /tmp/drawio/src/main/webapp/* public/drawio/ - rm -rf public/drawio/WEB-INF - rm -rf public/drawio/META-INF - + # npm run build downloads draw.io first (prebuild) - name: Install dependencies run: npm install diff --git a/Dockerfile b/Dockerfile index 037b48c1..54149c7c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -23,8 +23,9 @@ COPY . . # Disable Next.js telemetry during build ENV NEXT_TELEMETRY_DISABLED=1 -# Build-time argument for self-hosted draw.io URL -ARG NEXT_PUBLIC_DRAWIO_BASE_URL=https://embed.diagrams.net +# Build-time argument for an external draw.io URL. Leave empty to use the +# copy bundled into public/drawio (downloaded by `npm run build`) +ARG NEXT_PUBLIC_DRAWIO_BASE_URL="" ENV NEXT_PUBLIC_DRAWIO_BASE_URL=${NEXT_PUBLIC_DRAWIO_BASE_URL} # Build-time argument to show About link and Notice icon diff --git a/README.md b/README.md index 5c94015a..cbf27f7e 100644 --- a/README.md +++ b/README.md @@ -107,7 +107,8 @@ Here are some example prompts and their generated diagrams: - **Image-Based Diagram Replication**: Upload existing diagrams or images and have the AI replicate and enhance them automatically - **PDF & Text File Upload**: Upload PDF documents and text files to extract content and generate diagrams from existing documents - **AI Reasoning Display**: View the AI's thinking process for supported models (OpenAI o1/o3, Gemini, Claude, etc.) -- **Diagram History**: Comprehensive version control that tracks all changes, allowing you to view and restore previous versions of your diagrams before the AI editing. +- **Versions and Undo**: Every AI change shows up in the chat as a version with a thumbnail. Compare it with the canvas, restore it, or undo it; Ctrl+Z on the canvas also takes back an AI change in one step. Shapes the AI just changed are highlighted. +- **Ask About a Selection**: Select shapes on the canvas and ask the AI to change just those. - **Interactive Chat Interface**: Communicate with AI to refine your diagrams in real-time - **Cloud Architecture Diagram Support**: Specialized support for generating cloud architecture diagrams (AWS, GCP, Azure) - **Animated Connectors**: Create dynamic and animated connectors between diagram elements for better visualization @@ -263,7 +264,7 @@ The application uses the following technologies: - **Next.js**: For the frontend framework and routing - **Vercel AI SDK** (`ai` + `@ai-sdk/*`): For streaming AI responses and multi-provider support -- **react-drawio**: For diagram representation and manipulation +- **draw.io**: The editor runs from a copy bundled into `public/drawio` (downloaded by `npm run dev` / `npm run build`), so the app can drive it directly Diagrams are represented as XML that can be rendered in draw.io. The AI processes your commands and generates or modifies this XML accordingly. diff --git a/app/[lang]/about/cn/page.tsx b/app/[lang]/about/cn/page.tsx index bacd18e9..fe283122 100644 --- a/app/[lang]/about/cn/page.tsx +++ b/app/[lang]/about/cn/page.tsx @@ -149,8 +149,10 @@ export default function AboutCN() { :上传现有图表或图像,让AI自动复制和增强
  • - 图表历史记录 - :全面的版本控制,跟踪所有更改,允许您查看和恢复AI编辑前的图表版本 + 版本与撤销 + :AI + 的每次修改都会成为聊天里的一个版本,可以对比、恢复或撤销;在画布上按 + Ctrl+Z 也能撤回
  • 交互式聊天界面 @@ -294,7 +296,8 @@ export default function AboutCN() { ):用于流式AI响应和多提供商支持
  • - react-drawio:用于图表表示和操作 + draw.io + :内置在应用里的编辑器,应用可以直接控制它
  • diff --git a/app/[lang]/about/ja/page.tsx b/app/[lang]/about/ja/page.tsx index 22f53fc0..cf9d7e87 100644 --- a/app/[lang]/about/ja/page.tsx +++ b/app/[lang]/about/ja/page.tsx @@ -138,8 +138,10 @@ export default function AboutJA() { :既存のダイアグラムや画像をアップロードし、AIが自動的に複製・強化

  • - ダイアグラム履歴 - :すべての変更を追跡する包括的なバージョン管理。AI編集前のダイアグラムの以前のバージョンを表示・復元可能 + バージョンと取り消し + :AI + の変更はそれぞれチャット内のバージョンになり、比較・復元・取り消しができます。キャンバスで + Ctrl+Z を押しても元に戻せます
  • @@ -290,8 +292,8 @@ export default function AboutJA() { ):ストリーミングAIレスポンスとマルチプロバイダーサポート
  • - react-drawio - :ダイアグラムの表現と操作 + draw.io + :アプリに同梱されたエディター。アプリから直接操作できます
  • diff --git a/app/[lang]/about/page.tsx b/app/[lang]/about/page.tsx index 85a46ef5..a25d8869 100644 --- a/app/[lang]/about/page.tsx +++ b/app/[lang]/about/page.tsx @@ -150,10 +150,10 @@ export default function About() { replicate and enhance them automatically

  • - Diagram History: Comprehensive - version control that tracks all changes, allowing - you to view and restore previous versions of your - diagrams before the AI editing + Versions and Undo: Every AI change + becomes a version in the chat that you can compare, + restore or undo; Ctrl+Z on the canvas takes it back + too
  • Interactive Chat Interface: @@ -304,8 +304,8 @@ export default function About() { and multi-provider support
  • - react-drawio: For diagram - representation and manipulation + draw.io: The editor, bundled with + the app so it can be driven directly
  • diff --git a/app/[lang]/layout.tsx b/app/[lang]/layout.tsx index 04bd4eb5..bc2fd112 100644 --- a/app/[lang]/layout.tsx +++ b/app/[lang]/layout.tsx @@ -1,19 +1,25 @@ import { GoogleAnalytics } from "@next/third-parties/google" import type { Metadata, Viewport } from "next" -import { JetBrains_Mono, Plus_Jakarta_Sans } from "next/font/google" +import { Instrument_Sans, JetBrains_Mono } from "next/font/google" import { notFound } from "next/navigation" +import Script from "next/script" +import { AppToaster } from "@/components/workspace/app-toaster" import { DiagramProvider } from "@/contexts/diagram-context" import { DictionaryProvider } from "@/hooks/use-dictionary" import type { Locale } from "@/lib/i18n/config" import { i18n } from "@/lib/i18n/config" import { getDictionary, hasLocale } from "@/lib/i18n/dictionaries" +import { THEME_INIT_SCRIPT } from "@/stores/settings-store" import "../globals.css" -const plusJakarta = Plus_Jakarta_Sans({ - variable: "--font-sans", +const instrumentSans = Instrument_Sans({ + variable: "--font-latin", subsets: ["latin"], weight: ["400", "500", "600", "700"], + // Chinese and Japanese text falls through to system fonts, in the + // order globals.css picks for the page's language + fallback: [], }) const jetbrainsMono = JetBrains_Mono({ @@ -165,17 +171,22 @@ export default async function RootLayout({ return ( + {/* Apply dark mode before the first paint (no white flash) */} +