mirror of
https://github.com/DayuanJiang/next-ai-draw-io.git
synced 2026-01-03 23:02:31 +08:00
Compare commits
48 Commits
chore/fix-
...
feat/impro
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
43021bafa2 | ||
|
|
a20d14ef9d | ||
|
|
e2adfb49aa | ||
|
|
45ab934288 | ||
|
|
af3173623a | ||
|
|
cd012f5e2f | ||
|
|
d4fb635d98 | ||
|
|
14740e35a8 | ||
|
|
5b31216917 | ||
|
|
c7d0260328 | ||
|
|
d2d4dd01cc | ||
|
|
b4679f6598 | ||
|
|
0d0d553e23 | ||
|
|
6e6de1eba6 | ||
|
|
00af87edbe | ||
|
|
468d6c0276 | ||
|
|
14f74b076f | ||
|
|
78d9229ca3 | ||
|
|
d8e0a1daad | ||
|
|
32e75ab556 | ||
|
|
b87e3a2de9 | ||
|
|
b758f63d7f | ||
|
|
b32d42d962 | ||
|
|
603865fdb0 | ||
|
|
4cb4e187b3 | ||
|
|
b4d0c6c18b | ||
|
|
c03c41d320 | ||
|
|
9d248e25ad | ||
|
|
4f4aae0e39 | ||
|
|
b00579b257 | ||
|
|
caf7ffe56c | ||
|
|
50d16cbe47 | ||
|
|
56167d363c | ||
|
|
3e2dbbb541 | ||
|
|
efebcea3ba | ||
|
|
68824bc951 | ||
|
|
794826550d | ||
|
|
de98cf60ae | ||
|
|
b3fc624e13 | ||
|
|
d2dd501f3f | ||
|
|
5964deeff7 | ||
|
|
663e1c8c77 | ||
|
|
455115935c | ||
|
|
8d36e0dfb0 | ||
|
|
60f4694752 | ||
|
|
aa330f74b3 | ||
|
|
7a6a7eaf7c | ||
|
|
7518285e98 |
6
.eslintrc.json
Normal file
6
.eslintrc.json
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"extends": [
|
||||||
|
"next/core-web-vitals",
|
||||||
|
"next/typescript"
|
||||||
|
]
|
||||||
|
}
|
||||||
57
README.md
57
README.md
@@ -12,8 +12,48 @@ Demo site: [https://next-ai-draw-io.vercel.app](https://next-ai-draw-io.vercel.a
|
|||||||
- **Image-Based Diagram Replication**: Upload existing diagrams or images and have the AI replicate and enhance them automatically
|
- **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.
|
- **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
|
- **Interactive Chat Interface**: Communicate with AI to refine your diagrams in real-time
|
||||||
- **Smart Editing**: Modify existing diagrams using simple text prompts
|
- **AWS Architecture Diagram Support**: Specialized support for generating AWS architecture diagrams
|
||||||
- **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
|
- **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
|
## How It Works
|
||||||
|
|
||||||
@@ -28,11 +68,13 @@ Diagrams are represented as XML that can be rendered in draw.io. The AI processe
|
|||||||
## Multi-Provider Support
|
## Multi-Provider Support
|
||||||
|
|
||||||
- AWS Bedrock (default)
|
- AWS Bedrock (default)
|
||||||
- OpenAI
|
- OpenAI / OpenAI-compatible APIs (via `OPENAI_BASE_URL`)
|
||||||
- Anthropic
|
- Anthropic
|
||||||
- Google AI
|
- Google AI
|
||||||
- Azure OpenAI
|
- Azure OpenAI
|
||||||
- Ollama
|
- Ollama
|
||||||
|
- OpenRouter
|
||||||
|
- DeepSeek
|
||||||
|
|
||||||
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.
|
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.
|
||||||
|
|
||||||
@@ -65,7 +107,7 @@ cp env.example .env.local
|
|||||||
|
|
||||||
Edit `.env.local` and configure your chosen provider:
|
Edit `.env.local` and configure your chosen provider:
|
||||||
|
|
||||||
- Set `AI_PROVIDER` to your chosen provider (bedrock, openai, anthropic, google, azure, ollama)
|
- 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
|
- Set `AI_MODEL` to the specific model you want to use
|
||||||
- Add the required API keys for your provider
|
- Add the required API keys for your provider
|
||||||
|
|
||||||
@@ -109,7 +151,8 @@ public/ # Static assets including example images
|
|||||||
- [x] Allow the LLM to modify the XML instead of generating it from scratch everytime.
|
- [x] Allow the LLM to modify the XML instead of generating it from scratch everytime.
|
||||||
- [x] Improve the smoothness of shape streaming updates.
|
- [x] Improve the smoothness of shape streaming updates.
|
||||||
- [x] Add multiple AI provider support (OpenAI, Anthropic, Google, Azure, Ollama)
|
- [x] Add multiple AI provider support (OpenAI, Anthropic, Google, Azure, Ollama)
|
||||||
- [ ] Solve the bug that generation will fail for session that longer than 60s.
|
- [x] Solve the bug that generation will fail for session that longer than 60s.
|
||||||
|
- [ ] Add API config on the UI.
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
@@ -121,4 +164,8 @@ For support or inquiries, please open an issue on the GitHub repository or conta
|
|||||||
|
|
||||||
- Email: me[at]jiang.jp
|
- Email: me[at]jiang.jp
|
||||||
|
|
||||||
|
## Star History
|
||||||
|
|
||||||
|
[](https://www.star-history.com/#DayuanJiang/next-ai-draw-io&type=date&legend=top-left)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -1,12 +1,67 @@
|
|||||||
import { streamText, convertToModelMessages } from 'ai';
|
import { streamText, convertToModelMessages, createUIMessageStream, createUIMessageStreamResponse } from 'ai';
|
||||||
import { getAIModel } from '@/lib/ai-providers';
|
import { getAIModel } from '@/lib/ai-providers';
|
||||||
|
import { findCachedResponse } from '@/lib/cached-responses';
|
||||||
|
import { formatXML } from '@/lib/utils';
|
||||||
import { z } from "zod";
|
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) {
|
export async function POST(req: Request) {
|
||||||
try {
|
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 = `
|
const systemMessage = `
|
||||||
You are an expert diagram creation assistant specializing in draw.io XML generation.
|
You are an expert diagram creation assistant specializing in draw.io XML generation.
|
||||||
@@ -32,6 +87,7 @@ parameters: {
|
|||||||
IMPORTANT: Choose the right tool:
|
IMPORTANT: Choose the right tool:
|
||||||
- Use display_diagram for: Creating new diagrams, major restructuring, or when the current diagram XML is empty
|
- 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
|
- 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:
|
Core capabilities:
|
||||||
- Generate valid, well-formed XML strings for draw.io diagrams
|
- 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
|
// Extract file parts (images) from the last message
|
||||||
const fileParts = lastMessage.parts?.filter((part: any) => part.type === 'file') || [];
|
const fileParts = lastMessage.parts?.filter((part: any) => part.type === 'file') || [];
|
||||||
|
|
||||||
const formattedTextContent = `
|
// Check diagram state - use formatted XML for reliable comparison
|
||||||
Current diagram XML:
|
const hasDiagram = xml && !isMinimalDiagram(xml);
|
||||||
"""xml
|
const noHistory = !lastGeneratedXml || lastGeneratedXml.trim() === '';
|
||||||
${xml || ''}
|
const formattedXml = hasDiagram ? formatXML(xml) : '';
|
||||||
"""
|
const formattedLastGenXml = lastGeneratedXml ? formatXML(lastGeneratedXml) : '';
|
||||||
User input:
|
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
|
"""md
|
||||||
${lastMessageText}
|
${lastMessageText}
|
||||||
"""`;
|
"""${diagramContext}`;
|
||||||
|
|
||||||
// Convert UIMessages to ModelMessages and add system message
|
// Convert UIMessages to ModelMessages and add system message
|
||||||
const modelMessages = convertToModelMessages(messages);
|
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
|
// Update the last message with formatted content if it's a user message
|
||||||
if (enhancedMessages.length >= 1) {
|
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
|
// 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({
|
const result = streamText({
|
||||||
model,
|
model,
|
||||||
system: systemMessage,
|
messages: [systemMessageWithCache, ...enhancedMessages],
|
||||||
messages: enhancedMessages,
|
|
||||||
...(providerOptions && { providerOptions }),
|
...(providerOptions && { providerOptions }),
|
||||||
|
...(headers && { headers }),
|
||||||
|
onFinish: ({ usage }) => {
|
||||||
|
console.log('[API] Tokens:', usage?.inputTokens, 'in /', usage?.outputTokens, 'out, cached:', usage?.cachedInputTokens);
|
||||||
|
},
|
||||||
tools: {
|
tools: {
|
||||||
// Client-side tool that will be executed on the client
|
// Client-side tool that will be executed on the client
|
||||||
display_diagram: {
|
display_diagram: {
|
||||||
@@ -141,6 +243,7 @@ ${lastMessageText}
|
|||||||
</mxCell>
|
</mxCell>
|
||||||
</root>
|
</root>
|
||||||
- Note that when you need to generate diagram about aws architecture, use **AWS 2025 icons**.
|
- 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({
|
inputSchema: z.object({
|
||||||
xml: z.string().describe("XML string to be displayed on draw.io")
|
xml: z.string().describe("XML string to be displayed on draw.io")
|
||||||
@@ -162,6 +265,7 @@ IMPORTANT: Keep edits concise:
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
temperature: 0,
|
temperature: 0,
|
||||||
|
maxSteps: 5, // Allow model to continue after server-side tool execution
|
||||||
});
|
});
|
||||||
|
|
||||||
// Error handler function to provide detailed error messages
|
// Error handler function to provide detailed error messages
|
||||||
|
|||||||
25
app/page.tsx
25
app/page.tsx
@@ -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 (
|
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`}>
|
<div className={`${isChatVisible ? 'w-2/3' : 'w-full'} p-1 h-full relative transition-all duration-300 ease-in-out`}>
|
||||||
<DrawIoEmbed
|
<DrawIoEmbed
|
||||||
ref={drawioRef}
|
ref={drawioRef}
|
||||||
|
|||||||
@@ -50,25 +50,34 @@ export default function ExamplePanel({
|
|||||||
{" "}
|
{" "}
|
||||||
You can also upload images to use as references.
|
You can also upload images to use as references.
|
||||||
</p>
|
</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">
|
<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
|
<button
|
||||||
className="text-xs bg-gray-100 hover:bg-gray-200 text-gray-800 font-medium py-1 px-2 rounded"
|
className="text-xs bg-gray-100 hover:bg-gray-200 text-gray-800 font-medium py-1 px-2 rounded"
|
||||||
onClick={handleReplicateArchitecture}
|
onClick={handleReplicateArchitecture}
|
||||||
>
|
>
|
||||||
Create this diagram in aws style
|
Create AWS architecture
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
className="text-xs bg-gray-100 hover:bg-gray-200 text-gray-800 font-medium py-1 px-2 rounded"
|
className="text-xs bg-gray-100 hover:bg-gray-200 text-gray-800 font-medium py-1 px-2 rounded"
|
||||||
onClick={handleReplicateFlowchart}
|
onClick={handleReplicateFlowchart}
|
||||||
>
|
>
|
||||||
Replicate this flowchart
|
Replicate flowchart
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
className="text-xs bg-gray-100 hover:bg-gray-200 text-gray-800 font-medium py-1 px-2 rounded"
|
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")}
|
onClick={() => setInput("Draw a cat for me")}
|
||||||
>
|
>
|
||||||
Draw a cat for me
|
Draw a cat
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,15 +1,23 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import type React from "react";
|
|
||||||
import { useRef, useEffect, useState, useCallback } from "react";
|
import { useRef, useEffect, useState, useCallback } from "react";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import { ScrollArea } from "@/components/ui/scroll-area";
|
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||||
import ExamplePanel from "./chat-example-panel";
|
import ExamplePanel from "./chat-example-panel";
|
||||||
import { UIMessage } from "ai";
|
import { UIMessage } from "ai";
|
||||||
import { convertToLegalXml, replaceNodes } from "@/lib/utils";
|
import { convertToLegalXml, replaceNodes } from "@/lib/utils";
|
||||||
|
import { Copy, Check, X } from "lucide-react";
|
||||||
|
|
||||||
import { useDiagram } from "@/contexts/diagram-context";
|
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 {
|
interface ChatMessageDisplayProps {
|
||||||
messages: UIMessage[];
|
messages: UIMessage[];
|
||||||
error?: Error | null;
|
error?: Error | null;
|
||||||
@@ -30,6 +38,21 @@ export function ChatMessageDisplay({
|
|||||||
const [expandedTools, setExpandedTools] = useState<Record<string, boolean>>(
|
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(
|
const handleDisplayChart = useCallback(
|
||||||
(xml: string) => {
|
(xml: string) => {
|
||||||
const currentXml = xml || "";
|
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" />
|
<div className="h-4 w-4 border-2 border-primary border-t-transparent rounded-full animate-spin" />
|
||||||
) : state === "output-available" ? (
|
) : state === "output-available" ? (
|
||||||
<div className="text-green-600">
|
<div className="text-green-600">
|
||||||
{output || (toolName === "display_diagram"
|
{typeof output === "object" && output !== null
|
||||||
? "Diagram generated"
|
? (output as any).message || JSON.stringify(output)
|
||||||
: toolName === "edit_diagram"
|
: output || (toolName === "display_diagram"
|
||||||
? "Diagram edited"
|
? "Diagram generated"
|
||||||
: "Tool executed")}
|
: toolName === "edit_diagram"
|
||||||
|
? "Diagram edited"
|
||||||
|
: "Tool executed")}
|
||||||
</div>
|
</div>
|
||||||
) : state === "output-error" ? (
|
) : state === "output-error" ? (
|
||||||
<div className="text-red-600">
|
<div className="text-red-600">
|
||||||
{output || (toolName === "display_diagram"
|
{typeof output === "object" && output !== null
|
||||||
? "Error generating diagram"
|
? (output as any).message || JSON.stringify(output)
|
||||||
: toolName === "edit_diagram"
|
: output || (toolName === "display_diagram"
|
||||||
? "Error editing diagram"
|
? "Error generating diagram"
|
||||||
: "Tool error")}
|
: toolName === "edit_diagram"
|
||||||
|
? "Error editing diagram"
|
||||||
|
: "Tool error")}
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
@@ -160,51 +187,66 @@ export function ChatMessageDisplay({
|
|||||||
{messages.length === 0 ? (
|
{messages.length === 0 ? (
|
||||||
<ExamplePanel setInput={setInput} setFiles={setFiles} />
|
<ExamplePanel setInput={setInput} setFiles={setFiles} />
|
||||||
) : (
|
) : (
|
||||||
messages.map((message) => (
|
messages.map((message) => {
|
||||||
<div
|
const userMessageText = message.role === "user" ? getMessageTextContent(message) : "";
|
||||||
key={message.id}
|
return (
|
||||||
className={`mb-4 ${
|
|
||||||
message.role === "user" ? "text-right" : "text-left"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
<div
|
<div
|
||||||
className={`inline-block px-4 py-2 whitespace-pre-wrap text-sm rounded-lg max-w-[85%] break-words ${
|
key={message.id}
|
||||||
message.role === "user"
|
className={`mb-4 flex ${message.role === "user" ? "justify-end" : "justify-start"}`}
|
||||||
? "bg-primary text-primary-foreground"
|
|
||||||
: "bg-muted text-muted-foreground"
|
|
||||||
}`}
|
|
||||||
>
|
>
|
||||||
{message.parts?.map((part: any, index: number) => {
|
{message.role === "user" && userMessageText && (
|
||||||
switch (part.type) {
|
<button
|
||||||
case "text":
|
onClick={() => copyMessageToClipboard(message.id, userMessageText)}
|
||||||
return (
|
className="p-1 text-gray-400 hover:text-gray-600 transition-colors self-center mr-1"
|
||||||
<div key={index}>{part.text}</div>
|
title={copiedMessageId === message.id ? "Copied!" : copyFailedMessageId === message.id ? "Failed to copy" : "Copy message"}
|
||||||
);
|
>
|
||||||
case "file":
|
{copiedMessageId === message.id ? (
|
||||||
return (
|
<Check className="h-3.5 w-3.5 text-green-500" />
|
||||||
<div key={index} className="mt-2">
|
) : copyFailedMessageId === message.id ? (
|
||||||
<Image
|
<X className="h-3.5 w-3.5 text-red-500" />
|
||||||
src={part.url}
|
) : (
|
||||||
width={200}
|
<Copy className="h-3.5 w-3.5" />
|
||||||
height={200}
|
)}
|
||||||
alt={`Uploaded diagram or image for AI analysis`}
|
</button>
|
||||||
className="rounded-md border"
|
)}
|
||||||
style={{
|
<div
|
||||||
objectFit: "contain",
|
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"
|
||||||
</div>
|
}`}
|
||||||
);
|
>
|
||||||
default:
|
{message.parts?.map((part: any, index: number) => {
|
||||||
if (part.type?.startsWith("tool-")) {
|
switch (part.type) {
|
||||||
return renderToolPart(part);
|
case "text":
|
||||||
}
|
return (
|
||||||
return null;
|
<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>
|
||||||
</div>
|
);
|
||||||
))
|
})
|
||||||
)}
|
)}
|
||||||
{error && (
|
{error && (
|
||||||
<div className="text-red-500 text-sm mt-2">
|
<div className="text-red-500 text-sm mt-2">
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import { ChatInput } from "@/components/chat-input";
|
|||||||
import { ChatMessageDisplay } from "./chat-message-display";
|
import { ChatMessageDisplay } from "./chat-message-display";
|
||||||
import { useDiagram } from "@/contexts/diagram-context";
|
import { useDiagram } from "@/contexts/diagram-context";
|
||||||
import { replaceNodes, formatXML } from "@/lib/utils";
|
import { replaceNodes, formatXML } from "@/lib/utils";
|
||||||
|
import { CACHED_TOOL_PREFIX } from "@/app/api/chat/route";
|
||||||
import { ButtonWithTooltip } from "@/components/button-with-tooltip";
|
import { ButtonWithTooltip } from "@/components/button-with-tooltip";
|
||||||
|
|
||||||
interface ChatPanelProps {
|
interface ChatPanelProps {
|
||||||
@@ -33,6 +34,8 @@ export default function ChatPanel({ isVisible, onToggleVisibility }: ChatPanelPr
|
|||||||
resolverRef,
|
resolverRef,
|
||||||
chartXML,
|
chartXML,
|
||||||
clearDiagram,
|
clearDiagram,
|
||||||
|
getLastAgentGeneratedXml,
|
||||||
|
markAgentDiagramPending,
|
||||||
} = useDiagram();
|
} = useDiagram();
|
||||||
|
|
||||||
const onFetchChart = () => {
|
const onFetchChart = () => {
|
||||||
@@ -73,7 +76,15 @@ export default function ChatPanel({ isVisible, onToggleVisibility }: ChatPanelPr
|
|||||||
}),
|
}),
|
||||||
async onToolCall({ toolCall }) {
|
async onToolCall({ toolCall }) {
|
||||||
if (toolCall.toolName === "display_diagram") {
|
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({
|
addToolResult({
|
||||||
tool: "display_diagram",
|
tool: "display_diagram",
|
||||||
toolCallId: toolCall.toolCallId,
|
toolCallId: toolCall.toolCallId,
|
||||||
@@ -96,6 +107,9 @@ export default function ChatPanel({ isVisible, onToggleVisibility }: ChatPanelPr
|
|||||||
// Load the edited diagram
|
// Load the edited diagram
|
||||||
onDisplayChart(editedXml);
|
onDisplayChart(editedXml);
|
||||||
|
|
||||||
|
// Mark that an agent diagram is pending - the next export will update lastAgentGeneratedXml
|
||||||
|
markAgentDiagramPending();
|
||||||
|
|
||||||
addToolResult({
|
addToolResult({
|
||||||
tool: "edit_diagram",
|
tool: "edit_diagram",
|
||||||
toolCallId: toolCall.toolCallId,
|
toolCallId: toolCall.toolCallId,
|
||||||
@@ -134,10 +148,6 @@ Please retry with an adjusted search pattern or use display_diagram if retries a
|
|||||||
}
|
}
|
||||||
}, [messages]);
|
}, [messages]);
|
||||||
|
|
||||||
// Debug: Log status changes
|
|
||||||
useEffect(() => {
|
|
||||||
console.log('[ChatPanel] Status changed to:', status);
|
|
||||||
}, [status]);
|
|
||||||
|
|
||||||
const onFormSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
|
const onFormSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@@ -171,11 +181,14 @@ Please retry with an adjusted search pattern or use display_diagram if retries a
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const lastGenXml = getLastAgentGeneratedXml();
|
||||||
|
|
||||||
sendMessage(
|
sendMessage(
|
||||||
{ parts },
|
{ parts },
|
||||||
{
|
{
|
||||||
body: {
|
body: {
|
||||||
xml: chartXml,
|
xml: chartXml,
|
||||||
|
lastGeneratedXml: lastGenXml,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -2,12 +2,16 @@
|
|||||||
|
|
||||||
import React, { createContext, useContext, useRef, useState } from "react";
|
import React, { createContext, useContext, useRef, useState } from "react";
|
||||||
import type { DrawIoEmbedRef } from "react-drawio";
|
import type { DrawIoEmbedRef } from "react-drawio";
|
||||||
import { extractDiagramXML } from "../lib/utils";
|
import { extractDiagramXML, formatXML } from "../lib/utils";
|
||||||
|
|
||||||
interface DiagramContextType {
|
interface DiagramContextType {
|
||||||
chartXML: string;
|
chartXML: string;
|
||||||
latestSvg: string;
|
latestSvg: string;
|
||||||
diagramHistory: { svg: string; xml: string }[];
|
diagramHistory: { svg: string; xml: string }[];
|
||||||
|
lastAgentGeneratedXml: string;
|
||||||
|
getLastAgentGeneratedXml: () => string;
|
||||||
|
setLastAgentGeneratedXml: (xml: string) => void;
|
||||||
|
markAgentDiagramPending: () => void;
|
||||||
loadDiagram: (chart: string) => void;
|
loadDiagram: (chart: string) => void;
|
||||||
handleExport: () => void;
|
handleExport: () => void;
|
||||||
resolverRef: React.Ref<((value: string) => void) | null>;
|
resolverRef: React.Ref<((value: string) => void) | null>;
|
||||||
@@ -24,9 +28,25 @@ export function DiagramProvider({ children }: { children: React.ReactNode }) {
|
|||||||
const [diagramHistory, setDiagramHistory] = useState<
|
const [diagramHistory, setDiagramHistory] = useState<
|
||||||
{ svg: string; xml: string }[]
|
{ 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 drawioRef = useRef<DrawIoEmbedRef | null>(null);
|
||||||
const resolverRef = useRef<((value: string) => void) | 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 = () => {
|
const handleExport = () => {
|
||||||
if (drawioRef.current) {
|
if (drawioRef.current) {
|
||||||
drawioRef.current.exportDiagram({
|
drawioRef.current.exportDiagram({
|
||||||
@@ -54,6 +74,15 @@ export function DiagramProvider({ children }: { children: React.ReactNode }) {
|
|||||||
xml: extractedXML,
|
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) {
|
if (resolverRef.current) {
|
||||||
resolverRef.current(extractedXML);
|
resolverRef.current(extractedXML);
|
||||||
resolverRef.current = null;
|
resolverRef.current = null;
|
||||||
@@ -66,6 +95,7 @@ export function DiagramProvider({ children }: { children: React.ReactNode }) {
|
|||||||
setChartXML(emptyDiagram);
|
setChartXML(emptyDiagram);
|
||||||
setLatestSvg("");
|
setLatestSvg("");
|
||||||
setDiagramHistory([]);
|
setDiagramHistory([]);
|
||||||
|
setLastAgentGeneratedXml("");
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -74,6 +104,10 @@ export function DiagramProvider({ children }: { children: React.ReactNode }) {
|
|||||||
chartXML,
|
chartXML,
|
||||||
latestSvg,
|
latestSvg,
|
||||||
diagramHistory,
|
diagramHistory,
|
||||||
|
lastAgentGeneratedXml,
|
||||||
|
getLastAgentGeneratedXml,
|
||||||
|
setLastAgentGeneratedXml,
|
||||||
|
markAgentDiagramPending,
|
||||||
loadDiagram,
|
loadDiagram,
|
||||||
handleExport,
|
handleExport,
|
||||||
resolverRef,
|
resolverRef,
|
||||||
|
|||||||
11
env.example
11
env.example
@@ -1,6 +1,6 @@
|
|||||||
# AI Provider Configuration
|
# AI Provider Configuration
|
||||||
# AI_PROVIDER: Which provider to use
|
# 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
|
# Default: bedrock
|
||||||
AI_PROVIDER=bedrock
|
AI_PROVIDER=bedrock
|
||||||
|
|
||||||
@@ -14,21 +14,30 @@ AI_MODEL=global.anthropic.claude-sonnet-4-5-20250929-v1:0
|
|||||||
|
|
||||||
# OpenAI Configuration
|
# OpenAI Configuration
|
||||||
# OPENAI_API_KEY=sk-...
|
# OPENAI_API_KEY=sk-...
|
||||||
|
# OPENAI_BASE_URL=https://api.openai.com/v1 # Optional: Custom OpenAI-compatible endpoint
|
||||||
# OPENAI_ORGANIZATION=org-... # Optional
|
# OPENAI_ORGANIZATION=org-... # Optional
|
||||||
# OPENAI_PROJECT=proj_... # Optional
|
# OPENAI_PROJECT=proj_... # Optional
|
||||||
|
|
||||||
# Anthropic (Direct) Configuration
|
# Anthropic (Direct) Configuration
|
||||||
# ANTHROPIC_API_KEY=sk-ant-...
|
# ANTHROPIC_API_KEY=sk-ant-...
|
||||||
|
# ANTHROPIC_BASE_URL=https://your-custom-anthropic/v1
|
||||||
|
|
||||||
# Google Generative AI Configuration
|
# Google Generative AI Configuration
|
||||||
# GOOGLE_GENERATIVE_AI_API_KEY=...
|
# GOOGLE_GENERATIVE_AI_API_KEY=...
|
||||||
|
# GOOGLE_BASE_URL=https://generativelanguage.googleapis.com/v1beta # Optional: Custom endpoint
|
||||||
|
|
||||||
# Azure OpenAI Configuration
|
# Azure OpenAI Configuration
|
||||||
# AZURE_RESOURCE_NAME=your-resource-name
|
# AZURE_RESOURCE_NAME=your-resource-name
|
||||||
# AZURE_API_KEY=...
|
# AZURE_API_KEY=...
|
||||||
|
# AZURE_BASE_URL=https://your-resource.openai.azure.com # Optional: Custom endpoint (overrides resourceName)
|
||||||
|
|
||||||
# Ollama (Local) Configuration
|
# Ollama (Local) Configuration
|
||||||
# OLLAMA_BASE_URL=http://localhost:11434/api # Optional, defaults to localhost
|
# OLLAMA_BASE_URL=http://localhost:11434/api # Optional, defaults to localhost
|
||||||
|
|
||||||
# OpenRouter Configuration
|
# OpenRouter Configuration
|
||||||
# OPENROUTER_API_KEY=sk-or-v1-...
|
# 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
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
import { bedrock } from '@ai-sdk/amazon-bedrock';
|
import { bedrock } from '@ai-sdk/amazon-bedrock';
|
||||||
import { openai } from '@ai-sdk/openai';
|
import { openai, createOpenAI } from '@ai-sdk/openai';
|
||||||
import { anthropic } from '@ai-sdk/anthropic';
|
import { createAnthropic } from '@ai-sdk/anthropic';
|
||||||
import { google } from '@ai-sdk/google';
|
import { google, createGoogleGenerativeAI } from '@ai-sdk/google';
|
||||||
import { azure } from '@ai-sdk/azure';
|
import { azure, createAzure } from '@ai-sdk/azure';
|
||||||
import { ollama } from 'ollama-ai-provider-v2';
|
import { ollama, createOllama } from 'ollama-ai-provider-v2';
|
||||||
import { createOpenRouter } from '@openrouter/ai-sdk-provider';
|
import { createOpenRouter } from '@openrouter/ai-sdk-provider';
|
||||||
|
import { deepseek, createDeepSeek } from '@ai-sdk/deepseek';
|
||||||
|
|
||||||
export type ProviderName =
|
export type ProviderName =
|
||||||
| 'bedrock'
|
| 'bedrock'
|
||||||
@@ -13,20 +14,25 @@ export type ProviderName =
|
|||||||
| 'google'
|
| 'google'
|
||||||
| 'azure'
|
| 'azure'
|
||||||
| 'ollama'
|
| 'ollama'
|
||||||
| 'openrouter';
|
| 'openrouter'
|
||||||
|
| 'deepseek';
|
||||||
|
|
||||||
interface ModelConfig {
|
interface ModelConfig {
|
||||||
model: any;
|
model: any;
|
||||||
providerOptions?: any;
|
providerOptions?: any;
|
||||||
|
headers?: Record<string, string>;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Anthropic beta headers for fine-grained tool streaming
|
// Bedrock provider options for Anthropic beta features
|
||||||
const ANTHROPIC_BETA_OPTIONS = {
|
const BEDROCK_ANTHROPIC_BETA = {
|
||||||
anthropic: {
|
bedrock: {
|
||||||
additionalModelRequestFields: {
|
anthropicBeta: ['fine-grained-tool-streaming-2025-05-14'],
|
||||||
anthropic_beta: ['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',
|
azure: 'AZURE_API_KEY',
|
||||||
ollama: null, // No credentials needed for local Ollama
|
ollama: null, // No credentials needed for local Ollama
|
||||||
openrouter: 'OPENROUTER_API_KEY',
|
openrouter: 'OPENROUTER_API_KEY',
|
||||||
|
deepseek: 'DEEPSEEK_API_KEY',
|
||||||
};
|
};
|
||||||
|
|
||||||
const requiredVar = requiredEnvVars[provider];
|
const requiredVar = requiredEnvVars[provider];
|
||||||
@@ -56,17 +63,20 @@ function validateProviderCredentials(provider: ProviderName): void {
|
|||||||
* Get the AI model based on environment variables
|
* Get the AI model based on environment variables
|
||||||
*
|
*
|
||||||
* 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
|
* - AI_MODEL: The model ID/name for the selected provider
|
||||||
*
|
*
|
||||||
* Provider-specific env vars:
|
* Provider-specific env vars:
|
||||||
* - OPENAI_API_KEY: OpenAI API key
|
* - OPENAI_API_KEY: OpenAI API key
|
||||||
|
* - OPENAI_BASE_URL: Custom OpenAI-compatible endpoint (optional)
|
||||||
* - ANTHROPIC_API_KEY: Anthropic API key
|
* - ANTHROPIC_API_KEY: Anthropic API key
|
||||||
* - GOOGLE_GENERATIVE_AI_API_KEY: Google API key
|
* - GOOGLE_GENERATIVE_AI_API_KEY: Google API key
|
||||||
* - AZURE_RESOURCE_NAME, AZURE_API_KEY: Azure OpenAI credentials
|
* - AZURE_RESOURCE_NAME, AZURE_API_KEY: Azure OpenAI credentials
|
||||||
* - AWS_REGION, AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY: AWS Bedrock 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)
|
* - OLLAMA_BASE_URL: Ollama server URL (optional, defaults to http://localhost:11434)
|
||||||
* - OPENROUTER_API_KEY: OpenRouter API key
|
* - OPENROUTER_API_KEY: OpenRouter API key
|
||||||
|
* - DEEPSEEK_API_KEY: DeepSeek API key
|
||||||
|
* - DEEPSEEK_BASE_URL: DeepSeek endpoint (optional)
|
||||||
*/
|
*/
|
||||||
export function getAIModel(): ModelConfig {
|
export function getAIModel(): ModelConfig {
|
||||||
const provider = (process.env.AI_PROVIDER || 'bedrock') as ProviderName;
|
const provider = (process.env.AI_PROVIDER || 'bedrock') as ProviderName;
|
||||||
@@ -86,55 +96,105 @@ export function getAIModel(): ModelConfig {
|
|||||||
|
|
||||||
let model: any;
|
let model: any;
|
||||||
let providerOptions: any = undefined;
|
let providerOptions: any = undefined;
|
||||||
|
let headers: Record<string, string> | undefined = undefined;
|
||||||
|
|
||||||
switch (provider) {
|
switch (provider) {
|
||||||
case 'bedrock':
|
case 'bedrock':
|
||||||
model = bedrock(modelId);
|
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')) {
|
if (modelId.includes('anthropic.claude')) {
|
||||||
providerOptions = ANTHROPIC_BETA_OPTIONS;
|
providerOptions = BEDROCK_ANTHROPIC_BETA;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'openai':
|
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;
|
break;
|
||||||
|
|
||||||
case 'anthropic':
|
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
|
// Add beta headers for fine-grained tool streaming
|
||||||
providerOptions = ANTHROPIC_BETA_OPTIONS;
|
headers = ANTHROPIC_BETA_HEADERS;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'google':
|
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;
|
break;
|
||||||
|
|
||||||
case 'azure':
|
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;
|
break;
|
||||||
|
|
||||||
case 'ollama':
|
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;
|
break;
|
||||||
|
|
||||||
case 'openrouter':
|
case 'openrouter':
|
||||||
const openrouter = createOpenRouter({
|
const openrouter = createOpenRouter({
|
||||||
apiKey: process.env.OPENROUTER_API_KEY,
|
apiKey: process.env.OPENROUTER_API_KEY,
|
||||||
|
...(process.env.OPENROUTER_BASE_URL && { baseURL: process.env.OPENROUTER_BASE_URL }),
|
||||||
});
|
});
|
||||||
model = openrouter(modelId);
|
model = openrouter(modelId);
|
||||||
break;
|
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:
|
default:
|
||||||
throw new Error(
|
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
|
// Log if provider options or headers are being applied
|
||||||
if (providerOptions) {
|
if (providerOptions || headers) {
|
||||||
console.log('[AI Provider] Applying provider-specific options');
|
console.log('[AI Provider] Applying provider-specific options/headers');
|
||||||
}
|
}
|
||||||
|
|
||||||
return { model, providerOptions };
|
return { model, providerOptions, headers };
|
||||||
}
|
}
|
||||||
|
|||||||
557
lib/cached-responses.ts
Normal file
557
lib/cached-responses.ts
Normal 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
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 & 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 & 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
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 & 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
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 & 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 & 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
(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<br>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<br>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
4821
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -9,9 +9,10 @@
|
|||||||
"lint": "next lint"
|
"lint": "next lint"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ai-sdk/amazon-bedrock": "^3.0.52",
|
"@ai-sdk/amazon-bedrock": "^3.0.62",
|
||||||
"@ai-sdk/anthropic": "^2.0.44",
|
"@ai-sdk/anthropic": "^2.0.44",
|
||||||
"@ai-sdk/azure": "^2.0.69",
|
"@ai-sdk/azure": "^2.0.69",
|
||||||
|
"@ai-sdk/deepseek": "^1.0.30",
|
||||||
"@ai-sdk/google": "^2.0.0",
|
"@ai-sdk/google": "^2.0.0",
|
||||||
"@ai-sdk/openai": "^2.0.19",
|
"@ai-sdk/openai": "^2.0.19",
|
||||||
"@ai-sdk/react": "^2.0.22",
|
"@ai-sdk/react": "^2.0.22",
|
||||||
@@ -46,6 +47,8 @@
|
|||||||
"@types/pako": "^2.0.3",
|
"@types/pako": "^2.0.3",
|
||||||
"@types/react": "^19",
|
"@types/react": "^19",
|
||||||
"@types/react-dom": "^19",
|
"@types/react-dom": "^19",
|
||||||
|
"eslint": "9.39.1",
|
||||||
|
"eslint-config-next": "16.0.5",
|
||||||
"tailwindcss": "^4",
|
"tailwindcss": "^4",
|
||||||
"typescript": "^5"
|
"typescript": "^5"
|
||||||
}
|
}
|
||||||
|
|||||||
4
public/animated_connectors.svg
Normal file
4
public/animated_connectors.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 261 KiB |
4
public/aws_demo.svg
Normal file
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
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
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
4
public/gcp_demo.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 100 KiB |
Reference in New Issue
Block a user