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.
This commit is contained in:
dayuan.jiang
2026-10-06 22:01:09 +09:00
parent 18a20aa1ca
commit 366d9ee4ff
106 changed files with 12453 additions and 6879 deletions
+11 -20
View File
@@ -1,26 +1,19 @@
// Centralized localStorage keys for quota tracking and settings
// Chat data is now stored in IndexedDB via session-storage.ts
// Centralized localStorage keys for settings.
// Chat data is stored in IndexedDB via session-storage.ts
export const STORAGE_KEYS = {
// Quota tracking
requestCount: "next-ai-draw-io-request-count",
requestDate: "next-ai-draw-io-request-date",
tokenCount: "next-ai-draw-io-token-count",
tokenDate: "next-ai-draw-io-token-date",
tpmCount: "next-ai-draw-io-tpm-count",
tpmMinute: "next-ai-draw-io-tpm-minute",
// Settings
// Access code for password-protected deployments
accessCode: "next-ai-draw-io-access-code",
accessCodeRequired: "next-ai-draw-io-access-code-required",
aiProvider: "next-ai-draw-io-ai-provider",
aiBaseUrl: "next-ai-draw-io-ai-base-url",
aiApiKey: "next-ai-draw-io-ai-api-key",
aiModel: "next-ai-draw-io-ai-model",
// Multi-model configuration
modelConfigs: "next-ai-draw-io-model-configs",
selectedModelId: "next-ai-draw-io-selected-model-id",
// Appearance
darkMode: "next-ai-draw-io-dark-mode",
locale: "next-ai-draw-io-locale",
minimalStyle: "next-ai-draw-io-minimal-style",
panelWidth: "next-ai-draw-io-panel-width",
// Chat input preferences
sendShortcut: "next-ai-draw-io-send-shortcut",
@@ -34,8 +27,6 @@ export const STORAGE_KEYS = {
// Output token budget per turn (empty = server default)
maxOutputTokens: "next-ai-draw-io-max-output-tokens",
// Panel visibility
showRecentChats: "next-ai-draw-io-show-recent-chats",
showMyTemplates: "next-ai-draw-io-show-my-templates",
showQuickExamples: "next-ai-draw-io-show-quick-examples",
// Langfuse session id
sessionId: "next-ai-draw-io-session-id",
} as const