Compare commits

..

1 Commits

Author SHA1 Message Date
dayuan.jiang
6ed05ed24d chore: always bundle latest draw.io version in Electron builds
Remove pinned v29.3.5 tag so the build always clones the latest draw.io.
This adds the Animated GIF export and other new features to the Electron app.

Closes #770
2026-04-06 10:13:40 +09:00
37 changed files with 326 additions and 531 deletions

View File

@@ -31,7 +31,7 @@ https://github.com/user-attachments/assets/9d60a3e8-4a1c-4b5e-acbb-26af2d3eabd1
- [Table of Contents](#table-of-contents) - [Table of Contents](#table-of-contents)
- [Examples](#examples) - [Examples](#examples)
- [Features](#features) - [Features](#features)
- [MCP Server](#mcp-server) - [MCP Server (Preview)](#mcp-server-preview)
- [Claude Code CLI](#claude-code-cli) - [Claude Code CLI](#claude-code-cli)
- [Getting Started](#getting-started) - [Getting Started](#getting-started)
- [Try it Online](#try-it-online) - [Try it Online](#try-it-online)
@@ -63,24 +63,24 @@ Here are some example prompts and their generated diagrams:
</tr> </tr>
<tr> <tr>
<td width="50%" valign="top"> <td width="50%" valign="top">
<strong>RAG Technique Diagram</strong><br /> <strong>GCP architecture diagram</strong><br />
<p><strong>Prompt:</strong> Generate a RAG architecture diagram for **chat application**. Use connected diagram for data ingestion</p> <p><strong>Prompt:</strong> Generate a GCP architecture diagram with **GCP icons**. In this diagram, users connect to a frontend hosted on an instance.</p>
<img src="./public/rag_prod.svg" alt="RAG Architecture Diagram" width="480" /> <img src="./public/gcp_demo.svg" alt="GCP Architecture Diagram" width="480" />
</td> </td>
<td width="50%" valign="top"> <td width="50%" valign="top">
<strong>Authentication using React and AWS</strong><br /> <strong>AWS architecture diagram</strong><br />
<p><strong>Prompt:</strong> Generate authentication process using React with **AWS**. Use Serverless architecture.</p> <p><strong>Prompt:</strong> Generate a AWS architecture diagram with **AWS icons**. In this diagram, users connect to a frontend hosted on an instance.</p>
<img src="./public/auth.svg" alt="Authentication Architecture Diagram" width="480" /> <img src="./public/aws_demo.svg" alt="AWS Architecture Diagram" width="480" />
</td> </td>
</tr> </tr>
<tr> <tr>
<td width="50%" valign="top"> <td width="50%" valign="top">
<strong>Open Innovation</strong><br /> <strong>Azure architecture diagram</strong><br />
<p><strong>Prompt:</strong> Create visualization of Henry Chesbrough's Open Innovation model.</p> <p><strong>Prompt:</strong> Generate a Azure architecture diagram with **Azure icons**. In this diagram, users connect to a frontend hosted on an instance.</p>
<img src="./public/inno.svg" alt="Open Innovation Diagram" width="480" /> <img src="./public/azure_demo.svg" alt="Azure Architecture Diagram" width="480" />
</td> </td>
<td width="50%" valign="top"> <td width="50%" valign="top">
<strong>Cat sketch</strong><br /> <strong>Cat sketch prompt</strong><br />
<p><strong>Prompt:</strong> Draw a cute cat for me.</p> <p><strong>Prompt:</strong> Draw a cute cat for me.</p>
<img src="./public/cat_demo.svg" alt="Cat Drawing" width="240" /> <img src="./public/cat_demo.svg" alt="Cat Drawing" width="240" />
</td> </td>
@@ -99,7 +99,9 @@ Here are some example prompts and their generated diagrams:
- **Cloud Architecture Diagram Support**: Specialized support for generating cloud architecture diagrams (AWS, GCP, Azure) - **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 - **Animated Connectors**: Create dynamic and animated connectors between diagram elements for better visualization
## MCP Server ## MCP Server (Preview)
> **Preview Feature**: This feature is experimental and may not be stable.
Use Next AI Draw.io with AI agents like Claude Desktop, Cursor, and VS Code via MCP (Model Context Protocol). Use Next AI Draw.io with AI agents like Claude Desktop, Cursor, and VS Code via MCP (Model Context Protocol).

View File

@@ -175,106 +175,92 @@ export default function AboutCN() {
</p> </p>
<div className="space-y-8"> <div className="space-y-8">
{/* ResNet50 Architecture */} {/* Animated Transformer */}
<div className="text-center"> <div className="text-center">
<h3 className="text-lg font-semibold text-gray-900 mb-2"> <h3 className="text-lg font-semibold text-gray-900 mb-2">
ResNet50模型架构动画 Transformer连接器
</h3> </h3>
<p className="text-gray-600 mb-4"> <p className="text-gray-600 mb-4">
<strong>Prompt:</strong> Give me an{" "} <strong></strong>
<strong>animated</strong> architecture diagram <strong></strong>Transformer架构图
of the ResNet50 model.
</p> </p>
<div className="bg-neutral-950 rounded-lg p-4 inline-block"> <Image
<Image src="/animated_connectors.svg"
src="/resnet50.svg" alt="带动画连接器的Transformer架构"
alt="ResNet50模型架构图" width={480}
width={480} height={360}
height={360} className="mx-auto"
className="mx-auto" />
/>
</div>
</div> </div>
{/* Diagram Grid */} {/* Cloud Architecture Grid */}
<div className="grid md:grid-cols-2 gap-6"> <div className="grid md:grid-cols-2 gap-6">
<div className="text-center"> <div className="text-center">
<h3 className="text-lg font-semibold text-gray-900 mb-2"> <h3 className="text-lg font-semibold text-gray-900 mb-2">
RAG技术 GCP架构
</h3> </h3>
<p className="text-gray-600 text-sm mb-4"> <p className="text-gray-600 text-sm mb-4">
<strong>Prompt:</strong> Generate a RAG <strong></strong> 使
architecture diagram for{" "} <strong>GCP图标</strong>
<strong>chat application</strong>. Use GCP架构图
connected diagram for data ingestion
</p> </p>
<div className="bg-neutral-950 rounded-lg p-4 flex items-center justify-center w-full h-[400px]"> <Image
<Image src="/gcp_demo.svg"
src="/rag_prod.svg" alt="GCP架构图"
alt="RAG架构图" width={400}
width={480} height={300}
height={360} className="mx-auto"
className="max-w-full max-h-full object-contain" />
/>
</div>
</div> </div>
<div className="text-center"> <div className="text-center">
<h3 className="text-lg font-semibold text-gray-900 mb-2"> <h3 className="text-lg font-semibold text-gray-900 mb-2">
React和AWS认证流程 AWS架构图
</h3> </h3>
<p className="text-gray-600 text-sm mb-4"> <p className="text-gray-600 text-sm mb-4">
<strong>Prompt:</strong> Generate <strong></strong> 使
authentication process using React with{" "} <strong>AWS图标</strong>
<strong>AWS</strong>. Use Serverless AWS架构图
architecture.
</p> </p>
<div className="bg-neutral-950 rounded-lg p-4 flex items-center justify-center w-full h-[400px]"> <Image
<Image src="/aws_demo.svg"
src="/auth.svg" alt="AWS架构图"
alt="认证架构图" width={400}
width={480} height={300}
height={360} className="mx-auto"
className="max-w-full max-h-full object-contain" />
/>
</div>
</div> </div>
<div className="text-center"> <div className="text-center">
<h3 className="text-lg font-semibold text-gray-900 mb-2"> <h3 className="text-lg font-semibold text-gray-900 mb-2">
Scrum流程 Azure架构图
</h3> </h3>
<p className="text-gray-600 text-sm mb-4"> <p className="text-gray-600 text-sm mb-4">
<strong>Prompt:</strong> Generate agile <strong></strong> 使
scrum workflow diagram for software <strong>Azure图标</strong>
development team. Azure架构图
</p> </p>
<div className="bg-neutral-950 rounded-lg p-4 flex items-center justify-center w-full h-[400px]"> <Image
<Image src="/azure_demo.svg"
src="/agile_scrum.svg" alt="Azure架构图"
alt="敏捷Scrum流程图" width={400}
width={480} height={300}
height={360} className="mx-auto"
className="max-w-full max-h-full object-contain" />
/>
</div>
</div> </div>
<div className="text-center"> <div className="text-center">
<h3 className="text-lg font-semibold text-gray-900 mb-2"> <h3 className="text-lg font-semibold text-gray-900 mb-2">
</h3> </h3>
<p className="text-gray-600 text-sm mb-4"> <p className="text-gray-600 text-sm mb-4">
<strong>Prompt:</strong> Create <strong></strong>{" "}
visualization of Henry Chesbrough&apos;s
Open Innovation model.
</p> </p>
<div className="bg-neutral-950 rounded-lg p-4 flex items-center justify-center w-full h-[400px]"> <Image
<Image src="/cat_demo.svg"
src="/inno.svg" alt="猫咪绘图"
alt="开放式创新图" width={240}
width={480} height={240}
height={360} className="mx-auto"
className="max-w-full max-h-full object-contain" />
/>
</div>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -168,106 +168,93 @@ export default function AboutJA() {
</p> </p>
<div className="space-y-8"> <div className="space-y-8">
{/* ResNet50 Architecture */} {/* Animated Transformer */}
<div className="text-center"> <div className="text-center">
<h3 className="text-lg font-semibold text-gray-900 mb-2"> <h3 className="text-lg font-semibold text-gray-900 mb-2">
ResNet50モデルアーキテクチャアニメーション Transformerコネクタ
</h3> </h3>
<p className="text-gray-600 mb-4"> <p className="text-gray-600 mb-4">
<strong>Prompt:</strong> Give me an{" "} <strong></strong>{" "}
<strong>animated</strong> architecture diagram <strong></strong>
of the ResNet50 model. Transformerアーキテクチャ図を作成してください
</p> </p>
<div className="bg-neutral-950 rounded-lg p-4 inline-block"> <Image
<Image src="/animated_connectors.svg"
src="/resnet50.svg" alt="アニメーションコネクタ付きTransformerアーキテクチャ"
alt="ResNet50モデルアーキテクチャ図" width={480}
width={480} height={360}
height={360} className="mx-auto"
className="mx-auto" />
/>
</div>
</div> </div>
{/* Diagram Grid */} {/* Cloud Architecture Grid */}
<div className="grid md:grid-cols-2 gap-6"> <div className="grid md:grid-cols-2 gap-6">
<div className="text-center"> <div className="text-center">
<h3 className="text-lg font-semibold text-gray-900 mb-2"> <h3 className="text-lg font-semibold text-gray-900 mb-2">
RAG技術ダイアグラム GCPアーキテクチャ図
</h3> </h3>
<p className="text-gray-600 text-sm mb-4"> <p className="text-gray-600 text-sm mb-4">
<strong>Prompt:</strong> Generate a RAG <strong></strong>{" "}
architecture diagram for{" "} <strong>GCPアイコン</strong>
<strong>chat application</strong>. Use 使GCPアーキテクチャ図を生成してください
connected diagram for data ingestion
</p> </p>
<div className="bg-neutral-950 rounded-lg p-4 flex items-center justify-center w-full h-[400px]"> <Image
<Image src="/gcp_demo.svg"
src="/rag_prod.svg" alt="GCPアーキテクチャ図"
alt="RAGアーキテクチャ図" width={400}
width={480} height={300}
height={360} className="mx-auto"
className="max-w-full max-h-full object-contain" />
/>
</div>
</div> </div>
<div className="text-center"> <div className="text-center">
<h3 className="text-lg font-semibold text-gray-900 mb-2"> <h3 className="text-lg font-semibold text-gray-900 mb-2">
ReactとAWSによる認証 AWSアーキテクチャ図
</h3> </h3>
<p className="text-gray-600 text-sm mb-4"> <p className="text-gray-600 text-sm mb-4">
<strong>Prompt:</strong> Generate <strong></strong>{" "}
authentication process using React with{" "} <strong>AWSアイコン</strong>
<strong>AWS</strong>. Use Serverless 使AWSアーキテクチャ図を生成してください
architecture.
</p> </p>
<div className="bg-neutral-950 rounded-lg p-4 flex items-center justify-center w-full h-[400px]"> <Image
<Image src="/aws_demo.svg"
src="/auth.svg" alt="AWSアーキテクチャ図"
alt="認証アーキテクチャ図" width={400}
width={480} height={300}
height={360} className="mx-auto"
className="max-w-full max-h-full object-contain" />
/>
</div>
</div> </div>
<div className="text-center"> <div className="text-center">
<h3 className="text-lg font-semibold text-gray-900 mb-2"> <h3 className="text-lg font-semibold text-gray-900 mb-2">
Azureアーキテクチャ図
</h3> </h3>
<p className="text-gray-600 text-sm mb-4"> <p className="text-gray-600 text-sm mb-4">
<strong>Prompt:</strong> Generate agile <strong></strong>{" "}
scrum workflow diagram for software <strong>Azureアイコン</strong>
development team. 使Azureアーキテクチャ図を生成してください
</p> </p>
<div className="bg-neutral-950 rounded-lg p-4 flex items-center justify-center w-full h-[400px]"> <Image
<Image src="/azure_demo.svg"
src="/agile_scrum.svg" alt="Azureアーキテクチャ図"
alt="アジャイルスクラム図" width={400}
width={480} height={300}
height={360} className="mx-auto"
className="max-w-full max-h-full object-contain" />
/>
</div>
</div> </div>
<div className="text-center"> <div className="text-center">
<h3 className="text-lg font-semibold text-gray-900 mb-2"> <h3 className="text-lg font-semibold text-gray-900 mb-2">
</h3> </h3>
<p className="text-gray-600 text-sm mb-4"> <p className="text-gray-600 text-sm mb-4">
<strong>Prompt:</strong> Create <strong></strong>{" "}
visualization of Henry Chesbrough&apos;s
Open Innovation model.
</p> </p>
<div className="bg-neutral-950 rounded-lg p-4 flex items-center justify-center w-full h-[400px]"> <Image
<Image src="/cat_demo.svg"
src="/inno.svg" alt="猫の絵"
alt="オープンイノベーション図" width={240}
width={480} height={240}
height={360} className="mx-auto"
className="max-w-full max-h-full object-contain" />
/>
</div>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -182,106 +182,96 @@ export default function About() {
</p> </p>
<div className="space-y-8"> <div className="space-y-8">
{/* ResNet50 Architecture */} {/* Animated Transformer */}
<div className="text-center"> <div className="text-center">
<h3 className="text-lg font-semibold text-gray-900 mb-2"> <h3 className="text-lg font-semibold text-gray-900 mb-2">
Animated ResNet50 Model Architecture Animated Transformer Connectors
</h3> </h3>
<p className="text-gray-600 mb-4"> <p className="text-gray-600 mb-4">
<strong>Prompt:</strong> Give me an{" "} <strong>Prompt:</strong> Give me an{" "}
<strong>animated</strong> architecture diagram <strong>animated connector</strong> diagram of
of the ResNet50 model. transformer&apos;s architecture.
</p> </p>
<div className="bg-neutral-950 rounded-lg p-4 inline-block"> <Image
<Image src="/animated_connectors.svg"
src="/resnet50.svg" alt="Transformer Architecture with Animated Connectors"
alt="Architecture diagram for ResNet50 model" width={480}
width={480} height={360}
height={360} className="mx-auto"
className="mx-auto" />
/>
</div>
</div> </div>
{/* Diagram Grid */} {/* Cloud Architecture Grid */}
<div className="grid md:grid-cols-2 gap-6"> <div className="grid md:grid-cols-2 gap-6">
<div className="text-center"> <div className="text-center">
<h3 className="text-lg font-semibold text-gray-900 mb-2"> <h3 className="text-lg font-semibold text-gray-900 mb-2">
RAG Technique Diagram GCP Architecture Diagram
</h3> </h3>
<p className="text-gray-600 text-sm mb-4"> <p className="text-gray-600 text-sm mb-4">
<strong>Prompt:</strong> Generate a RAG <strong>Prompt:</strong> Generate a GCP
architecture diagram for{" "} architecture diagram with{" "}
<strong>chat application</strong>. Use <strong>GCP icons</strong>. Users connect to
connected diagram for data ingestion a frontend hosted on an instance.
</p> </p>
<div className="bg-neutral-950 rounded-lg p-4 flex items-center justify-center w-full h-[400px]"> <Image
<Image src="/gcp_demo.svg"
src="/rag_prod.svg" alt="GCP Architecture Diagram"
alt="RAG Architecture Diagram" width={400}
width={480} height={300}
height={360} className="mx-auto"
className="max-w-full max-h-full object-contain" />
/>
</div>
</div> </div>
<div className="text-center"> <div className="text-center">
<h3 className="text-lg font-semibold text-gray-900 mb-2"> <h3 className="text-lg font-semibold text-gray-900 mb-2">
Authentication using React and AWS AWS Architecture Diagram
</h3> </h3>
<p className="text-gray-600 text-sm mb-4"> <p className="text-gray-600 text-sm mb-4">
<strong>Prompt:</strong> Generate <strong>Prompt:</strong> Generate an AWS
authentication process using React with{" "} architecture diagram with{" "}
<strong>AWS</strong>. Use Serverless <strong>AWS icons</strong>. Users connect to
architecture. a frontend hosted on an instance.
</p> </p>
<div className="bg-neutral-950 rounded-lg p-4 flex items-center justify-center w-full h-[400px]"> <Image
<Image src="/aws_demo.svg"
src="/auth.svg" alt="AWS Architecture Diagram"
alt="Authentication Architecture Diagram" width={400}
width={480} height={300}
height={360} className="mx-auto"
className="max-w-full max-h-full object-contain" />
/>
</div>
</div> </div>
<div className="text-center"> <div className="text-center">
<h3 className="text-lg font-semibold text-gray-900 mb-2"> <h3 className="text-lg font-semibold text-gray-900 mb-2">
Agile Scrum Process Azure Architecture Diagram
</h3> </h3>
<p className="text-gray-600 text-sm mb-4"> <p className="text-gray-600 text-sm mb-4">
<strong>Prompt:</strong> Generate agile <strong>Prompt:</strong> Generate an Azure
scrum workflow diagram for software architecture diagram with{" "}
development team. <strong>Azure icons</strong>. Users connect
to a frontend hosted on an instance.
</p> </p>
<div className="bg-neutral-950 rounded-lg p-4 flex items-center justify-center w-full h-[400px]"> <Image
<Image src="/azure_demo.svg"
src="/agile_scrum.svg" alt="Azure Architecture Diagram"
alt="Agile Scrum Diagram" width={400}
width={480} height={300}
height={360} className="mx-auto"
className="max-w-full max-h-full object-contain" />
/>
</div>
</div> </div>
<div className="text-center"> <div className="text-center">
<h3 className="text-lg font-semibold text-gray-900 mb-2"> <h3 className="text-lg font-semibold text-gray-900 mb-2">
Open Innovation Cat Sketch
</h3> </h3>
<p className="text-gray-600 text-sm mb-4"> <p className="text-gray-600 text-sm mb-4">
<strong>Prompt:</strong> Create <strong>Prompt:</strong> Draw a cute cat for
visualization of Henry Chesbrough&apos;s me.
Open Innovation model.
</p> </p>
<div className="bg-neutral-950 rounded-lg p-4 flex items-center justify-center w-full h-[400px]"> <Image
<Image src="/cat_demo.svg"
src="/inno.svg" alt="Cat Drawing"
alt="Open Innovation Diagram" width={240}
width={480} height={240}
height={360} className="mx-auto"
className="max-w-full max-h-full object-contain" />
/>
</div>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -10,7 +10,6 @@ import {
ResizablePanelGroup, ResizablePanelGroup,
} from "@/components/ui/resizable" } from "@/components/ui/resizable"
import { useDiagram } from "@/contexts/diagram-context" import { useDiagram } from "@/contexts/diagram-context"
import { type DrawioTheme, isDrawioTheme } from "@/lib/drawio-themes"
import { i18n, type Locale } from "@/lib/i18n/config" import { i18n, type Locale } from "@/lib/i18n/config"
import { isIndexedDBUsable } from "@/lib/session-storage" import { isIndexedDBUsable } from "@/lib/session-storage"
@@ -28,7 +27,7 @@ export default function Home() {
const currentLang = (pathname.split("/")[1] || i18n.defaultLocale) as Locale const currentLang = (pathname.split("/")[1] || i18n.defaultLocale) as Locale
const [isMobile, setIsMobile] = useState(false) const [isMobile, setIsMobile] = useState(false)
const [isChatVisible, setIsChatVisible] = useState(true) const [isChatVisible, setIsChatVisible] = useState(true)
const [drawioUi, setDrawioUi] = useState<DrawioTheme>("kennedy") const [drawioUi, setDrawioUi] = useState<"min" | "sketch">("min")
const [darkMode, setDarkMode] = useState(false) const [darkMode, setDarkMode] = useState(false)
const [isLoaded, setIsLoaded] = useState(false) const [isLoaded, setIsLoaded] = useState(false)
const [isDrawioReady, setIsDrawioReady] = useState(false) const [isDrawioReady, setIsDrawioReady] = useState(false)
@@ -57,7 +56,7 @@ export default function Home() {
} }
const savedUi = localStorage.getItem("drawio-theme") const savedUi = localStorage.getItem("drawio-theme")
if (isDrawioTheme(savedUi)) { if (savedUi === "min" || savedUi === "sketch") {
setDrawioUi(savedUi) setDrawioUi(savedUi)
} }
@@ -101,8 +100,12 @@ export default function Home() {
const handleDrawioAutoSave = useCallback( const handleDrawioAutoSave = useCallback(
(data: { xml?: string }) => { (data: { xml?: string }) => {
handleDiagramAutoSave(data) handleDiagramAutoSave(data)
// Only suppress modified state when persistence is available
if (canPersist) {
drawioRef.current?.status({ message: "", modified: false })
}
}, },
[handleDiagramAutoSave], [canPersist, drawioRef, handleDiagramAutoSave],
) )
const handleDarkModeChange = () => { const handleDarkModeChange = () => {
@@ -114,9 +117,10 @@ export default function Home() {
resetDrawioReady() resetDrawioReady()
} }
const handleDrawioUiChange = (theme: DrawioTheme) => { const handleDrawioUiChange = () => {
localStorage.setItem("drawio-theme", theme) const newUi = drawioUi === "min" ? "sketch" : "min"
setDrawioUi(theme) localStorage.setItem("drawio-theme", newUi)
setDrawioUi(newUi)
setIsDrawioReady(false) setIsDrawioReady(false)
resetDrawioReady() resetDrawioReady()
} }
@@ -216,8 +220,7 @@ export default function Home() {
saveAndExit: false, saveAndExit: false,
noSaveBtn: true, noSaveBtn: true,
noExitBtn: true, noExitBtn: true,
dark: dark: darkMode,
darkMode || drawioUi === "dark",
lang: currentLang, lang: currentLang,
// Enable offline mode in Electron to disable external service calls // Enable offline mode in Electron to disable external service calls
...(isElectron && { ...(isElectron && {
@@ -265,7 +268,7 @@ export default function Home() {
isVisible={isChatVisible} isVisible={isChatVisible}
onToggleVisibility={toggleChatPanel} onToggleVisibility={toggleChatPanel}
drawioUi={drawioUi} drawioUi={drawioUi}
onDrawioUiChange={handleDrawioUiChange} onToggleDrawioUi={handleDrawioUiChange}
darkMode={darkMode} darkMode={darkMode}
onToggleDarkMode={handleDarkModeChange} onToggleDarkMode={handleDarkModeChange}
isMobile={isMobile} isMobile={isMobile}

View File

@@ -1,5 +1,5 @@
{ {
"$schema": "https://biomejs.dev/schemas/2.4.14/schema.json", "$schema": "https://biomejs.dev/schemas/2.4.4/schema.json",
"vcs": { "vcs": {
"enabled": true, "enabled": true,
"clientKind": "git", "clientKind": "git",

View File

@@ -141,6 +141,9 @@ export default function ExamplePanel({
<span className="text-sm font-medium text-foreground group-hover:text-purple-500 transition-colors"> <span className="text-sm font-medium text-foreground group-hover:text-purple-500 transition-colors">
{dict.examples.mcpServer} {dict.examples.mcpServer}
</span> </span>
<span className="px-1.5 py-0.5 text-[10px] font-semibold bg-purple-500 text-white rounded">
{dict.examples.preview}
</span>
</div> </div>
<p className="text-xs text-muted-foreground"> <p className="text-xs text-muted-foreground">
{dict.examples.mcpDescription} {dict.examples.mcpDescription}

View File

@@ -417,7 +417,6 @@ export function ChatMessageDisplay({
// Track previous message count to detect bulk loads vs streaming // Track previous message count to detect bulk loads vs streaming
const prevMessageCountRef = useRef(0) const prevMessageCountRef = useRef(0)
const scrollThrottleRef = useRef<ReturnType<typeof setTimeout> | null>(null)
useEffect(() => { useEffect(() => {
if (messagesEndRef.current && messages.length > 0) { if (messagesEndRef.current && messages.length > 0) {
@@ -431,17 +430,8 @@ export function ChatMessageDisplay({
return return
} }
// Throttle scroll during streaming to avoid layout thrashing // Single message added - smooth scroll
// Leading + trailing: scroll immediately, then once more after cooldown messagesEndRef.current.scrollIntoView({ behavior: "smooth" })
if (!scrollThrottleRef.current) {
messagesEndRef.current.scrollIntoView({ behavior: "smooth" })
scrollThrottleRef.current = setTimeout(() => {
scrollThrottleRef.current = null
messagesEndRef.current?.scrollIntoView({
behavior: "smooth",
})
}, 150)
}
} }
}, [messages]) }, [messages])

View File

@@ -32,7 +32,6 @@ import { useSessionManager } from "@/hooks/use-session-manager"
import { useValidateDiagram } from "@/hooks/use-validate-diagram" import { useValidateDiagram } from "@/hooks/use-validate-diagram"
import { getApiEndpoint } from "@/lib/base-path" import { getApiEndpoint } from "@/lib/base-path"
import { findCachedResponse } from "@/lib/cached-responses" import { findCachedResponse } from "@/lib/cached-responses"
import type { DrawioTheme } from "@/lib/drawio-themes"
import { formatMessage } from "@/lib/i18n/utils" import { formatMessage } from "@/lib/i18n/utils"
import { isPdfFile, isTextFile } from "@/lib/pdf-utils" import { isPdfFile, isTextFile } from "@/lib/pdf-utils"
import { sanitizeMessages } from "@/lib/session-storage" import { sanitizeMessages } from "@/lib/session-storage"
@@ -69,8 +68,8 @@ interface ChatMessage {
interface ChatPanelProps { interface ChatPanelProps {
isVisible: boolean isVisible: boolean
onToggleVisibility: () => void onToggleVisibility: () => void
drawioUi: DrawioTheme drawioUi: "min" | "sketch"
onDrawioUiChange: (theme: DrawioTheme) => void onToggleDrawioUi: () => void
darkMode: boolean darkMode: boolean
onToggleDarkMode: () => void onToggleDarkMode: () => void
isMobile?: boolean isMobile?: boolean
@@ -111,7 +110,7 @@ export default function ChatPanel({
isVisible, isVisible,
onToggleVisibility, onToggleVisibility,
drawioUi, drawioUi,
onDrawioUiChange, onToggleDrawioUi,
darkMode, darkMode,
onToggleDarkMode, onToggleDarkMode,
isMobile = false, isMobile = false,
@@ -1443,7 +1442,7 @@ export default function ChatPanel({
open={showSettingsDialog} open={showSettingsDialog}
onOpenChange={setShowSettingsDialog} onOpenChange={setShowSettingsDialog}
drawioUi={drawioUi} drawioUi={drawioUi}
onDrawioUiChange={onDrawioUiChange} onToggleDrawioUi={onToggleDrawioUi}
darkMode={darkMode} darkMode={darkMode}
onToggleDarkMode={onToggleDarkMode} onToggleDarkMode={onToggleDarkMode}
minimalStyle={minimalStyle} minimalStyle={minimalStyle}

View File

@@ -107,8 +107,8 @@ export function ChatLobby({
currentInput = "", currentInput = "",
dict, dict,
}: ChatLobbyProps) { }: ChatLobbyProps) {
const [templatesExpanded, setTemplatesExpanded] = useState(true) const [templatesExpanded, setTemplatesExpanded] = useState(false)
const [examplesExpanded, setExamplesExpanded] = useState(true) const [examplesExpanded, setExamplesExpanded] = useState(false)
const [panelVisibility, setPanelVisibility] = useState(getPanelVisibility) const [panelVisibility, setPanelVisibility] = useState(getPanelVisibility)
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false) const [deleteDialogOpen, setDeleteDialogOpen] = useState(false)
const [sessionToDelete, setSessionToDelete] = useState<string | null>(null) const [sessionToDelete, setSessionToDelete] = useState<string | null>(null)
@@ -125,25 +125,19 @@ export function ChatLobby({
const hasHistory = sessions.length > 0 const hasHistory = sessions.length > 0
if (!hasHistory) { if (!hasHistory) {
if (!panelVisibility.myTemplates && !panelVisibility.quickExamples) { if (panelVisibility.myTemplates) {
return null return (
<TemplatePanel
setInput={setInput}
onSendTemplate={onSendTemplate}
currentInput={currentInput}
/>
)
} }
return ( if (panelVisibility.quickExamples) {
<div className="animate-fade-in"> return <ExamplePanel setInput={setInput} setFiles={setFiles} />
{panelVisibility.myTemplates && ( }
<TemplatePanel return null
setInput={setInput}
onSendTemplate={onSendTemplate}
currentInput={currentInput}
/>
)}
{panelVisibility.quickExamples && (
<div className={panelVisibility.myTemplates ? "mt-6" : ""}>
<ExamplePanel setInput={setInput} setFiles={setFiles} />
</div>
)}
</div>
)
} }
// Show history + collapsible examples when there are sessions // Show history + collapsible examples when there are sessions

View File

@@ -195,22 +195,7 @@ export function ToolCallCard({
{input && isExpanded && ( {input && isExpanded && (
<div className="px-4 py-3 border-t border-border/40 bg-muted/20"> <div className="px-4 py-3 border-t border-border/40 bg-muted/20">
{typeof input === "object" && input.xml ? ( {typeof input === "object" && input.xml ? (
state === "input-streaming" || <CodeBlock code={input.xml} language="xml" />
state === "input-available" ? (
<pre
className="text-[11px] leading-relaxed overflow-x-auto overflow-y-auto max-h-48 scrollbar-thin break-all whitespace-pre-wrap"
style={{
fontFamily:
"var(--font-mono), ui-monospace, monospace",
margin: 0,
padding: 0,
}}
>
{input.xml}
</pre>
) : (
<CodeBlock code={input.xml} language="xml" />
)
) : typeof input === "object" && ) : typeof input === "object" &&
input.operations && input.operations &&
Array.isArray(input.operations) ? ( Array.isArray(input.operations) ? (

View File

@@ -405,7 +405,7 @@ export function ModelConfigDialog({
</span> </span>
</div> </div>
<ScrollArea className="flex-1 px-2 min-h-0"> <ScrollArea className="flex-1 px-2">
<div className="space-y-1 pb-2"> <div className="space-y-1 pb-2">
{config.providers.length === 0 ? ( {config.providers.length === 0 ? (
<div className="px-3 py-8 text-center"> <div className="px-3 py-8 text-center">

View File

@@ -25,7 +25,6 @@ import { Switch } from "@/components/ui/switch"
import { Textarea } from "@/components/ui/textarea" import { Textarea } from "@/components/ui/textarea"
import { useDictionary } from "@/hooks/use-dictionary" import { useDictionary } from "@/hooks/use-dictionary"
import { getApiEndpoint } from "@/lib/base-path" import { getApiEndpoint } from "@/lib/base-path"
import type { DrawioTheme } from "@/lib/drawio-themes"
import { i18n, type Locale } from "@/lib/i18n/config" import { i18n, type Locale } from "@/lib/i18n/config"
import { STORAGE_KEYS } from "@/lib/storage" import { STORAGE_KEYS } from "@/lib/storage"
@@ -64,8 +63,8 @@ const LANGUAGE_LABELS: Record<Locale, string> = {
interface SettingsDialogProps { interface SettingsDialogProps {
open: boolean open: boolean
onOpenChange: (open: boolean) => void onOpenChange: (open: boolean) => void
drawioUi: DrawioTheme drawioUi: "min" | "sketch"
onDrawioUiChange: (theme: DrawioTheme) => void onToggleDrawioUi: () => void
darkMode: boolean darkMode: boolean
onToggleDarkMode: () => void onToggleDarkMode: () => void
minimalStyle?: boolean minimalStyle?: boolean
@@ -91,7 +90,7 @@ function SettingsContent({
open, open,
onOpenChange, onOpenChange,
drawioUi, drawioUi,
onDrawioUiChange, onToggleDrawioUi,
darkMode, darkMode,
onToggleDarkMode, onToggleDarkMode,
minimalStyle = false, minimalStyle = false,
@@ -135,11 +134,8 @@ function SettingsContent({
const [isApplyingProxy, setIsApplyingProxy] = useState(false) const [isApplyingProxy, setIsApplyingProxy] = useState(false)
useEffect(() => { useEffect(() => {
// Re-fetch config whenever the dialog opens to ensure we always show // Only fetch if not cached in localStorage
// the access code input if the server requires it. This fixes the case if (getStoredAccessCodeRequired() !== null) return
// where a stale localStorage cache (from before ACCESS_CODE_LIST was
// configured) would hide the access code input.
if (!open) return
fetch(getApiEndpoint("/api/config")) fetch(getApiEndpoint("/api/config"))
.then((res) => { .then((res) => {
@@ -155,9 +151,10 @@ function SettingsContent({
setAccessCodeRequired(required) setAccessCodeRequired(required)
}) })
.catch(() => { .catch(() => {
// Keep existing cached value on error // Don't cache on error - allow retry on next mount
setAccessCodeRequired(false)
}) })
}, [open]) }, [])
// Detect current language from pathname // Detect current language from pathname
useEffect(() => { useEffect(() => {
@@ -433,40 +430,23 @@ function SettingsContent({
{/* Draw.io Style */} {/* Draw.io Style */}
<SettingItem <SettingItem
label={dict.settings.drawioStyle} label={dict.settings.drawioStyle}
description={dict.settings.drawioStyleDescription} description={`${dict.settings.drawioStyleDescription} ${
drawioUi === "min"
? dict.settings.minimal
: dict.settings.sketch
}`}
> >
<Select <Button
value={drawioUi} id="drawio-ui"
onValueChange={(v) => variant="outline"
onDrawioUiChange(v as DrawioTheme) onClick={onToggleDrawioUi}
} className="h-9 w-[120px] rounded-xl border-border-subtle hover:bg-interactive-hover font-normal"
> >
<SelectTrigger {dict.settings.switchTo}{" "}
id="drawio-ui-select" {drawioUi === "min"
aria-label={dict.settings.drawioStyle} ? dict.settings.sketch
className="w-[120px] h-9 rounded-xl" : dict.settings.minimal}
> </Button>
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="kennedy">
{dict.settings.themeDefault}
</SelectItem>
<SelectItem value="atlas">Atlas</SelectItem>
<SelectItem value="dark">
{dict.settings.themeDark}
</SelectItem>
<SelectItem value="min">
{dict.settings.themeMinimal}
</SelectItem>
<SelectItem value="sketch">
{dict.settings.themeSketch}
</SelectItem>
<SelectItem value="simple">
{dict.settings.themeSimple}
</SelectItem>
</SelectContent>
</Select>
</SettingItem> </SettingItem>
{/* Diagram Style */} {/* Diagram Style */}

View File

@@ -30,7 +30,7 @@ https://github.com/user-attachments/assets/b2eef5f3-b335-4e71-a755-dc2e80931979
- [目录](#目录) - [目录](#目录)
- [示例](#示例) - [示例](#示例)
- [功能特性](#功能特性) - [功能特性](#功能特性)
- [MCP服务器](#mcp服务器) - [MCP服务器(预览)](#mcp服务器预览)
- [Claude Code CLI](#claude-code-cli) - [Claude Code CLI](#claude-code-cli)
- [快速开始](#快速开始) - [快速开始](#快速开始)
- [在线试用](#在线试用) - [在线试用](#在线试用)
@@ -56,31 +56,31 @@ https://github.com/user-attachments/assets/b2eef5f3-b335-4e71-a755-dc2e80931979
<tr> <tr>
<td colspan="2" valign="top" align="center"> <td colspan="2" valign="top" align="center">
<strong>动画Transformer连接器</strong><br /> <strong>动画Transformer连接器</strong><br />
<p><strong>Prompt:</strong> Give me a **animated connector** diagram of transformer's architecture.</p> <p><strong>提示词:</strong> 给我一个带有**动画连接器**的Transformer架构图。</p>
<img src="../../public/animated_connectors.svg" alt="带动画连接器的Transformer架构" width="480" /> <img src="../../public/animated_connectors.svg" alt="带动画连接器的Transformer架构" width="480" />
</td> </td>
</tr> </tr>
<tr> <tr>
<td width="50%" valign="top"> <td width="50%" valign="top">
<strong>RAG技术图</strong><br /> <strong>GCP架构图</strong><br />
<p><strong>Prompt:</strong> Generate a RAG architecture diagram for **chat application**. Use connected diagram for data ingestion</p> <p><strong>提示词:</strong> 使用**GCP图标**生成一个GCP架构图。在这个图中用户连接到托管在实例上的前端。</p>
<img src="../../public/rag_prod.svg" alt="RAG架构图" width="480" /> <img src="../../public/gcp_demo.svg" alt="GCP架构图" width="480" />
</td> </td>
<td width="50%" valign="top"> <td width="50%" valign="top">
<strong>React和AWS认证流程</strong><br /> <strong>AWS架构图</strong><br />
<p><strong>Prompt:</strong> Generate authentication process using React with **AWS**. Use Serverless architecture.</p> <p><strong>提示词:</strong> 使用**AWS图标**生成一个AWS架构图。在这个图中用户连接到托管在实例上的前端。</p>
<img src="../../public/auth.svg" alt="认证架构图" width="480" /> <img src="../../public/aws_demo.svg" alt="AWS架构图" width="480" />
</td> </td>
</tr> </tr>
<tr> <tr>
<td width="50%" valign="top"> <td width="50%" valign="top">
<strong>开放式创新</strong><br /> <strong>Azure架构图</strong><br />
<p><strong>Prompt:</strong> Create visualization of Henry Chesbrough's Open Innovation model.</p> <p><strong>提示词:</strong> 使用**Azure图标**生成一个Azure架构图。在这个图中用户连接到托管在实例上的前端。</p>
<img src="../../public/inno.svg" alt="开放式创新图" width="480" /> <img src="../../public/azure_demo.svg" alt="Azure架构图" width="480" />
</td> </td>
<td width="50%" valign="top"> <td width="50%" valign="top">
<strong>猫咪素描</strong><br /> <strong>猫咪素描</strong><br />
<p><strong>Prompt:</strong> Draw a cute cat for me.</p> <p><strong>提示词:</strong> 给我画一只可爱的猫。</p>
<img src="../../public/cat_demo.svg" alt="猫咪绘图" width="240" /> <img src="../../public/cat_demo.svg" alt="猫咪绘图" width="240" />
</td> </td>
</tr> </tr>
@@ -98,7 +98,9 @@ https://github.com/user-attachments/assets/b2eef5f3-b335-4e71-a755-dc2e80931979
- **云架构图支持**专门支持生成云架构图AWS、GCP、Azure - **云架构图支持**专门支持生成云架构图AWS、GCP、Azure
- **动画连接器**:在图表元素之间创建动态动画连接器,实现更好的可视化效果 - **动画连接器**:在图表元素之间创建动态动画连接器,实现更好的可视化效果
## MCP服务器 ## MCP服务器(预览)
> **预览功能**:此功能为实验性功能,可能不稳定。
通过MCP模型上下文协议在Claude Desktop、Cursor和VS Code等AI代理中使用Next AI Draw.io。 通过MCP模型上下文协议在Claude Desktop、Cursor和VS Code等AI代理中使用Next AI Draw.io。

View File

@@ -28,7 +28,7 @@ https://github.com/user-attachments/assets/b2eef5f3-b335-4e71-a755-dc2e80931979
- [目次](#目次) - [目次](#目次)
- [](#例) - [](#例)
- [機能](#機能) - [機能](#機能)
- [MCPサーバー](#mcpサーバー) - [MCPサーバー(プレビュー)](#mcpサーバープレビュー)
- [Claude Code CLI](#claude-code-cli) - [Claude Code CLI](#claude-code-cli)
- [はじめに](#はじめに) - [はじめに](#はじめに)
- [オンラインで試す](#オンラインで試す) - [オンラインで試す](#オンラインで試す)
@@ -54,31 +54,31 @@ https://github.com/user-attachments/assets/b2eef5f3-b335-4e71-a755-dc2e80931979
<tr> <tr>
<td colspan="2" valign="top" align="center"> <td colspan="2" valign="top" align="center">
<strong>アニメーションTransformerコネクタ</strong><br /> <strong>アニメーションTransformerコネクタ</strong><br />
<p><strong>Prompt:</strong> Give me a **animated connector** diagram of transformer's architecture.</p> <p><strong>プロンプト:</strong> **アニメーションコネクタ**付きのTransformerアーキテクチャ図を作成してください。</p>
<img src="../../public/animated_connectors.svg" alt="アニメーションコネクタ付きTransformerアーキテクチャ" width="480" /> <img src="../../public/animated_connectors.svg" alt="アニメーションコネクタ付きTransformerアーキテクチャ" width="480" />
</td> </td>
</tr> </tr>
<tr> <tr>
<td width="50%" valign="top"> <td width="50%" valign="top">
<strong>RAG技術ダイアグラム</strong><br /> <strong>GCPアーキテクチャ図</strong><br />
<p><strong>Prompt:</strong> Generate a RAG architecture diagram for **chat application**. Use connected diagram for data ingestion</p> <p><strong>プロンプト:</strong> **GCPアイコン**を使用してGCPアーキテクチャ図を生成してください。この図では、ユーザーがインスタンス上でホストされているフロントエンドに接続します。</p>
<img src="../../public/rag_prod.svg" alt="RAGアーキテクチャ図" width="480" /> <img src="../../public/gcp_demo.svg" alt="GCPアーキテクチャ図" width="480" />
</td> </td>
<td width="50%" valign="top"> <td width="50%" valign="top">
<strong>ReactとAWSによる認証</strong><br /> <strong>AWSアーキテクチャ図</strong><br />
<p><strong>Prompt:</strong> Generate authentication process using React with **AWS**. Use Serverless architecture.</p> <p><strong>プロンプト:</strong> **AWSアイコン**を使用してAWSアーキテクチャ図を生成してください。この図では、ユーザーがインスタンス上でホストされているフロントエンドに接続します。</p>
<img src="../../public/auth.svg" alt="認証アーキテクチャ図" width="480" /> <img src="../../public/aws_demo.svg" alt="AWSアーキテクチャ図" width="480" />
</td> </td>
</tr> </tr>
<tr> <tr>
<td width="50%" valign="top"> <td width="50%" valign="top">
<strong>オープンイノベーション</strong><br /> <strong>Azureアーキテクチャ図</strong><br />
<p><strong>Prompt:</strong> Create visualization of Henry Chesbrough's Open Innovation model.</p> <p><strong>プロンプト:</strong> **Azureアイコン**を使用してAzureアーキテクチャ図を生成してください。この図では、ユーザーがインスタンス上でホストされているフロントエンドに接続します。</p>
<img src="../../public/inno.svg" alt="オープンイノベーション図" width="480" /> <img src="../../public/azure_demo.svg" alt="Azureアーキテクチャ図" width="480" />
</td> </td>
<td width="50%" valign="top"> <td width="50%" valign="top">
<strong>猫のスケッチ</strong><br /> <strong>猫のスケッチ</strong><br />
<p><strong>Prompt:</strong> Draw a cute cat for me.</p> <p><strong>プロンプト:</strong> かわいい猫を描いてください。</p>
<img src="../../public/cat_demo.svg" alt="猫の絵" width="240" /> <img src="../../public/cat_demo.svg" alt="猫の絵" width="240" />
</td> </td>
</tr> </tr>
@@ -96,7 +96,9 @@ https://github.com/user-attachments/assets/b2eef5f3-b335-4e71-a755-dc2e80931979
- **クラウドアーキテクチャダイアグラムサポート**クラウドアーキテクチャダイアグラムの生成を専門的にサポートAWS、GCP、Azure - **クラウドアーキテクチャダイアグラムサポート**クラウドアーキテクチャダイアグラムの生成を専門的にサポートAWS、GCP、Azure
- **アニメーションコネクタ**:より良い可視化のためにダイアグラム要素間に動的でアニメーション化されたコネクタを作成 - **アニメーションコネクタ**:より良い可視化のためにダイアグラム要素間に動的でアニメーション化されたコネクタを作成
## MCPサーバー ## MCPサーバー(プレビュー)
> **プレビュー機能**:この機能は実験的であり、安定しない可能性があります。
MCPModel Context Protocolを介して、Claude Desktop、Cursor、VS CodeなどのAIエージェントでNext AI Draw.ioを使用できます。 MCPModel Context Protocolを介して、Claude Desktop、Cursor、VS CodeなどのAIエージェントでNext AI Draw.ioを使用できます。

View File

@@ -95,10 +95,6 @@ linux:
arch: arch:
- x64 - x64
- arm64 - arm64
- target: rpm
arch:
- x64
- arm64
# Publish configuration (optional) # Publish configuration (optional)
publish: publish:

View File

@@ -101,8 +101,8 @@ declare global {
} }
export type { export type {
ApplyPresetResult,
ConfigPreset, ConfigPreset,
ApplyPresetResult,
ProxyConfig, ProxyConfig,
SetProxyResult, SetProxyResult,
SetUserLocaleResult, SetUserLocaleResult,

View File

@@ -60,13 +60,6 @@ export function createWindow(serverUrl: string): BrowserWindow {
mainWindow.webContents.openDevTools() mainWindow.webContents.openDevTools()
} }
// Override the draw.io iframe's beforeunload handler so the window can
// close after the user edits text in a shape (fixes #815). Diagrams are
// already persisted via autosave, so the prompt is unnecessary.
mainWindow.webContents.on("will-prevent-unload", (event) => {
event.preventDefault()
})
mainWindow.on("closed", () => { mainWindow.on("closed", () => {
mainWindow = null mainWindow = null
}) })

View File

@@ -1262,6 +1262,7 @@ export function getAIModel(overrides?: ClientOverrides): ModelConfig {
case "glm": case "glm":
case "qwen": case "qwen":
case "qiniu": case "qiniu":
case "kimi":
case "novita": { case "novita": {
const envVar = PROVIDER_ENV_VARS[provider] const envVar = PROVIDER_ENV_VARS[provider]
if (!envVar) { if (!envVar) {
@@ -1287,23 +1288,6 @@ export function getAIModel(overrides?: ClientOverrides): ModelConfig {
break break
} }
case "kimi": {
const apiKey = resolveApiKey(overrides, "KIMI_API_KEY")
const baseURL = resolveBaseURL(
overrides?.apiKey,
overrides?.baseUrl,
resolveBaseUrlEnv(overrides, "KIMI_BASE_URL"),
PROVIDER_INFO["kimi"]?.defaultBaseUrl,
)
// Use createDeepSeek to properly handle reasoning_content for Kimi
// thinking models (e.g., kimi-k2.6). Kimi's API uses the same
// reasoning_content field as DeepSeek, so this provider correctly
// captures and replays reasoning in multi-turn conversations.
const customProvider = createDeepSeek({ apiKey, baseURL })
model = customProvider(modelId)
break
}
default: default:
throw new Error( throw new Error(
`Unknown AI provider: ${provider}. Supported providers: bedrock, openai, anthropic, google, azure, ollama, openrouter, deepseek, siliconflow, sglang, gateway, edgeone, doubao, modelscope, glm, qwen, qiniu, kimi, minimax, novita`, `Unknown AI provider: ${provider}. Supported providers: bedrock, openai, anthropic, google, azure, ollama, openrouter, deepseek, siliconflow, sglang, gateway, edgeone, doubao, modelscope, glm, qwen, qiniu, kimi, minimax, novita`,
@@ -1372,13 +1356,12 @@ export function supportsImageInput(modelId: string): boolean {
} }
// Qwen text models (not vision variants like qwen-vl) // Qwen text models (not vision variants like qwen-vl)
// Qwen3.5 series (qwen3.5, qwen3.5-plus, qwen3.5-flash) natively support image input // qwen3.5-plus is a vision model
// QvQ (Qwen Visual QA) models are vision models — exclude them even when prefixed with "qwen/"
if ( if (
lowerModelId.includes("qwen") && lowerModelId.includes("qwen") &&
!hasVisionIndicator && !hasVisionIndicator &&
!lowerModelId.includes("qwen3.5") && !lowerModelId.includes("qwen3.5-plus") &&
!lowerModelId.includes("qvq") !lowerModelId.includes("qwen3.5-flash")
) { ) {
return false return false
} }

View File

@@ -1,17 +0,0 @@
export const DRAWIO_THEMES = [
"kennedy",
"atlas",
"dark",
"min",
"sketch",
"simple",
] as const
export type DrawioTheme = (typeof DRAWIO_THEMES)[number]
export function isDrawioTheme(value: unknown): value is DrawioTheme {
return (
typeof value === "string" &&
(DRAWIO_THEMES as readonly string[]).includes(value)
)
}

View File

@@ -76,7 +76,8 @@
"creativeDescription": "Draw something fun and creative", "creativeDescription": "Draw something fun and creative",
"cachedNote": "Examples are cached for instant response", "cachedNote": "Examples are cached for instant response",
"mcpServer": "MCP Server", "mcpServer": "MCP Server",
"mcpDescription": "Use in Claude Desktop, VS Code & Cursor" "mcpDescription": "Use in Claude Desktop, VS Code & Cursor",
"preview": "PREVIEW"
}, },
"settings": { "settings": {
"title": "Settings", "title": "Settings",
@@ -102,12 +103,10 @@
"theme": "Theme", "theme": "Theme",
"themeDescription": "Dark/Light mode for interface and DrawIO canvas.", "themeDescription": "Dark/Light mode for interface and DrawIO canvas.",
"drawioStyle": "DrawIO Style", "drawioStyle": "DrawIO Style",
"drawioStyleDescription": "Canvas style", "drawioStyleDescription": "Canvas style:",
"themeDefault": "Default", "switchTo": "Switch to",
"themeDark": "Dark", "minimal": "Minimal",
"themeMinimal": "Minimal", "sketch": "Sketch",
"themeSketch": "Sketch",
"themeSimple": "Simple",
"diagramStyle": "Diagram Style", "diagramStyle": "Diagram Style",
"diagramStyleDescription": "Toggle between minimal and styled diagram output.", "diagramStyleDescription": "Toggle between minimal and styled diagram output.",
"sendShortcut": "Send Shortcut", "sendShortcut": "Send Shortcut",

View File

@@ -76,7 +76,8 @@
"creativeDescription": "楽しくてクリエイティブなものを描く", "creativeDescription": "楽しくてクリエイティブなものを描く",
"cachedNote": "例はキャッシュされ、即座に応答します", "cachedNote": "例はキャッシュされ、即座に応答します",
"mcpServer": "MCP サーバー", "mcpServer": "MCP サーバー",
"mcpDescription": "Claude Desktop、VS Code、Cursor で使用" "mcpDescription": "Claude Desktop、VS Code、Cursor で使用",
"preview": "プレビュー"
}, },
"settings": { "settings": {
"title": "設定", "title": "設定",
@@ -102,12 +103,10 @@
"theme": "テーマ", "theme": "テーマ",
"themeDescription": "インターフェースと DrawIO キャンバスのダーク/ライトモード。", "themeDescription": "インターフェースと DrawIO キャンバスのダーク/ライトモード。",
"drawioStyle": "DrawIO スタイル", "drawioStyle": "DrawIO スタイル",
"drawioStyleDescription": "キャンバススタイル", "drawioStyleDescription": "キャンバススタイル",
"themeDefault": "デフォルト", "switchTo": "切り替え",
"themeDark": "ダーク", "minimal": "ミニマル",
"themeMinimal": "ミニマル", "sketch": "スケッチ",
"themeSketch": "スケッチ",
"themeSimple": "シンプル",
"diagramStyle": "ダイアグラムスタイル", "diagramStyle": "ダイアグラムスタイル",
"diagramStyleDescription": "ミニマルとスタイル付きの出力を切り替えます。", "diagramStyleDescription": "ミニマルとスタイル付きの出力を切り替えます。",
"sendShortcut": "送信ショートカット", "sendShortcut": "送信ショートカット",

View File

@@ -76,7 +76,8 @@
"creativeDescription": "繪製有趣且富有創意的內容", "creativeDescription": "繪製有趣且富有創意的內容",
"cachedNote": "範例已快取,可即時回應", "cachedNote": "範例已快取,可即時回應",
"mcpServer": "MCP 伺服器", "mcpServer": "MCP 伺服器",
"mcpDescription": "在 Claude Desktop、VS Code 和 Cursor 中使用" "mcpDescription": "在 Claude Desktop、VS Code 和 Cursor 中使用",
"preview": "預覽"
}, },
"settings": { "settings": {
"title": "設定", "title": "設定",
@@ -102,12 +103,10 @@
"theme": "主題", "theme": "主題",
"themeDescription": "介面和 DrawIO 畫布的深色/淺色模式。", "themeDescription": "介面和 DrawIO 畫布的深色/淺色模式。",
"drawioStyle": "DrawIO 樣式", "drawioStyle": "DrawIO 樣式",
"drawioStyleDescription": "畫布樣式", "drawioStyleDescription": "畫布樣式",
"themeDefault": "預設", "switchTo": "切換到",
"themeDark": "深色", "minimal": "簡約",
"themeMinimal": "簡約", "sketch": "草圖",
"themeSketch": "草圖",
"themeSimple": "簡單",
"diagramStyle": "圖表樣式", "diagramStyle": "圖表樣式",
"diagramStyleDescription": "切換簡約與精緻圖表輸出模式。", "diagramStyleDescription": "切換簡約與精緻圖表輸出模式。",
"sendShortcut": "傳送快捷鍵", "sendShortcut": "傳送快捷鍵",

View File

@@ -76,7 +76,8 @@
"creativeDescription": "绘制有趣且富有创意的内容", "creativeDescription": "绘制有趣且富有创意的内容",
"cachedNote": "示例已缓存,可即时响应", "cachedNote": "示例已缓存,可即时响应",
"mcpServer": "MCP 服务器", "mcpServer": "MCP 服务器",
"mcpDescription": "在 Claude Desktop、VS Code 和 Cursor 中使用" "mcpDescription": "在 Claude Desktop、VS Code 和 Cursor 中使用",
"preview": "预览"
}, },
"settings": { "settings": {
"title": "设置", "title": "设置",
@@ -102,12 +103,10 @@
"theme": "主题", "theme": "主题",
"themeDescription": "界面和 DrawIO 画布的深色/浅色模式。", "themeDescription": "界面和 DrawIO 画布的深色/浅色模式。",
"drawioStyle": "DrawIO 样式", "drawioStyle": "DrawIO 样式",
"drawioStyleDescription": "画布样式", "drawioStyleDescription": "画布样式",
"themeDefault": "默认", "switchTo": "切换到",
"themeDark": "深色", "minimal": "简约",
"themeMinimal": "简约", "sketch": "草图",
"themeSketch": "草图",
"themeSimple": "简单",
"diagramStyle": "图表样式", "diagramStyle": "图表样式",
"diagramStyleDescription": "切换简约与精致图表输出模式。", "diagramStyleDescription": "切换简约与精致图表输出模式。",
"sendShortcut": "发送快捷键", "sendShortcut": "发送快捷键",

12
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{ {
"name": "next-ai-draw-io", "name": "next-ai-draw-io",
"version": "0.4.15", "version": "0.4.14",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "next-ai-draw-io", "name": "next-ai-draw-io",
"version": "0.4.15", "version": "0.4.14",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@ai-sdk/amazon-bedrock": "^4.0.1", "@ai-sdk/amazon-bedrock": "^4.0.1",
@@ -50,6 +50,8 @@
"cmdk": "^1.1.1", "cmdk": "^1.1.1",
"idb": "^8.0.3", "idb": "^8.0.3",
"jsonrepair": "^3.13.1", "jsonrepair": "^3.13.1",
"lightningcss": "^1.32.0",
"lightningcss-linux-x64-gnu": "^1.32.0",
"lucide-react": "^0.577.0", "lucide-react": "^0.577.0",
"motion": "^12.23.25", "motion": "^12.23.25",
"nanoid": "^5.0.0", "nanoid": "^5.0.0",
@@ -9673,9 +9675,9 @@
} }
}, },
"node_modules/@xmldom/xmldom": { "node_modules/@xmldom/xmldom": {
"version": "0.9.10", "version": "0.9.9",
"resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.9.10.tgz", "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.9.9.tgz",
"integrity": "sha512-A9gOqLdi6cV4ibazAjcQufGj0B1y/vDqYrcuP6d/6x8P27gRS8643Dj9o1dEKtB6O7fwxb2FgBmJS2mX7gpvdw==", "integrity": "sha512-qycIHAucxy/LXAYIjmLmtQ8q9GPnMbnjG1KXhWm9o5sCr6pOYDATkMPiTNa6/v8eELyqOQ2FsEqeoFYmgv/gJg==",
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=14.6" "node": ">=14.6"

View File

@@ -1,6 +1,6 @@
{ {
"name": "next-ai-draw-io", "name": "next-ai-draw-io",
"version": "0.4.15", "version": "0.4.14",
"license": "Apache-2.0", "license": "Apache-2.0",
"private": true, "private": true,
"main": "dist-electron/main/index.js", "main": "dist-electron/main/index.js",

View File

@@ -1,12 +1,12 @@
{ {
"name": "@next-ai-drawio/mcp-server", "name": "@next-ai-drawio/mcp-server",
"version": "0.2.0", "version": "0.1.17",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "@next-ai-drawio/mcp-server", "name": "@next-ai-drawio/mcp-server",
"version": "0.2.0", "version": "0.1.17",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@modelcontextprotocol/sdk": "^1.0.4", "@modelcontextprotocol/sdk": "^1.0.4",

View File

@@ -1,6 +1,6 @@
{ {
"name": "@next-ai-drawio/mcp-server", "name": "@next-ai-drawio/mcp-server",
"version": "0.2.0", "version": "0.1.19",
"description": "MCP server for Next AI Draw.io - AI-powered diagram generation with real-time browser preview", "description": "MCP server for Next AI Draw.io - AI-powered diagram generation with real-time browser preview",
"type": "module", "type": "module",
"main": "dist/index.js", "main": "dist/index.js",

View File

@@ -155,22 +155,19 @@ server.registerTool(
server.registerTool( server.registerTool(
"create_new_diagram", "create_new_diagram",
{ {
description: `Create a NEW diagram from mxGraphModel XML. ONLY use this when creating a diagram from scratch. description: `Create a NEW diagram from mxGraphModel XML. Use this when creating a diagram from scratch or replacing the current diagram entirely.
⚠️ DO NOT use this tool to modify an existing diagram — it will DESTROY all existing content and user changes. Use edit_diagram instead for ANY modifications to an existing diagram.
CRITICAL: You MUST provide the 'xml' argument in EVERY call. Do NOT call this tool without xml. CRITICAL: You MUST provide the 'xml' argument in EVERY call. Do NOT call this tool without xml.
When to use this tool: When to use this tool:
- Creating a new diagram from scratch (no existing diagram) - Creating a new diagram from scratch
- The user explicitly asks to "start over" or "create a new diagram" - Replacing the current diagram with a completely different one
- Major structural changes that require regenerating the diagram
When to use edit_diagram instead (ALWAYS prefer edit_diagram if a diagram already exists): When to use edit_diagram instead:
- ANY modifications to an existing diagram - Small modifications to existing diagram
- Adding/removing/moving elements - Adding/removing individual elements
- Changing labels, colors, styles, or positions - Changing labels, colors, or positions
- Restructuring or reorganizing existing content
- Adding new elements to an existing diagram
XML FORMAT - Full mxGraphModel structure: XML FORMAT - Full mxGraphModel structure:
<mxGraphModel> <mxGraphModel>

View File

@@ -12,8 +12,7 @@
"declaration": true, "declaration": true,
"declarationMap": true, "declarationMap": true,
"sourceMap": true, "sourceMap": true,
"resolveJsonModule": true, "resolveJsonModule": true
"types": ["node"]
}, },
"include": ["src/**/*"], "include": ["src/**/*"],
"exclude": ["node_modules", "dist"] "exclude": ["node_modules", "dist"]

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 30 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 42 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 27 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 35 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 39 KiB

View File

@@ -35,10 +35,8 @@ test.describe("Iframe Interaction", () => {
await expect( await expect(
frame frame
.locator('text="Diagram"') .locator('text="Diagram"')
.or(frame.locator('[title*="Diagram"]')) .or(frame.locator('[title*="Diagram"]')),
.filter({ visible: true }) ).toBeVisible({ timeout: 10000 })
.first(),
).toBeVisible({ timeout: 30000 })
}) })
test("diagram XML is rendered in iframe after generation", async ({ test("diagram XML is rendered in iframe after generation", async ({

View File

@@ -200,21 +200,12 @@ describe("supportsImageInput", () => {
it("returns true for Qwen vision models", () => { it("returns true for Qwen vision models", () => {
expect(supportsImageInput("qwen-vl")).toBe(true) expect(supportsImageInput("qwen-vl")).toBe(true)
expect(supportsImageInput("Qwen3.5")).toBe(true)
expect(supportsImageInput("qwen3.5")).toBe(true)
expect(supportsImageInput("qwen3.5-plus")).toBe(true) expect(supportsImageInput("qwen3.5-plus")).toBe(true)
expect(supportsImageInput("qwen3.5-flash")).toBe(true) expect(supportsImageInput("qwen3.5-flash")).toBe(true)
expect(supportsImageInput("qwen3-vl-plus")).toBe(true) expect(supportsImageInput("qwen3-vl-plus")).toBe(true)
expect(supportsImageInput("qwen3-vl-flash")).toBe(true) expect(supportsImageInput("qwen3-vl-flash")).toBe(true)
}) })
it("returns true for QvQ (Qwen Visual QA) models including OpenRouter-prefixed names", () => {
expect(supportsImageInput("qvq-72b-preview")).toBe(true)
expect(supportsImageInput("qvq-max")).toBe(true)
expect(supportsImageInput("qwen/qvq-72b-preview")).toBe(true)
expect(supportsImageInput("qwen/qvq-max")).toBe(true)
})
it("returns false for GLM text models", () => { it("returns false for GLM text models", () => {
expect(supportsImageInput("glm-4")).toBe(false) expect(supportsImageInput("glm-4")).toBe(false)
expect(supportsImageInput("glm-4-plus")).toBe(false) expect(supportsImageInput("glm-4-plus")).toBe(false)
@@ -245,67 +236,6 @@ vi.mock("ollama-ai-provider-v2", () => {
return { createOllama: mockCreateOllama, ollama: mockOllama } return { createOllama: mockCreateOllama, ollama: mockOllama }
}) })
vi.mock("@ai-sdk/deepseek", () => {
const mockModel = { modelId: "test-model" }
const mockProviderFn = vi.fn(() => mockModel)
const mockCreateDeepSeek = vi.fn(() => mockProviderFn)
const mockDeepseek = vi.fn(() => mockModel)
return { createDeepSeek: mockCreateDeepSeek, deepseek: mockDeepseek }
})
describe("Kimi provider uses createDeepSeek for reasoning_content support", () => {
let createDeepSeekMock: ReturnType<typeof vi.fn>
const savedEnv: Record<string, string | undefined> = {}
beforeEach(async () => {
savedEnv.KIMI_API_KEY = process.env.KIMI_API_KEY
savedEnv.KIMI_BASE_URL = process.env.KIMI_BASE_URL
delete process.env.KIMI_BASE_URL
const mod = await import("@ai-sdk/deepseek")
createDeepSeekMock = mod.createDeepSeek as ReturnType<typeof vi.fn>
createDeepSeekMock.mockClear()
})
afterEach(() => {
process.env.KIMI_API_KEY = savedEnv.KIMI_API_KEY
process.env.KIMI_BASE_URL = savedEnv.KIMI_BASE_URL
})
it("uses createDeepSeek with Kimi default base URL for reasoning_content support", () => {
process.env.KIMI_API_KEY = "test-kimi-key"
getAIModel({
provider: "kimi",
apiKey: "test-kimi-key",
modelId: "moonshot-v1-8k",
})
expect(createDeepSeekMock).toHaveBeenCalledWith(
expect.objectContaining({
baseURL: "https://api.moonshot.cn/v1",
}),
)
})
it("uses custom base URL when provided for kimi provider", () => {
process.env.KIMI_API_KEY = "test-kimi-key"
getAIModel({
provider: "kimi",
apiKey: "test-kimi-key",
baseUrl: "https://custom-kimi-endpoint.com/v1",
modelId: "kimi-k2.6",
})
expect(createDeepSeekMock).toHaveBeenCalledWith(
expect.objectContaining({
baseURL: "https://custom-kimi-endpoint.com/v1",
}),
)
})
})
describe("Ollama API key security", () => { describe("Ollama API key security", () => {
let createOllamaMock: ReturnType<typeof vi.fn> let createOllamaMock: ReturnType<typeof vi.fn>
const savedEnv: Record<string, string | undefined> = {} const savedEnv: Record<string, string | undefined> = {}