Compare commits

..

1 Commits

Author SHA1 Message Date
dayuan.jiang
015537e97d 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 11:36:42 +09:00
13 changed files with 248 additions and 5722 deletions

View File

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

114
.github/workflows/claude-code.yml vendored Normal file
View File

@@ -0,0 +1,114 @@
name: Claude Code
on:
issue_comment:
types: [created]
pull_request_review_comment:
types: [created]
issues:
types: [opened, assigned]
pull_request_review:
types: [submitted]
pull_request_target:
types: [opened, synchronize, reopened]
jobs:
claude:
if: |
(github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) ||
(github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) ||
(github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) ||
(github.event_name == 'issues' && contains(github.event.issue.body, '@claude'))
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
issues: write
id-token: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ secrets.AWS_ROLE_TO_ASSUME }}
aws-region: us-east-1
- name: Run Claude Code
uses: anthropics/claude-code-action@v1
with:
use_bedrock: "true"
github_token: ${{ secrets.GITHUB_TOKEN }}
pr-review:
if: github.event_name == 'pull_request_target'
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write
id-token: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ secrets.AWS_ROLE_TO_ASSUME }}
aws-region: us-east-1
- name: Run Claude Code PR Review
uses: anthropics/claude-code-action@v1
with:
use_bedrock: "true"
github_token: ${{ secrets.GITHUB_TOKEN }}
allowed_non_write_users: "*"
prompt: |
REPO: ${{ github.repository }}
PR NUMBER: ${{ github.event.pull_request.number }}
This is a personal project - an AI-powered draw.io diagram generator built with:
- Next.js 15 with React 19
- Vercel AI SDK (streamText, useChat, tool calling)
- Multiple AI providers: Bedrock, Anthropic, OpenAI, Google, Azure, OpenRouter, Ollama
First, check previous review comments from github-actions bot using `gh api repos/${{ github.repository }}/pulls/${{ github.event.pull_request.number }}/comments`.
For each previous comment:
- If the issue is fixed in the current code, resolve the comment thread using:
`gh api graphql -f query='mutation { resolveReviewThread(input: {threadId: "THREAD_ID"}) { thread { isResolved } } }'`
Get the thread ID from the comment's node_id field.
- If the issue still exists, leave it alone
Then review the current diff for NEW issues only:
Review this PR for these issues (report ALL that apply):
1. Bugs that would cause runtime errors or broken functionality
2. Security issues (exposed secrets, API key leaks)
3. AI SDK misuse - specifically check for:
- Client-side: Should use useChat/useCompletion/useObject hooks, NOT raw fetch()
- Server-side: Should use streamText/generateText/streamObject/generateObject
- Message handling: Access message.parts array, not legacy content property
- Tool definitions: Must use Zod schemas for inputSchema
- Status handling: Check status (submitted/streaming/ready/error) before actions
- Stream cleanup: Call stop() when aborting streams
4. Unrelated changes that should be in separate PRs (scope creep)
5. Suspicious .gitignore additions or accidentally committed files
6. UI/UX inconsistencies (e.g., alignment issues)
When reviewing AI SDK usage, fetch https://ai-sdk.dev/docs/ to verify correct patterns.
Key doc pages: /docs/ai-sdk-ui/chatbot, /docs/ai-sdk-core/generating-text, /docs/ai-sdk-core/tools-and-tool-calling
DO NOT comment on:
- Minor performance optimizations
- Code style preferences (unless clearly wrong)
- Type annotations that don't affect functionality
Use `mcp__github_inline_comment__create_inline_comment` for inline comments.
Report ALL issues found - create multiple inline comments if needed. Only say "LGTM" if there are truly no issues.
claude_args: |
--allowedTools "mcp__github_inline_comment__create_inline_comment,Bash(gh pr comment:*),Bash(gh pr diff:*),Bash(gh pr view:*),Bash(gh api:*),WebFetch(domain:ai-sdk.dev)"

View File

@@ -73,8 +73,6 @@ Diagrams are represented as XML that can be rendered in draw.io. The AI processe
- Google AI
- Azure OpenAI
- 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.
@@ -107,7 +105,7 @@ 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, openrouter, deepseek)
- 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
@@ -151,8 +149,7 @@ 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.
- [ ] Solve the bug that generation will fail for session that longer than 60s.
## License
@@ -164,8 +161,4 @@ 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)
---

