mirror of
https://github.com/chartdb/chartdb.git
synced 2025-11-03 21:43:23 +00:00
add export AI script functionality
This commit is contained in:
committed by
Jonathan Fishner
parent
15404fa113
commit
e380200a75
@@ -5,6 +5,7 @@
|
||||
|
||||
<p align="center">
|
||||
<b>Open-source database diagrams editor</b> <br />
|
||||
<b>No installations • No Database password required.</b> <br />
|
||||
</p>
|
||||
|
||||
<h3 align="center">
|
||||
|
||||
643
package-lock.json
generated
643
package-lock.json
generated
@@ -8,6 +8,7 @@
|
||||
"name": "chartdb",
|
||||
"version": "0.0.0",
|
||||
"dependencies": {
|
||||
"@ai-sdk/openai": "^0.0.51",
|
||||
"@radix-ui/react-accordion": "^1.2.0",
|
||||
"@radix-ui/react-checkbox": "^1.1.1",
|
||||
"@radix-ui/react-collapsible": "^1.1.0",
|
||||
@@ -27,6 +28,7 @@
|
||||
"@radix-ui/react-tooltip": "^1.1.2",
|
||||
"@uidotdev/usehooks": "^2.4.1",
|
||||
"@xyflow/react": "^12.0.4",
|
||||
"ai": "^3.3.14",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "^2.1.1",
|
||||
"cmdk": "^1.0.0",
|
||||
@@ -69,6 +71,181 @@
|
||||
"vite": "^5.3.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@ai-sdk/openai": {
|
||||
"version": "0.0.51",
|
||||
"resolved": "https://registry.npmjs.org/@ai-sdk/openai/-/openai-0.0.51.tgz",
|
||||
"integrity": "sha512-e+badQnVzAuY0CXThjXZM4IdbztGnntz0Oo44jyklVsWjhJxnpr5m47ALR+0C/Wdakl5oHFGy4CZfiJ9K6ZyVw==",
|
||||
"dependencies": {
|
||||
"@ai-sdk/provider": "0.0.21",
|
||||
"@ai-sdk/provider-utils": "1.0.15"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"zod": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@ai-sdk/provider": {
|
||||
"version": "0.0.21",
|
||||
"resolved": "https://registry.npmjs.org/@ai-sdk/provider/-/provider-0.0.21.tgz",
|
||||
"integrity": "sha512-9j95uaPRxwYkzQdkl4XO/MmWWW5c5vcVSXtqvALpD9SMB9fzH46dO3UN4VbOJR2J3Z84CZAqgZu5tNlkptT9qQ==",
|
||||
"dependencies": {
|
||||
"json-schema": "0.4.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@ai-sdk/provider-utils": {
|
||||
"version": "1.0.15",
|
||||
"resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-1.0.15.tgz",
|
||||
"integrity": "sha512-icZqf2kpV8XdSViei4pX9ylYcVn+pk9AnVquJJGjGQGnwZ/5OgShqnFcLYrMjQfQcSVkz0PxdQVsIhZHzlT9Og==",
|
||||
"dependencies": {
|
||||
"@ai-sdk/provider": "0.0.21",
|
||||
"eventsource-parser": "1.1.2",
|
||||
"nanoid": "3.3.6",
|
||||
"secure-json-parse": "2.7.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"zod": "^3.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"zod": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@ai-sdk/provider-utils/node_modules/nanoid": {
|
||||
"version": "3.3.6",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz",
|
||||
"integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ai"
|
||||
}
|
||||
],
|
||||
"bin": {
|
||||
"nanoid": "bin/nanoid.cjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@ai-sdk/react": {
|
||||
"version": "0.0.48",
|
||||
"resolved": "https://registry.npmjs.org/@ai-sdk/react/-/react-0.0.48.tgz",
|
||||
"integrity": "sha512-KfW33Gj5/qDA6RWfJ42al3QsgIA2UO+x0gX1M6Kk6LY4bTFgy7+F4GLmo4eflM/9o2M7fUZrNddoOuJ15vbgZg==",
|
||||
"dependencies": {
|
||||
"@ai-sdk/provider-utils": "1.0.15",
|
||||
"@ai-sdk/ui-utils": "0.0.35",
|
||||
"swr": "2.2.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^18 || ^19",
|
||||
"zod": "^3.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"react": {
|
||||
"optional": true
|
||||
},
|
||||
"zod": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@ai-sdk/solid": {
|
||||
"version": "0.0.38",
|
||||
"resolved": "https://registry.npmjs.org/@ai-sdk/solid/-/solid-0.0.38.tgz",
|
||||
"integrity": "sha512-7pMW6leig8Y05UIL8jy/1dEDTjtfA2WG9qkVMWjnKSKiucT/Z5uOO3zWNHYq8EVwdJJnv+RR8gUASXcZLTh7og==",
|
||||
"dependencies": {
|
||||
"@ai-sdk/provider-utils": "1.0.15",
|
||||
"@ai-sdk/ui-utils": "0.0.35"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"solid-js": "^1.7.7"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"solid-js": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@ai-sdk/svelte": {
|
||||
"version": "0.0.40",
|
||||
"resolved": "https://registry.npmjs.org/@ai-sdk/svelte/-/svelte-0.0.40.tgz",
|
||||
"integrity": "sha512-S62aB2aT7gjrVY2uDhxwZFBg9hl4wNwu+kd31zsowByC/yyZp9MRIMXkDCkj0qQLFXvfUzaUuzk8v9gvuPOFCQ==",
|
||||
"dependencies": {
|
||||
"@ai-sdk/provider-utils": "1.0.15",
|
||||
"@ai-sdk/ui-utils": "0.0.35",
|
||||
"sswr": "2.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"svelte": "^3.0.0 || ^4.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"svelte": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@ai-sdk/ui-utils": {
|
||||
"version": "0.0.35",
|
||||
"resolved": "https://registry.npmjs.org/@ai-sdk/ui-utils/-/ui-utils-0.0.35.tgz",
|
||||
"integrity": "sha512-JZWp5gbH9K0/qmmqv0JFrH97JNMB9dU1xtrR2a8uzRE0wYtNmd3KsM9x3KW/f9OGjxUHzAkrboMvxKv/3uz24w==",
|
||||
"dependencies": {
|
||||
"@ai-sdk/provider": "0.0.21",
|
||||
"@ai-sdk/provider-utils": "1.0.15",
|
||||
"json-schema": "0.4.0",
|
||||
"secure-json-parse": "2.7.0",
|
||||
"zod-to-json-schema": "3.22.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"zod": "^3.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"zod": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@ai-sdk/vue": {
|
||||
"version": "0.0.40",
|
||||
"resolved": "https://registry.npmjs.org/@ai-sdk/vue/-/vue-0.0.40.tgz",
|
||||
"integrity": "sha512-01LuQT+Cx2e19fYB4nlMlQhmpJ826S1HfGcB4BY30+/XOJebdHRPPOZ3WV9BytBD7kha/tnngBruiYzegGR+Ug==",
|
||||
"dependencies": {
|
||||
"@ai-sdk/provider-utils": "1.0.15",
|
||||
"@ai-sdk/ui-utils": "0.0.35",
|
||||
"swrv": "1.0.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"vue": "^3.3.4"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"vue": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@alloc/quick-lru": {
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz",
|
||||
@@ -85,7 +262,6 @@
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz",
|
||||
"integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@jridgewell/gen-mapping": "^0.3.5",
|
||||
@@ -264,7 +440,6 @@
|
||||
"version": "7.24.8",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.8.tgz",
|
||||
"integrity": "sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
@@ -274,7 +449,6 @@
|
||||
"version": "7.24.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz",
|
||||
"integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
@@ -324,7 +498,6 @@
|
||||
"version": "7.25.3",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.3.tgz",
|
||||
"integrity": "sha512-iLTJKDbJ4hMvFPgQwwsVoxtHyWpKKPBrxkANrSYewDPaPpT5py5yeVkgPIJ7XYXhndxJpaA3PyALSXQ7u8e/Dw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/types": "^7.25.2"
|
||||
@@ -418,7 +591,6 @@
|
||||
"version": "7.25.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.2.tgz",
|
||||
"integrity": "sha512-YTnYtra7W9e6/oAZEHj0bJehPRUlLH9/fbpT5LfB0NhQXyALCRkRs3zH9v07IYhkgpqX6Z78FnuccZr/l4Fs4Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-string-parser": "^7.24.8",
|
||||
@@ -1156,6 +1328,14 @@
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/@opentelemetry/api": {
|
||||
"version": "1.9.0",
|
||||
"resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz",
|
||||
"integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==",
|
||||
"engines": {
|
||||
"node": ">=8.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@pkgjs/parseargs": {
|
||||
"version": "0.11.0",
|
||||
"resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
|
||||
@@ -2511,11 +2691,15 @@
|
||||
"@types/d3-selection": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/diff-match-patch": {
|
||||
"version": "1.0.36",
|
||||
"resolved": "https://registry.npmjs.org/@types/diff-match-patch/-/diff-match-patch-1.0.36.tgz",
|
||||
"integrity": "sha512-xFdR6tkm0MWvBfO8xXCSsinYxHcqkQUlcHeSpMC2ukzOb6lwQAfDmW+Qt0AvlGd8HpsS28qKsB+oPeJn9I39jg=="
|
||||
},
|
||||
"node_modules/@types/estree": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz",
|
||||
"integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/hast": {
|
||||
@@ -2813,6 +2997,118 @@
|
||||
"vite": "^4.2.0 || ^5.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/compiler-core": {
|
||||
"version": "3.4.38",
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.38.tgz",
|
||||
"integrity": "sha512-8IQOTCWnLFqfHzOGm9+P8OPSEDukgg3Huc92qSG49if/xI2SAwLHQO2qaPQbjCWPBcQoO1WYfXfTACUrWV3c5A==",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/parser": "^7.24.7",
|
||||
"@vue/shared": "3.4.38",
|
||||
"entities": "^4.5.0",
|
||||
"estree-walker": "^2.0.2",
|
||||
"source-map-js": "^1.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/compiler-core/node_modules/estree-walker": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
|
||||
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/@vue/compiler-dom": {
|
||||
"version": "3.4.38",
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.38.tgz",
|
||||
"integrity": "sha512-Osc/c7ABsHXTsETLgykcOwIxFktHfGSUDkb05V61rocEfsFDcjDLH/IHJSNJP+/Sv9KeN2Lx1V6McZzlSb9EhQ==",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@vue/compiler-core": "3.4.38",
|
||||
"@vue/shared": "3.4.38"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/compiler-sfc": {
|
||||
"version": "3.4.38",
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.38.tgz",
|
||||
"integrity": "sha512-s5QfZ+9PzPh3T5H4hsQDJtI8x7zdJaew/dCGgqZ2630XdzaZ3AD8xGZfBqpT8oaD/p2eedd+pL8tD5vvt5ZYJQ==",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/parser": "^7.24.7",
|
||||
"@vue/compiler-core": "3.4.38",
|
||||
"@vue/compiler-dom": "3.4.38",
|
||||
"@vue/compiler-ssr": "3.4.38",
|
||||
"@vue/shared": "3.4.38",
|
||||
"estree-walker": "^2.0.2",
|
||||
"magic-string": "^0.30.10",
|
||||
"postcss": "^8.4.40",
|
||||
"source-map-js": "^1.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/compiler-sfc/node_modules/estree-walker": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
|
||||
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/@vue/compiler-ssr": {
|
||||
"version": "3.4.38",
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.38.tgz",
|
||||
"integrity": "sha512-YXznKFQ8dxYpAz9zLuVvfcXhc31FSPFDcqr0kyujbOwNhlmaNvL2QfIy+RZeJgSn5Fk54CWoEUeW+NVBAogGaw==",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@vue/compiler-dom": "3.4.38",
|
||||
"@vue/shared": "3.4.38"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/reactivity": {
|
||||
"version": "3.4.38",
|
||||
"resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.4.38.tgz",
|
||||
"integrity": "sha512-4vl4wMMVniLsSYYeldAKzbk72+D3hUnkw9z8lDeJacTxAkXeDAP1uE9xr2+aKIN0ipOL8EG2GPouVTH6yF7Gnw==",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@vue/shared": "3.4.38"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/runtime-core": {
|
||||
"version": "3.4.38",
|
||||
"resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.4.38.tgz",
|
||||
"integrity": "sha512-21z3wA99EABtuf+O3IhdxP0iHgkBs1vuoCAsCKLVJPEjpVqvblwBnTj42vzHRlWDCyxu9ptDm7sI2ZMcWrQqlA==",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@vue/reactivity": "3.4.38",
|
||||
"@vue/shared": "3.4.38"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/runtime-dom": {
|
||||
"version": "3.4.38",
|
||||
"resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.4.38.tgz",
|
||||
"integrity": "sha512-afZzmUreU7vKwKsV17H1NDThEEmdYI+GCAK/KY1U957Ig2NATPVjCROv61R19fjZNzMmiU03n79OMnXyJVN0UA==",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@vue/reactivity": "3.4.38",
|
||||
"@vue/runtime-core": "3.4.38",
|
||||
"@vue/shared": "3.4.38",
|
||||
"csstype": "^3.1.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/server-renderer": {
|
||||
"version": "3.4.38",
|
||||
"resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.4.38.tgz",
|
||||
"integrity": "sha512-NggOTr82FbPEkkUvBm4fTGcwUY8UuTsnWC/L2YZBmvaQ4C4Jl/Ao4HHTB+l7WnFCt5M/dN3l0XLuyjzswGYVCA==",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@vue/compiler-ssr": "3.4.38",
|
||||
"@vue/shared": "3.4.38"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"vue": "3.4.38"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/shared": {
|
||||
"version": "3.4.38",
|
||||
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.38.tgz",
|
||||
"integrity": "sha512-q0xCiLkuWWQLzVrecPb0RMsNWyxICOjPrcrwxTUEHb1fsnvni4dcuyG7RT/Ie7VPTvnjzIaWzRMUBsrqNj/hhw==",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/@xobotyi/scrollbar-width": {
|
||||
"version": "1.9.5",
|
||||
"resolved": "https://registry.npmjs.org/@xobotyi/scrollbar-width/-/scrollbar-width-1.9.5.tgz",
|
||||
@@ -2870,6 +3166,71 @@
|
||||
"acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/ai": {
|
||||
"version": "3.3.14",
|
||||
"resolved": "https://registry.npmjs.org/ai/-/ai-3.3.14.tgz",
|
||||
"integrity": "sha512-GF3CVS1rnOtgN68OQGlT/2quhg/D3sMFwak48OGXeqv4VRcDgGJx3UqSwT7ipFa9BncRqo7TIqDHHji3Doamaw==",
|
||||
"dependencies": {
|
||||
"@ai-sdk/provider": "0.0.21",
|
||||
"@ai-sdk/provider-utils": "1.0.15",
|
||||
"@ai-sdk/react": "0.0.48",
|
||||
"@ai-sdk/solid": "0.0.38",
|
||||
"@ai-sdk/svelte": "0.0.40",
|
||||
"@ai-sdk/ui-utils": "0.0.35",
|
||||
"@ai-sdk/vue": "0.0.40",
|
||||
"@opentelemetry/api": "1.9.0",
|
||||
"eventsource-parser": "1.1.2",
|
||||
"json-schema": "0.4.0",
|
||||
"jsondiffpatch": "0.6.0",
|
||||
"nanoid": "3.3.6",
|
||||
"secure-json-parse": "2.7.0",
|
||||
"zod-to-json-schema": "3.22.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"openai": "^4.42.0",
|
||||
"react": "^18 || ^19",
|
||||
"sswr": "^2.1.0",
|
||||
"svelte": "^3.0.0 || ^4.0.0",
|
||||
"zod": "^3.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"openai": {
|
||||
"optional": true
|
||||
},
|
||||
"react": {
|
||||
"optional": true
|
||||
},
|
||||
"sswr": {
|
||||
"optional": true
|
||||
},
|
||||
"svelte": {
|
||||
"optional": true
|
||||
},
|
||||
"zod": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/ai/node_modules/nanoid": {
|
||||
"version": "3.3.6",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz",
|
||||
"integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ai"
|
||||
}
|
||||
],
|
||||
"bin": {
|
||||
"nanoid": "bin/nanoid.cjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/ajv": {
|
||||
"version": "6.12.6",
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
|
||||
@@ -3449,6 +3810,11 @@
|
||||
"integrity": "sha512-JhZUT7JFcQy/EzW605k/ktHtncoo9vnyW/2GspNYwFlN1C/WmjuV/xtS04e9SOkL2sTdw0VAZ2UGCcQ9lR6p6w==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/client-only": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz",
|
||||
"integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA=="
|
||||
},
|
||||
"node_modules/clsx": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
|
||||
@@ -3836,6 +4202,19 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/code-red": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/code-red/-/code-red-1.0.4.tgz",
|
||||
"integrity": "sha512-7qJWqItLA8/VPVlKJlFXU+NBlo/qyfs39aJcuMT/2ere32ZqvF5OSxgdM5xOfJJ7O429gg2HM47y8v9P+9wrNw==",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@jridgewell/sourcemap-codec": "^1.4.15",
|
||||
"@types/estree": "^1.0.1",
|
||||
"acorn": "^8.10.0",
|
||||
"estree-walker": "^3.0.3",
|
||||
"periscopic": "^3.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/color-convert": {
|
||||
"version": "1.9.3",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
|
||||
@@ -4227,6 +4606,15 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/dequal": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
|
||||
"integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/detect-node-es": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz",
|
||||
@@ -4245,6 +4633,11 @@
|
||||
"integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==",
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/diff-match-patch": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/diff-match-patch/-/diff-match-patch-1.0.5.tgz",
|
||||
"integrity": "sha512-IayShXAgj/QMXgB0IWmKx+rOPuGMhqm5w6jvFxmVenXKIzRqTAAsbBPT3kWQeGANj3jGgvcvv4yK6SxqYmikgw=="
|
||||
},
|
||||
"node_modules/dir-glob": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
|
||||
@@ -4294,6 +4687,18 @@
|
||||
"integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/entities": {
|
||||
"version": "4.5.0",
|
||||
"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
|
||||
"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=0.12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/fb55/entities?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/error-stack-parser": {
|
||||
"version": "2.1.4",
|
||||
"resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.1.4.tgz",
|
||||
@@ -5207,6 +5612,15 @@
|
||||
"node": ">=4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/estree-walker": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz",
|
||||
"integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@types/estree": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/esutils": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
|
||||
@@ -5216,6 +5630,14 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/eventsource-parser": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-1.1.2.tgz",
|
||||
"integrity": "sha512-v0eOBUbiaFojBu2s2NPBfYUoRR9GjcDNvCXVaqEf5vVfpIAh9f8RCo4vXTP8c63QRKCFwoLpMpTdPwwhEKVgzA==",
|
||||
"engines": {
|
||||
"node": ">=14.18"
|
||||
}
|
||||
},
|
||||
"node_modules/fast-deep-equal": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
||||
@@ -6159,6 +6581,15 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/is-reference": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.2.tgz",
|
||||
"integrity": "sha512-v3rht/LgVcsdZa3O2Nqs+NMowLOxeOm7Ay9+/ARQ2F+qEoANRcqrjAZKGN0v8ymUetZGgkp26LTnGT7H0Qo9Pg==",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@types/estree": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/is-regex": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz",
|
||||
@@ -6383,6 +6814,11 @@
|
||||
"integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/json-schema": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz",
|
||||
"integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA=="
|
||||
},
|
||||
"node_modules/json-schema-traverse": {
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
|
||||
@@ -6408,6 +6844,33 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/jsondiffpatch": {
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/jsondiffpatch/-/jsondiffpatch-0.6.0.tgz",
|
||||
"integrity": "sha512-3QItJOXp2AP1uv7waBkao5nCvhEv+QmJAd38Ybq7wNI74Q+BBmnLn4EDKz6yI9xGAIQoUF87qHt+kc1IVxB4zQ==",
|
||||
"dependencies": {
|
||||
"@types/diff-match-patch": "^1.0.36",
|
||||
"chalk": "^5.3.0",
|
||||
"diff-match-patch": "^1.0.5"
|
||||
},
|
||||
"bin": {
|
||||
"jsondiffpatch": "bin/jsondiffpatch.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.0.0 || >=20.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/jsondiffpatch/node_modules/chalk": {
|
||||
"version": "5.3.0",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz",
|
||||
"integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==",
|
||||
"engines": {
|
||||
"node": "^12.17.0 || ^14.13 || >=16.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/chalk?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/jsx-ast-utils": {
|
||||
"version": "3.3.5",
|
||||
"resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz",
|
||||
@@ -6481,6 +6944,12 @@
|
||||
"integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/locate-character": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz",
|
||||
"integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/locate-path": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
|
||||
@@ -6547,6 +7016,15 @@
|
||||
"react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0-rc"
|
||||
}
|
||||
},
|
||||
"node_modules/magic-string": {
|
||||
"version": "0.30.11",
|
||||
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.11.tgz",
|
||||
"integrity": "sha512-+Wri9p0QHMy+545hKww7YAu5NyzF8iomPL/RQazugQ9+Ez4Ic3mERMd8ZTX5rfK944j+560ZJi8iAwgak1Ac7A==",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@jridgewell/sourcemap-codec": "^1.5.0"
|
||||
}
|
||||
},
|
||||
"node_modules/mdn-data": {
|
||||
"version": "2.0.14",
|
||||
"resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz",
|
||||
@@ -6988,6 +7466,17 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/periscopic": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/periscopic/-/periscopic-3.1.0.tgz",
|
||||
"integrity": "sha512-vKiQ8RRtkl9P+r/+oefh25C3fhybptkHKCZSPlcXiJux2tJF55GnEj3BVn4A5gKfq9NWWXXrxkHBwVPUfH0opw==",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@types/estree": "^1.0.0",
|
||||
"estree-walker": "^3.0.0",
|
||||
"is-reference": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/picocolors": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz",
|
||||
@@ -7803,6 +8292,11 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/secure-json-parse": {
|
||||
"version": "2.7.0",
|
||||
"resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-2.7.0.tgz",
|
||||
"integrity": "sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw=="
|
||||
},
|
||||
"node_modules/semver": {
|
||||
"version": "7.6.3",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
|
||||
@@ -7950,6 +8444,17 @@
|
||||
"url": "https://github.com/sponsors/wooorm"
|
||||
}
|
||||
},
|
||||
"node_modules/sswr": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/sswr/-/sswr-2.1.0.tgz",
|
||||
"integrity": "sha512-Cqc355SYlTAaUt8iDPaC/4DPPXK925PePLMxyBKuWd5kKc5mwsG3nT9+Mq2tyguL5s7b4Jg+IRMpTRsNTAfpSQ==",
|
||||
"dependencies": {
|
||||
"swrev": "^4.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"svelte": "^4.0.0 || ^5.0.0-next.0"
|
||||
}
|
||||
},
|
||||
"node_modules/stack-generator": {
|
||||
"version": "2.0.10",
|
||||
"resolved": "https://registry.npmjs.org/stack-generator/-/stack-generator-2.0.10.tgz",
|
||||
@@ -8370,6 +8875,93 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/svelte": {
|
||||
"version": "4.2.18",
|
||||
"resolved": "https://registry.npmjs.org/svelte/-/svelte-4.2.18.tgz",
|
||||
"integrity": "sha512-d0FdzYIiAePqRJEb90WlJDkjUEx42xhivxN8muUBmfZnP+tzUgz12DJ2hRJi8sIHCME7jeK1PTMgKPSfTd8JrA==",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@ampproject/remapping": "^2.2.1",
|
||||
"@jridgewell/sourcemap-codec": "^1.4.15",
|
||||
"@jridgewell/trace-mapping": "^0.3.18",
|
||||
"@types/estree": "^1.0.1",
|
||||
"acorn": "^8.9.0",
|
||||
"aria-query": "^5.3.0",
|
||||
"axobject-query": "^4.0.0",
|
||||
"code-red": "^1.0.3",
|
||||
"css-tree": "^2.3.1",
|
||||
"estree-walker": "^3.0.3",
|
||||
"is-reference": "^3.0.1",
|
||||
"locate-character": "^3.0.0",
|
||||
"magic-string": "^0.30.4",
|
||||
"periscopic": "^3.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
}
|
||||
},
|
||||
"node_modules/svelte/node_modules/aria-query": {
|
||||
"version": "5.3.0",
|
||||
"resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz",
|
||||
"integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"dequal": "^2.0.3"
|
||||
}
|
||||
},
|
||||
"node_modules/svelte/node_modules/axobject-query": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz",
|
||||
"integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/svelte/node_modules/css-tree": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz",
|
||||
"integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"mdn-data": "2.0.30",
|
||||
"source-map-js": "^1.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/svelte/node_modules/mdn-data": {
|
||||
"version": "2.0.30",
|
||||
"resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz",
|
||||
"integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/swr": {
|
||||
"version": "2.2.5",
|
||||
"resolved": "https://registry.npmjs.org/swr/-/swr-2.2.5.tgz",
|
||||
"integrity": "sha512-QtxqyclFeAsxEUeZIYmsaQ0UjimSq1RZ9Un7I68/0ClKK/U3LoyQunwkQfJZr2fc22DfIXLNDc2wFyTEikCUpg==",
|
||||
"dependencies": {
|
||||
"client-only": "^0.0.1",
|
||||
"use-sync-external-store": "^1.2.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.11.0 || ^17.0.0 || ^18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/swrev": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/swrev/-/swrev-4.0.0.tgz",
|
||||
"integrity": "sha512-LqVcOHSB4cPGgitD1riJ1Hh4vdmITOp+BkmfmXRh4hSF/t7EnS4iD+SOTmq7w5pPm/SiPeto4ADbKS6dHUDWFA=="
|
||||
},
|
||||
"node_modules/swrv": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/swrv/-/swrv-1.0.4.tgz",
|
||||
"integrity": "sha512-zjEkcP8Ywmj+xOJW3lIT65ciY/4AL4e/Or7Gj0MzU3zBJNMdJiT8geVZhINavnlHRMMCcJLHhraLTAiDOTmQ9g==",
|
||||
"peerDependencies": {
|
||||
"vue": ">=3.2.26 < 4"
|
||||
}
|
||||
},
|
||||
"node_modules/synckit": {
|
||||
"version": "0.9.1",
|
||||
"resolved": "https://registry.npmjs.org/synckit/-/synckit-0.9.1.tgz",
|
||||
@@ -8501,7 +9093,6 @@
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
|
||||
"integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
@@ -8865,6 +9456,27 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/vue": {
|
||||
"version": "3.4.38",
|
||||
"resolved": "https://registry.npmjs.org/vue/-/vue-3.4.38.tgz",
|
||||
"integrity": "sha512-f0ZgN+mZ5KFgVv9wz0f4OgVKukoXtS3nwET4c2vLBGQR50aI8G0cqbFtLlX9Yiyg3LFGBitruPHt2PxwTduJEw==",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@vue/compiler-dom": "3.4.38",
|
||||
"@vue/compiler-sfc": "3.4.38",
|
||||
"@vue/runtime-dom": "3.4.38",
|
||||
"@vue/server-renderer": "3.4.38",
|
||||
"@vue/shared": "3.4.38"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": "*"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"typescript": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/which": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
||||
@@ -9143,6 +9755,23 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/zod": {
|
||||
"version": "3.23.8",
|
||||
"resolved": "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz",
|
||||
"integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==",
|
||||
"peer": true,
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/colinhacks"
|
||||
}
|
||||
},
|
||||
"node_modules/zod-to-json-schema": {
|
||||
"version": "3.22.5",
|
||||
"resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.22.5.tgz",
|
||||
"integrity": "sha512-+akaPo6a0zpVCCseDed504KBJUQpEW5QZw7RMneNmKw+fGaML1Z9tUNLnHHAC8x6dzVRO1eB2oEMyZRnuBZg7Q==",
|
||||
"peerDependencies": {
|
||||
"zod": "^3.22.4"
|
||||
}
|
||||
},
|
||||
"node_modules/zustand": {
|
||||
"version": "4.5.4",
|
||||
"resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.4.tgz",
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ai-sdk/openai": "^0.0.51",
|
||||
"@radix-ui/react-accordion": "^1.2.0",
|
||||
"@radix-ui/react-checkbox": "^1.1.1",
|
||||
"@radix-ui/react-collapsible": "^1.1.0",
|
||||
@@ -30,6 +31,7 @@
|
||||
"@radix-ui/react-tooltip": "^1.1.2",
|
||||
"@uidotdev/usehooks": "^2.4.1",
|
||||
"@xyflow/react": "^12.0.4",
|
||||
"ai": "^3.3.14",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "^2.1.1",
|
||||
"cmdk": "^1.0.0",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React, { useCallback, useMemo, useState } from 'react';
|
||||
import { DBTable } from '@/lib/domain/db-table';
|
||||
import { generateId, randomHSLA } from '@/lib/utils';
|
||||
import { generateId } from '@/lib/utils';
|
||||
import { randomColor } from '@/lib/colors';
|
||||
import { ChartDBContext, chartDBContext } from './chartdb-context';
|
||||
import { DatabaseType } from '@/lib/domain/database-type';
|
||||
import { DBField } from '@/lib/domain/db-field';
|
||||
@@ -151,7 +152,7 @@ export const ChartDBProvider: React.FC<React.PropsWithChildren> = ({
|
||||
},
|
||||
],
|
||||
indexes: [],
|
||||
color: randomHSLA(),
|
||||
color: randomColor(),
|
||||
createdAt: Date.now(),
|
||||
isView: false,
|
||||
};
|
||||
@@ -718,11 +719,18 @@ export const ChartDBProvider: React.FC<React.PropsWithChildren> = ({
|
||||
sourceFieldId,
|
||||
targetFieldId,
|
||||
}) => {
|
||||
const sourceTableName = getTable(sourceTableId)?.name ?? '';
|
||||
const targetTableName = getTable(targetTableId)?.name ?? '';
|
||||
const sourceTable = getTable(sourceTableId);
|
||||
const sourceTableName = sourceTable?.name ?? '';
|
||||
|
||||
const sourceField = sourceTable?.fields.find(
|
||||
(field: { id: string }) => field.id === sourceFieldId
|
||||
);
|
||||
|
||||
const sourceFieldName = sourceField?.name ?? '';
|
||||
|
||||
const relationship: DBRelationship = {
|
||||
id: generateId(),
|
||||
name: `${sourceTableName}_${targetTableName}_fk`,
|
||||
name: `${sourceTableName}_${sourceFieldName}_fk`,
|
||||
sourceTableId,
|
||||
targetTableId,
|
||||
sourceFieldId,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { createContext } from 'react';
|
||||
import { emptyFn } from '@/lib/utils';
|
||||
import { DatabaseType } from '@/lib/domain/database-type';
|
||||
|
||||
export interface DialogContext {
|
||||
// Create diagram dialog
|
||||
@@ -9,6 +10,10 @@ export interface DialogContext {
|
||||
// Open diagram dialog
|
||||
openOpenDiagramDialog: () => void;
|
||||
closeOpenDiagramDialog: () => void;
|
||||
|
||||
// Export SQL dialog
|
||||
openExportSQLDialog: (params: { targetDatabaseType: DatabaseType }) => void;
|
||||
closeExportSQLDialog: () => void;
|
||||
}
|
||||
|
||||
export const dialogContext = createContext<DialogContext>({
|
||||
@@ -16,4 +21,6 @@ export const dialogContext = createContext<DialogContext>({
|
||||
closeCreateDiagramDialog: emptyFn,
|
||||
openOpenDiagramDialog: emptyFn,
|
||||
closeOpenDiagramDialog: emptyFn,
|
||||
openExportSQLDialog: emptyFn,
|
||||
closeExportSQLDialog: emptyFn,
|
||||
});
|
||||
|
||||
@@ -1,13 +1,28 @@
|
||||
import React, { useState } from 'react';
|
||||
import { dialogContext } from './dialog-context';
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import { DialogContext, dialogContext } from './dialog-context';
|
||||
import { CreateDiagramDialog } from '@/dialogs/create-diagram-dialog/create-diagram-dialog';
|
||||
import { OpenDiagramDialog } from '@/dialogs/open-diagram-dialog/open-diagram-dialog';
|
||||
import { ExportSQLDialog } from '@/dialogs/export-sql-dialog/export-sql-dialog';
|
||||
import { DatabaseType } from '@/lib/domain/database-type';
|
||||
|
||||
export const DialogProvider: React.FC<React.PropsWithChildren> = ({
|
||||
children,
|
||||
}) => {
|
||||
const [openNewDiagramDialog, setOpenNewDiagramDialog] = useState(false);
|
||||
const [openOpenDiagramDialog, setOpenOpenDiagramDialog] = useState(false);
|
||||
const [openExportSQLDialog, setOpenExportSQLDialog] = useState(false);
|
||||
const [openExportSQLDialogParams, setOpenExportSQLDialogParams] = useState<{
|
||||
targetDatabaseType: DatabaseType;
|
||||
}>({ targetDatabaseType: DatabaseType.GENERIC });
|
||||
|
||||
const openExportSQLDialogHandler: DialogContext['openExportSQLDialog'] =
|
||||
useCallback(
|
||||
({ targetDatabaseType }) => {
|
||||
setOpenExportSQLDialog(true);
|
||||
setOpenExportSQLDialogParams({ targetDatabaseType });
|
||||
},
|
||||
[setOpenExportSQLDialog]
|
||||
);
|
||||
|
||||
return (
|
||||
<dialogContext.Provider
|
||||
@@ -16,11 +31,17 @@ export const DialogProvider: React.FC<React.PropsWithChildren> = ({
|
||||
closeCreateDiagramDialog: () => setOpenNewDiagramDialog(false),
|
||||
openOpenDiagramDialog: () => setOpenOpenDiagramDialog(true),
|
||||
closeOpenDiagramDialog: () => setOpenOpenDiagramDialog(false),
|
||||
openExportSQLDialog: openExportSQLDialogHandler,
|
||||
closeExportSQLDialog: () => setOpenExportSQLDialog(false),
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
<CreateDiagramDialog dialog={{ open: openNewDiagramDialog }} />
|
||||
<OpenDiagramDialog dialog={{ open: openOpenDiagramDialog }} />
|
||||
<ExportSQLDialog
|
||||
dialog={{ open: openExportSQLDialog }}
|
||||
{...openExportSQLDialogParams}
|
||||
/>
|
||||
</dialogContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -12,7 +12,10 @@ export interface StorageContext {
|
||||
|
||||
// Diagram operations
|
||||
addDiagram: (params: { diagram: Diagram }) => Promise<void>;
|
||||
listDiagrams: () => Promise<Diagram[]>;
|
||||
listDiagrams: (options?: {
|
||||
includeTables?: boolean;
|
||||
includeRelationships?: boolean;
|
||||
}) => Promise<Diagram[]>;
|
||||
getDiagram: (
|
||||
id: string,
|
||||
options?: {
|
||||
|
||||
@@ -92,10 +92,33 @@ export const StorageProvider: React.FC<React.PropsWithChildren> = ({
|
||||
await Promise.all(promises);
|
||||
};
|
||||
|
||||
const listDiagrams: StorageContext['listDiagrams'] = async (): Promise<
|
||||
Diagram[]
|
||||
> => {
|
||||
return await db.diagrams.toArray();
|
||||
const listDiagrams: StorageContext['listDiagrams'] = async (
|
||||
options: {
|
||||
includeTables?: boolean;
|
||||
includeRelationships?: boolean;
|
||||
} = { includeRelationships: false, includeTables: false }
|
||||
): Promise<Diagram[]> => {
|
||||
let diagrams = await db.diagrams.toArray();
|
||||
|
||||
if (options.includeTables) {
|
||||
diagrams = await Promise.all(
|
||||
diagrams.map(async (diagram) => {
|
||||
diagram.tables = await listTables(diagram.id);
|
||||
return diagram;
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
if (options.includeRelationships) {
|
||||
diagrams = await Promise.all(
|
||||
diagrams.map(async (diagram) => {
|
||||
diagram.relationships = await listRelationships(diagram.id);
|
||||
return diagram;
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
return diagrams;
|
||||
};
|
||||
|
||||
const getDiagram: StorageContext['getDiagram'] = async (
|
||||
@@ -248,10 +271,15 @@ export const StorageProvider: React.FC<React.PropsWithChildren> = ({
|
||||
const listRelationships: StorageContext['listRelationships'] = async (
|
||||
diagramId: string
|
||||
): Promise<DBRelationship[]> => {
|
||||
return await db.db_relationships
|
||||
.where('diagramId')
|
||||
.equals(diagramId)
|
||||
.toArray();
|
||||
// Sort relationships alphabetically
|
||||
return await (
|
||||
await db.db_relationships
|
||||
.where('diagramId')
|
||||
.equals(diagramId)
|
||||
.toArray()
|
||||
).sort((a, b) => {
|
||||
return a.name.localeCompare(b.name);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
107
src/dialogs/export-sql-dialog/export-sql-dialog.tsx
Normal file
107
src/dialogs/export-sql-dialog/export-sql-dialog.tsx
Normal file
@@ -0,0 +1,107 @@
|
||||
import { Button } from '@/components/button/button';
|
||||
import { CodeSnippet } from '@/components/code-snippet/code-snippet';
|
||||
import {
|
||||
Dialog,
|
||||
DialogClose,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from '@/components/dialog/dialog';
|
||||
import { Label } from '@/components/label/label';
|
||||
import { Spinner } from '@/components/spinner/spinner';
|
||||
import { useChartDB } from '@/hooks/use-chartdb';
|
||||
import { useDialog } from '@/hooks/use-dialog';
|
||||
import {
|
||||
exportBaseSQL,
|
||||
exportSQL,
|
||||
} from '@/lib/data/export-metadata/export-sql-script';
|
||||
import { databaseTypeToLabelMap } from '@/lib/databases';
|
||||
import { DatabaseType } from '@/lib/domain/database-type';
|
||||
import { DialogProps } from '@radix-ui/react-dialog';
|
||||
import { WandSparkles } from 'lucide-react';
|
||||
import React, { useCallback, useEffect } from 'react';
|
||||
|
||||
export interface ExportSQLDialogProps {
|
||||
dialog: DialogProps;
|
||||
targetDatabaseType: DatabaseType;
|
||||
}
|
||||
|
||||
export const ExportSQLDialog: React.FC<ExportSQLDialogProps> = ({
|
||||
dialog,
|
||||
targetDatabaseType,
|
||||
}) => {
|
||||
const { closeExportSQLDialog } = useDialog();
|
||||
const { currentDiagram } = useChartDB();
|
||||
const [script, setScript] = React.useState<string>();
|
||||
|
||||
const exportSQLScript = useCallback(async () => {
|
||||
if (targetDatabaseType === DatabaseType.GENERIC) {
|
||||
return Promise.resolve(exportBaseSQL(currentDiagram));
|
||||
} else {
|
||||
return exportSQL(currentDiagram, targetDatabaseType);
|
||||
}
|
||||
}, [targetDatabaseType, currentDiagram]);
|
||||
|
||||
useEffect(() => {
|
||||
setScript(undefined);
|
||||
const fetchScript = async () => {
|
||||
const script = await exportSQLScript();
|
||||
setScript(script);
|
||||
};
|
||||
fetchScript();
|
||||
}, [dialog.open, setScript, exportSQLScript]);
|
||||
|
||||
const renderLoader = useCallback(
|
||||
() => (
|
||||
<div className="flex flex-col gap-2">
|
||||
<Spinner />
|
||||
<div className="flex items-center justify-center gap-1">
|
||||
<WandSparkles className="h-5" />
|
||||
<Label>
|
||||
Generating SQL for{' '}
|
||||
{databaseTypeToLabelMap[targetDatabaseType]}...
|
||||
</Label>
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
[targetDatabaseType]
|
||||
);
|
||||
return (
|
||||
<Dialog
|
||||
{...dialog}
|
||||
onOpenChange={(open) => {
|
||||
if (!open) {
|
||||
closeExportSQLDialog();
|
||||
}
|
||||
}}
|
||||
>
|
||||
<DialogContent
|
||||
className="flex flex-col min-w-[500px] xl:min-w-[75vw] max-h-[80vh] overflow-y-auto"
|
||||
showClose
|
||||
>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Export SQL</DialogTitle>
|
||||
<DialogDescription>
|
||||
Export the SQL of the current diagram
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="flex flex-1 items-center justify-center">
|
||||
{(script?.length ?? 0) === 0 ? (
|
||||
renderLoader()
|
||||
) : (
|
||||
<CodeSnippet code={script!} />
|
||||
)}
|
||||
</div>
|
||||
|
||||
<DialogFooter className="flex !justify-between gap-2">
|
||||
<div />
|
||||
<DialogClose asChild>
|
||||
<Button type="button">Close</Button>
|
||||
</DialogClose>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
@@ -46,7 +46,7 @@ export const OpenDiagramDialog: React.FC<OpenDiagramDialogProps> = ({
|
||||
|
||||
useEffect(() => {
|
||||
const fetchDiagrams = async () => {
|
||||
const diagrams = await listDiagrams();
|
||||
const diagrams = await listDiagrams({ includeTables: true });
|
||||
setDiagrams(
|
||||
diagrams.sort(
|
||||
(a, b) => b.updatedAt.getTime() - a.updatedAt.getTime()
|
||||
@@ -87,6 +87,7 @@ export const OpenDiagramDialog: React.FC<OpenDiagramDialogProps> = ({
|
||||
<TableHead>Name</TableHead>
|
||||
<TableHead>Created at</TableHead>
|
||||
<TableHead>Last modified</TableHead>
|
||||
<TableHead>Tables count</TableHead>
|
||||
<TableHead>Type</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
@@ -120,6 +121,9 @@ export const OpenDiagramDialog: React.FC<OpenDiagramDialogProps> = ({
|
||||
<TableCell>
|
||||
{diagram.updatedAt.toLocaleString()}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{diagram.tables?.length}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{
|
||||
databaseTypeToLabelMap[
|
||||
|
||||
19
src/lib/colors.ts
Normal file
19
src/lib/colors.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
export const randomColor = () => {
|
||||
const colors = [
|
||||
'#ff6363', // A brighter red.
|
||||
'#ff6b8a', // A vibrant pink.
|
||||
'#c05dcf', // A rich purple.
|
||||
'#b067e9', // A lighter purple.
|
||||
'#8a61f5', // A bold indigo.
|
||||
'#7175fa', // A lighter indigo.
|
||||
'#8eb7ff', // A sky blue.
|
||||
'#42e0c0', // A fresh aqua.
|
||||
'#4dee8a', // A mint green.
|
||||
'#9ef07a', // A lime green.
|
||||
'#ffe374', // A warm yellow.
|
||||
'#ff9f74', // A peachy orange.
|
||||
];
|
||||
return colors[Math.floor(Math.random() * colors.length)];
|
||||
};
|
||||
|
||||
export const greyColor = '#b0b0b0'; // A Cloudy Grey.
|
||||
@@ -295,6 +295,12 @@ export const sqliteDataTypes = [
|
||||
// Date/Time Types (SQLite uses TEXT, REAL, or INTEGER types for dates and times)
|
||||
'date',
|
||||
'datetime',
|
||||
|
||||
'int',
|
||||
'float',
|
||||
'boolean',
|
||||
'varchar',
|
||||
'decimal',
|
||||
] as const;
|
||||
|
||||
export const dataTypes = [
|
||||
|
||||
@@ -1,6 +1,14 @@
|
||||
import { Diagram } from "../domain/diagram";
|
||||
import { generateText } from 'ai';
|
||||
import { createOpenAI } from '@ai-sdk/openai';
|
||||
import { Diagram } from '../../domain/diagram';
|
||||
import { OPENAI_API_KEY } from '@/lib/env';
|
||||
import { DatabaseType } from '@/lib/domain/database-type';
|
||||
|
||||
export const exportSQL = (diagram: Diagram): string => {
|
||||
const openai = createOpenAI({
|
||||
apiKey: OPENAI_API_KEY,
|
||||
});
|
||||
|
||||
export const exportBaseSQL = (diagram: Diagram): string => {
|
||||
const { tables, relationships } = diagram;
|
||||
|
||||
if (!tables || tables.length === 0) {
|
||||
@@ -8,26 +16,48 @@ export const exportSQL = (diagram: Diagram): string => {
|
||||
}
|
||||
|
||||
// Filter out the tables that are views
|
||||
const nonViewTables = tables.filter(table => !table.isView);
|
||||
const nonViewTables = tables.filter((table) => !table.isView);
|
||||
|
||||
// Align the data types based on foreign key relationships
|
||||
alignForeignKeyDataTypes(diagram);
|
||||
|
||||
// Initialize the SQL script string
|
||||
let sqlScript = '';
|
||||
|
||||
// Loop through each non-view table to generate the SQL statements
|
||||
nonViewTables.forEach(table => {
|
||||
nonViewTables.forEach((table) => {
|
||||
sqlScript += `CREATE TABLE ${table.name} (\n`;
|
||||
|
||||
table.fields.forEach((field, index) => {
|
||||
sqlScript += ` ${field.name} ${field.type}`;
|
||||
|
||||
if (field.primaryKey) {
|
||||
sqlScript += ' PRIMARY KEY';
|
||||
// Add size for character types
|
||||
if (field.characterMaximumLength) {
|
||||
sqlScript += `(${field.characterMaximumLength})`;
|
||||
}
|
||||
|
||||
// Add precision and scale for numeric types
|
||||
if (field.precision !== null && field.scale !== null) {
|
||||
sqlScript += `(${field.precision}, ${field.scale})`;
|
||||
} else if (field.precision !== null) {
|
||||
sqlScript += `(${field.precision})`;
|
||||
}
|
||||
|
||||
// Handle NOT NULL constraint
|
||||
if (!field.nullable) {
|
||||
sqlScript += ' NOT NULL';
|
||||
}
|
||||
|
||||
// Handle DEFAULT value
|
||||
if (field.default) {
|
||||
sqlScript += ` DEFAULT ${field.default}`;
|
||||
}
|
||||
|
||||
// Handle PRIMARY KEY constraint
|
||||
if (field.primaryKey) {
|
||||
sqlScript += ' PRIMARY KEY';
|
||||
}
|
||||
|
||||
// Add a comma after each field except the last one
|
||||
if (index < table.fields.length - 1) {
|
||||
sqlScript += ',\n';
|
||||
@@ -35,17 +65,230 @@ export const exportSQL = (diagram: Diagram): string => {
|
||||
});
|
||||
|
||||
sqlScript += '\n);\n\n';
|
||||
|
||||
// Generate SQL for indexes
|
||||
table.indexes.forEach((index) => {
|
||||
const fieldNames = index.fieldIds
|
||||
.map(
|
||||
(fieldId) =>
|
||||
table.fields.find((field) => field.id === fieldId)?.name
|
||||
)
|
||||
.filter(Boolean)
|
||||
.join(', ');
|
||||
|
||||
if (fieldNames) {
|
||||
sqlScript += `CREATE ${index.unique ? 'UNIQUE ' : ''}INDEX ${index.name} ON ${table.name} (${fieldNames});\n`;
|
||||
}
|
||||
});
|
||||
|
||||
sqlScript += '\n';
|
||||
});
|
||||
|
||||
// Handle relationships (foreign keys) if needed
|
||||
relationships?.forEach(relationship => {
|
||||
const sourceTable = nonViewTables.find(table => table.id === relationship.sourceTableId);
|
||||
const targetTable = nonViewTables.find(table => table.id === relationship.targetTableId);
|
||||
// Handle relationships (foreign keys)
|
||||
relationships?.forEach((relationship) => {
|
||||
const sourceTable = nonViewTables.find(
|
||||
(table) => table.id === relationship.sourceTableId
|
||||
);
|
||||
const targetTable = nonViewTables.find(
|
||||
(table) => table.id === relationship.targetTableId
|
||||
);
|
||||
|
||||
if (sourceTable && targetTable) {
|
||||
sqlScript += `ALTER TABLE ${sourceTable.name} ADD CONSTRAINT ${relationship.name} FOREIGN KEY (${relationship.sourceFieldId}) REFERENCES ${targetTable.name} (${relationship.targetFieldId});\n`;
|
||||
const sourceTableField = sourceTable?.fields.find(
|
||||
(field) => field.id === relationship.sourceFieldId
|
||||
);
|
||||
const targetTableField = targetTable?.fields.find(
|
||||
(field) => field.id === relationship.targetFieldId
|
||||
);
|
||||
|
||||
if (
|
||||
sourceTable &&
|
||||
targetTable &&
|
||||
sourceTableField &&
|
||||
targetTableField
|
||||
) {
|
||||
sqlScript += `ALTER TABLE ${sourceTable.name} ADD CONSTRAINT ${relationship.name} FOREIGN KEY (${sourceTableField.name}) REFERENCES ${targetTable.name} (${targetTableField.name});\n`;
|
||||
}
|
||||
});
|
||||
|
||||
return sqlScript;
|
||||
};
|
||||
|
||||
export const exportSQL = async (
|
||||
diagram: Diagram,
|
||||
databaseType: DatabaseType
|
||||
): Promise<string> => {
|
||||
const sqlScript = exportBaseSQL(diagram);
|
||||
const prompt = generateSQLPrompt(databaseType, sqlScript);
|
||||
|
||||
const { text } = await generateText({
|
||||
model: openai('gpt-4o-mini-2024-07-18'),
|
||||
prompt: prompt,
|
||||
});
|
||||
|
||||
return text;
|
||||
};
|
||||
|
||||
function getMySQLDataTypeSize(type: string) {
|
||||
return (
|
||||
{
|
||||
tinyint: 1,
|
||||
smallint: 2,
|
||||
mediumint: 3,
|
||||
integer: 4,
|
||||
bigint: 8,
|
||||
float: 4,
|
||||
double: 8,
|
||||
decimal: 16,
|
||||
numeric: 16,
|
||||
// Add other relevant data types if needed
|
||||
}[type.toLowerCase()] || 0
|
||||
);
|
||||
}
|
||||
|
||||
function alignForeignKeyDataTypes(diagram: Diagram) {
|
||||
const { tables, relationships } = diagram;
|
||||
|
||||
if (
|
||||
!tables ||
|
||||
tables.length === 0 ||
|
||||
!relationships ||
|
||||
relationships.length === 0
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Convert tables to a map for quick lookup
|
||||
const tableMap = new Map();
|
||||
tables.forEach((table) => {
|
||||
tableMap.set(table.id, table);
|
||||
});
|
||||
|
||||
// Iterate through each relationship to update the child table column data types
|
||||
relationships.forEach((relationship) => {
|
||||
const { sourceTableId, sourceFieldId, targetTableId, targetFieldId } =
|
||||
relationship;
|
||||
|
||||
const sourceTable = tableMap.get(sourceTableId);
|
||||
const targetTable = tableMap.get(targetTableId);
|
||||
|
||||
if (sourceTable && targetTable) {
|
||||
const sourceField = sourceTable.fields.find(
|
||||
(field: { id: string }) => field.id === sourceFieldId
|
||||
);
|
||||
const targetField = targetTable.fields.find(
|
||||
(field: { id: string }) => field.id === targetFieldId
|
||||
);
|
||||
|
||||
if (sourceField && targetField) {
|
||||
const sourceSize = getMySQLDataTypeSize(sourceField.type);
|
||||
const targetSize = getMySQLDataTypeSize(targetField.type);
|
||||
|
||||
if (sourceSize > targetSize) {
|
||||
// Print a message indicating that the data type is being adjusted
|
||||
console.log(
|
||||
`Adjusting data type of '${targetTable.name}.${targetField.name}' from '${targetField.type}' to '${sourceField.type}' to match '${sourceTable.name}.${sourceField.name}'`
|
||||
);
|
||||
|
||||
// Adjust the child field data type to the larger data type
|
||||
targetField.type = sourceField.type;
|
||||
} else if (targetSize > sourceSize) {
|
||||
// Print a message indicating that the data type is being adjusted
|
||||
console.log(
|
||||
`Adjusting data type of '${targetTable.name}.${sourceField.name}' from '${sourceField.type}' to '${targetField.type}' to match '${targetTable.name}.${targetField.name}' ('${targetField.type}')`
|
||||
);
|
||||
|
||||
// Adjust the child field data type to the larger data type
|
||||
sourceField.type = targetField.type;
|
||||
} else {
|
||||
console.log(
|
||||
`same data type '${targetField.type}' = '${sourceField.type}'.`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const generateSQLPrompt = (databaseType: string, sqlScript: string) => {
|
||||
const basePrompt = `
|
||||
You are generating SQL scripts for creating database tables and sequences, handling primary keys, indices, and other table attributes.
|
||||
The following instructions will guide you in optimizing the scripts for the ${databaseType} dialect:
|
||||
- **Column Names**: Do **not** modify the names of columns. Ensure that all column names in the generated SQL script are exactly as provided in the input schema. If the input specifies a column name, it must appear in the output script unchanged.
|
||||
- **Column Name Conflicts**: When a column name conflicts with a data type or reserved keyword (e.g., fulltext), escape the column name by enclosing it.
|
||||
`;
|
||||
|
||||
const dialectInstructions =
|
||||
{
|
||||
POSTGRESQL: `
|
||||
- **Sequence Creation**: Use \`CREATE SEQUENCE IF NOT EXISTS\` for sequence creation.
|
||||
- **Table and Index Creation**: Use \`CREATE TABLE IF NOT EXISTS\` and \`CREATE INDEX IF NOT EXISTS\` to avoid errors if the object already exists.
|
||||
- **Serial and Identity Columns**: For auto-increment columns, use \`SERIAL\` or \`GENERATED BY DEFAULT AS IDENTITY\`.
|
||||
- **Conditional Statements**: Utilize PostgreSQL’s support for \`IF NOT EXISTS\` in relevant \`CREATE\` statements.
|
||||
`,
|
||||
MYSQL: `
|
||||
- **Table Creation**: Use \`CREATE TABLE IF NOT EXISTS\` for creating tables. While creating the table structure, ensure that all foreign key columns use the correct data types as determined in the foreign key review.
|
||||
- **Auto-Increment**: Use \`AUTO_INCREMENT\` for auto-incrementing primary key columns.
|
||||
- **Index Creation**: Place all \`CREATE INDEX\` statements separately after the \`CREATE TABLE\` statement. Avoid using \`IF NOT EXISTS\` in \`CREATE INDEX\` statements.
|
||||
- **Indexing TEXT/BLOB Columns**: Do **not** create regular indexes on \`TEXT\` or \`BLOB\` columns. If indexing these types is required, use \`FULLTEXT\` indexes specifically for \`TEXT\` columns where appropriate, or consider alternative strategies.
|
||||
- **Date Column Defaults**: Avoid using \`CURRENT_DATE\` as a default for \`DATE\` columns. Instead, consider using \`DEFAULT NULL\` or handle default values programmatically.
|
||||
- **Timestamp Default Value**: Use \`DEFAULT CURRENT_TIMESTAMP\` for \`TIMESTAMP\` columns. Only one \`TIMESTAMP\` column can have \`CURRENT_TIMESTAMP\` as the default without specifying \`ON UPDATE\`.
|
||||
- **Boolean Columns**: Use \`TINYINT(1)\` instead of \`BOOLEAN\` for better compatibility with MySQL/MariaDB versions that might not fully support the \`BOOLEAN\` data type.
|
||||
- **TEXT and BLOB Constraints**: Do not use \`NOT NULL\` with \`TEXT\` or \`BLOB\` columns, as these types do not support the \`NOT NULL\` constraint in MariaDB.
|
||||
- **ENUM Data Type**: Ensure that default values are compatible and that the \`ENUM\` declaration adheres to MariaDB's syntax requirements.
|
||||
- **Default Values**: Ensure that default values for columns, especially \`DECIMAL\` and \`ENUM\`, are correctly formatted and comply with MariaDB's SQL syntax.
|
||||
- **Sequences**: Recognize that MySQL does not natively support sequences. Use \`AUTO_INCREMENT\` instead.
|
||||
|
||||
**Reminder**: Ensure all column names that conflict with reserved keywords or data types (like \`fulltext\`) are escaped using backticks (\`).
|
||||
`,
|
||||
SQL_SERVER: `
|
||||
- **Sequence Creation**: Use \`CREATE SEQUENCE\` without \`IF NOT EXISTS\`, and employ conditional logic (\`IF NOT EXISTS\`) to check for sequence existence before creation.
|
||||
- **Identity Columns**: Always prefer using the \`IDENTITY\` keyword (e.g., \`INT IDENTITY(1,1)\`) for auto-incrementing primary key columns when possible.
|
||||
- **Conditional Logic**: Use a conditional block like \`IF NOT EXISTS (SELECT * FROM sys.objects WHERE ...)\` since SQL Server doesn’t support \`IF NOT EXISTS\` directly in \`CREATE\` statements.
|
||||
- **Avoid Unsupported Syntax**: Ensure the script does not include unsupported statements like \`CREATE TABLE IF NOT EXISTS\`.
|
||||
`,
|
||||
MARIADB: `
|
||||
- **Table Creation**: Use \`CREATE TABLE IF NOT EXISTS\` for creating tables. While creating the table structure, ensure that all foreign key columns use the correct data types as determined in the foreign key review.
|
||||
- **Auto-Increment**: Use \`AUTO_INCREMENT\` for auto-incrementing primary key columns.
|
||||
- **Index Creation**: Place all \`CREATE INDEX\` statements separately after the \`CREATE TABLE\` statement. Avoid using \`IF NOT EXISTS\` in \`CREATE INDEX\` statements.
|
||||
- **Indexing TEXT/BLOB Columns**: Do **not** create regular indexes on \`TEXT\` or \`BLOB\` columns. If indexing these types is required, use \`FULLTEXT\` indexes specifically for \`TEXT\` columns where appropriate, or consider alternative strategies.
|
||||
- **Date Column Defaults**: Avoid using \`CURRENT_DATE\` as a default for \`DATE\` columns. Instead, consider using \`DEFAULT NULL\` or handle default values programmatically.
|
||||
- **Timestamp Default Value**: Use \`DEFAULT CURRENT_TIMESTAMP\` for \`TIMESTAMP\` columns. Only one \`TIMESTAMP\` column can have \`CURRENT_TIMESTAMP\` as the default without specifying \`ON UPDATE\`.
|
||||
- **Boolean Columns**: Use \`TINYINT(1)\` instead of \`BOOLEAN\` for better compatibility with MySQL/MariaDB versions that might not fully support the \`BOOLEAN\` data type.
|
||||
- **TEXT and BLOB Constraints**: Do not use \`NOT NULL\` with \`TEXT\` or \`BLOB\` columns, as these types do not support the \`NOT NULL\` constraint in MariaDB.
|
||||
- **ENUM Data Type**: Ensure that default values are compatible and that the \`ENUM\` declaration adheres to MariaDB's syntax requirements.
|
||||
- **Default Values**: Ensure that default values for columns, especially \`DECIMAL\` and \`ENUM\`, are correctly formatted and comply with MariaDB's SQL syntax.
|
||||
- **Sequences**: Recognize that MySQL does not natively support sequences. Use \`AUTO_INCREMENT\` instead.
|
||||
|
||||
**Reminder**: Ensure all column names that conflict with reserved keywords or data types (like \`fulltext\`) are escaped using backticks (\`).
|
||||
`,
|
||||
SQLITE: `
|
||||
- **Table Creation**: Use \`CREATE TABLE IF NOT EXISTS\`.
|
||||
- **Auto-Increment**: Use \`AUTOINCREMENT\` with \`INTEGER PRIMARY KEY\` for auto-increment functionality.
|
||||
- **No Sequence Support**: SQLite does not support sequences; rely solely on \`AUTOINCREMENT\` for similar functionality.
|
||||
- **Foreign Key Constraints**: Do not use \`ALTER TABLE\` to add foreign key constraints. SQLite does not support adding foreign keys to an existing table after it has been created. Always define foreign key constraints during the \`CREATE TABLE\` statement. Avoid using named constraints in foreign key definitions.
|
||||
- **Adding Foreign Keys to Existing Tables**: If adding a foreign key to an existing table is required, suggest creating a new table with the foreign key constraint, migrating the data, and renaming the new table to the original name.
|
||||
- **General SQLite Constraints**: Remember, \`ALTER TABLE\` in SQLite is limited and cannot add constraints after the table is created.
|
||||
- **Conditional Logic**: Ensure the script uses SQLite-compatible syntax and does not include unsupported features.
|
||||
`,
|
||||
}[databaseType] || '';
|
||||
|
||||
const additionalInstructions = `
|
||||
Just answer with the script with no additional details. give the commands flat without markdown.
|
||||
|
||||
No images are allowed. Do not try to generate or link images, including base64 data URLs.
|
||||
|
||||
Feel free to suggest corrections for suspected typos.
|
||||
`;
|
||||
|
||||
return `${basePrompt}\n${dialectInstructions}\n
|
||||
- **Validation**: After generating the script, validate it against the respective SQL dialect by attempting to execute it in a corresponding database environment.
|
||||
- **Syntax Checking**: Use SQL linting tools specific to each dialect to ensure the script is free from syntax errors.
|
||||
- **Manual Review**: Include a step where a knowledgeable developer reviews the generated script to ensure it meets the required specifications and adheres to best practices.
|
||||
|
||||
Here is the SQL script that needs to be optimized or generated according to the instructions above:
|
||||
|
||||
${sqlScript}
|
||||
|
||||
${additionalInstructions}
|
||||
`;
|
||||
};
|
||||
|
||||
@@ -5,5 +5,11 @@ export interface ColumnInfo {
|
||||
type: string;
|
||||
ordinal_position: number;
|
||||
nullable: boolean;
|
||||
collation: string;
|
||||
character_maximum_length?: string | null; // The maximum length of the column (if applicable), nullable
|
||||
precision?: {
|
||||
precision: number | null; // The precision for numeric types
|
||||
scale: number | null; // The scale for numeric types
|
||||
} | null; // Nullable, not all types have precision
|
||||
default?: string | null; // Default value for the column, nullable
|
||||
collation?: string | null;
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ export interface DatabaseMetadata {
|
||||
indexes: IndexInfo[];
|
||||
tables: TableInfo[];
|
||||
views: ViewInfo[];
|
||||
server_name: string;
|
||||
database_name: string;
|
||||
version: string;
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,11 @@ export interface DBField {
|
||||
unique: boolean;
|
||||
nullable: boolean;
|
||||
createdAt: number;
|
||||
characterMaximumLength?: string;
|
||||
precision?: number;
|
||||
scale?: number;
|
||||
default?: string;
|
||||
collation?: string;
|
||||
}
|
||||
|
||||
export type FieldType = (typeof dataTypes)[number];
|
||||
|
||||
@@ -3,10 +3,11 @@ import { DBField, FieldType } from './db-field';
|
||||
import { TableInfo } from '../data/import-metadata/metadata-types/table-info';
|
||||
import { ColumnInfo } from '../data/import-metadata/metadata-types/column-info';
|
||||
import { IndexInfo } from '../data/import-metadata/metadata-types/index-info';
|
||||
import { generateId, greyColor, randomHSLA } from '@/lib/utils';
|
||||
import { greyColor, randomColor } from '@/lib/colors';
|
||||
import { DBRelationship } from './db-relationship';
|
||||
import { PrimaryKeyInfo } from '../data/import-metadata/metadata-types/primary-key-info';
|
||||
import { ViewInfo } from '../data/import-metadata/metadata-types/view-info';
|
||||
import { generateId } from '../utils';
|
||||
|
||||
export interface DBTable {
|
||||
id: string;
|
||||
@@ -38,7 +39,11 @@ export const createTablesFromMetadata = ({
|
||||
// Filter, make unique, and sort columns based on ordinal_position
|
||||
const uniqueColumns = new Map<string, ColumnInfo>();
|
||||
columns
|
||||
.filter((col) => col.table === tableInfo.table)
|
||||
.filter(
|
||||
(col) =>
|
||||
col.schema === tableInfo.schema &&
|
||||
col.table === tableInfo.table
|
||||
)
|
||||
.forEach((col) => {
|
||||
if (!uniqueColumns.has(col.name)) {
|
||||
uniqueColumns.set(col.name, col);
|
||||
@@ -46,9 +51,7 @@ export const createTablesFromMetadata = ({
|
||||
});
|
||||
|
||||
const sortedColumns = Array.from(uniqueColumns.values()).sort(
|
||||
(a, b) => {
|
||||
return a.ordinal_position - b.ordinal_position;
|
||||
}
|
||||
(a, b) => a.ordinal_position - b.ordinal_position
|
||||
);
|
||||
|
||||
const tablePrimaryKeys = primaryKeys.filter(
|
||||
@@ -71,6 +74,16 @@ export const createTablesFromMetadata = ({
|
||||
(idx) => idx.column === col.name && idx.unique
|
||||
),
|
||||
nullable: col.nullable,
|
||||
...(col.character_maximum_length &&
|
||||
col.character_maximum_length !== 'null'
|
||||
? { character_maximum_length: col.character_maximum_length }
|
||||
: {}),
|
||||
...(col.precision?.precision
|
||||
? { precision: col.precision.precision }
|
||||
: {}),
|
||||
...(col.precision?.scale ? { scale: col.precision.scale } : {}),
|
||||
...(col.default ? { default: col.default } : {}),
|
||||
...(col.collation ? { collation: col.collation } : {}),
|
||||
createdAt: Date.now(),
|
||||
})
|
||||
);
|
||||
@@ -102,7 +115,7 @@ export const createTablesFromMetadata = ({
|
||||
y: Math.random() * 800, // Placeholder Y
|
||||
fields,
|
||||
indexes: dbIndexes,
|
||||
color: isView ? greyColor : randomHSLA(),
|
||||
color: isView ? greyColor : randomColor(),
|
||||
isView: isView,
|
||||
createdAt: Date.now(),
|
||||
};
|
||||
|
||||
@@ -67,7 +67,9 @@ export const loadFromDatabaseMetadata = ({
|
||||
|
||||
return {
|
||||
id: generateId(),
|
||||
name: databaseMetadata.server_name || `Diagram ${diagramNumber}`,
|
||||
name:
|
||||
`${databaseMetadata.database_name}-db` ||
|
||||
`Diagram ${diagramNumber}`,
|
||||
databaseType: databaseType ?? DatabaseType.GENERIC,
|
||||
tables: sortedTables,
|
||||
relationships,
|
||||
|
||||
1
src/lib/env.ts
Normal file
1
src/lib/env.ts
Normal file
@@ -0,0 +1 @@
|
||||
export const OPENAI_API_KEY = import.meta.env.VITE_OPENAI_API_KEY;
|
||||
@@ -2,7 +2,6 @@ import { type ClassValue, clsx } from 'clsx';
|
||||
import { customAlphabet } from 'nanoid';
|
||||
|
||||
const randomId = customAlphabet('0123456789abcdefghijklmnopqrstuvwxyz', 25);
|
||||
const randonNumber = customAlphabet('1234567890', 18);
|
||||
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
|
||||
@@ -10,16 +9,6 @@ export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs));
|
||||
}
|
||||
|
||||
export const convertToDecimal = (number: number) => {
|
||||
const digits = number.toString().length; // Get the number of digits
|
||||
return number / Math.pow(10, digits); // Divide the number by 10^digits
|
||||
};
|
||||
|
||||
export const randomHSLA = () =>
|
||||
`hsla(${~~(360 * convertToDecimal(parseInt(randonNumber())))}, 70%, 72%, 0.8)`;
|
||||
|
||||
export const greyColor = 'hsla(0, 0%, 65%, 1)';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export const emptyFn = (): any => undefined;
|
||||
|
||||
|
||||
@@ -31,12 +31,17 @@ import {
|
||||
databaseSecondaryLogoMap,
|
||||
databaseTypeToLabelMap,
|
||||
} from '@/lib/databases';
|
||||
import { DatabaseType } from '@/lib/domain/database-type';
|
||||
|
||||
export interface TopNavbarProps {}
|
||||
|
||||
export const TopNavbar: React.FC<TopNavbarProps> = () => {
|
||||
const { diagramName, updateDiagramName, currentDiagram } = useChartDB();
|
||||
const { openCreateDiagramDialog, openOpenDiagramDialog } = useDialog();
|
||||
const {
|
||||
openCreateDiagramDialog,
|
||||
openOpenDiagramDialog,
|
||||
openExportSQLDialog,
|
||||
} = useDialog();
|
||||
const [editMode, setEditMode] = useState(false);
|
||||
const { exportImage } = useExportImage();
|
||||
const [editedDiagramName, setEditedDiagramName] =
|
||||
@@ -74,11 +79,6 @@ export const TopNavbar: React.FC<TopNavbarProps> = () => {
|
||||
setEditMode(true);
|
||||
};
|
||||
|
||||
const exportSql = useCallback(() => {
|
||||
console.log('Export SQL');
|
||||
console.log({ currentDiagram });
|
||||
}, [currentDiagram]);
|
||||
|
||||
const exportPNG = useCallback(() => {
|
||||
exportImage('png');
|
||||
}, [exportImage]);
|
||||
@@ -95,6 +95,13 @@ export const TopNavbar: React.FC<TopNavbarProps> = () => {
|
||||
window.open('https://chartdb.io', '_blank');
|
||||
}, []);
|
||||
|
||||
const openJoinSlack = useCallback(() => {
|
||||
window.open(
|
||||
'https://join.slack.com/t/chartdb/shared_invite/zt-2ourrlh5e-mKIHCRML3_~m_gHjD5EcUg',
|
||||
'_blank'
|
||||
);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<nav className="flex flex-row items-center justify-between px-4 h-12 border-b">
|
||||
<div className="flex flex-1 justify-start gap-x-3">
|
||||
@@ -125,9 +132,81 @@ export const TopNavbar: React.FC<TopNavbarProps> = () => {
|
||||
Open
|
||||
</MenubarItem>
|
||||
<MenubarSeparator />
|
||||
<MenubarItem onClick={exportSql}>
|
||||
Export
|
||||
</MenubarItem>
|
||||
<MenubarSub>
|
||||
<MenubarSubTrigger>
|
||||
Export SQL
|
||||
</MenubarSubTrigger>
|
||||
<MenubarSubContent>
|
||||
<MenubarItem
|
||||
onClick={() =>
|
||||
openExportSQLDialog({
|
||||
targetDatabaseType:
|
||||
DatabaseType.GENERIC,
|
||||
})
|
||||
}
|
||||
>
|
||||
{databaseTypeToLabelMap['generic']}
|
||||
</MenubarItem>
|
||||
<MenubarItem
|
||||
onClick={() =>
|
||||
openExportSQLDialog({
|
||||
targetDatabaseType:
|
||||
DatabaseType.POSTGRESQL,
|
||||
})
|
||||
}
|
||||
>
|
||||
{
|
||||
databaseTypeToLabelMap[
|
||||
'postgresql'
|
||||
]
|
||||
}
|
||||
</MenubarItem>
|
||||
<MenubarItem
|
||||
onClick={() =>
|
||||
openExportSQLDialog({
|
||||
targetDatabaseType:
|
||||
DatabaseType.MYSQL,
|
||||
})
|
||||
}
|
||||
>
|
||||
{databaseTypeToLabelMap['mysql']}
|
||||
</MenubarItem>
|
||||
<MenubarItem
|
||||
onClick={() =>
|
||||
openExportSQLDialog({
|
||||
targetDatabaseType:
|
||||
DatabaseType.SQL_SERVER,
|
||||
})
|
||||
}
|
||||
>
|
||||
{
|
||||
databaseTypeToLabelMap[
|
||||
'sql_server'
|
||||
]
|
||||
}
|
||||
</MenubarItem>
|
||||
<MenubarItem
|
||||
onClick={() =>
|
||||
openExportSQLDialog({
|
||||
targetDatabaseType:
|
||||
DatabaseType.MARIADB,
|
||||
})
|
||||
}
|
||||
>
|
||||
{databaseTypeToLabelMap['mariadb']}
|
||||
</MenubarItem>
|
||||
<MenubarItem
|
||||
onClick={() =>
|
||||
openExportSQLDialog({
|
||||
targetDatabaseType:
|
||||
DatabaseType.SQLITE,
|
||||
})
|
||||
}
|
||||
>
|
||||
{databaseTypeToLabelMap['sqlite']}
|
||||
</MenubarItem>
|
||||
</MenubarSubContent>
|
||||
</MenubarSub>
|
||||
<MenubarSub>
|
||||
<MenubarSubTrigger>
|
||||
Export as
|
||||
@@ -167,6 +246,9 @@ export const TopNavbar: React.FC<TopNavbarProps> = () => {
|
||||
<MenubarItem onClick={openChartDBIO}>
|
||||
Visit ChartDB
|
||||
</MenubarItem>
|
||||
<MenubarItem onClick={openJoinSlack}>
|
||||
Join us on Slack
|
||||
</MenubarItem>
|
||||
</MenubarContent>
|
||||
</MenubarMenu>
|
||||
</Menubar>
|
||||
|
||||
Reference in New Issue
Block a user