mirror of
https://github.com/DayuanJiang/next-ai-draw-io.git
synced 2026-01-02 22:32:27 +08:00
fix: enhance permissions in settings and update .gitignore for local config
This commit is contained in:
@@ -1,7 +1,11 @@
|
|||||||
{
|
{
|
||||||
"permissions": {
|
"permissions": {
|
||||||
"allow": [
|
"allow": [
|
||||||
"Bash(npm update:*)"
|
"Bash(npm update:*)",
|
||||||
|
"WebFetch(domain:ai-sdk.dev)",
|
||||||
|
"mcp__ide__getDiagnostics",
|
||||||
|
"Bash(npx tsc:*)",
|
||||||
|
"WebFetch(domain:sdk.vercel.ai)"
|
||||||
],
|
],
|
||||||
"deny": [],
|
"deny": [],
|
||||||
"ask": []
|
"ask": []
|
||||||
|
|||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -40,3 +40,4 @@ yarn-error.log*
|
|||||||
*.tsbuildinfo
|
*.tsbuildinfo
|
||||||
next-env.d.ts
|
next-env.d.ts
|
||||||
push-via-ec2.sh
|
push-via-ec2.sh
|
||||||
|
.claude/settings.local.json
|
||||||
|
|||||||
@@ -3,8 +3,6 @@ import { openai } from '@ai-sdk/openai';
|
|||||||
import { google } from '@ai-sdk/google';
|
import { google } from '@ai-sdk/google';
|
||||||
import { smoothStream, streamText, convertToModelMessages } from 'ai';
|
import { smoothStream, streamText, convertToModelMessages } from 'ai';
|
||||||
import { createOpenRouter } from '@openrouter/ai-sdk-provider';
|
import { createOpenRouter } from '@openrouter/ai-sdk-provider';
|
||||||
import { readFileSync } from 'fs';
|
|
||||||
import { resolve } from 'path';
|
|
||||||
import { createGoogleGenerativeAI } from '@ai-sdk/google';
|
import { createGoogleGenerativeAI } from '@ai-sdk/google';
|
||||||
import { createOpenAI } from '@ai-sdk/openai';
|
import { createOpenAI } from '@ai-sdk/openai';
|
||||||
|
|
||||||
@@ -13,18 +11,16 @@ import { replaceXMLParts } from "@/lib/utils";
|
|||||||
|
|
||||||
export const maxDuration = 60
|
export const maxDuration = 60
|
||||||
const openrouter = createOpenRouter({ apiKey: process.env.OPENROUTER_API_KEY });
|
const openrouter = createOpenRouter({ apiKey: process.env.OPENROUTER_API_KEY });
|
||||||
// Read the XML guide from file
|
|
||||||
export async function POST(req: Request) {
|
export async function POST(req: Request) {
|
||||||
const body = await req.json();
|
try {
|
||||||
|
const { messages, xml } = await req.json();
|
||||||
|
|
||||||
// Extract messages and xml directly from the body
|
const systemMessage = `
|
||||||
const { messages, xml } = body;
|
You are an expert diagram creation assistant specializing in draw.io XML generation.
|
||||||
const guide = readFileSync(resolve('./app/api/chat/xml_guide.md'), 'utf8');
|
Your primary function is crafting clear, well-organized visual diagrams through precise XML specifications.
|
||||||
|
|
||||||
// Read and escape the guide content
|
|
||||||
const systemMessage = `
|
|
||||||
You are an expert diagram creation assistant specializing in draw.io XML generation. Your primary function is crafting clear, well-organized visual diagrams through precise XML specifications.
|
|
||||||
You can see the image that user uploaded.
|
You can see the image that user uploaded.
|
||||||
|
When you need to generate diagram about aws architecture, use AWS 2025 icons.
|
||||||
You utilize the following tools:
|
You utilize the following tools:
|
||||||
---Tool1---
|
---Tool1---
|
||||||
tool name: display_diagram
|
tool name: display_diagram
|
||||||
@@ -60,16 +56,17 @@ When using edit_diagram tool:
|
|||||||
- Example GOOD edit: {"search": " <mxCell id=\"2\" value=\"Old Text\">", "replace": " <mxCell id=\"2\" value=\"New Text\">"}
|
- Example GOOD edit: {"search": " <mxCell id=\"2\" value=\"Old Text\">", "replace": " <mxCell id=\"2\" value=\"New Text\">"}
|
||||||
- Example BAD edit: Including 10+ unchanged lines just to change one attribute
|
- Example BAD edit: Including 10+ unchanged lines just to change one attribute
|
||||||
- For multiple changes, use separate edits: [{"search": "line1", "replace": "new1"}, {"search": "line2", "replace": "new2"}]
|
- For multiple changes, use separate edits: [{"search": "line1", "replace": "new1"}, {"search": "line2", "replace": "new2"}]
|
||||||
|
|
||||||
here is a guide for the XML format: ${guide}
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const lastMessage = messages[messages.length - 1];
|
const lastMessage = messages[messages.length - 1];
|
||||||
|
|
||||||
// Extract text from the last message parts
|
// Extract text from the last message parts
|
||||||
const lastMessageText = lastMessage.parts?.find((part: any) => part.type === 'text')?.text || '';
|
const lastMessageText = lastMessage.parts?.find((part: any) => part.type === 'text')?.text || '';
|
||||||
|
|
||||||
const formattedContent = `
|
// Extract file parts (images) from the last message
|
||||||
|
const fileParts = lastMessage.parts?.filter((part: any) => part.type === 'file') || [];
|
||||||
|
|
||||||
|
const formattedTextContent = `
|
||||||
Current diagram XML:
|
Current diagram XML:
|
||||||
"""xml
|
"""xml
|
||||||
${xml || ''}
|
${xml || ''}
|
||||||
@@ -79,99 +76,118 @@ User input:
|
|||||||
${lastMessageText}
|
${lastMessageText}
|
||||||
"""`;
|
"""`;
|
||||||
|
|
||||||
// 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];
|
let enhancedMessages = [...modelMessages];
|
||||||
|
|
||||||
// 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) {
|
||||||
const lastModelMessage = enhancedMessages[enhancedMessages.length - 1];
|
const lastModelMessage = enhancedMessages[enhancedMessages.length - 1];
|
||||||
if (lastModelMessage.role === 'user') {
|
if (lastModelMessage.role === 'user') {
|
||||||
enhancedMessages = [
|
// Build content array with text and file parts
|
||||||
...enhancedMessages.slice(0, -1),
|
const contentParts: any[] = [
|
||||||
{ ...lastModelMessage, content: formattedContent }
|
{ type: 'text', text: formattedTextContent }
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// Add image parts back
|
||||||
|
for (const filePart of fileParts) {
|
||||||
|
contentParts.push({
|
||||||
|
type: 'image',
|
||||||
|
image: filePart.url,
|
||||||
|
mimeType: filePart.mediaType
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
enhancedMessages = [
|
||||||
|
...enhancedMessages.slice(0, -1),
|
||||||
|
{ ...lastModelMessage, content: contentParts }
|
||||||
|
];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
console.log("Enhanced messages:", enhancedMessages);
|
console.log("Enhanced messages:", enhancedMessages);
|
||||||
|
|
||||||
const result = streamText({
|
const result = streamText({
|
||||||
// model: google("gemini-2.5-flash-preview-05-20"),
|
// model: google("gemini-2.5-flash-preview-05-20"),
|
||||||
// model: google("gemini-2.5-pro"),
|
// model: google("gemini-2.5-pro"),
|
||||||
// model: bedrock('anthropic.claude-sonnet-4-20250514-v1:0'),
|
// model: bedrock('anthropic.claude-sonnet-4-20250514-v1:0'),
|
||||||
system: systemMessage,
|
system: systemMessage,
|
||||||
model: bedrock('global.anthropic.claude-sonnet-4-5-20250929-v1:0'),
|
model: bedrock('global.anthropic.claude-sonnet-4-5-20250929-v1:0'),
|
||||||
// model: openrouter('moonshotai/kimi-k2:free'),
|
// model: openrouter('moonshotai/kimi-k2:free'),
|
||||||
// model: model,
|
// model: model,
|
||||||
// providerOptions: {
|
// providerOptions: {
|
||||||
// google: {
|
// google: {
|
||||||
// thinkingConfig: {
|
// thinkingConfig: {
|
||||||
// thinkingBudget: 128,
|
// thinkingBudget: 128,
|
||||||
// },
|
// },
|
||||||
// }
|
// }
|
||||||
// },
|
// },
|
||||||
// providerOptions: {
|
// providerOptions: {
|
||||||
// openai: {
|
// openai: {
|
||||||
// reasoningEffort: "minimal"
|
// reasoningEffort: "minimal"
|
||||||
// },
|
// },
|
||||||
// },
|
// },
|
||||||
messages: enhancedMessages,
|
messages: enhancedMessages,
|
||||||
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: {
|
||||||
description: `Display a diagram on draw.io. You only need to pass the nodes inside the <root> tag (including the <root> tag itself) in the XML string.
|
description: `Display a diagram on draw.io. You only need to pass the nodes inside the <root> tag (including the <root> tag itself) in the XML string.
|
||||||
For example:
|
For example:
|
||||||
<root>
|
<root>
|
||||||
<mxCell id="0"/>
|
<mxCell id="0"/>
|
||||||
<mxCell id="1" parent="0"/>
|
<mxCell id="1" parent="0"/>
|
||||||
<mxGeometry x="20" y="20" width="100" height="100" as="geometry"/>
|
|
||||||
<mxCell id="2" value="Hello, World!" style="shape=rectangle" parent="1">
|
|
||||||
<mxGeometry x="20" y="20" width="100" height="100" as="geometry"/>
|
<mxGeometry x="20" y="20" width="100" height="100" as="geometry"/>
|
||||||
</mxCell>
|
<mxCell id="2" value="Hello, World!" style="shape=rectangle" parent="1">
|
||||||
</root>`,
|
<mxGeometry x="20" y="20" width="100" height="100" as="geometry"/>
|
||||||
inputSchema: z.object({
|
</mxCell>
|
||||||
xml: z.string().describe("XML string to be displayed on draw.io")
|
</root>`,
|
||||||
})
|
inputSchema: z.object({
|
||||||
},
|
xml: z.string().describe("XML string to be displayed on draw.io")
|
||||||
edit_diagram: {
|
})
|
||||||
description: `Edit specific parts of the current diagram by replacing exact line matches. Use this tool to make targeted fixes without regenerating the entire XML.
|
},
|
||||||
|
edit_diagram: {
|
||||||
|
description: `Edit specific parts of the current diagram by replacing exact line matches. Use this tool to make targeted fixes without regenerating the entire XML.
|
||||||
IMPORTANT: Keep edits concise:
|
IMPORTANT: Keep edits concise:
|
||||||
- Only include the lines that are changing, plus 1-2 surrounding lines for context if needed
|
- Only include the lines that are changing, plus 1-2 surrounding lines for context if needed
|
||||||
- Break large changes into multiple smaller edits
|
- Break large changes into multiple smaller edits
|
||||||
- Each search must contain complete lines (never truncate mid-line)
|
- Each search must contain complete lines (never truncate mid-line)
|
||||||
- First match only - be specific enough to target the right element`,
|
- First match only - be specific enough to target the right element`,
|
||||||
inputSchema: z.object({
|
inputSchema: z.object({
|
||||||
edits: z.array(z.object({
|
edits: z.array(z.object({
|
||||||
search: z.string().describe("Exact lines to search for (including whitespace and indentation)"),
|
search: z.string().describe("Exact lines to search for (including whitespace and indentation)"),
|
||||||
replace: z.string().describe("Replacement lines")
|
replace: z.string().describe("Replacement lines")
|
||||||
})).describe("Array of search/replace pairs to apply sequentially")
|
})).describe("Array of search/replace pairs to apply sequentially")
|
||||||
})
|
})
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
temperature: 0,
|
||||||
temperature: 0,
|
});
|
||||||
});
|
|
||||||
|
|
||||||
// Error handler function to provide detailed error messages
|
// Error handler function to provide detailed error messages
|
||||||
|
function errorHandler(error: unknown) {
|
||||||
|
if (error == null) {
|
||||||
|
return 'unknown error';
|
||||||
|
}
|
||||||
|
|
||||||
function errorHandler(error: unknown) {
|
if (typeof error === 'string') {
|
||||||
if (error == null) {
|
return error;
|
||||||
return 'unknown error';
|
}
|
||||||
|
|
||||||
|
if (error instanceof Error) {
|
||||||
|
return error.message;
|
||||||
|
}
|
||||||
|
|
||||||
|
return JSON.stringify(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof error === 'string') {
|
return result.toUIMessageStreamResponse({
|
||||||
return error;
|
onError: errorHandler,
|
||||||
}
|
});
|
||||||
|
} catch (error) {
|
||||||
if (error instanceof Error) {
|
console.error('Error in chat route:', error);
|
||||||
return error.message;
|
return Response.json(
|
||||||
}
|
{ error: 'Internal server error' },
|
||||||
|
{ status: 500 }
|
||||||
return JSON.stringify(error);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return result.toUIMessageStreamResponse({
|
|
||||||
onError: errorHandler,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user