mirror of
https://github.com/DayuanJiang/next-ai-draw-io.git
synced 2026-01-02 22:32:27 +08:00
Compare commits
2 Commits
d3fb2314ee
...
chore/add-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
68f74f74e0 | ||
|
|
4ad505f432 |
47
.github/workflows/auto-format.yml
vendored
Normal file
47
.github/workflows/auto-format.yml
vendored
Normal file
@@ -0,0 +1,47 @@
|
||||
name: Auto Format
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, synchronize]
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
format:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.head_ref }}
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
|
||||
- name: Install Biome
|
||||
run: npm install --save-dev @biomejs/biome
|
||||
|
||||
- name: Run Biome format
|
||||
run: npx @biomejs/biome check --write --no-errors-on-unmatched .
|
||||
|
||||
- name: Check for changes
|
||||
id: changes
|
||||
run: |
|
||||
if git diff --quiet; then
|
||||
echo "has_changes=false" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "has_changes=true" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Commit changes
|
||||
if: steps.changes.outputs.has_changes == 'true'
|
||||
run: |
|
||||
git config --global user.name "github-actions[bot]"
|
||||
git config --global user.email "github-actions[bot]@users.noreply.github.com"
|
||||
git add .
|
||||
git commit -m "style: auto-format with Biome"
|
||||
git push
|
||||
@@ -1,27 +1,28 @@
|
||||
import type { MetadataRoute } from "next";
|
||||
import type { MetadataRoute } from "next"
|
||||
|
||||
export default function manifest(): MetadataRoute.Manifest {
|
||||
return {
|
||||
name: 'Next AI Draw.io',
|
||||
short_name: 'AIDraw.io',
|
||||
description: 'Create AWS architecture diagrams, flowcharts, and technical diagrams using AI. Free online tool integrating draw.io with AI assistance for professional diagram creation.',
|
||||
start_url: '/',
|
||||
display: 'standalone',
|
||||
background_color: '#f9fafb',
|
||||
theme_color: '#171d26',
|
||||
icons: [
|
||||
{
|
||||
src: '/favicon-192x192.png',
|
||||
sizes: '192x192',
|
||||
type: 'image/png',
|
||||
purpose: 'any',
|
||||
},
|
||||
{
|
||||
src: '/favicon-512x512.png',
|
||||
sizes: '512x512',
|
||||
type: 'image/png',
|
||||
purpose: 'any',
|
||||
},
|
||||
],
|
||||
}
|
||||
return {
|
||||
name: "Next AI Draw.io",
|
||||
short_name: "AIDraw.io",
|
||||
description:
|
||||
"Create AWS architecture diagrams, flowcharts, and technical diagrams using AI. Free online tool integrating draw.io with AI assistance for professional diagram creation.",
|
||||
start_url: "/",
|
||||
display: "standalone",
|
||||
background_color: "#f9fafb",
|
||||
theme_color: "#171d26",
|
||||
icons: [
|
||||
{
|
||||
src: "/favicon-192x192.png",
|
||||
sizes: "192x192",
|
||||
type: "image/png",
|
||||
purpose: "any",
|
||||
},
|
||||
{
|
||||
src: "/favicon-512x512.png",
|
||||
sizes: "512x512",
|
||||
type: "image/png",
|
||||
purpose: "any",
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
6
package-lock.json
generated
6
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "next-ai-draw-io",
|
||||
"version": "0.4.2",
|
||||
"version": "0.4.3",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "next-ai-draw-io",
|
||||
"version": "0.4.2",
|
||||
"version": "0.4.3",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@ai-sdk/amazon-bedrock": "^3.0.70",
|
||||
@@ -63,7 +63,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@anthropic-ai/tokenizer": "^0.0.4",
|
||||
"@biomejs/biome": "2.3.8",
|
||||
"@biomejs/biome": "^2.3.8",
|
||||
"@tailwindcss/postcss": "^4",
|
||||
"@tailwindcss/typography": "^0.5.19",
|
||||
"@types/node": "^20",
|
||||
|
||||
@@ -73,7 +73,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@anthropic-ai/tokenizer": "^0.0.4",
|
||||
"@biomejs/biome": "2.3.8",
|
||||
"@biomejs/biome": "^2.3.8",
|
||||
"@tailwindcss/postcss": "^4",
|
||||
"@tailwindcss/typography": "^0.5.19",
|
||||
"@types/node": "^20",
|
||||
|
||||
@@ -20,7 +20,13 @@ function applyDiagramOperations(xmlContent, operations) {
|
||||
if (parseError) {
|
||||
return {
|
||||
result: xmlContent,
|
||||
errors: [{ type: "update", cellId: "", message: `XML parse error: ${parseError.textContent}` }],
|
||||
errors: [
|
||||
{
|
||||
type: "update",
|
||||
cellId: "",
|
||||
message: `XML parse error: ${parseError.textContent}`,
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +34,13 @@ function applyDiagramOperations(xmlContent, operations) {
|
||||
if (!root) {
|
||||
return {
|
||||
result: xmlContent,
|
||||
errors: [{ type: "update", cellId: "", message: "Could not find <root> element in XML" }],
|
||||
errors: [
|
||||
{
|
||||
type: "update",
|
||||
cellId: "",
|
||||
message: "Could not find <root> element in XML",
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,22 +54,41 @@ function applyDiagramOperations(xmlContent, operations) {
|
||||
if (op.type === "update") {
|
||||
const existingCell = cellMap.get(op.cell_id)
|
||||
if (!existingCell) {
|
||||
errors.push({ type: "update", cellId: op.cell_id, message: `Cell with id="${op.cell_id}" not found` })
|
||||
errors.push({
|
||||
type: "update",
|
||||
cellId: op.cell_id,
|
||||
message: `Cell with id="${op.cell_id}" not found`,
|
||||
})
|
||||
continue
|
||||
}
|
||||
if (!op.new_xml) {
|
||||
errors.push({ type: "update", cellId: op.cell_id, message: "new_xml is required for update operation" })
|
||||
errors.push({
|
||||
type: "update",
|
||||
cellId: op.cell_id,
|
||||
message: "new_xml is required for update operation",
|
||||
})
|
||||
continue
|
||||
}
|
||||
const newDoc = parser.parseFromString(`<wrapper>${op.new_xml}</wrapper>`, "text/xml")
|
||||
const newDoc = parser.parseFromString(
|
||||
`<wrapper>${op.new_xml}</wrapper>`,
|
||||
"text/xml",
|
||||
)
|
||||
const newCell = newDoc.querySelector("mxCell")
|
||||
if (!newCell) {
|
||||
errors.push({ type: "update", cellId: op.cell_id, message: "new_xml must contain an mxCell element" })
|
||||
errors.push({
|
||||
type: "update",
|
||||
cellId: op.cell_id,
|
||||
message: "new_xml must contain an mxCell element",
|
||||
})
|
||||
continue
|
||||
}
|
||||
const newCellId = newCell.getAttribute("id")
|
||||
if (newCellId !== op.cell_id) {
|
||||
errors.push({ type: "update", cellId: op.cell_id, message: `ID mismatch: cell_id is "${op.cell_id}" but new_xml has id="${newCellId}"` })
|
||||
errors.push({
|
||||
type: "update",
|
||||
cellId: op.cell_id,
|
||||
message: `ID mismatch: cell_id is "${op.cell_id}" but new_xml has id="${newCellId}"`,
|
||||
})
|
||||
continue
|
||||
}
|
||||
const importedNode = doc.importNode(newCell, true)
|
||||
@@ -65,22 +96,41 @@ function applyDiagramOperations(xmlContent, operations) {
|
||||
cellMap.set(op.cell_id, importedNode)
|
||||
} else if (op.type === "add") {
|
||||
if (cellMap.has(op.cell_id)) {
|
||||
errors.push({ type: "add", cellId: op.cell_id, message: `Cell with id="${op.cell_id}" already exists` })
|
||||
errors.push({
|
||||
type: "add",
|
||||
cellId: op.cell_id,
|
||||
message: `Cell with id="${op.cell_id}" already exists`,
|
||||
})
|
||||
continue
|
||||
}
|
||||
if (!op.new_xml) {
|
||||
errors.push({ type: "add", cellId: op.cell_id, message: "new_xml is required for add operation" })
|
||||
errors.push({
|
||||
type: "add",
|
||||
cellId: op.cell_id,
|
||||
message: "new_xml is required for add operation",
|
||||
})
|
||||
continue
|
||||
}
|
||||
const newDoc = parser.parseFromString(`<wrapper>${op.new_xml}</wrapper>`, "text/xml")
|
||||
const newDoc = parser.parseFromString(
|
||||
`<wrapper>${op.new_xml}</wrapper>`,
|
||||
"text/xml",
|
||||
)
|
||||
const newCell = newDoc.querySelector("mxCell")
|
||||
if (!newCell) {
|
||||
errors.push({ type: "add", cellId: op.cell_id, message: "new_xml must contain an mxCell element" })
|
||||
errors.push({
|
||||
type: "add",
|
||||
cellId: op.cell_id,
|
||||
message: "new_xml must contain an mxCell element",
|
||||
})
|
||||
continue
|
||||
}
|
||||
const newCellId = newCell.getAttribute("id")
|
||||
if (newCellId !== op.cell_id) {
|
||||
errors.push({ type: "add", cellId: op.cell_id, message: `ID mismatch: cell_id is "${op.cell_id}" but new_xml has id="${newCellId}"` })
|
||||
errors.push({
|
||||
type: "add",
|
||||
cellId: op.cell_id,
|
||||
message: `ID mismatch: cell_id is "${op.cell_id}" but new_xml has id="${newCellId}"`,
|
||||
})
|
||||
continue
|
||||
}
|
||||
const importedNode = doc.importNode(newCell, true)
|
||||
@@ -89,7 +139,11 @@ function applyDiagramOperations(xmlContent, operations) {
|
||||
} else if (op.type === "delete") {
|
||||
const existingCell = cellMap.get(op.cell_id)
|
||||
if (!existingCell) {
|
||||
errors.push({ type: "delete", cellId: op.cell_id, message: `Cell with id="${op.cell_id}" not found` })
|
||||
errors.push({
|
||||
type: "delete",
|
||||
cellId: op.cell_id,
|
||||
message: `Cell with id="${op.cell_id}" not found`,
|
||||
})
|
||||
continue
|
||||
}
|
||||
existingCell.parentNode?.removeChild(existingCell)
|
||||
@@ -149,28 +203,52 @@ test("Update operation changes cell value", () => {
|
||||
{
|
||||
type: "update",
|
||||
cell_id: "2",
|
||||
new_xml: '<mxCell id="2" value="Updated Box A" style="rounded=1;" vertex="1" parent="1"><mxGeometry x="100" y="100" width="120" height="60" as="geometry"/></mxCell>',
|
||||
new_xml:
|
||||
'<mxCell id="2" value="Updated Box A" style="rounded=1;" vertex="1" parent="1"><mxGeometry x="100" y="100" width="120" height="60" as="geometry"/></mxCell>',
|
||||
},
|
||||
])
|
||||
assert(errors.length === 0, `Expected no errors, got: ${JSON.stringify(errors)}`)
|
||||
assert(result.includes('value="Updated Box A"'), "Updated value should be in result")
|
||||
assert(!result.includes('value="Box A"'), "Old value should not be in result")
|
||||
assert(
|
||||
errors.length === 0,
|
||||
`Expected no errors, got: ${JSON.stringify(errors)}`,
|
||||
)
|
||||
assert(
|
||||
result.includes('value="Updated Box A"'),
|
||||
"Updated value should be in result",
|
||||
)
|
||||
assert(
|
||||
!result.includes('value="Box A"'),
|
||||
"Old value should not be in result",
|
||||
)
|
||||
})
|
||||
|
||||
test("Update operation fails for non-existent cell", () => {
|
||||
const { errors } = applyDiagramOperations(sampleXml, [
|
||||
{ type: "update", cell_id: "999", new_xml: '<mxCell id="999" value="Test"/>' },
|
||||
{
|
||||
type: "update",
|
||||
cell_id: "999",
|
||||
new_xml: '<mxCell id="999" value="Test"/>',
|
||||
},
|
||||
])
|
||||
assert(errors.length === 1, "Should have one error")
|
||||
assert(errors[0].message.includes("not found"), "Error should mention not found")
|
||||
assert(
|
||||
errors[0].message.includes("not found"),
|
||||
"Error should mention not found",
|
||||
)
|
||||
})
|
||||
|
||||
test("Update operation fails on ID mismatch", () => {
|
||||
const { errors } = applyDiagramOperations(sampleXml, [
|
||||
{ type: "update", cell_id: "2", new_xml: '<mxCell id="WRONG" value="Test"/>' },
|
||||
{
|
||||
type: "update",
|
||||
cell_id: "2",
|
||||
new_xml: '<mxCell id="WRONG" value="Test"/>',
|
||||
},
|
||||
])
|
||||
assert(errors.length === 1, "Should have one error")
|
||||
assert(errors[0].message.includes("ID mismatch"), "Error should mention ID mismatch")
|
||||
assert(
|
||||
errors[0].message.includes("ID mismatch"),
|
||||
"Error should mention ID mismatch",
|
||||
)
|
||||
})
|
||||
|
||||
test("Add operation creates new cell", () => {
|
||||
@@ -178,41 +256,72 @@ test("Add operation creates new cell", () => {
|
||||
{
|
||||
type: "add",
|
||||
cell_id: "new1",
|
||||
new_xml: '<mxCell id="new1" value="New Box" style="rounded=1;" vertex="1" parent="1"><mxGeometry x="500" y="100" width="120" height="60" as="geometry"/></mxCell>',
|
||||
new_xml:
|
||||
'<mxCell id="new1" value="New Box" style="rounded=1;" vertex="1" parent="1"><mxGeometry x="500" y="100" width="120" height="60" as="geometry"/></mxCell>',
|
||||
},
|
||||
])
|
||||
assert(errors.length === 0, `Expected no errors, got: ${JSON.stringify(errors)}`)
|
||||
assert(
|
||||
errors.length === 0,
|
||||
`Expected no errors, got: ${JSON.stringify(errors)}`,
|
||||
)
|
||||
assert(result.includes('id="new1"'), "New cell should be in result")
|
||||
assert(result.includes('value="New Box"'), "New cell value should be in result")
|
||||
assert(
|
||||
result.includes('value="New Box"'),
|
||||
"New cell value should be in result",
|
||||
)
|
||||
})
|
||||
|
||||
test("Add operation fails for duplicate ID", () => {
|
||||
const { errors } = applyDiagramOperations(sampleXml, [
|
||||
{ type: "add", cell_id: "2", new_xml: '<mxCell id="2" value="Duplicate"/>' },
|
||||
{
|
||||
type: "add",
|
||||
cell_id: "2",
|
||||
new_xml: '<mxCell id="2" value="Duplicate"/>',
|
||||
},
|
||||
])
|
||||
assert(errors.length === 1, "Should have one error")
|
||||
assert(errors[0].message.includes("already exists"), "Error should mention already exists")
|
||||
assert(
|
||||
errors[0].message.includes("already exists"),
|
||||
"Error should mention already exists",
|
||||
)
|
||||
})
|
||||
|
||||
test("Add operation fails on ID mismatch", () => {
|
||||
const { errors } = applyDiagramOperations(sampleXml, [
|
||||
{ type: "add", cell_id: "new1", new_xml: '<mxCell id="WRONG" value="Test"/>' },
|
||||
{
|
||||
type: "add",
|
||||
cell_id: "new1",
|
||||
new_xml: '<mxCell id="WRONG" value="Test"/>',
|
||||
},
|
||||
])
|
||||
assert(errors.length === 1, "Should have one error")
|
||||
assert(errors[0].message.includes("ID mismatch"), "Error should mention ID mismatch")
|
||||
assert(
|
||||
errors[0].message.includes("ID mismatch"),
|
||||
"Error should mention ID mismatch",
|
||||
)
|
||||
})
|
||||
|
||||
test("Delete operation removes cell", () => {
|
||||
const { result, errors } = applyDiagramOperations(sampleXml, [{ type: "delete", cell_id: "3" }])
|
||||
assert(errors.length === 0, `Expected no errors, got: ${JSON.stringify(errors)}`)
|
||||
const { result, errors } = applyDiagramOperations(sampleXml, [
|
||||
{ type: "delete", cell_id: "3" },
|
||||
])
|
||||
assert(
|
||||
errors.length === 0,
|
||||
`Expected no errors, got: ${JSON.stringify(errors)}`,
|
||||
)
|
||||
assert(!result.includes('id="3"'), "Deleted cell should not be in result")
|
||||
assert(result.includes('id="2"'), "Other cells should remain")
|
||||
})
|
||||
|
||||
test("Delete operation fails for non-existent cell", () => {
|
||||
const { errors } = applyDiagramOperations(sampleXml, [{ type: "delete", cell_id: "999" }])
|
||||
const { errors } = applyDiagramOperations(sampleXml, [
|
||||
{ type: "delete", cell_id: "999" },
|
||||
])
|
||||
assert(errors.length === 1, "Should have one error")
|
||||
assert(errors[0].message.includes("not found"), "Error should mention not found")
|
||||
assert(
|
||||
errors[0].message.includes("not found"),
|
||||
"Error should mention not found",
|
||||
)
|
||||
})
|
||||
|
||||
test("Multiple operations in sequence", () => {
|
||||
@@ -220,30 +329,45 @@ test("Multiple operations in sequence", () => {
|
||||
{
|
||||
type: "update",
|
||||
cell_id: "2",
|
||||
new_xml: '<mxCell id="2" value="Updated" style="rounded=1;" vertex="1" parent="1"><mxGeometry x="100" y="100" width="120" height="60" as="geometry"/></mxCell>',
|
||||
new_xml:
|
||||
'<mxCell id="2" value="Updated" style="rounded=1;" vertex="1" parent="1"><mxGeometry x="100" y="100" width="120" height="60" as="geometry"/></mxCell>',
|
||||
},
|
||||
{
|
||||
type: "add",
|
||||
cell_id: "new1",
|
||||
new_xml: '<mxCell id="new1" value="Added" style="rounded=1;" vertex="1" parent="1"><mxGeometry x="500" y="100" width="120" height="60" as="geometry"/></mxCell>',
|
||||
new_xml:
|
||||
'<mxCell id="new1" value="Added" style="rounded=1;" vertex="1" parent="1"><mxGeometry x="500" y="100" width="120" height="60" as="geometry"/></mxCell>',
|
||||
},
|
||||
{ type: "delete", cell_id: "3" },
|
||||
])
|
||||
assert(errors.length === 0, `Expected no errors, got: ${JSON.stringify(errors)}`)
|
||||
assert(result.includes('value="Updated"'), "Updated value should be present")
|
||||
assert(
|
||||
errors.length === 0,
|
||||
`Expected no errors, got: ${JSON.stringify(errors)}`,
|
||||
)
|
||||
assert(
|
||||
result.includes('value="Updated"'),
|
||||
"Updated value should be present",
|
||||
)
|
||||
assert(result.includes('id="new1"'), "Added cell should be present")
|
||||
assert(!result.includes('id="3"'), "Deleted cell should not be present")
|
||||
})
|
||||
|
||||
test("Invalid XML returns parse error", () => {
|
||||
const { errors } = applyDiagramOperations("<not valid xml", [{ type: "delete", cell_id: "1" }])
|
||||
const { errors } = applyDiagramOperations("<not valid xml", [
|
||||
{ type: "delete", cell_id: "1" },
|
||||
])
|
||||
assert(errors.length === 1, "Should have one error")
|
||||
})
|
||||
|
||||
test("Missing root element returns error", () => {
|
||||
const { errors } = applyDiagramOperations("<mxfile></mxfile>", [{ type: "delete", cell_id: "1" }])
|
||||
const { errors } = applyDiagramOperations("<mxfile></mxfile>", [
|
||||
{ type: "delete", cell_id: "1" },
|
||||
])
|
||||
assert(errors.length === 1, "Should have one error")
|
||||
assert(errors[0].message.includes("root"), "Error should mention root element")
|
||||
assert(
|
||||
errors[0].message.includes("root"),
|
||||
"Error should mention root element",
|
||||
)
|
||||
})
|
||||
|
||||
// Summary
|
||||
|
||||
Reference in New Issue
Block a user