diff --git a/README.md b/README.md index e5860dc2..432d7e96 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,7 @@

Open-source database diagrams editor
+ No installations • No Database password required.

diff --git a/package-lock.json b/package-lock.json index 88bf0616..00af81a5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", diff --git a/package.json b/package.json index 5c5ca1ab..5db83dc3 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/context/chartdb-context/chartdb-provider.tsx b/src/context/chartdb-context/chartdb-provider.tsx index 8c5045b1..2c3a0559 100644 --- a/src/context/chartdb-context/chartdb-provider.tsx +++ b/src/context/chartdb-context/chartdb-provider.tsx @@ -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 = ({ }, ], indexes: [], - color: randomHSLA(), + color: randomColor(), createdAt: Date.now(), isView: false, }; @@ -718,11 +719,18 @@ export const ChartDBProvider: React.FC = ({ 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, diff --git a/src/context/dialog-context/dialog-context.tsx b/src/context/dialog-context/dialog-context.tsx index 7c7ce646..c6c2fe3a 100644 --- a/src/context/dialog-context/dialog-context.tsx +++ b/src/context/dialog-context/dialog-context.tsx @@ -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({ @@ -16,4 +21,6 @@ export const dialogContext = createContext({ closeCreateDiagramDialog: emptyFn, openOpenDiagramDialog: emptyFn, closeOpenDiagramDialog: emptyFn, + openExportSQLDialog: emptyFn, + closeExportSQLDialog: emptyFn, }); diff --git a/src/context/dialog-context/dialog-provider.tsx b/src/context/dialog-context/dialog-provider.tsx index deb597ad..f059b260 100644 --- a/src/context/dialog-context/dialog-provider.tsx +++ b/src/context/dialog-context/dialog-provider.tsx @@ -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 = ({ 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 ( = ({ closeCreateDiagramDialog: () => setOpenNewDiagramDialog(false), openOpenDiagramDialog: () => setOpenOpenDiagramDialog(true), closeOpenDiagramDialog: () => setOpenOpenDiagramDialog(false), + openExportSQLDialog: openExportSQLDialogHandler, + closeExportSQLDialog: () => setOpenExportSQLDialog(false), }} > {children} + ); }; diff --git a/src/context/storage-context/storage-context.tsx b/src/context/storage-context/storage-context.tsx index 807fef57..96e8236d 100644 --- a/src/context/storage-context/storage-context.tsx +++ b/src/context/storage-context/storage-context.tsx @@ -12,7 +12,10 @@ export interface StorageContext { // Diagram operations addDiagram: (params: { diagram: Diagram }) => Promise; - listDiagrams: () => Promise; + listDiagrams: (options?: { + includeTables?: boolean; + includeRelationships?: boolean; + }) => Promise; getDiagram: ( id: string, options?: { diff --git a/src/context/storage-context/storage-provider.tsx b/src/context/storage-context/storage-provider.tsx index e323f9d5..7e3efea4 100644 --- a/src/context/storage-context/storage-provider.tsx +++ b/src/context/storage-context/storage-provider.tsx @@ -92,10 +92,33 @@ export const StorageProvider: React.FC = ({ 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 => { + 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 = ({ const listRelationships: StorageContext['listRelationships'] = async ( diagramId: string ): Promise => { - 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 ( diff --git a/src/dialogs/export-sql-dialog/export-sql-dialog.tsx b/src/dialogs/export-sql-dialog/export-sql-dialog.tsx new file mode 100644 index 00000000..ac0f3c35 --- /dev/null +++ b/src/dialogs/export-sql-dialog/export-sql-dialog.tsx @@ -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 = ({ + dialog, + targetDatabaseType, +}) => { + const { closeExportSQLDialog } = useDialog(); + const { currentDiagram } = useChartDB(); + const [script, setScript] = React.useState(); + + 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( + () => ( +
+ +
+ + +
+
+ ), + [targetDatabaseType] + ); + return ( + { + if (!open) { + closeExportSQLDialog(); + } + }} + > + + + Export SQL + + Export the SQL of the current diagram + + +
+ {(script?.length ?? 0) === 0 ? ( + renderLoader() + ) : ( + + )} +
+ + +
+ + + + + +
+ ); +}; diff --git a/src/dialogs/open-diagram-dialog/open-diagram-dialog.tsx b/src/dialogs/open-diagram-dialog/open-diagram-dialog.tsx index 3adbb759..889e2b38 100644 --- a/src/dialogs/open-diagram-dialog/open-diagram-dialog.tsx +++ b/src/dialogs/open-diagram-dialog/open-diagram-dialog.tsx @@ -46,7 +46,7 @@ export const OpenDiagramDialog: React.FC = ({ 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 = ({ Name Created at Last modified + Tables count Type @@ -120,6 +121,9 @@ export const OpenDiagramDialog: React.FC = ({ {diagram.updatedAt.toLocaleString()} + + {diagram.tables?.length} + { databaseTypeToLabelMap[ diff --git a/src/lib/colors.ts b/src/lib/colors.ts new file mode 100644 index 00000000..7d42e4d3 --- /dev/null +++ b/src/lib/colors.ts @@ -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. diff --git a/src/lib/data/data-types.ts b/src/lib/data/data-types.ts index 04fc3d4b..7da70fb8 100644 --- a/src/lib/data/data-types.ts +++ b/src/lib/data/data-types.ts @@ -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 = [ diff --git a/src/lib/data/export-metadata/export-sql-script.ts b/src/lib/data/export-metadata/export-sql-script.ts index f1f21ff5..999ce3dd 100644 --- a/src/lib/data/export-metadata/export-sql-script.ts +++ b/src/lib/data/export-metadata/export-sql-script.ts @@ -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 => { + 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} + `; +}; diff --git a/src/lib/data/import-metadata/metadata-types/column-info.ts b/src/lib/data/import-metadata/metadata-types/column-info.ts index 55f1ff14..d8962605 100644 --- a/src/lib/data/import-metadata/metadata-types/column-info.ts +++ b/src/lib/data/import-metadata/metadata-types/column-info.ts @@ -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; } diff --git a/src/lib/data/import-metadata/metadata-types/database-metadata.ts b/src/lib/data/import-metadata/metadata-types/database-metadata.ts index 0f670b5f..8a4307ff 100644 --- a/src/lib/data/import-metadata/metadata-types/database-metadata.ts +++ b/src/lib/data/import-metadata/metadata-types/database-metadata.ts @@ -11,7 +11,7 @@ export interface DatabaseMetadata { indexes: IndexInfo[]; tables: TableInfo[]; views: ViewInfo[]; - server_name: string; + database_name: string; version: string; } diff --git a/src/lib/domain/db-field.ts b/src/lib/domain/db-field.ts index 996d3a97..d575b5e9 100644 --- a/src/lib/domain/db-field.ts +++ b/src/lib/domain/db-field.ts @@ -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]; diff --git a/src/lib/domain/db-table.ts b/src/lib/domain/db-table.ts index 39fe98c4..aef17171 100644 --- a/src/lib/domain/db-table.ts +++ b/src/lib/domain/db-table.ts @@ -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(); 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(), }; diff --git a/src/lib/domain/diagram.ts b/src/lib/domain/diagram.ts index 8b664d61..75d0088f 100644 --- a/src/lib/domain/diagram.ts +++ b/src/lib/domain/diagram.ts @@ -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, diff --git a/src/lib/env.ts b/src/lib/env.ts new file mode 100644 index 00000000..c5267248 --- /dev/null +++ b/src/lib/env.ts @@ -0,0 +1 @@ +export const OPENAI_API_KEY = import.meta.env.VITE_OPENAI_API_KEY; diff --git a/src/lib/utils.ts b/src/lib/utils.ts index 598960d2..5c9a660e 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -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; diff --git a/src/pages/editor-page/top-navbar/top-navbar.tsx b/src/pages/editor-page/top-navbar/top-navbar.tsx index b5b2bfe7..8858d42f 100644 --- a/src/pages/editor-page/top-navbar/top-navbar.tsx +++ b/src/pages/editor-page/top-navbar/top-navbar.tsx @@ -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 = () => { 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 = () => { 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 = () => { 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 (