View File

@@ -1,67 +1,12 @@
import { streamText, convertToModelMessages, createUIMessageStream, createUIMessageStreamResponse } from 'ai';
import { streamText, convertToModelMessages } 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 = 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 const maxDuration = 60;
export async function POST(req: Request) {
try {
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 { messages, xml } = await req.json();
const systemMessage = `
You are an expert diagram creation assistant specializing in draw.io XML generation.
@@ -87,7 +32,6 @@ 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
@@ -134,37 +78,19 @@ When using edit_diagram tool:
// Extract file parts (images) from the last message
const fileParts = lastMessage.parts?.filter((part: any) => part.type === 'file') || [];
// 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:
const formattedTextContent = `
Current diagram XML:
"""xml
${xml || ''}
"""
User input:
"""md
${lastMessageText}
"""${diagramContext}`;
"""`;
// Convert UIMessages to ModelMessages and add system message
const modelMessages = convertToModelMessages(messages);
// 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
);
let enhancedMessages = [...modelMessages];
// Update the last message with formatted content if it's a user message
if (enhancedMessages.length >= 1) {
@@ -191,44 +117,16 @@ ${lastMessageText}
}
}
// 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
}
}
}
console.log("Enhanced messages:", enhancedMessages);
// Get AI model from environment configuration
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 { model, providerOptions } = getAIModel();
const result = streamText({
model,
messages: [systemMessageWithCache, ...enhancedMessages],
system: systemMessage,
messages: 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: {
@@ -265,7 +163,6 @@ 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

@@ -50,10 +50,7 @@ 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{" "}
<span className="text-xs text-gray-400">(cached for instant response)</span>:
</p>
<p className="text-sm text-gray-500 mb-2">Try these examples:</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"

View File

@@ -1,23 +1,15 @@
"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;
@@ -38,21 +30,6 @@ 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 || "";
@@ -157,23 +134,19 @@ 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">
{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")}
{output || (toolName === "display_diagram"
? "Diagram generated"
: toolName === "edit_diagram"
? "Diagram edited"
: "Tool executed")}
</div>
) : state === "output-error" ? (
<div className="text-red-600">
{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")}
{output || (toolName === "display_diagram"
? "Error generating diagram"
: toolName === "edit_diagram"
? "Error editing diagram"
: "Tool error")}
</div>
) : null}
</div>
@@ -187,66 +160,51 @@ export function ChatMessageDisplay({
{messages.length === 0 ? (
<ExamplePanel setInput={setInput} setFiles={setFiles} />
) : (
messages.map((message) => {
const userMessageText = message.role === "user" ? getMessageTextContent(message) : "";
return (
messages.map((message) => (
<div
key={message.id}
className={`mb-4 ${
message.role === "user" ? "text-right" : "text-left"
}`}
>
<div
key={message.id}
className={`mb-4 flex ${message.role === "user" ? "justify-end" : "justify-start"}`}
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"
}`}
>
{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>
{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>
))
)}
{error && (
<div className="text-red-500 text-sm mt-2">

View File

@@ -19,7 +19,6 @@ 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 {
@@ -34,8 +33,6 @@ export default function ChatPanel({ isVisible, onToggleVisibility }: ChatPanelPr
resolverRef,
chartXML,
clearDiagram,
getLastAgentGeneratedXml,
markAgentDiagramPending,
} = useDiagram();
const onFetchChart = () => {
@@ -76,15 +73,7 @@ export default function ChatPanel({ isVisible, onToggleVisibility }: ChatPanelPr
}),
async onToolCall({ toolCall }) {
if (toolCall.toolName === "display_diagram") {
// 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();
}
// Diagram is handled streamingly in the ChatMessageDisplay component
addToolResult({
tool: "display_diagram",
toolCallId: toolCall.toolCallId,
@@ -107,9 +96,6 @@ 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,
@@ -148,6 +134,10 @@ 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();
@@ -181,14 +171,11 @@ Please retry with an adjusted search pattern or use display_diagram if retries a
}
}
const lastGenXml = getLastAgentGeneratedXml();
sendMessage(
{ parts },
{
body: {
xml: chartXml,
lastGeneratedXml: lastGenXml,
},
}
);

View File

@@ -2,16 +2,12 @@
import React, { createContext, useContext, useRef, useState } from "react";
import type { DrawIoEmbedRef } from "react-drawio";
import { extractDiagramXML, formatXML } from "../lib/utils";
import { extractDiagramXML } 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>;
@@ -28,25 +24,9 @@ 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({
@@ -74,15 +54,6 @@ 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;
@@ -95,7 +66,6 @@ export function DiagramProvider({ children }: { children: React.ReactNode }) {
setChartXML(emptyDiagram);
setLatestSvg("");
setDiagramHistory([]);
setLastAgentGeneratedXml("");
};
return (
@@ -104,10 +74,6 @@ 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, deepseek
# Options: bedrock, openai, anthropic, google, azure, ollama, openrouter
# Default: bedrock
AI_PROVIDER=bedrock
@@ -20,24 +20,16 @@ AI_MODEL=global.anthropic.claude-sonnet-4-5-20250929-v1:0
# 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,11 +1,10 @@
import { bedrock } from '@ai-sdk/amazon-bedrock';
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 { 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 { createOpenRouter } from '@openrouter/ai-sdk-provider';
import { deepseek, createDeepSeek } from '@ai-sdk/deepseek';
export type ProviderName =
| 'bedrock'
@@ -14,25 +13,20 @@ export type ProviderName =
| 'google'
| 'azure'
| 'ollama'
| 'openrouter'
| 'deepseek';
| 'openrouter';
interface ModelConfig {
model: any;
providerOptions?: any;
headers?: Record<string, string>;
}
// 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',
// Anthropic beta headers for fine-grained tool streaming
const ANTHROPIC_BETA_OPTIONS = {
anthropic: {
additionalModelRequestFields: {
anthropic_beta: ['fine-grained-tool-streaming-2025-05-14']
}
}
};
/**
@@ -47,7 +41,6 @@ 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];
@@ -63,7 +56,7 @@ 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, deepseek)
* - AI_PROVIDER: The provider to use (bedrock, openai, anthropic, google, azure, ollama, openrouter)
* - AI_MODEL: The model ID/name for the selected provider
*
* Provider-specific env vars:
@@ -75,8 +68,6 @@ function validateProviderCredentials(provider: ProviderName): void {
* - 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;
@@ -96,14 +87,13 @@ 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 options if using Claude models via Bedrock
// Add Anthropic beta headers if using Claude models via Bedrock
if (modelId.includes('anthropic.claude')) {
providerOptions = BEDROCK_ANTHROPIC_BETA;
providerOptions = ANTHROPIC_BETA_OPTIONS;
}
break;
@@ -120,81 +110,40 @@ export function getAIModel(): ModelConfig {
break;
case 'anthropic':
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);
model = anthropic(modelId);
// Add beta headers for fine-grained tool streaming
headers = ANTHROPIC_BETA_HEADERS;
providerOptions = ANTHROPIC_BETA_OPTIONS;
break;
case 'google':
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);
}
model = google(modelId);
break;
case 'azure':
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);
}
model = azure(modelId);
break;
case 'ollama':
if (process.env.OLLAMA_BASE_URL) {
const customOllama = createOllama({
baseURL: process.env.OLLAMA_BASE_URL,
});
model = customOllama(modelId);
} else {
model = ollama(modelId);
}
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, deepseek`
`Unknown AI provider: ${provider}. Supported providers: bedrock, openai, anthropic, google, azure, ollama, openrouter`
);
}
// Log if provider options or headers are being applied
if (providerOptions || headers) {
console.log('[AI Provider] Applying provider-specific options/headers');
// Log if provider options are being applied
if (providerOptions) {
console.log('[AI Provider] Applying provider-specific options');
}
return { model, providerOptions, headers };
return { model, providerOptions };
}

View File

@@ -1,557 +0,0 @@
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,10 +9,9 @@
"lint": "next lint"
},
"dependencies": {
"@ai-sdk/amazon-bedrock": "^3.0.62",
"@ai-sdk/amazon-bedrock": "^3.0.52",
"@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",
@@ -47,8 +46,6 @@
"@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"
}