Compare commits

..

56 Commits

Author SHA1 Message Date
dayuan.jiang
43021bafa2 fix: address code review issues
- Remove verbose debug console.logs (kept only token usage log)
- Add basic input validation for messages array
- Fix XML comparison using formatXML for consistency
- Extract CACHED_TOOL_PREFIX constant to avoid magic string
- Fix addToolResult API to use correct tool/output params
2025-12-02 19:20:06 +09:00
dayuan.jiang
a20d14ef9d feat: improve diagram edit tracking and cache handling
- Track lastGeneratedXml to detect user modifications
- Only send XML context when needed (saves tokens)
- Fix cached response streaming to include tool output
- Add debug logging for model messages and steps
- Enable multi-step tool execution with maxSteps: 5
2025-12-02 19:11:23 +09:00
Dayuan Jiang
e2adfb49aa Merge pull request #38 from DayuanJiang/feat/add-deepseek-provider
feat: add DeepSeek as AI provider
2025-12-02 11:59:35 +09:00
dayuan.jiang
45ab934288 feat: add DeepSeek as AI provider
- Install @ai-sdk/deepseek package
- Add DeepSeek provider support to lib/ai-providers.ts
- Add DeepSeek configuration to env.example
- Update README.md with DeepSeek in provider list
- Support both default and custom base URL for DeepSeek
2025-12-02 11:52:09 +09:00
Dayuan Jiang
af3173623a Merge pull request #36 from DayuanJiang/chore/remove-github-workflows
chore: remove github workflows
2025-12-02 01:19:45 +09:00
dayuan.jiang
cd012f5e2f chore: remove github workflows 2025-12-02 01:12:49 +09:00
Dan Zheng
d4fb635d98 fix: add customize anthropic baseURL (#28)
* fix: add custom anthropic baseURL

* feat: add baseURL support for all AI providers

- Add GOOGLE_BASE_URL for Google Generative AI
- Add AZURE_BASE_URL for Azure OpenAI
- Add OLLAMA_BASE_URL support (was documented but not implemented)
- Add OPENROUTER_BASE_URL for OpenRouter
- Fix missing semicolon in Anthropic case
- Update env.example with new environment variables

Closes #20

---------

Co-authored-by: dayuan.jiang <jdy.toh@gmail.com>
2025-12-02 01:08:06 +09:00
Dayuan Jiang
14740e35a8 Update README with bug fix and star history section 2025-12-01 22:40:43 +09:00
Dayuan Jiang
5b31216917 feat: cache example prompt responses to save tokens (#34)
- Add lib/cached-responses.ts with pre-generated XML for 4 example prompts
- Modify chat API route to check cache before calling AI
- Cache returns instant response (~0.26s) vs AI generation (~20-25s)
- Add "(cached for instant response)" text to example panel
- Cache only activates for first message with empty diagram
2025-12-01 14:07:50 +09:00
Dayuan Jiang
c7d0260328 feat: add Bedrock prompt caching for system and conversation messages (#32)
* feat: add Bedrock prompt caching for system and conversation messages

- Add cache point to system message (2558+ tokens cached)
- Add cache point to last assistant message in conversation history
- This caches the entire conversation prefix for subsequent requests
- Reduces latency and costs for multi-turn conversations

* refactor: remove duplicated system prompt
2025-12-01 10:43:33 +09:00
Dayuan Jiang
d2d4dd01cc fix: filter out messages with empty content arrays for Bedrock API (#31)
* fix: filter out messages with empty content arrays for Bedrock API

The convertToModelMessages function from AI SDK can produce messages with
empty content arrays when assistant messages have only tool call parts or
when tool results aren't properly converted. Bedrock API rejects these with
400 errors. This fix filters out invalid messages before sending to the API.

* fix: add diagnostic logging for empty message content

Added logging to capture the original UI message structure when empty content
is detected after conversion. This helps debug the root cause while the
filter provides a safety net for Bedrock API compatibility.
2025-12-01 01:15:43 +09:00
Dayuan Jiang
b4679f6598 fix: increase maxDuration to 300s for Fluid Compute (#30) 2025-12-01 00:46:40 +09:00
Dayuan Jiang
0d0d553e23 fix: correct anthropic beta header config for fine-grained tool streaming (#27)
* fix: correct anthropic beta header config for fine-grained tool streaming

- Use bedrock.anthropicBeta for Bedrock provider (not additionalModelRequestFields)
- Use top-level headers for direct Anthropic API
- Update @ai-sdk/amazon-bedrock to 3.0.62
- Add headers support to ModelConfig interface

* fix: update @ai-sdk/amazon-bedrock to 3.0.62 for tool streaming support
2025-11-30 16:34:42 +09:00
Ming long Hu
6e6de1eba6 feat: add copy button to user messages in chat sidebar (#21)
* Initial plan

* Initial plan for adding copy button to user messages

Co-authored-by: huminglong <63436986+huminglong@users.noreply.github.com>

* Add copy button to user messages in chat sidebar

Co-authored-by: huminglong <63436986+huminglong@users.noreply.github.com>

* Refactor: extract userMessageText to avoid duplicate function calls

Co-authored-by: huminglong <63436986+huminglong@users.noreply.github.com>

* feat(ui): 增加消息复制功能支持非 HTTPS 环境

优化复制消息到剪贴板的逻辑,增加对非 HTTPS 环境的降级处理,提升用户体验。

* fix(package): 添加 peer 属性以支持依赖关系

* chore(.gitignore): 添加 next 和 next-ai-draw-io@0.2.0 相关条目

* fix: improve copy button implementation

- Fix copy button alignment (place left of user message)
- Remove unused React import
- Move getMessageTextContent outside component
- Add error state with X icon for copy failures
- Translate Chinese comments to English
- Simplify copy function
- Add missing semicolon
- Remove accidental .gitignore entries

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: dayuan.jiang <jdy.toh@gmail.com>
2025-11-29 13:39:35 +09:00
dayuan.jiang
00af87edbe fix: prevent duplicate PR review comments by tracking existing file:line 2025-11-29 12:55:53 +09:00
dayuan.jiang
468d6c0276 fix: require suggested fixes in PR review comments 2025-11-29 12:46:24 +09:00
dayuan.jiang
14f74b076f fix: remove gh pr comment from allowed tools to force inline comments only 2025-11-29 12:38:35 +09:00
dayuan.jiang
78d9229ca3 fix: remove gh pr comment from allowed tools to force inline comments only 2025-11-29 12:36:35 +09:00
dayuan.jiang
d8e0a1daad fix: enforce inline comments for all issues found
- Explicitly require mcp inline comment tool for each issue
- Clarify gh pr comment is only for final summary
- Forbid dismissing issues as minor/harmless
2025-11-29 12:31:46 +09:00
Dayuan Jiang
32e75ab556 feat: expand PR review scope to catch more issues (#25)
* feat: use pull_request_target to support fork PR reviews

* feat: expand PR review scope to catch more issues

- Add categories for scope creep, suspicious .gitignore additions, UI inconsistencies
- Change from 'be very selective' to 'report ALL issues found'
- Simplify DO NOT comment list to allow more actionable feedback
2025-11-29 12:22:21 +09:00
Dayuan Jiang
b87e3a2de9 feat: use pull_request_target to support fork PR reviews (#24) 2025-11-29 11:14:49 +09:00
dayuan.jiang
b758f63d7f feat: use pull_request_target to support fork PR reviews 2025-11-29 11:09:51 +09:00
Dayuan Jiang
b32d42d962 Merge pull request #23 from DayuanJiang/feature/claude-code-actions
Feature/claude code actions
2025-11-29 01:50:27 +09:00
dayuan.jiang
603865fdb0 fix: add reopened trigger for pr-review workflow 2025-11-29 01:45:44 +09:00
dayuan.jiang
4cb4e187b3 Trigger PR review 2025-11-29 01:40:13 +09:00
Dayuan Jiang
b4d0c6c18b Merge pull request #22 from DayuanJiang/feature/claude-code-actions
Add Claude Code GitHub Actions with Bedrock and auto PR review
2025-11-29 01:32:06 +09:00
dayuan.jiang
c03c41d320 Run PR review on both opened and synchronize events 2025-11-29 01:29:25 +09:00
dayuan.jiang
9d248e25ad Resolve/collapse comment threads when issues are fixed 2025-11-29 01:26:00 +09:00
dayuan.jiang
4f4aae0e39 Check previous comments and mark resolved if fixed 2025-11-29 01:25:15 +09:00
dayuan.jiang
b00579b257 Remove automatic comment cleanup - GitHub handles outdated comments 2025-11-29 01:23:01 +09:00
dayuan.jiang
caf7ffe56c Clean up outdated review comments before new review 2025-11-29 01:21:44 +09:00
dayuan.jiang
50d16cbe47 Allow Claude to fetch AI SDK docs during PR review 2025-11-29 01:20:20 +09:00
dayuan.jiang
56167d363c Add specific AI SDK patterns to PR review prompt 2025-11-29 01:18:25 +09:00
dayuan.jiang
3e2dbbb541 Improve PR review prompt: add project context, narrow scope to real bugs only 2025-11-29 01:14:36 +09:00
dayuan.jiang
efebcea3ba Fix PR review: use correct params and enable inline comments 2025-11-29 00:58:02 +09:00
dayuan.jiang
68824bc951 Enable inline code review comments like Vercel bot 2025-11-29 00:52:57 +09:00
dayuan.jiang
794826550d Use stable v1 instead of beta for claude-code-action 2025-11-29 00:44:58 +09:00
dayuan.jiang
de98cf60ae Add Claude Code GitHub Actions with Bedrock and auto PR review 2025-11-29 00:29:56 +09:00
Dayuan Jiang
b3fc624e13 Merge pull request #16 from ylxmf2005/main
feat: support OpenAI compatible llm
2025-11-22 09:52:30 +09:00
ylxmf
d2dd501f3f feat: support OpenAI compatible llm 2025-11-21 17:03:47 +08:00
Dayuan Jiang
5964deeff7 Merge pull request #15 from DayuanJiang/docs/update-examples
Docs/update examples
2025-11-17 20:47:00 +09:00
Dayuan Jiang
663e1c8c77 Update examples section in README.md 2025-11-17 19:35:55 +09:00
dayuan.jiang
455115935c fix: Remove conflicting width attribute from colspan cell in README table
The cell with colspan=2 should not have width=50% as it creates
a layout conflict causing horizontal table spanning on GitHub.
2025-11-17 19:07:43 +09:00
Dayuan Jiang
8d36e0dfb0 Merge pull request #14 from DayuanJiang/docs/update-examples
docs: Update examples with new prompts and demo images
2025-11-17 19:03:29 +09:00
Dayuan Jiang
60f4694752 Merge pull request #13 from DayuanJiang/fix/resize
fix: Preserve state when resizing window to mobile size
2025-11-17 18:59:07 +09:00
dayuan.jiang
aa330f74b3 fix: Preserve state when resizing window to mobile size
Change mobile warning from unmounting components to showing an overlay.
This prevents losing diagram and chat state when window is resized.
2025-11-17 15:18:29 +09:00
dayuan.jiang
7a6a7eaf7c docs: Update examples with new prompts and demo images
- Add Examples section to README with 2-column grid layout
- Include demo images for GCP, AWS, Azure, animated connectors, and cat
- Update example panel buttons with clearer labels
- Add animated connector example button
- Add instruction for animated connectors in chat route
2025-11-17 15:12:16 +09:00
Dayuan Jiang
7518285e98 Merge pull request #12 from DayuanJiang/chore/fix-readme
chore: Update README for demo site link and simplify multi-provider s…
2025-11-16 09:16:53 +09:00
dayuan.jiang
5c5da397df chore: Update README for demo site link and simplify multi-provider support section 2025-11-16 09:16:02 +09:00
Dayuan Jiang
0385818b98 Merge pull request #11 from DayuanJiang/feature/seo-improvements
Feature/seo improvements
2025-11-16 09:06:27 +09:00
dayuan.jiang
5e5c1fc31c feat: Separate SEO content to /about page (best practice)
- Remove header from main page for clean editor-only interface
- Create /app/about/page.tsx with comprehensive SEO content (1000+ words)
- Add About link next to 'Next-AI-Drawio' title in chat panel
- Add GitHub icon link to /about page navigation
- Update sitemap.ts to include /about page (priority: 0.8)

SEO improvements following industry best practices:
- Separate marketing content from app interface (Figma/Canva/Miro approach)
- Server-rendered /about page for optimal crawlability
- Clean URL structure for better internal linking
- Multiple indexable pages for broader keyword coverage
- Proper semantic HTML: H1, H2, H3, article, section tags
- 1000+ words of keyword-rich content

/about page includes:
- AI diagram generator overview with value proposition
- 6 detailed feature sections (AI creation, AWS diagrams, image replication, etc.)
- 3 popular use cases (AWS architecture, flowcharts, system design)
- Step-by-step usage guide (4 steps)
- Benefits section (save time, precision, free, privacy)
- Clear call-to-action with link back to editor
- GitHub link in navigation for social proof

This follows Google-approved architecture and avoids hidden content penalties.
2025-11-16 09:04:01 +09:00
dayuan.jiang
50b860bdea fix: Update domain and improve SEO implementation
- Update all URLs from vercel.app to next-ai-drawio.jiang.jp
- Remove fake aggregateRating from JSON-LD (better SEO credibility)
- Move JSON-LD script to <head> for better SEO crawler detection
- Enhance JSON-LD description with specific features
- Add 'free diagram generator' and 'online diagram maker' keywords
- Improve Twitter Card description with call-to-action

Addresses critical issues identified in SEO review:
- Artificial rating data removed
- JSON-LD placement improved
- Domain consistency across all files
2025-11-16 08:41:49 +09:00
dayuan.jiang
533b3ccace feat: Add comprehensive SEO improvements
- Enhanced metadata with Open Graph and Twitter Card tags
- Added robots.ts for search engine crawling directives
- Added sitemap.ts for site structure
- Improved image alt text for better accessibility and SEO
- Added JSON-LD structured data (Schema.org SoftwareApplication)
- Configured proper meta tags including keywords, authors, and robots settings
- Set metadataBase for proper URL resolution
2025-11-16 08:36:13 +09:00
Dayuan Jiang
8084905711 chore: Add bug fix task for session timeout issue as todo
Add a task to solve the bug for long sessions.
2025-11-15 15:37:44 +09:00
Dayuan Jiang
0dffe8cc20 Merge pull request #10 from DayuanJiang/chore/modify_readme
chore: update readme
2025-11-15 15:35:50 +09:00
dayuan.jiang
065e8f0d62 chore: update readme 2025-11-15 15:32:21 +09:00
23 changed files with 6270 additions and 238 deletions

6
.eslintrc.json Normal file
View File

@@ -0,0 +1,6 @@
{
"extends": [
"next/core-web-vitals",
"next/typescript"
]
}

149
README.md
View File

@@ -4,7 +4,7 @@ A next.js web application that integrates AI capabilities with draw.io diagrams.
https://github.com/user-attachments/assets/b2eef5f3-b335-4e71-a755-dc2e80931979
Demo site: [https://next-ai-draw-io.vercel.app/](https://next-ai-draw-io.vercel.app/)
Demo site: [https://next-ai-draw-io.vercel.app](https://next-ai-draw-io.vercel.app)
## Features
@@ -12,9 +12,48 @@ Demo site: [https://next-ai-draw-io.vercel.app/](https://next-ai-draw-io.vercel.
- **Image-Based Diagram Replication**: Upload existing diagrams or images and have the AI 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.
- **Interactive Chat Interface**: Communicate with AI to refine your diagrams in real-time
- **Smart Editing**: Modify existing diagrams using simple text prompts
- **Targeted XML Editing**: AI can now make precise edits to specific parts of diagrams without regenerating the entire XML, making updates faster and more efficient
- **Improved XML Handling**: Automatic formatting of single-line XML for better compatibility and reliability
- **AWS Architecture Diagram Support**: Specialized support for generating AWS architecture diagrams
- **Animated Connectors**: Create dynamic and animated connectors between diagram elements for better visualization
## **Examples**
Here are some example prompts and their generated diagrams:
<div align="center">
<table width="100%">
<tr>
<td width="50%" valign="top">
<strong>GCP architecture diagram</strong><br />
<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/gcp_demo.svg" alt="GCP Architecture Diagram" width="480" />
</td>
<td width="50%" valign="top">
<strong>AWS architecture diagram</strong><br />
<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/aws_demo.svg" alt="AWS Architecture Diagram" width="480" />
</td>
</tr>
<tr>
<td width="50%" valign="top">
<strong>Azure architecture diagram</strong><br />
<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/azure_demo.svg" alt="Azure Architecture Diagram" width="480" />
</td>
<td width="50%" valign="top">
<strong>Animated transformer connectors</strong><br />
<p><strong>Prompt:</strong> Give me a **animated connector** diagram of transformer's architecture.</p>
<img src="./public/animated_connectors.svg" alt="Transformer Architecture with Animated Connectors" width="480" />
</td>
</tr>
<tr>
<td colspan="2" valign="top" align="center">
<strong>Cat sketch prompt</strong><br />
<p><strong>Prompt:</strong> Draw a cute cat for me.</p>
<img src="./public/cat_demo.svg" alt="Cat Drawing" width="260" />
</td>
</tr>
</table>
</div>
## How It Works
@@ -28,83 +67,16 @@ Diagrams are represented as XML that can be rendered in draw.io. The AI processe
## Multi-Provider Support
This application supports multiple AI providers, making it easy to deploy with your preferred service. Choose from:
- AWS Bedrock (default)
- OpenAI / OpenAI-compatible APIs (via `OPENAI_BASE_URL`)
- Anthropic
- Google AI
- Azure OpenAI
- Ollama
- OpenRouter
- DeepSeek
### Supported Providers
| Provider | Status | Best For |
|----------|--------|----------|
| **AWS Bedrock** | ✅ Default | Claude models via AWS infrastructure |
| **OpenAI** | ✅ Supported | GPT-4, GPT-5, and reasoning models |
| **Anthropic** | ✅ Supported | Direct access to Claude models |
| **Google AI** | ✅ Supported | Gemini models with multi-modal capabilities |
| **Azure OpenAI** | ✅ Supported | Enterprise OpenAI deployments |
| **Ollama** | ✅ Supported | Local/self-hosted open source models |
### Quick Setup by Provider
#### AWS Bedrock (Default)
```bash
AI_PROVIDER=bedrock
AI_MODEL=global.anthropic.claude-sonnet-4-5-20250929-v1:0
AWS_REGION=us-east-1
AWS_ACCESS_KEY_ID=your-access-key
AWS_SECRET_ACCESS_KEY=your-secret-key
```
#### OpenAI
```bash
AI_PROVIDER=openai
AI_MODEL=gpt-4o
OPENAI_API_KEY=sk-...
```
#### Anthropic
```bash
AI_PROVIDER=anthropic
AI_MODEL=claude-sonnet-4-5
ANTHROPIC_API_KEY=sk-ant-...
```
#### Google Generative AI
```bash
AI_PROVIDER=google
AI_MODEL=gemini-2.5-flash
GOOGLE_GENERATIVE_AI_API_KEY=...
```
#### Azure OpenAI
```bash
AI_PROVIDER=azure
AI_MODEL=your-deployment-name
AZURE_RESOURCE_NAME=your-resource
AZURE_API_KEY=...
```
#### Ollama (Local)
```bash
AI_PROVIDER=ollama
AI_MODEL=phi3
OLLAMA_BASE_URL=http://localhost:11434/api # Optional
```
Note: Install models locally first with `ollama pull <model-name>`
### Recommended Models
**Best Quality:**
- AWS Bedrock: `global.anthropic.claude-sonnet-4-5-20250929-v1:0`
- Anthropic: `claude-sonnet-4-5`
- OpenAI: `gpt-4o` or `gpt-5`
**Best Speed:**
- Google: `gemini-2.5-flash`
- OpenAI: `gpt-4o`
- Anthropic: `claude-haiku-4-5`
**Best Cost:**
- Ollama: Free (local models)
- Google: `gemini-1.5-flash-8b`
- OpenAI: `gpt-4o-mini`
Note that `claude-sonnet-4-5` has trained on draw.io diagrams with AWS logos, so if you want to create AWS architecture diagrams, this is the best choice.
## Getting Started
@@ -130,13 +102,14 @@ yarn install
Create a `.env.local` file in the root directory:
```bash
cp .env.example .env.local
cp env.example .env.local
```
Edit `.env.local` and configure your chosen provider:
- Set `AI_PROVIDER` to your chosen provider (bedrock, openai, anthropic, google, azure, ollama)
- Set `AI_MODEL` to the specific model you want to use
- Add the required API keys for your provider
- Set `AI_PROVIDER` to your chosen provider (bedrock, openai, anthropic, google, azure, ollama, openrouter, deepseek)
- Set `AI_MODEL` to the specific model you want to use
- Add the required API keys for your provider
See the [Multi-Provider Support](#multi-provider-support) section above for provider-specific configuration examples.
@@ -157,6 +130,8 @@ Check out the [Next.js deployment documentation](https://nextjs.org/docs/app/bui
Or you can deploy by this button.
[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2FDayuanJiang%2Fnext-ai-draw-io)
Be sure to **set the environment variables** in the Vercel dashboard as you did in your local `.env.local` file.
## Project Structure
```
@@ -176,6 +151,8 @@ public/ # Static assets including example images
- [x] Allow the LLM to modify the XML instead of generating it from scratch everytime.
- [x] Improve the smoothness of shape streaming updates.
- [x] Add multiple AI provider support (OpenAI, Anthropic, Google, Azure, Ollama)
- [x] Solve the bug that generation will fail for session that longer than 60s.
- [ ] Add API config on the UI.
## License
@@ -187,4 +164,8 @@ For support or inquiries, please open an issue on the GitHub repository or conta
- Email: me[at]jiang.jp
## Star History
[![Star History Chart](https://api.star-history.com/svg?repos=DayuanJiang/next-ai-draw-io&type=date&legend=top-left)](https://www.star-history.com/#DayuanJiang/next-ai-draw-io&type=date&legend=top-left)
---

336
app/about/page.tsx Normal file
View File

@@ -0,0 +1,336 @@
import type { Metadata } from "next";
import Link from "next/link";
import { FaGithub } from "react-icons/fa";
export const metadata: Metadata = {
title: "About - AI-Powered Diagram Generator | Next AI Draw.io",
description: "Learn about Next AI Draw.io, a free AI-powered diagram creation tool. Create AWS architecture diagrams, flowcharts, and UML diagrams using Claude Sonnet and GPT-4. No login required.",
keywords: ["about AI diagram generator", "diagram tool features", "how to create diagrams", "AI drawing tool capabilities", "draw.io integration"],
};
export default function About() {
return (
<div className="min-h-screen bg-gray-50">
{/* Navigation */}
<header className="bg-white border-b border-gray-200">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-4">
<div className="flex items-center justify-between">
<Link href="/" className="text-xl font-bold text-gray-900 hover:text-gray-700">
Next AI Draw.io
</Link>
<nav className="flex items-center gap-6 text-sm">
<Link href="/" className="text-gray-600 hover:text-gray-900 transition-colors">
Editor
</Link>
<Link href="/about" className="text-blue-600 font-semibold">
About
</Link>
<a
href="https://github.com/DayuanJiang/next-ai-draw-io"
target="_blank"
rel="noopener noreferrer"
className="text-gray-600 hover:text-gray-900 transition-colors"
aria-label="View on GitHub"
>
<FaGithub className="w-5 h-5" />
</a>
</nav>
</div>
</div>
</header>
{/* Main Content */}
<main className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
<article>
{/* Hero Section */}
<header className="mb-12">
<h1 className="text-4xl font-bold text-gray-900 mb-4">
AI-Powered Diagram Generator | Create Professional Diagrams Instantly
</h1>
<p className="text-xl text-gray-600">
Free, open-source diagram creation tool powered by AI. No login required, no installation needed.
</p>
</header>
{/* Introduction */}
<section className="mb-12">
<h2 className="text-2xl font-semibold text-gray-900 mb-4">What is Next AI Draw.io?</h2>
<div className="prose prose-lg max-w-none text-gray-700">
<p className="mb-4">
<strong>Next AI Draw.io</strong> is a free, AI-powered diagram creation tool that integrates seamlessly with draw.io.
Generate AWS architecture diagrams, flowcharts, UML diagrams, and technical documentation diagrams using natural language
prompts. No login required, no installation neededstart creating professional diagrams instantly in your browser.
</p>
<p className="mb-4">
Our intelligent diagram generator uses advanced AI models including <strong>Claude Sonnet</strong> and <strong>GPT-4</strong> to
understand your requirements and automatically create properly structured diagrams with appropriate symbols, layouts, and connections.
Simply describe what you need, upload reference images, or ask the AI to modify existing diagrams with our targeted XML editing feature.
</p>
<p>
Whether you're a software architect designing system infrastructure, a developer documenting APIs, a business analyst creating
process flows, or a student working on technical assignments, Next AI Draw.io makes diagram creation fast, accurate, and effortless.
</p>
</div>
</section>
{/* Key Features */}
<section className="mb-12">
<h2 className="text-2xl font-semibold text-gray-900 mb-6">Key Features</h2>
<div className="grid md:grid-cols-2 gap-6">
<div className="bg-white p-6 rounded-lg border border-gray-200 shadow-sm">
<h3 className="text-lg font-semibold text-gray-900 mb-3 flex items-center">
<span className="text-blue-600 mr-2">✓</span>
AI-Powered Diagram Creation
</h3>
<p className="text-gray-700">
Generate diagrams from natural language descriptions using Claude Sonnet or GPT-4.
Describe your diagram in plain English, and watch the AI create it with proper symbols,
layouts, and connections automatically.
</p>
</div>
<div className="bg-white p-6 rounded-lg border border-gray-200 shadow-sm">
<h3 className="text-lg font-semibold text-gray-900 mb-3 flex items-center">
<span className="text-blue-600 mr-2">✓</span>
AWS Architecture Diagrams
</h3>
<p className="text-gray-700">
Create professional cloud infrastructure diagrams with AWS-style icons and layouts.
Perfect for designing EC2 instances, Lambda functions, S3 buckets, RDS databases, VPCs,
and complete AWS solution architectures.
</p>
</div>
<div className="bg-white p-6 rounded-lg border border-gray-200 shadow-sm">
<h3 className="text-lg font-semibold text-gray-900 mb-3 flex items-center">
<span className="text-blue-600 mr-2">✓</span>
Image-Based Diagram Replication
</h3>
<p className="text-gray-700">
Upload existing diagrams or sketches, and the AI will automatically recreate them in draw.io format.
Modify uploaded images by describing the changes you want—the AI handles the rest.
</p>
</div>
<div className="bg-white p-6 rounded-lg border border-gray-200 shadow-sm">
<h3 className="text-lg font-semibold text-gray-900 mb-3 flex items-center">
<span className="text-blue-600 mr-2">✓</span>
Diagram History & Version Control
</h3>
<p className="text-gray-700">
Access previous versions of your diagrams and restore any version from your session history.
Never lose work—every AI modification is saved and can be undone with a single click.
</p>
</div>
<div className="bg-white p-6 rounded-lg border border-gray-200 shadow-sm">
<h3 className="text-lg font-semibold text-gray-900 mb-3 flex items-center">
<span className="text-blue-600 mr-2">✓</span>
Targeted XML Editing
</h3>
<p className="text-gray-700">
Precise diagram modifications using intelligent XML manipulation. Unlike full diagram regeneration,
targeted edits preserve your existing layout while making specific changes, ensuring consistent
and predictable results.
</p>
</div>
<div className="bg-white p-6 rounded-lg border border-gray-200 shadow-sm">
<h3 className="text-lg font-semibold text-gray-900 mb-3 flex items-center">
<span className="text-blue-600 mr-2">✓</span>
Multi-Provider AI Support
</h3>
<p className="text-gray-700">
Choose between Claude Sonnet, GPT-4, and other leading AI models for optimal results.
Each model has unique strengths—select the one that best fits your diagram complexity and style.
</p>
</div>
</div>
</section>
{/* Use Cases */}
<section className="mb-12">
<h2 className="text-2xl font-semibold text-gray-900 mb-6">Popular Use Cases</h2>
<div className="grid md:grid-cols-3 gap-6">
<div className="bg-blue-50 p-6 rounded-lg border border-blue-200">
<h3 className="text-xl font-semibold text-gray-900 mb-3">AWS Cloud Architecture</h3>
<p className="text-gray-700 mb-4">
Design scalable cloud infrastructure with EC2 instances, Lambda functions, S3 storage,
RDS databases, and VPC networking. Perfect for solution architects, cloud engineers,
and DevOps teams planning AWS deployments.
</p>
<p className="text-sm text-gray-600 italic">
Example: "Create an AWS diagram with an Application Load Balancer, two EC2 instances
in different availability zones, an RDS database, and an S3 bucket for static assets."
</p>
</div>
<div className="bg-green-50 p-6 rounded-lg border border-green-200">
<h3 className="text-xl font-semibold text-gray-900 mb-3">Flowcharts & Process Diagrams</h3>
<p className="text-gray-700 mb-4">
Create business process flows, decision trees, workflow diagrams, and algorithm flowcharts
for documentation, presentations, and process optimization. Ideal for business analysts,
project managers, and operations teams.
</p>
<p className="text-sm text-gray-600 italic">
Example: "Draw a flowchart for user authentication: check if user exists, verify password,
generate JWT token on success, show error message on failure."
</p>
</div>
<div className="bg-purple-50 p-6 rounded-lg border border-purple-200">
<h3 className="text-xl font-semibold text-gray-900 mb-3">System Design & UML Diagrams</h3>
<p className="text-gray-700 mb-4">
Generate system architecture diagrams, class diagrams, sequence diagrams, and
entity-relationship diagrams for software projects. Essential for software engineers,
system designers, and technical documentation.
</p>
<p className="text-sm text-gray-600 italic">
Example: "Create a class diagram for an e-commerce system with User, Product, Order,
and Payment classes showing their relationships and key methods."
</p>
</div>
</div>
</section>
{/* How It Works */}
<section className="mb-12 bg-white p-8 rounded-lg border border-gray-200">
<h2 className="text-2xl font-semibold text-gray-900 mb-6">How to Use Next AI Draw.io</h2>
<div className="space-y-6">
<div className="flex items-start">
<div className="flex-shrink-0 w-10 h-10 bg-blue-600 text-white rounded-full flex items-center justify-center font-bold mr-4">
1
</div>
<div>
<h3 className="text-lg font-semibold text-gray-900 mb-2">Open the Editor</h3>
<p className="text-gray-700">
Navigate to the main page and you'll see the draw.io editor with an AI chat panel on the right.
No account creation or login requiredstart immediately.
</p>
</div>
</div>
<div className="flex items-start">
<div className="flex-shrink-0 w-10 h-10 bg-blue-600 text-white rounded-full flex items-center justify-center font-bold mr-4">
2
</div>
<div>
<h3 className="text-lg font-semibold text-gray-900 mb-2">Describe Your Diagram</h3>
<p className="text-gray-700">
Type your diagram request in natural language. Be as detailed or as general as you like.
You can also upload reference images for the AI to analyze and replicate.
</p>
</div>
</div>
<div className="flex items-start">
<div className="flex-shrink-0 w-10 h-10 bg-blue-600 text-white rounded-full flex items-center justify-center font-bold mr-4">
3
</div>
<div>
<h3 className="text-lg font-semibold text-gray-900 mb-2">AI Generates Your Diagram</h3>
<p className="text-gray-700">
The AI processes your request and automatically creates your diagram in seconds.
Watch as it appears in the editor with proper symbols, layouts, and connections.
</p>
</div>
</div>
<div className="flex items-start">
<div className="flex-shrink-0 w-10 h-10 bg-blue-600 text-white rounded-full flex items-center justify-center font-bold mr-4">
4
</div>
<div>
<h3 className="text-lg font-semibold text-gray-900 mb-2">Refine and Export</h3>
<p className="text-gray-700">
Request modifications using the chat, manually edit in draw.io, or export to PNG, SVG,
or XML format. Access diagram history to restore previous versions anytime.
</p>
</div>
</div>
</div>
</section>
{/* Benefits */}
<section className="mb-12">
<h2 className="text-2xl font-semibold text-gray-900 mb-6">Why Choose Next AI Draw.io?</h2>
<div className="grid md:grid-cols-2 gap-6">
<div className="flex items-start">
<div className="flex-shrink-0 text-blue-600 text-2xl mr-3"></div>
<div>
<h3 className="text-lg font-semibold text-gray-900 mb-2">Save Time</h3>
<p className="text-gray-700">
Create complex diagrams in seconds instead of hours. No more dragging, aligning,
or searching for the right symbolsAI handles it all.
</p>
</div>
</div>
<div className="flex items-start">
<div className="flex-shrink-0 text-blue-600 text-2xl mr-3">🎯</div>
<div>
<h3 className="text-lg font-semibold text-gray-900 mb-2">Precision Editing</h3>
<p className="text-gray-700">
Targeted XML editing ensures changes are precise and predictable, unlike tools
that regenerate entire diagrams and lose your layout.
</p>
</div>
</div>
<div className="flex items-start">
<div className="flex-shrink-0 text-blue-600 text-2xl mr-3">🆓</div>
<div>
<h3 className="text-lg font-semibold text-gray-900 mb-2">Completely Free</h3>
<p className="text-gray-700">
No subscriptions, no usage limits, no hidden costs. Open-source and free forever.
Use it for personal projects, work, or education.
</p>
</div>
</div>
<div className="flex items-start">
<div className="flex-shrink-0 text-blue-600 text-2xl mr-3">🔒</div>
<div>
<h3 className="text-lg font-semibold text-gray-900 mb-2">Privacy First</h3>
<p className="text-gray-700">
No account required means your diagrams stay private. Work on sensitive
architecture designs without worrying about data storage or privacy policies.
</p>
</div>
</div>
</div>
</section>
{/* CTA Section */}
<section className="bg-blue-600 text-white p-8 rounded-lg text-center">
<h2 className="text-3xl font-bold mb-4">Ready to Create Your First AI Diagram?</h2>
<p className="text-xl mb-6">
Start generating professional diagrams in seconds. No signup required.
</p>
<Link
href="/"
className="inline-block bg-white text-blue-600 px-8 py-3 rounded-lg font-semibold hover:bg-gray-100 transition-colors"
>
Open Editor
</Link>
</section>
</article>
</main>
{/* Footer */}
<footer className="bg-white border-t border-gray-200 mt-16">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
<div className="text-center text-gray-600 text-sm">
<p className="mb-2">
Next AI Draw.io - Free AI-Powered Diagram Generator
</p>
<p>
Perfect for developers, architects, students, and business analysts.
Open source. No login required.
</p>
</div>
</div>
</footer>
</div>
);
}

View File

@@ -1,12 +1,67 @@
import { streamText, convertToModelMessages } from 'ai';
import { streamText, convertToModelMessages, createUIMessageStream, createUIMessageStreamResponse } from 'ai';
import { getAIModel } from '@/lib/ai-providers';
import { findCachedResponse } from '@/lib/cached-responses';
import { formatXML } from '@/lib/utils';
import { z } from "zod";
export const maxDuration = 60;
export const maxDuration = 300;
// Prefix for cached tool call IDs (used by client to detect cached responses)
export const CACHED_TOOL_PREFIX = 'cached-';
// Helper function to check if diagram is minimal/empty
function isMinimalDiagram(xml: string): boolean {
const stripped = xml.replace(/\s/g, '');
return !stripped.includes('id="2"');
}
// Helper function to create cached stream response
function createCachedStreamResponse(xml: string): Response {
const toolCallId = `${CACHED_TOOL_PREFIX}${Date.now()}`;
const stream = createUIMessageStream({
execute: async ({ writer }) => {
writer.write({ type: 'start' });
writer.write({ type: 'tool-input-start', toolCallId, toolName: 'display_diagram' });
// Stream the XML as JSON input so it matches the tool schema exactly
writer.write({ type: 'tool-input-delta', toolCallId, inputTextDelta: JSON.stringify({ xml }) });
// Input must match the tool schema (only xml field, no extra fields like fromCache)
writer.write({ type: 'tool-input-available', toolCallId, toolName: 'display_diagram', input: { xml } });
// Include tool output so the message is complete for follow-up conversations
writer.write({ type: 'tool-output-available', toolCallId, output: 'Successfully displayed the diagram.' });
writer.write({ type: 'finish' });
},
});
return createUIMessageStreamResponse({ stream });
}
export async function POST(req: Request) {
try {
const { messages, xml } = await req.json();
const { messages, xml, lastGeneratedXml } = await req.json();
// Basic validation for demo app
if (!messages || !Array.isArray(messages) || messages.length === 0) {
return Response.json({ error: 'Invalid messages' }, { status: 400 });
}
// === CACHE CHECK START ===
const isFirstMessage = messages.length === 1;
const isEmptyDiagram = !xml || xml.trim() === '' || isMinimalDiagram(xml);
if (isFirstMessage && isEmptyDiagram) {
const lastMessage = messages[0];
const textPart = lastMessage.parts?.find((p: any) => p.type === 'text');
const filePart = lastMessage.parts?.find((p: any) => p.type === 'file');
const cached = findCachedResponse(textPart?.text || '', !!filePart);
if (cached) {
console.log('[Cache] Returning cached response for:', textPart?.text);
return createCachedStreamResponse(cached.xml);
}
}
// === CACHE CHECK END ===
const systemMessage = `
You are an expert diagram creation assistant specializing in draw.io XML generation.
@@ -32,6 +87,7 @@ parameters: {
IMPORTANT: Choose the right tool:
- Use display_diagram for: Creating new diagrams, major restructuring, or when the current diagram XML is empty
- Use edit_diagram for: Small modifications, adding/removing elements, changing text/colors, repositioning items
- When using edit_diagram: If the current diagram XML is provided in the user message context, use it as the source of truth for constructing search patterns. If no XML is provided, you can use your memory of the diagram structure.
Core capabilities:
- Generate valid, well-formed XML strings for draw.io diagrams
@@ -78,19 +134,37 @@ When using edit_diagram tool:
// Extract file parts (images) from the last message
const fileParts = lastMessage.parts?.filter((part: any) => part.type === 'file') || [];
const formattedTextContent = `
Current diagram XML:
"""xml
${xml || ''}
"""
User input:
// Check diagram state - use formatted XML for reliable comparison
const hasDiagram = xml && !isMinimalDiagram(xml);
const noHistory = !lastGeneratedXml || lastGeneratedXml.trim() === '';
const formattedXml = hasDiagram ? formatXML(xml) : '';
const formattedLastGenXml = lastGeneratedXml ? formatXML(lastGeneratedXml) : '';
const userModified = hasDiagram && formattedLastGenXml && formattedXml !== formattedLastGenXml;
// Build context based on diagram state
let diagramContext = '';
if (hasDiagram && noHistory) {
// No history (e.g., cached response) - include XML directly
diagramContext = `\n\n[Current diagram XML - use this as source of truth for edits:]\n\`\`\`xml\n${xml}\n\`\`\``;
} else if (userModified) {
// User modified - include XML
diagramContext = `\n\n[User modified the diagram. Current XML:]\n\`\`\`xml\n${xml}\n\`\`\``;
}
// If unchanged and has history, agent can use memory (no XML sent = save tokens)
const formattedTextContent = `User input:
"""md
${lastMessageText}
"""`;
"""${diagramContext}`;
// Convert UIMessages to ModelMessages and add system message
const modelMessages = convertToModelMessages(messages);
let enhancedMessages = [...modelMessages];
// Filter out messages with empty content arrays (Bedrock API rejects these)
// This is a safety measure - ideally convertToModelMessages should handle all cases
let enhancedMessages = modelMessages.filter((msg: any) =>
msg.content && Array.isArray(msg.content) && msg.content.length > 0
);
// Update the last message with formatted content if it's a user message
if (enhancedMessages.length >= 1) {
@@ -117,16 +191,44 @@ ${lastMessageText}
}
}
console.log("Enhanced messages:", enhancedMessages);
// Add cache point to the last assistant message in conversation history
// This caches the entire conversation prefix for subsequent requests
// Strategy: system (cached) + history with last assistant (cached) + new user message
if (enhancedMessages.length >= 2) {
// Find the last assistant message (should be second-to-last, before current user message)
for (let i = enhancedMessages.length - 2; i >= 0; i--) {
if (enhancedMessages[i].role === 'assistant') {
enhancedMessages[i] = {
...enhancedMessages[i],
providerOptions: {
bedrock: { cachePoint: { type: 'default' } },
},
};
break; // Only cache the last assistant message
}
}
}
// Get AI model from environment configuration
const { model, providerOptions } = getAIModel();
const { model, providerOptions, headers } = getAIModel();
// System message with cache point for Bedrock (requires 1024+ tokens)
const systemMessageWithCache = {
role: 'system' as const,
content: systemMessage,
providerOptions: {
bedrock: { cachePoint: { type: 'default' } },
},
};
const result = streamText({
model,
system: systemMessage,
messages: enhancedMessages,
messages: [systemMessageWithCache, ...enhancedMessages],
...(providerOptions && { providerOptions }),
...(headers && { headers }),
onFinish: ({ usage }) => {
console.log('[API] Tokens:', usage?.inputTokens, 'in /', usage?.outputTokens, 'out, cached:', usage?.cachedInputTokens);
},
tools: {
// Client-side tool that will be executed on the client
display_diagram: {
@@ -141,6 +243,7 @@ ${lastMessageText}
</mxCell>
</root>
- Note that when you need to generate diagram about aws architecture, use **AWS 2025 icons**.
- If you are asked to generate animated connectors, make sure to include "flowAnimation=1" in the style of the connector elements.
`,
inputSchema: z.object({
xml: z.string().describe("XML string to be displayed on draw.io")
@@ -162,6 +265,7 @@ IMPORTANT: Keep edits concise:
},
},
temperature: 0,
maxSteps: 5, // Allow model to continue after server-side tool execution
});
// Error handler function to provide detailed error messages

View File

@@ -16,8 +16,49 @@ const geistMono = Geist_Mono({
});
export const metadata: Metadata = {
title: "Next-AI-Drawio",
description: "An AI-powered drawing tool that integrates with draw.io",
title: "Next AI Draw.io - AI-Powered Diagram Generator",
description: "Create AWS architecture diagrams, flowcharts, and technical diagrams using AI. Free online tool integrating draw.io with AI assistance for professional diagram creation.",
keywords: ["AI diagram generator", "AWS architecture", "flowchart creator", "draw.io", "AI drawing tool", "technical diagrams", "diagram automation", "free diagram generator", "online diagram maker"],
authors: [{ name: "Next AI Draw.io" }],
creator: "Next AI Draw.io",
publisher: "Next AI Draw.io",
metadataBase: new URL("https://next-ai-drawio.jiang.jp"),
openGraph: {
title: "Next AI Draw.io - AI Diagram Generator",
description: "Create professional diagrams with AI assistance. Supports AWS architecture, flowcharts, and more.",
type: "website",
url: "https://next-ai-drawio.jiang.jp",
siteName: "Next AI Draw.io",
locale: "en_US",
images: [
{
url: "/architecture.png",
width: 1200,
height: 630,
alt: "Next AI Draw.io - AI-powered diagram creation tool",
},
],
},
twitter: {
card: "summary_large_image",
title: "Next AI Draw.io - AI Diagram Generator",
description: "Create professional diagrams with AI assistance. Free, no login required.",
images: ["/architecture.png"],
},
robots: {
index: true,
follow: true,
googleBot: {
index: true,
follow: true,
"max-video-preview": -1,
"max-image-preview": "large",
"max-snippet": -1,
},
},
icons: {
icon: "/favicon.ico",
},
};
export default function RootLayout({
@@ -25,8 +66,29 @@ export default function RootLayout({
}: Readonly<{
children: React.ReactNode;
}>) {
const jsonLd = {
'@context': 'https://schema.org',
'@type': 'SoftwareApplication',
name: 'Next AI Draw.io',
applicationCategory: 'DesignApplication',
operatingSystem: 'Web Browser',
description: 'AI-powered diagram generator with targeted XML editing capabilities that integrates with draw.io for creating AWS architecture diagrams, flowcharts, and technical diagrams. Features diagram history, multi-provider AI support, and real-time collaboration.',
url: 'https://next-ai-drawio.jiang.jp',
offers: {
'@type': 'Offer',
price: '0',
priceCurrency: 'USD',
},
};
return (
<html lang="en">
<head>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
/>
</head>
<body
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
>

View File

@@ -40,20 +40,19 @@ export default function Home() {
};
}, []);
if (isMobile) {
return (
<div className="flex items-center justify-center h-screen bg-gray-100">
<div className="text-center p-8">
<h1 className="text-2xl font-semibold text-gray-800">
Please open this application on a desktop or laptop
</h1>
</div>
</div>
);
}
return (
<div className="flex h-screen bg-gray-100">
<div className="flex h-screen bg-gray-100 relative">
{/* Mobile warning overlay - keeps components mounted */}
{isMobile && (
<div className="absolute inset-0 z-50 flex items-center justify-center bg-gray-100">
<div className="text-center p-8">
<h1 className="text-2xl font-semibold text-gray-800">
Please open this application on a desktop or laptop
</h1>
</div>
</div>
)}
<div className={`${isChatVisible ? 'w-2/3' : 'w-full'} p-1 h-full relative transition-all duration-300 ease-in-out`}>
<DrawIoEmbed
ref={drawioRef}

12
app/robots.ts Normal file
View File

@@ -0,0 +1,12 @@
import { MetadataRoute } from 'next'
export default function robots(): MetadataRoute.Robots {
return {
rules: {
userAgent: '*',
allow: '/',
disallow: '/api/',
},
sitemap: 'https://next-ai-drawio.jiang.jp/sitemap.xml',
}
}

18
app/sitemap.ts Normal file
View File

@@ -0,0 +1,18 @@
import { MetadataRoute } from 'next'
export default function sitemap(): MetadataRoute.Sitemap {
return [
{
url: 'https://next-ai-drawio.jiang.jp',
lastModified: new Date(),
changeFrequency: 'weekly',
priority: 1,
},
{
url: 'https://next-ai-drawio.jiang.jp/about',
lastModified: new Date(),
changeFrequency: 'monthly',
priority: 0.8,
},
]
}

View File

@@ -50,25 +50,34 @@ export default function ExamplePanel({
{" "}
You can also upload images to use as references.
</p>
<p className="text-sm text-gray-500 mb-2">Try these examples:</p>
<p className="text-sm text-gray-500 mb-2">
Try these examples{" "}
<span className="text-xs text-gray-400">(cached for instant response)</span>:
</p>
<div className="flex flex-wrap gap-5">
<button
className="text-xs bg-gray-100 hover:bg-gray-200 text-gray-800 font-medium py-1 px-2 rounded"
onClick={() => setInput("Give me a **animated connector** diagram of transformer's architecture")}
>
Draw diagram with Animated Connectors
</button>
<button
className="text-xs bg-gray-100 hover:bg-gray-200 text-gray-800 font-medium py-1 px-2 rounded"
onClick={handleReplicateArchitecture}
>
Create this diagram in aws style
Create AWS architecture
</button>
<button
className="text-xs bg-gray-100 hover:bg-gray-200 text-gray-800 font-medium py-1 px-2 rounded"
onClick={handleReplicateFlowchart}
>
Replicate this flowchart
Replicate flowchart
</button>
<button
className="text-xs bg-gray-100 hover:bg-gray-200 text-gray-800 font-medium py-1 px-2 rounded"
onClick={() => setInput("Draw a cat for me")}
>
Draw a cat for me
Draw a cat
</button>
</div>
</div>

View File

@@ -1,15 +1,23 @@
"use client";
import type React from "react";
import { useRef, useEffect, useState, useCallback } from "react";
import Image from "next/image";
import { ScrollArea } from "@/components/ui/scroll-area";
import ExamplePanel from "./chat-example-panel";
import { UIMessage } from "ai";
import { convertToLegalXml, replaceNodes } from "@/lib/utils";
import { Copy, Check, X } from "lucide-react";
import { useDiagram } from "@/contexts/diagram-context";
const getMessageTextContent = (message: UIMessage): string => {
if (!message.parts) return "";
return message.parts
.filter((part: any) => part.type === "text")
.map((part: any) => part.text)
.join("\n");
};
interface ChatMessageDisplayProps {
messages: UIMessage[];
error?: Error | null;
@@ -30,6 +38,21 @@ export function ChatMessageDisplay({
const [expandedTools, setExpandedTools] = useState<Record<string, boolean>>(
{}
);
const [copiedMessageId, setCopiedMessageId] = useState<string | null>(null);
const [copyFailedMessageId, setCopyFailedMessageId] = useState<string | null>(null);
const copyMessageToClipboard = async (messageId: string, text: string) => {
try {
await navigator.clipboard.writeText(text);
setCopiedMessageId(messageId);
setTimeout(() => setCopiedMessageId(null), 2000);
} catch (err) {
console.error("Failed to copy message:", err);
setCopyFailedMessageId(messageId);
setTimeout(() => setCopyFailedMessageId(null), 2000);
}
};
const handleDisplayChart = useCallback(
(xml: string) => {
const currentXml = xml || "";
@@ -134,19 +157,23 @@ export function ChatMessageDisplay({
<div className="h-4 w-4 border-2 border-primary border-t-transparent rounded-full animate-spin" />
) : state === "output-available" ? (
<div className="text-green-600">
{output || (toolName === "display_diagram"
? "Diagram generated"
: toolName === "edit_diagram"
? "Diagram edited"
: "Tool executed")}
{typeof output === "object" && output !== null
? (output as any).message || JSON.stringify(output)
: output || (toolName === "display_diagram"
? "Diagram generated"
: toolName === "edit_diagram"
? "Diagram edited"
: "Tool executed")}
</div>
) : state === "output-error" ? (
<div className="text-red-600">
{output || (toolName === "display_diagram"
? "Error generating diagram"
: toolName === "edit_diagram"
? "Error editing diagram"
: "Tool error")}
{typeof output === "object" && output !== null
? (output as any).message || JSON.stringify(output)
: output || (toolName === "display_diagram"
? "Error generating diagram"
: toolName === "edit_diagram"
? "Error editing diagram"
: "Tool error")}
</div>
) : null}
</div>
@@ -160,51 +187,66 @@ export function ChatMessageDisplay({
{messages.length === 0 ? (
<ExamplePanel setInput={setInput} setFiles={setFiles} />
) : (
messages.map((message) => (
<div
key={message.id}
className={`mb-4 ${
message.role === "user" ? "text-right" : "text-left"
}`}
>
messages.map((message) => {
const userMessageText = message.role === "user" ? getMessageTextContent(message) : "";
return (
<div
className={`inline-block px-4 py-2 whitespace-pre-wrap text-sm rounded-lg max-w-[85%] break-words ${
message.role === "user"
? "bg-primary text-primary-foreground"
: "bg-muted text-muted-foreground"
}`}
key={message.id}
className={`mb-4 flex ${message.role === "user" ? "justify-end" : "justify-start"}`}
>
{message.parts?.map((part: any, index: number) => {
switch (part.type) {
case "text":
return (
<div key={index}>{part.text}</div>
);
case "file":
return (
<div key={index} className="mt-2">
<Image
src={part.url}
width={200}
height={200}
alt={`file-${index}`}
className="rounded-md border"
style={{
objectFit: "contain",
}}
/>
</div>
);
default:
if (part.type?.startsWith("tool-")) {
return renderToolPart(part);
}
return null;
}
})}
{message.role === "user" && userMessageText && (
<button
onClick={() => copyMessageToClipboard(message.id, userMessageText)}
className="p-1 text-gray-400 hover:text-gray-600 transition-colors self-center mr-1"
title={copiedMessageId === message.id ? "Copied!" : copyFailedMessageId === message.id ? "Failed to copy" : "Copy message"}
>
{copiedMessageId === message.id ? (
<Check className="h-3.5 w-3.5 text-green-500" />
) : copyFailedMessageId === message.id ? (
<X className="h-3.5 w-3.5 text-red-500" />
) : (
<Copy className="h-3.5 w-3.5" />
)}
</button>
)}
<div
className={`px-4 py-2 whitespace-pre-wrap text-sm rounded-lg max-w-[85%] break-words ${message.role === "user"
? "bg-primary text-primary-foreground"
: "bg-muted text-muted-foreground"
}`}
>
{message.parts?.map((part: any, index: number) => {
switch (part.type) {
case "text":
return (
<div key={index}>{part.text}</div>
);
case "file":
return (
<div key={index} className="mt-2">
<Image
src={part.url}
width={200}
height={200}
alt={`Uploaded diagram or image for AI analysis`}
className="rounded-md border"
style={{
objectFit: "contain",
}}
/>
</div>
);
default:
if (part.type?.startsWith("tool-")) {
return renderToolPart(part);
}
return null;
}
})}
</div>
</div>
</div>
))
);
})
)}
{error && (
<div className="text-red-500 text-sm mt-2">

View File

@@ -4,6 +4,7 @@ import type React from "react";
import { useRef, useEffect, useState } from "react";
import { FaGithub } from "react-icons/fa";
import { PanelRightClose, PanelRightOpen } from "lucide-react";
import Link from "next/link";
import {
Card,
@@ -18,6 +19,7 @@ import { ChatInput } from "@/components/chat-input";
import { ChatMessageDisplay } from "./chat-message-display";
import { useDiagram } from "@/contexts/diagram-context";
import { replaceNodes, formatXML } from "@/lib/utils";
import { CACHED_TOOL_PREFIX } from "@/app/api/chat/route";
import { ButtonWithTooltip } from "@/components/button-with-tooltip";
interface ChatPanelProps {
@@ -32,6 +34,8 @@ export default function ChatPanel({ isVisible, onToggleVisibility }: ChatPanelPr
resolverRef,
chartXML,
clearDiagram,
getLastAgentGeneratedXml,
markAgentDiagramPending,
} = useDiagram();
const onFetchChart = () => {
@@ -72,7 +76,15 @@ export default function ChatPanel({ isVisible, onToggleVisibility }: ChatPanelPr
}),
async onToolCall({ toolCall }) {
if (toolCall.toolName === "display_diagram") {
// Diagram is handled streamingly in the ChatMessageDisplay component
// Check if this is a cached response by looking at the toolCallId prefix
const isCached = toolCall.toolCallId.startsWith(CACHED_TOOL_PREFIX);
// Only mark as pending if agent actually generated it (not cached)
// This ensures lastAgentGeneratedXml stays empty for cached responses
if (!isCached) {
markAgentDiagramPending();
}
addToolResult({
tool: "display_diagram",
toolCallId: toolCall.toolCallId,
@@ -95,6 +107,9 @@ export default function ChatPanel({ isVisible, onToggleVisibility }: ChatPanelPr
// Load the edited diagram
onDisplayChart(editedXml);
// Mark that an agent diagram is pending - the next export will update lastAgentGeneratedXml
markAgentDiagramPending();
addToolResult({
tool: "edit_diagram",
toolCallId: toolCall.toolCallId,
@@ -133,10 +148,6 @@ Please retry with an adjusted search pattern or use display_diagram if retries a
}
}, [messages]);
// Debug: Log status changes
useEffect(() => {
console.log('[ChatPanel] Status changed to:', status);
}, [status]);
const onFormSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
@@ -170,11 +181,14 @@ Please retry with an adjusted search pattern or use display_diagram if retries a
}
}
const lastGenXml = getLastAgentGeneratedXml();
sendMessage(
{ parts },
{
body: {
xml: chartXml,
lastGeneratedXml: lastGenXml,
},
}
);
@@ -226,7 +240,12 @@ Please retry with an adjusted search pattern or use display_diagram if retries a
return (
<Card className="h-full flex flex-col rounded-none py-0 gap-0">
<CardHeader className="p-4 flex flex-row justify-between items-center">
<CardTitle>Next-AI-Drawio</CardTitle>
<div className="flex items-center gap-3">
<CardTitle>Next-AI-Drawio</CardTitle>
<Link href="/about" className="text-sm text-gray-600 hover:text-gray-900 transition-colors">
About
</Link>
</div>
<div className="flex items-center gap-2">
<ButtonWithTooltip
tooltipContent="Hide chat panel (Ctrl+B)"

View File

@@ -79,7 +79,7 @@ export function FilePreviewList({ files, onRemoveFile }: FilePreviewListProps) {
<div className="relative w-auto h-auto max-w-[90vw] max-h-[90vh]">
<Image
src={selectedImage}
alt="Preview"
alt="Full size preview of uploaded diagram or image"
width={1200}
height={900}
className="object-contain max-w-full max-h-[90vh] w-auto h-auto"

View File

@@ -2,12 +2,16 @@
import React, { createContext, useContext, useRef, useState } from "react";
import type { DrawIoEmbedRef } from "react-drawio";
import { extractDiagramXML } from "../lib/utils";
import { extractDiagramXML, formatXML } from "../lib/utils";
interface DiagramContextType {
chartXML: string;
latestSvg: string;
diagramHistory: { svg: string; xml: string }[];
lastAgentGeneratedXml: string;
getLastAgentGeneratedXml: () => string;
setLastAgentGeneratedXml: (xml: string) => void;
markAgentDiagramPending: () => void;
loadDiagram: (chart: string) => void;
handleExport: () => void;
resolverRef: React.Ref<((value: string) => void) | null>;
@@ -24,9 +28,25 @@ export function DiagramProvider({ children }: { children: React.ReactNode }) {
const [diagramHistory, setDiagramHistory] = useState<
{ svg: string; xml: string }[]
>([]);
const [lastAgentGeneratedXml, setLastAgentGeneratedXmlState] = useState<string>("");
const lastAgentGeneratedXmlRef = useRef<string>("");
const agentDiagramPendingRef = useRef<boolean>(false);
const drawioRef = useRef<DrawIoEmbedRef | null>(null);
const resolverRef = useRef<((value: string) => void) | null>(null);
// Wrapper to keep ref and state in sync
const setLastAgentGeneratedXml = (xml: string) => {
lastAgentGeneratedXmlRef.current = xml;
setLastAgentGeneratedXmlState(xml);
};
// Getter that returns the ref value (always up-to-date, even in async contexts)
const getLastAgentGeneratedXml = () => lastAgentGeneratedXmlRef.current;
const markAgentDiagramPending = () => {
agentDiagramPendingRef.current = true;
};
const handleExport = () => {
if (drawioRef.current) {
drawioRef.current.exportDiagram({
@@ -54,6 +74,15 @@ export function DiagramProvider({ children }: { children: React.ReactNode }) {
xml: extractedXML,
},
]);
// If agent just generated a diagram, update lastAgentGeneratedXml with the exported XML
// This ensures we compare apples-to-apples (both formatted the same way)
if (agentDiagramPendingRef.current) {
const formatted = formatXML(extractedXML);
setLastAgentGeneratedXml(formatted);
agentDiagramPendingRef.current = false;
}
if (resolverRef.current) {
resolverRef.current(extractedXML);
resolverRef.current = null;
@@ -66,6 +95,7 @@ export function DiagramProvider({ children }: { children: React.ReactNode }) {
setChartXML(emptyDiagram);
setLatestSvg("");
setDiagramHistory([]);
setLastAgentGeneratedXml("");
};
return (
@@ -74,6 +104,10 @@ export function DiagramProvider({ children }: { children: React.ReactNode }) {
chartXML,
latestSvg,
diagramHistory,
lastAgentGeneratedXml,
getLastAgentGeneratedXml,
setLastAgentGeneratedXml,
markAgentDiagramPending,
loadDiagram,
handleExport,
resolverRef,

View File

@@ -1,6 +1,6 @@
# AI Provider Configuration
# AI_PROVIDER: Which provider to use
# Options: bedrock, openai, anthropic, google, azure, ollama, openrouter
# Options: bedrock, openai, anthropic, google, azure, ollama, openrouter, deepseek
# Default: bedrock
AI_PROVIDER=bedrock
@@ -14,21 +14,30 @@ AI_MODEL=global.anthropic.claude-sonnet-4-5-20250929-v1:0
# OpenAI Configuration
# OPENAI_API_KEY=sk-...
# OPENAI_BASE_URL=https://api.openai.com/v1 # Optional: Custom OpenAI-compatible endpoint
# OPENAI_ORGANIZATION=org-... # Optional
# OPENAI_PROJECT=proj_... # Optional
# Anthropic (Direct) Configuration
# ANTHROPIC_API_KEY=sk-ant-...
# ANTHROPIC_BASE_URL=https://your-custom-anthropic/v1
# Google Generative AI Configuration
# GOOGLE_GENERATIVE_AI_API_KEY=...
# GOOGLE_BASE_URL=https://generativelanguage.googleapis.com/v1beta # Optional: Custom endpoint
# Azure OpenAI Configuration
# AZURE_RESOURCE_NAME=your-resource-name
# AZURE_API_KEY=...
# AZURE_BASE_URL=https://your-resource.openai.azure.com # Optional: Custom endpoint (overrides resourceName)
# Ollama (Local) Configuration
# OLLAMA_BASE_URL=http://localhost:11434/api # Optional, defaults to localhost
# OpenRouter Configuration
# OPENROUTER_API_KEY=sk-or-v1-...
# OPENROUTER_BASE_URL=https://openrouter.ai/api/v1 # Optional: Custom endpoint
# DeepSeek Configuration
# DEEPSEEK_API_KEY=sk-...
# DEEPSEEK_BASE_URL=https://api.deepseek.com/v1 # Optional: Custom endpoint

View File

@@ -1,10 +1,11 @@
import { bedrock } from '@ai-sdk/amazon-bedrock';
import { openai } from '@ai-sdk/openai';
import { anthropic } from '@ai-sdk/anthropic';
import { google } from '@ai-sdk/google';
import { azure } from '@ai-sdk/azure';
import { ollama } from 'ollama-ai-provider-v2';
import { openai, createOpenAI } from '@ai-sdk/openai';
import { createAnthropic } from '@ai-sdk/anthropic';
import { google, createGoogleGenerativeAI } from '@ai-sdk/google';
import { azure, createAzure } from '@ai-sdk/azure';
import { ollama, createOllama } from 'ollama-ai-provider-v2';
import { createOpenRouter } from '@openrouter/ai-sdk-provider';
import { deepseek, createDeepSeek } from '@ai-sdk/deepseek';
export type ProviderName =
| 'bedrock'
@@ -13,20 +14,25 @@ export type ProviderName =
| 'google'
| 'azure'
| 'ollama'
| 'openrouter';
| 'openrouter'
| 'deepseek';
interface ModelConfig {
model: any;
providerOptions?: any;
headers?: Record<string, string>;
}
// Anthropic beta headers for fine-grained tool streaming
const ANTHROPIC_BETA_OPTIONS = {
anthropic: {
additionalModelRequestFields: {
anthropic_beta: ['fine-grained-tool-streaming-2025-05-14']
}
}
// Bedrock provider options for Anthropic beta features
const BEDROCK_ANTHROPIC_BETA = {
bedrock: {
anthropicBeta: ['fine-grained-tool-streaming-2025-05-14'],
},
};
// Direct Anthropic API headers for beta features
const ANTHROPIC_BETA_HEADERS = {
'anthropic-beta': 'fine-grained-tool-streaming-2025-05-14',
};
/**
@@ -41,6 +47,7 @@ function validateProviderCredentials(provider: ProviderName): void {
azure: 'AZURE_API_KEY',
ollama: null, // No credentials needed for local Ollama
openrouter: 'OPENROUTER_API_KEY',
deepseek: 'DEEPSEEK_API_KEY',
};
const requiredVar = requiredEnvVars[provider];
@@ -56,17 +63,20 @@ function validateProviderCredentials(provider: ProviderName): void {
* Get the AI model based on environment variables
*
* Environment variables:
* - AI_PROVIDER: The provider to use (bedrock, openai, anthropic, google, azure, ollama, openrouter)
* - AI_PROVIDER: The provider to use (bedrock, openai, anthropic, google, azure, ollama, openrouter, deepseek)
* - AI_MODEL: The model ID/name for the selected provider
*
* Provider-specific env vars:
* - OPENAI_API_KEY: OpenAI API key
* - OPENAI_BASE_URL: Custom OpenAI-compatible endpoint (optional)
* - ANTHROPIC_API_KEY: Anthropic API key
* - GOOGLE_GENERATIVE_AI_API_KEY: Google API key
* - AZURE_RESOURCE_NAME, AZURE_API_KEY: Azure OpenAI credentials
* - AWS_REGION, AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY: AWS Bedrock credentials
* - OLLAMA_BASE_URL: Ollama server URL (optional, defaults to http://localhost:11434)
* - OPENROUTER_API_KEY: OpenRouter API key
* - DEEPSEEK_API_KEY: DeepSeek API key
* - DEEPSEEK_BASE_URL: DeepSeek endpoint (optional)
*/
export function getAIModel(): ModelConfig {
const provider = (process.env.AI_PROVIDER || 'bedrock') as ProviderName;
@@ -86,55 +96,105 @@ export function getAIModel(): ModelConfig {
let model: any;
let providerOptions: any = undefined;
let headers: Record<string, string> | undefined = undefined;
switch (provider) {
case 'bedrock':
model = bedrock(modelId);
// Add Anthropic beta headers if using Claude models via Bedrock
// Add Anthropic beta options if using Claude models via Bedrock
if (modelId.includes('anthropic.claude')) {
providerOptions = ANTHROPIC_BETA_OPTIONS;
providerOptions = BEDROCK_ANTHROPIC_BETA;
}
break;
case 'openai':
model = openai(modelId);
if (process.env.OPENAI_BASE_URL) {
const customOpenAI = createOpenAI({
apiKey: process.env.OPENAI_API_KEY,
baseURL: process.env.OPENAI_BASE_URL,
});
model = customOpenAI.chat(modelId);
} else {
model = openai(modelId);
}
break;
case 'anthropic':
model = anthropic(modelId);
const customProvider = createAnthropic({
apiKey: process.env.ANTHROPIC_API_KEY,
baseURL: process.env.ANTHROPIC_BASE_URL || 'https://api.anthropic.com/v1',
headers: ANTHROPIC_BETA_HEADERS,
});
model = customProvider(modelId);
// Add beta headers for fine-grained tool streaming
providerOptions = ANTHROPIC_BETA_OPTIONS;
headers = ANTHROPIC_BETA_HEADERS;
break;
case 'google':
model = google(modelId);
if (process.env.GOOGLE_BASE_URL) {
const customGoogle = createGoogleGenerativeAI({
apiKey: process.env.GOOGLE_GENERATIVE_AI_API_KEY,
baseURL: process.env.GOOGLE_BASE_URL,
});
model = customGoogle(modelId);
} else {
model = google(modelId);
}
break;
case 'azure':
model = azure(modelId);
if (process.env.AZURE_BASE_URL) {
const customAzure = createAzure({
apiKey: process.env.AZURE_API_KEY,
baseURL: process.env.AZURE_BASE_URL,
});
model = customAzure(modelId);
} else {
model = azure(modelId);
}
break;
case 'ollama':
model = ollama(modelId);
if (process.env.OLLAMA_BASE_URL) {
const customOllama = createOllama({
baseURL: process.env.OLLAMA_BASE_URL,
});
model = customOllama(modelId);
} else {
model = ollama(modelId);
}
break;
case 'openrouter':
const openrouter = createOpenRouter({
apiKey: process.env.OPENROUTER_API_KEY,
...(process.env.OPENROUTER_BASE_URL && { baseURL: process.env.OPENROUTER_BASE_URL }),
});
model = openrouter(modelId);
break;
case 'deepseek':
if (process.env.DEEPSEEK_BASE_URL) {
const customDeepSeek = createDeepSeek({
apiKey: process.env.DEEPSEEK_API_KEY,
baseURL: process.env.DEEPSEEK_BASE_URL,
});
model = customDeepSeek(modelId);
} else {
model = deepseek(modelId);
}
break;
default:
throw new Error(
`Unknown AI provider: ${provider}. Supported providers: bedrock, openai, anthropic, google, azure, ollama, openrouter`
`Unknown AI provider: ${provider}. Supported providers: bedrock, openai, anthropic, google, azure, ollama, openrouter, deepseek`
);
}
// Log if provider options are being applied
if (providerOptions) {
console.log('[AI Provider] Applying provider-specific options');
// Log if provider options or headers are being applied
if (providerOptions || headers) {
console.log('[AI Provider] Applying provider-specific options/headers');
}
return { model, providerOptions };
return { model, providerOptions, headers };
}

557
lib/cached-responses.ts Normal file
View File

@@ -0,0 +1,557 @@
export interface CachedResponse {
promptText: string;
hasImage: boolean;
xml: string;
}
export const CACHED_EXAMPLE_RESPONSES: CachedResponse[] = [
{
promptText: "Give me a **animated connector** diagram of transformer's architecture",
hasImage: false,
xml: `<root>
<mxCell id="0"/>
<mxCell id="1" parent="0"/>
<!-- Title -->
<mxCell id="title" value="Transformer Architecture" style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=20;fontStyle=1;" vertex="1" parent="1">
<mxGeometry x="300" y="20" width="250" height="30" as="geometry"/>
</mxCell>
<!-- Input Embedding (Left - Encoder Side) -->
<mxCell id="input_embed" value="Input Embedding" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;fontSize=11;" vertex="1" parent="1">
<mxGeometry x="80" y="480" width="120" height="40" as="geometry"/>
</mxCell>
<!-- Positional Encoding (Left) -->
<mxCell id="pos_enc_left" value="Positional Encoding" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;fontSize=11;" vertex="1" parent="1">
<mxGeometry x="80" y="420" width="120" height="40" as="geometry"/>
</mxCell>
<!-- Encoder Stack -->
<mxCell id="encoder_box" value="ENCODER" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#e1d5e7;strokeColor=#9673a6;verticalAlign=top;fontSize=12;fontStyle=1;" vertex="1" parent="1">
<mxGeometry x="60" y="180" width="160" height="220" as="geometry"/>
</mxCell>
<!-- Multi-Head Attention (Encoder) -->
<mxCell id="mha_enc" value="Multi-Head&#xa;Attention" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=10;" vertex="1" parent="1">
<mxGeometry x="80" y="330" width="120" height="50" as="geometry"/>
</mxCell>
<!-- Add & Norm 1 (Encoder) -->
<mxCell id="add_norm1_enc" value="Add &amp; Norm" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;fontSize=10;" vertex="1" parent="1">
<mxGeometry x="80" y="280" width="120" height="30" as="geometry"/>
</mxCell>
<!-- Feed Forward (Encoder) -->
<mxCell id="ff_enc" value="Feed Forward" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=10;" vertex="1" parent="1">
<mxGeometry x="80" y="240" width="120" height="30" as="geometry"/>
</mxCell>
<!-- Add & Norm 2 (Encoder) -->
<mxCell id="add_norm2_enc" value="Add &amp; Norm" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;fontSize=10;" vertex="1" parent="1">
<mxGeometry x="80" y="200" width="120" height="30" as="geometry"/>
</mxCell>
<!-- Nx label for encoder -->
<mxCell id="nx_enc" value="Nx" style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=11;fontStyle=2;" vertex="1" parent="1">
<mxGeometry x="30" y="275" width="30" height="30" as="geometry"/>
</mxCell>
<!-- Output Embedding (Right - Decoder Side) -->
<mxCell id="output_embed" value="Output Embedding" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;fontSize=11;" vertex="1" parent="1">
<mxGeometry x="650" y="480" width="120" height="40" as="geometry"/>
</mxCell>
<!-- Positional Encoding (Right) -->
<mxCell id="pos_enc_right" value="Positional Encoding" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;fontSize=11;" vertex="1" parent="1">
<mxGeometry x="650" y="420" width="120" height="40" as="geometry"/>
</mxCell>
<!-- Decoder Stack -->
<mxCell id="decoder_box" value="DECODER" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#ffe6cc;strokeColor=#d79b00;verticalAlign=top;fontSize=12;fontStyle=1;" vertex="1" parent="1">
<mxGeometry x="630" y="140" width="160" height="260" as="geometry"/>
</mxCell>
<!-- Masked Multi-Head Attention (Decoder) -->
<mxCell id="masked_mha_dec" value="Masked Multi-Head&#xa;Attention" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=10;" vertex="1" parent="1">
<mxGeometry x="650" y="340" width="120" height="50" as="geometry"/>
</mxCell>
<!-- Add & Norm 1 (Decoder) -->
<mxCell id="add_norm1_dec" value="Add &amp; Norm" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;fontSize=10;" vertex="1" parent="1">
<mxGeometry x="650" y="290" width="120" height="30" as="geometry"/>
</mxCell>
<!-- Multi-Head Attention (Decoder - Cross Attention) -->
<mxCell id="mha_dec" value="Multi-Head&#xa;Attention" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=10;" vertex="1" parent="1">
<mxGeometry x="650" y="240" width="120" height="40" as="geometry"/>
</mxCell>
<!-- Add & Norm 2 (Decoder) -->
<mxCell id="add_norm2_dec" value="Add &amp; Norm" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;fontSize=10;" vertex="1" parent="1">
<mxGeometry x="650" y="200" width="120" height="30" as="geometry"/>
</mxCell>
<!-- Feed Forward (Decoder) -->
<mxCell id="ff_dec" value="Feed Forward" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=10;" vertex="1" parent="1">
<mxGeometry x="650" y="160" width="120" height="30" as="geometry"/>
</mxCell>
<!-- Add & Norm 3 (Decoder) -->
<mxCell id="add_norm3_dec" value="Add &amp; Norm" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;fontSize=10;" vertex="1" parent="1">
<mxGeometry x="650" y="120" width="120" height="30" as="geometry"/>
</mxCell>
<!-- Nx label for decoder -->
<mxCell id="nx_dec" value="Nx" style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=11;fontStyle=2;" vertex="1" parent="1">
<mxGeometry x="790" y="255" width="30" height="30" as="geometry"/>
</mxCell>
<!-- Linear -->
<mxCell id="linear" value="Linear" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f8cecc;strokeColor=#b85450;fontSize=11;" vertex="1" parent="1">
<mxGeometry x="650" y="80" width="120" height="30" as="geometry"/>
</mxCell>
<!-- Softmax -->
<mxCell id="softmax" value="Softmax" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f8cecc;strokeColor=#b85450;fontSize=11;" vertex="1" parent="1">
<mxGeometry x="650" y="40" width="120" height="30" as="geometry"/>
</mxCell>
<!-- Output Probabilities -->
<mxCell id="output" value="Output Probabilities" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;fontSize=11;fontStyle=1;" vertex="1" parent="1">
<mxGeometry x="640" y="0" width="140" height="30" as="geometry"/>
</mxCell>
<!-- Animated Connectors - Encoder Side -->
<mxCell id="conn1" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;entryX=0.5;entryY=1;entryDx=0;entryDy=0;strokeWidth=2;strokeColor=#6c8ebf;flowAnimation=1;" edge="1" parent="1" source="input_embed" target="pos_enc_left">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="conn2" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;entryX=0.5;entryY=1;entryDx=0;entryDy=0;strokeWidth=2;strokeColor=#6c8ebf;flowAnimation=1;" edge="1" parent="1" source="pos_enc_left" target="mha_enc">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="conn3" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;entryX=0.5;entryY=1;entryDx=0;entryDy=0;strokeWidth=2;strokeColor=#82b366;flowAnimation=1;" edge="1" parent="1" source="mha_enc" target="add_norm1_enc">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="conn4" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;entryX=0.5;entryY=1;entryDx=0;entryDy=0;strokeWidth=2;strokeColor=#d6b656;flowAnimation=1;" edge="1" parent="1" source="add_norm1_enc" target="ff_enc">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="conn5" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;entryX=0.5;entryY=1;entryDx=0;entryDy=0;strokeWidth=2;strokeColor=#82b366;flowAnimation=1;" edge="1" parent="1" source="ff_enc" target="add_norm2_enc">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<!-- Encoder to Decoder Cross Attention -->
<mxCell id="conn_cross" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;strokeWidth=3;strokeColor=#9673a6;flowAnimation=1;dashed=1;" edge="1" parent="1" source="add_norm2_enc" target="mha_dec">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="400" y="215"/>
<mxPoint x="400" y="260"/>
</Array>
</mxGeometry>
</mxCell>
<mxCell id="cross_label" value="K, V" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];fontSize=10;fontStyle=1;fillColor=#ffffff;" vertex="1" connectable="0" parent="conn_cross">
<mxGeometry x="-0.1" y="1" relative="1" as="geometry">
<mxPoint x="10" y="-9" as="offset"/>
</mxGeometry>
</mxCell>
<!-- Animated Connectors - Decoder Side -->
<mxCell id="conn6" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;entryX=0.5;entryY=1;entryDx=0;entryDy=0;strokeWidth=2;strokeColor=#d79b00;flowAnimation=1;" edge="1" parent="1" source="output_embed" target="pos_enc_right">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="conn7" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;entryX=0.5;entryY=1;entryDx=0;entryDy=0;strokeWidth=2;strokeColor=#d79b00;flowAnimation=1;" edge="1" parent="1" source="pos_enc_right" target="masked_mha_dec">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="conn8" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;entryX=0.5;entryY=1;entryDx=0;entryDy=0;strokeWidth=2;strokeColor=#82b366;flowAnimation=1;" edge="1" parent="1" source="masked_mha_dec" target="add_norm1_dec">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="conn9" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;entryX=0.5;entryY=1;entryDx=0;entryDy=0;strokeWidth=2;strokeColor=#d6b656;flowAnimation=1;" edge="1" parent="1" source="add_norm1_dec" target="mha_dec">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="conn10" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;entryX=0.5;entryY=1;entryDx=0;entryDy=0;strokeWidth=2;strokeColor=#82b366;flowAnimation=1;" edge="1" parent="1" source="mha_dec" target="add_norm2_dec">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="conn11" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;entryX=0.5;entryY=1;entryDx=0;entryDy=0;strokeWidth=2;strokeColor=#d6b656;flowAnimation=1;" edge="1" parent="1" source="add_norm2_dec" target="ff_dec">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="conn12" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;entryX=0.5;entryY=1;entryDx=0;entryDy=0;strokeWidth=2;strokeColor=#82b366;flowAnimation=1;" edge="1" parent="1" source="ff_dec" target="add_norm3_dec">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="conn13" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;entryX=0.5;entryY=1;entryDx=0;entryDy=0;strokeWidth=2;strokeColor=#b85450;flowAnimation=1;" edge="1" parent="1" source="add_norm3_dec" target="linear">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="conn14" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;entryX=0.5;entryY=1;entryDx=0;entryDy=0;strokeWidth=2;strokeColor=#b85450;flowAnimation=1;" edge="1" parent="1" source="linear" target="softmax">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="conn15" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;entryX=0.5;entryY=1;entryDx=0;entryDy=0;strokeWidth=2;strokeColor=#6c8ebf;flowAnimation=1;" edge="1" parent="1" source="softmax" target="output">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<!-- Residual Connections (Encoder) -->
<mxCell id="res1_enc" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;strokeWidth=1.5;strokeColor=#999999;dashed=1;flowAnimation=1;" edge="1" parent="1" source="mha_enc" target="add_norm1_enc">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="50" y="355"/>
<mxPoint x="50" y="295"/>
</Array>
</mxGeometry>
</mxCell>
<mxCell id="res2_enc" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;strokeWidth=1.5;strokeColor=#999999;dashed=1;flowAnimation=1;" edge="1" parent="1" source="ff_enc" target="add_norm2_enc">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="50" y="255"/>
<mxPoint x="50" y="215"/>
</Array>
</mxGeometry>
</mxCell>
<!-- Residual Connections (Decoder) -->
<mxCell id="res1_dec" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=1;entryY=0.5;entryDx=0;entryDy=0;strokeWidth=1.5;strokeColor=#999999;dashed=1;flowAnimation=1;" edge="1" parent="1" source="masked_mha_dec" target="add_norm1_dec">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="800" y="365"/>
<mxPoint x="800" y="305"/>
</Array>
</mxGeometry>
</mxCell>
<mxCell id="res2_dec" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=1;entryY=0.5;entryDx=0;entryDy=0;strokeWidth=1.5;strokeColor=#999999;dashed=1;flowAnimation=1;" edge="1" parent="1" source="mha_dec" target="add_norm2_dec">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="800" y="260"/>
<mxPoint x="800" y="215"/>
</Array>
</mxGeometry>
</mxCell>
<mxCell id="res3_dec" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=1;entryY=0.5;entryDx=0;entryDy=0;strokeWidth=1.5;strokeColor=#999999;dashed=1;flowAnimation=1;" edge="1" parent="1" source="ff_dec" target="add_norm3_dec">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="800" y="175"/>
<mxPoint x="800" y="135"/>
</Array>
</mxGeometry>
</mxCell>
<!-- Input/Output Labels -->
<mxCell id="input_label" value="Inputs" style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=12;fontStyle=1;" vertex="1" parent="1">
<mxGeometry x="110" y="530" width="60" height="20" as="geometry"/>
</mxCell>
<mxCell id="output_label" value="Outputs&#xa;(shifted right)" style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=12;fontStyle=1;" vertex="1" parent="1">
<mxGeometry x="660" y="530" width="100" height="30" as="geometry"/>
</mxCell>
</root>`,
},
{
promptText: "Replicate this in aws style",
hasImage: true,
xml: `<root>
<mxCell id="0"/>
<mxCell id="1" parent="0"/>
<!-- AWS Cloud Container -->
<mxCell id="2" value="AWS" style="sketch=0;outlineConnect=0;gradientColor=none;html=1;whiteSpace=wrap;fontSize=12;fontStyle=0;container=1;pointerEvents=0;collapsible=0;recursiveResize=0;shape=mxgraph.aws4.group;grIcon=mxgraph.aws4.group_aws_cloud;strokeColor=#232F3E;fillColor=none;verticalAlign=top;align=left;spacingLeft=30;fontColor=#232F3E;dashed=0;rounded=1;arcSize=5;" vertex="1" parent="1">
<mxGeometry x="340" y="40" width="880" height="520" as="geometry"/>
</mxCell>
<!-- User -->
<mxCell id="3" value="User" style="sketch=0;outlineConnect=0;fontColor=#232F3E;gradientColor=none;fillColor=#232F3D;strokeColor=none;dashed=0;verticalLabelPosition=bottom;verticalAlign=top;align=center;html=1;fontSize=14;fontStyle=0;aspect=fixed;pointerEvents=1;shape=mxgraph.aws4.user;rounded=1;" vertex="1" parent="1">
<mxGeometry x="80" y="240" width="78" height="78" as="geometry"/>
</mxCell>
<!-- EC2 Instance -->
<mxCell id="4" value="EC2" style="sketch=0;points=[[0,0,0],[0.25,0,0],[0.5,0,0],[0.75,0,0],[1,0,0],[0,1,0],[0.25,1,0],[0.5,1,0],[0.75,1,0],[1,1,0],[0,0.25,0],[0,0.5,0],[0,0.75,0],[1,0.25,0],[1,0.5,0],[1,0.75,0]];outlineConnect=0;fontColor=#232F3E;fillColor=#ED7100;strokeColor=#ffffff;dashed=0;verticalLabelPosition=bottom;verticalAlign=top;align=center;html=1;fontSize=14;fontStyle=0;aspect=fixed;shape=mxgraph.aws4.resourceIcon;resIcon=mxgraph.aws4.ec2;rounded=1;" vertex="1" parent="1">
<mxGeometry x="560" y="240" width="78" height="78" as="geometry"/>
</mxCell>
<!-- S3 Bucket -->
<mxCell id="5" value="S3" style="sketch=0;points=[[0,0,0],[0.25,0,0],[0.5,0,0],[0.75,0,0],[1,0,0],[0,1,0],[0.25,1,0],[0.5,1,0],[0.75,1,0],[1,1,0],[0,0.25,0],[0,0.5,0],[0,0.75,0],[1,0.25,0],[1,0.5,0],[1,0.75,0]];outlineConnect=0;fontColor=#232F3E;fillColor=#7AA116;strokeColor=#ffffff;dashed=0;verticalLabelPosition=bottom;verticalAlign=top;align=center;html=1;fontSize=14;fontStyle=0;aspect=fixed;shape=mxgraph.aws4.resourceIcon;resIcon=mxgraph.aws4.s3;rounded=1;" vertex="1" parent="1">
<mxGeometry x="960" y="120" width="78" height="78" as="geometry"/>
</mxCell>
<!-- Bedrock -->
<mxCell id="6" value="bedrock" style="sketch=0;points=[[0,0,0],[0.25,0,0],[0.5,0,0],[0.75,0,0],[1,0,0],[0,1,0],[0.25,1,0],[0.5,1,0],[0.75,1,0],[1,1,0],[0,0.25,0],[0,0.5,0],[0,0.75,0],[1,0.25,0],[1,0.5,0],[1,0.75,0]];outlineConnect=0;fontColor=#232F3E;fillColor=#01A88D;strokeColor=#ffffff;dashed=0;verticalLabelPosition=bottom;verticalAlign=top;align=center;html=1;fontSize=14;fontStyle=0;aspect=fixed;shape=mxgraph.aws4.resourceIcon;resIcon=mxgraph.aws4.bedrock;rounded=1;" vertex="1" parent="1">
<mxGeometry x="960" y="260" width="78" height="78" as="geometry"/>
</mxCell>
<!-- DynamoDB -->
<mxCell id="7" value="DynamoDB" style="sketch=0;points=[[0,0,0],[0.25,0,0],[0.5,0,0],[0.75,0,0],[1,0,0],[0,1,0],[0.25,1,0],[0.5,1,0],[0.75,1,0],[1,1,0],[0,0.25,0],[0,0.5,0],[0,0.75,0],[1,0.25,0],[1,0.5,0],[1,0.75,0]];outlineConnect=0;fontColor=#232F3E;fillColor=#C925D1;strokeColor=#ffffff;dashed=0;verticalLabelPosition=bottom;verticalAlign=top;align=center;html=1;fontSize=14;fontStyle=0;aspect=fixed;shape=mxgraph.aws4.resourceIcon;resIcon=mxgraph.aws4.dynamodb;rounded=1;" vertex="1" parent="1">
<mxGeometry x="960" y="400" width="78" height="78" as="geometry"/>
</mxCell>
<!-- Arrow: User to EC2 -->
<mxCell id="8" value="" style="endArrow=classic;html=1;rounded=0;strokeColor=#232F3E;strokeWidth=2;exitX=1;exitY=0.5;exitDx=0;exitDy=0;exitPerimeter=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;entryPerimeter=0;" edge="1" parent="1" source="3" target="4">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="400" y="350" as="sourcePoint"/>
<mxPoint x="450" y="300" as="targetPoint"/>
</mxGeometry>
</mxCell>
<!-- Arrow: EC2 to S3 -->
<mxCell id="9" value="" style="endArrow=classic;html=1;rounded=0;strokeColor=#232F3E;strokeWidth=2;exitX=1;exitY=0.25;exitDx=0;exitDy=0;exitPerimeter=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;entryPerimeter=0;" edge="1" parent="1" source="4" target="5">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="700" y="350" as="sourcePoint"/>
<mxPoint x="750" y="300" as="targetPoint"/>
</mxGeometry>
</mxCell>
<!-- Arrow: EC2 to Bedrock -->
<mxCell id="10" value="" style="endArrow=classic;html=1;rounded=0;strokeColor=#232F3E;strokeWidth=2;exitX=1;exitY=0.5;exitDx=0;exitDy=0;exitPerimeter=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;entryPerimeter=0;" edge="1" parent="1" source="4" target="6">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="700" y="350" as="sourcePoint"/>
<mxPoint x="750" y="300" as="targetPoint"/>
</mxGeometry>
</mxCell>
<!-- Arrow: EC2 to DynamoDB -->
<mxCell id="11" value="" style="endArrow=classic;html=1;rounded=0;strokeColor=#232F3E;strokeWidth=2;exitX=1;exitY=0.75;exitDx=0;exitDy=0;exitPerimeter=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;entryPerimeter=0;" edge="1" parent="1" source="4" target="7">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="700" y="350" as="sourcePoint"/>
<mxPoint x="750" y="300" as="targetPoint"/>
</mxGeometry>
</mxCell>
</root>`,
},
{
promptText: "Replicate this flowchart.",
hasImage: true,
xml: `<root>
<mxCell id="0"/>
<mxCell id="1" parent="0"/>
<!-- Start: Lamp doesn't work -->
<mxCell id="2" value="Lamp doesn't work" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#ffcccc;strokeColor=#000000;strokeWidth=2;fontSize=18;fontStyle=0;" vertex="1" parent="1">
<mxGeometry x="140" y="40" width="180" height="60" as="geometry"/>
</mxCell>
<!-- Arrow from start to first decision -->
<mxCell id="3" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#000000;strokeWidth=2;endArrow=block;endFill=1;" edge="1" parent="1" source="2" target="4">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<!-- Decision: Lamp plugged in? -->
<mxCell id="4" value="Lamp&lt;br&gt;plugged in?" style="rhombus;whiteSpace=wrap;html=1;fillColor=#ffff99;strokeColor=#000000;strokeWidth=2;fontSize=18;fontStyle=0;" vertex="1" parent="1">
<mxGeometry x="130" y="150" width="200" height="200" as="geometry"/>
</mxCell>
<!-- Arrow to Plug in lamp (No) -->
<mxCell id="5" value="No" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#000000;strokeWidth=2;endArrow=block;endFill=1;fontSize=16;" edge="1" parent="1" source="4" target="6">
<mxGeometry x="-0.2" relative="1" as="geometry">
<mxPoint as="offset"/>
</mxGeometry>
</mxCell>
<!-- Action: Plug in lamp -->
<mxCell id="6" value="Plug in lamp" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#99ff99;strokeColor=#000000;strokeWidth=2;fontSize=18;fontStyle=0;" vertex="1" parent="1">
<mxGeometry x="420" y="220" width="200" height="60" as="geometry"/>
</mxCell>
<!-- Arrow down to second decision (Yes) -->
<mxCell id="7" value="Yes" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#000000;strokeWidth=2;endArrow=block;endFill=1;fontSize=16;" edge="1" parent="1" source="4" target="8">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<!-- Decision: Bulb burned out? -->
<mxCell id="8" value="Bulb&lt;br&gt;burned out?" style="rhombus;whiteSpace=wrap;html=1;fillColor=#ffff99;strokeColor=#000000;strokeWidth=2;fontSize=18;fontStyle=0;" vertex="1" parent="1">
<mxGeometry x="130" y="400" width="200" height="200" as="geometry"/>
</mxCell>
<!-- Arrow to Replace bulb (Yes) -->
<mxCell id="9" value="Yes" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#000000;strokeWidth=2;endArrow=block;endFill=1;fontSize=16;" edge="1" parent="1" source="8" target="10">
<mxGeometry x="-0.2" relative="1" as="geometry">
<mxPoint as="offset"/>
</mxGeometry>
</mxCell>
<!-- Action: Replace bulb -->
<mxCell id="10" value="Replace bulb" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#99ff99;strokeColor=#000000;strokeWidth=2;fontSize=18;fontStyle=0;" vertex="1" parent="1">
<mxGeometry x="420" y="470" width="200" height="60" as="geometry"/>
</mxCell>
<!-- Arrow down to Repair lamp (No) -->
<mxCell id="11" value="No" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#000000;strokeWidth=2;endArrow=block;endFill=1;fontSize=16;" edge="1" parent="1" source="8" target="12">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<!-- Action: Repair lamp -->
<mxCell id="12" value="Repair lamp" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#99ff99;strokeColor=#000000;strokeWidth=2;fontSize=18;fontStyle=0;" vertex="1" parent="1">
<mxGeometry x="130" y="650" width="200" height="60" as="geometry"/>
</mxCell>
</root>`,
},
{
promptText: "Draw a cat for me",
hasImage: false,
xml: `<root>
<mxCell id="0"/>
<mxCell id="1" parent="0"/>
<!-- Cat's head -->
<mxCell id="2" value="" style="ellipse;whiteSpace=wrap;html=1;aspect=fixed;fillColor=#FFE6CC;strokeColor=#000000;strokeWidth=2;" vertex="1" parent="1">
<mxGeometry x="300" y="150" width="120" height="120" as="geometry"/>
</mxCell>
<!-- Left ear -->
<mxCell id="3" value="" style="triangle;whiteSpace=wrap;html=1;fillColor=#FFE6CC;strokeColor=#000000;strokeWidth=2;rotation=30;" vertex="1" parent="1">
<mxGeometry x="280" y="120" width="50" height="60" as="geometry"/>
</mxCell>
<!-- Right ear -->
<mxCell id="4" value="" style="triangle;whiteSpace=wrap;html=1;fillColor=#FFE6CC;strokeColor=#000000;strokeWidth=2;rotation=-30;" vertex="1" parent="1">
<mxGeometry x="390" y="120" width="50" height="60" as="geometry"/>
</mxCell>
<!-- Left ear inner -->
<mxCell id="5" value="" style="triangle;whiteSpace=wrap;html=1;fillColor=#FFB6C1;strokeColor=none;rotation=30;" vertex="1" parent="1">
<mxGeometry x="290" y="135" width="30" height="35" as="geometry"/>
</mxCell>
<!-- Right ear inner -->
<mxCell id="6" value="" style="triangle;whiteSpace=wrap;html=1;fillColor=#FFB6C1;strokeColor=none;rotation=-30;" vertex="1" parent="1">
<mxGeometry x="400" y="135" width="30" height="35" as="geometry"/>
</mxCell>
<!-- Left eye -->
<mxCell id="7" value="" style="ellipse;whiteSpace=wrap;html=1;aspect=fixed;fillColor=#000000;strokeColor=#000000;" vertex="1" parent="1">
<mxGeometry x="325" y="185" width="15" height="15" as="geometry"/>
</mxCell>
<!-- Right eye -->
<mxCell id="8" value="" style="ellipse;whiteSpace=wrap;html=1;aspect=fixed;fillColor=#000000;strokeColor=#000000;" vertex="1" parent="1">
<mxGeometry x="380" y="185" width="15" height="15" as="geometry"/>
</mxCell>
<!-- Nose -->
<mxCell id="9" value="" style="triangle;whiteSpace=wrap;html=1;fillColor=#FFB6C1;strokeColor=#000000;strokeWidth=1;rotation=180;" vertex="1" parent="1">
<mxGeometry x="350" y="210" width="20" height="15" as="geometry"/>
</mxCell>
<!-- Mouth left -->
<mxCell id="10" value="" style="curved=1;endArrow=none;html=1;strokeColor=#000000;strokeWidth=2;exitX=0.5;exitY=1;exitDx=0;exitDy=0;" edge="1" parent="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="360" y="220" as="sourcePoint"/>
<mxPoint x="340" y="235" as="targetPoint"/>
<Array as="points">
<mxPoint x="355" y="230"/>
</Array>
</mxGeometry>
</mxCell>
<!-- Mouth right -->
<mxCell id="11" value="" style="curved=1;endArrow=none;html=1;strokeColor=#000000;strokeWidth=2;" edge="1" parent="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="360" y="220" as="sourcePoint"/>
<mxPoint x="380" y="235" as="targetPoint"/>
<Array as="points">
<mxPoint x="365" y="230"/>
</Array>
</mxGeometry>
</mxCell>
<!-- Left whisker 1 -->
<mxCell id="12" value="" style="endArrow=none;html=1;strokeColor=#000000;strokeWidth=1.5;" edge="1" parent="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="310" y="200" as="sourcePoint"/>
<mxPoint x="260" y="195" as="targetPoint"/>
</mxGeometry>
</mxCell>
<!-- Left whisker 2 -->
<mxCell id="13" value="" style="endArrow=none;html=1;strokeColor=#000000;strokeWidth=1.5;" edge="1" parent="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="310" y="210" as="sourcePoint"/>
<mxPoint x="260" y="210" as="targetPoint"/>
</mxGeometry>
</mxCell>
<!-- Left whisker 3 -->
<mxCell id="14" value="" style="endArrow=none;html=1;strokeColor=#000000;strokeWidth=1.5;" edge="1" parent="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="310" y="220" as="sourcePoint"/>
<mxPoint x="260" y="225" as="targetPoint"/>
</mxGeometry>
</mxCell>
<!-- Right whisker 1 -->
<mxCell id="15" value="" style="endArrow=none;html=1;strokeColor=#000000;strokeWidth=1.5;" edge="1" parent="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="410" y="200" as="sourcePoint"/>
<mxPoint x="460" y="195" as="targetPoint"/>
</mxGeometry>
</mxCell>
<!-- Right whisker 2 -->
<mxCell id="16" value="" style="endArrow=none;html=1;strokeColor=#000000;strokeWidth=1.5;" edge="1" parent="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="410" y="210" as="sourcePoint"/>
<mxPoint x="460" y="210" as="targetPoint"/>
</mxGeometry>
</mxCell>
<!-- Right whisker 3 -->
<mxCell id="17" value="" style="endArrow=none;html=1;strokeColor=#000000;strokeWidth=1.5;" edge="1" parent="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="410" y="220" as="sourcePoint"/>
<mxPoint x="460" y="225" as="targetPoint"/>
</mxGeometry>
</mxCell>
<!-- Body -->
<mxCell id="18" value="" style="ellipse;whiteSpace=wrap;html=1;fillColor=#FFE6CC;strokeColor=#000000;strokeWidth=2;" vertex="1" parent="1">
<mxGeometry x="285" y="250" width="150" height="180" as="geometry"/>
</mxCell>
<!-- Belly -->
<mxCell id="19" value="" style="ellipse;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeColor=none;" vertex="1" parent="1">
<mxGeometry x="315" y="280" width="90" height="120" as="geometry"/>
</mxCell>
<!-- Left front paw -->
<mxCell id="20" value="" style="ellipse;whiteSpace=wrap;html=1;fillColor=#FFE6CC;strokeColor=#000000;strokeWidth=2;" vertex="1" parent="1">
<mxGeometry x="300" y="410" width="40" height="50" as="geometry"/>
</mxCell>
<!-- Right front paw -->
<mxCell id="21" value="" style="ellipse;whiteSpace=wrap;html=1;fillColor=#FFE6CC;strokeColor=#000000;strokeWidth=2;" vertex="1" parent="1">
<mxGeometry x="380" y="410" width="40" height="50" as="geometry"/>
</mxCell>
<!-- Tail -->
<mxCell id="22" value="" style="curved=1;endArrow=none;html=1;strokeColor=#000000;strokeWidth=3;fillColor=#FFE6CC;" edge="1" parent="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="285" y="340" as="sourcePoint"/>
<mxPoint x="240" y="260" as="targetPoint"/>
<Array as="points">
<mxPoint x="260" y="350"/>
<mxPoint x="240" y="320"/>
<mxPoint x="235" y="290"/>
</Array>
</mxGeometry>
</mxCell>
</root>`,
},
];
export function findCachedResponse(
promptText: string,
hasImage: boolean
): CachedResponse | undefined {
return CACHED_EXAMPLE_RESPONSES.find(
(c) => c.promptText === promptText && c.hasImage === hasImage && c.xml !== ''
);
}

4821
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -9,9 +9,10 @@
"lint": "next lint"
},
"dependencies": {
"@ai-sdk/amazon-bedrock": "^3.0.52",
"@ai-sdk/amazon-bedrock": "^3.0.62",
"@ai-sdk/anthropic": "^2.0.44",
"@ai-sdk/azure": "^2.0.69",
"@ai-sdk/deepseek": "^1.0.30",
"@ai-sdk/google": "^2.0.0",
"@ai-sdk/openai": "^2.0.19",
"@ai-sdk/react": "^2.0.22",
@@ -46,6 +47,8 @@
"@types/pako": "^2.0.3",
"@types/react": "^19",
"@types/react-dom": "^19",
"eslint": "9.39.1",
"eslint-config-next": "16.0.5",
"tailwindcss": "^4",
"typescript": "^5"
}

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 261 KiB

4
public/aws_demo.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 95 KiB

4
public/azure_demo.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 88 KiB

4
public/cat_demo.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 33 KiB

4
public/gcp_demo.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 100 KiB