mirror of
https://github.com/sqlchat/sqlchat.git
synced 2025-07-24 15:27:00 +08:00
chore: use prettier for project and apply to all files
This commit is contained in:
36
.vscode/settings.json
vendored
36
.vscode/settings.json
vendored
@ -1,5 +1,33 @@
|
||||
{
|
||||
"i18n-ally.localesPaths": [
|
||||
"src/locales"
|
||||
]
|
||||
}
|
||||
"i18n-ally.localesPaths": ["src/locales"],
|
||||
"[javascript]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||
"editor.formatOnSave": true,
|
||||
"editor.formatOnSaveMode": "file"
|
||||
},
|
||||
"[typescript]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||
"editor.formatOnSave": true,
|
||||
"editor.formatOnSaveMode": "file"
|
||||
},
|
||||
"[json]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||
"editor.formatOnSave": true,
|
||||
"editor.formatOnSaveMode": "file"
|
||||
},
|
||||
"[jsonc]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||
"editor.formatOnSave": true,
|
||||
"editor.formatOnSaveMode": "file"
|
||||
},
|
||||
"[typescriptreact]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||
"editor.formatOnSave": true,
|
||||
"editor.formatOnSaveMode": "file"
|
||||
},
|
||||
"[yaml]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||
"editor.formatOnSave": true,
|
||||
"editor.formatOnSaveMode": "file"
|
||||
}
|
||||
}
|
||||
|
@ -64,6 +64,7 @@
|
||||
"mysql2": "^3.2.0",
|
||||
"pg": "^8.10.0",
|
||||
"postcss": "^8.4.20",
|
||||
"prettier": "^2.8.8",
|
||||
"prisma": "^4.13.0",
|
||||
"react-syntax-highlighter": "^15.5.0",
|
||||
"tailwindcss": "^3.2.4",
|
||||
|
345
pnpm-lock.yaml
generated
345
pnpm-lock.yaml
generated
@ -159,6 +159,9 @@ devDependencies:
|
||||
postcss:
|
||||
specifier: ^8.4.20
|
||||
version: 8.4.21
|
||||
prettier:
|
||||
specifier: ^2.8.8
|
||||
version: 2.8.8
|
||||
prisma:
|
||||
specifier: ^4.13.0
|
||||
version: 4.13.0
|
||||
@ -883,6 +886,123 @@ packages:
|
||||
glob: 7.1.7
|
||||
dev: true
|
||||
|
||||
/@next/swc-android-arm-eabi@13.2.4:
|
||||
resolution: {integrity: sha512-DWlalTSkLjDU11MY11jg17O1gGQzpRccM9Oes2yTqj2DpHndajrXHGxj9HGtJ+idq2k7ImUdJVWS2h2l/EDJOw==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm]
|
||||
os: [android]
|
||||
requiresBuild: true
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
/@next/swc-android-arm64@13.2.4:
|
||||
resolution: {integrity: sha512-sRavmUImUCf332Gy+PjIfLkMhiRX1Ez4SI+3vFDRs1N5eXp+uNzjFUK/oLMMOzk6KFSkbiK/3Wt8+dHQR/flNg==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [android]
|
||||
requiresBuild: true
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
/@next/swc-darwin-arm64@13.2.4:
|
||||
resolution: {integrity: sha512-S6vBl+OrInP47TM3LlYx65betocKUUlTZDDKzTiRDbsRESeyIkBtZ6Qi5uT2zQs4imqllJznVjFd1bXLx3Aa6A==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
requiresBuild: true
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
/@next/swc-darwin-x64@13.2.4:
|
||||
resolution: {integrity: sha512-a6LBuoYGcFOPGd4o8TPo7wmv5FnMr+Prz+vYHopEDuhDoMSHOnC+v+Ab4D7F0NMZkvQjEJQdJS3rqgFhlZmKlw==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
requiresBuild: true
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
/@next/swc-freebsd-x64@13.2.4:
|
||||
resolution: {integrity: sha512-kkbzKVZGPaXRBPisoAQkh3xh22r+TD+5HwoC5bOkALraJ0dsOQgSMAvzMXKsN3tMzJUPS0tjtRf1cTzrQ0I5vQ==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [freebsd]
|
||||
requiresBuild: true
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
/@next/swc-linux-arm-gnueabihf@13.2.4:
|
||||
resolution: {integrity: sha512-7qA1++UY0fjprqtjBZaOA6cas/7GekpjVsZn/0uHvquuITFCdKGFCsKNBx3S0Rpxmx6WYo0GcmhNRM9ru08BGg==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
requiresBuild: true
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
/@next/swc-linux-arm64-gnu@13.2.4:
|
||||
resolution: {integrity: sha512-xzYZdAeq883MwXgcwc72hqo/F/dwUxCukpDOkx/j1HTq/J0wJthMGjinN9wH5bPR98Mfeh1MZJ91WWPnZOedOg==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
requiresBuild: true
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
/@next/swc-linux-arm64-musl@13.2.4:
|
||||
resolution: {integrity: sha512-8rXr3WfmqSiYkb71qzuDP6I6R2T2tpkmf83elDN8z783N9nvTJf2E7eLx86wu2OJCi4T05nuxCsh4IOU3LQ5xw==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
requiresBuild: true
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
/@next/swc-linux-x64-gnu@13.2.4:
|
||||
resolution: {integrity: sha512-Ngxh51zGSlYJ4EfpKG4LI6WfquulNdtmHg1yuOYlaAr33KyPJp4HeN/tivBnAHcZkoNy0hh/SbwDyCnz5PFJQQ==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
requiresBuild: true
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
/@next/swc-linux-x64-musl@13.2.4:
|
||||
resolution: {integrity: sha512-gOvwIYoSxd+j14LOcvJr+ekd9fwYT1RyMAHOp7znA10+l40wkFiMONPLWiZuHxfRk+Dy7YdNdDh3ImumvL6VwA==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
requiresBuild: true
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
/@next/swc-win32-arm64-msvc@13.2.4:
|
||||
resolution: {integrity: sha512-q3NJzcfClgBm4HvdcnoEncmztxrA5GXqKeiZ/hADvC56pwNALt3ngDC6t6qr1YW9V/EPDxCYeaX4zYxHciW4Dw==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
requiresBuild: true
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
/@next/swc-win32-ia32-msvc@13.2.4:
|
||||
resolution: {integrity: sha512-/eZ5ncmHUYtD2fc6EUmAIZlAJnVT2YmxDsKs1Ourx0ttTtvtma/WKlMV5NoUsyOez0f9ExLyOpeCoz5aj+MPXw==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [ia32]
|
||||
os: [win32]
|
||||
requiresBuild: true
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
/@next/swc-win32-x64-msvc@13.2.4:
|
||||
resolution: {integrity: sha512-0MffFmyv7tBLlji01qc0IaPP/LVExzvj7/R5x1Jph1bTAIj4Vu81yFQWHHQAP6r4ff9Ukj1mBK6MDNVXm7Tcvw==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
requiresBuild: true
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
/@nodelib/fs.scandir@2.1.5:
|
||||
resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
|
||||
engines: {node: '>= 8'}
|
||||
@ -1966,7 +2086,7 @@ packages:
|
||||
normalize-path: 3.0.0
|
||||
readdirp: 3.6.0
|
||||
optionalDependencies:
|
||||
fsevents: registry.npmmirror.com/fsevents@2.3.2
|
||||
fsevents: 2.3.2
|
||||
dev: true
|
||||
|
||||
/client-only@0.0.1:
|
||||
@ -2793,6 +2913,14 @@ packages:
|
||||
resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==}
|
||||
dev: true
|
||||
|
||||
/fsevents@2.3.2:
|
||||
resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==}
|
||||
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
|
||||
os: [darwin]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/function-bind@1.1.1:
|
||||
resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==}
|
||||
|
||||
@ -4016,19 +4144,19 @@ packages:
|
||||
react-dom: 18.2.0(react@18.2.0)
|
||||
styled-jsx: 5.1.1(react@18.2.0)
|
||||
optionalDependencies:
|
||||
'@next/swc-android-arm-eabi': registry.npmmirror.com/@next/swc-android-arm-eabi@13.2.4
|
||||
'@next/swc-android-arm64': registry.npmmirror.com/@next/swc-android-arm64@13.2.4
|
||||
'@next/swc-darwin-arm64': registry.npmmirror.com/@next/swc-darwin-arm64@13.2.4
|
||||
'@next/swc-darwin-x64': registry.npmmirror.com/@next/swc-darwin-x64@13.2.4
|
||||
'@next/swc-freebsd-x64': registry.npmmirror.com/@next/swc-freebsd-x64@13.2.4
|
||||
'@next/swc-linux-arm-gnueabihf': registry.npmmirror.com/@next/swc-linux-arm-gnueabihf@13.2.4
|
||||
'@next/swc-linux-arm64-gnu': registry.npmmirror.com/@next/swc-linux-arm64-gnu@13.2.4
|
||||
'@next/swc-linux-arm64-musl': registry.npmmirror.com/@next/swc-linux-arm64-musl@13.2.4
|
||||
'@next/swc-linux-x64-gnu': registry.npmmirror.com/@next/swc-linux-x64-gnu@13.2.4
|
||||
'@next/swc-linux-x64-musl': registry.npmmirror.com/@next/swc-linux-x64-musl@13.2.4
|
||||
'@next/swc-win32-arm64-msvc': registry.npmmirror.com/@next/swc-win32-arm64-msvc@13.2.4
|
||||
'@next/swc-win32-ia32-msvc': registry.npmmirror.com/@next/swc-win32-ia32-msvc@13.2.4
|
||||
'@next/swc-win32-x64-msvc': registry.npmmirror.com/@next/swc-win32-x64-msvc@13.2.4
|
||||
'@next/swc-android-arm-eabi': 13.2.4
|
||||
'@next/swc-android-arm64': 13.2.4
|
||||
'@next/swc-darwin-arm64': 13.2.4
|
||||
'@next/swc-darwin-x64': 13.2.4
|
||||
'@next/swc-freebsd-x64': 13.2.4
|
||||
'@next/swc-linux-arm-gnueabihf': 13.2.4
|
||||
'@next/swc-linux-arm64-gnu': 13.2.4
|
||||
'@next/swc-linux-arm64-musl': 13.2.4
|
||||
'@next/swc-linux-x64-gnu': 13.2.4
|
||||
'@next/swc-linux-x64-musl': 13.2.4
|
||||
'@next/swc-win32-arm64-msvc': 13.2.4
|
||||
'@next/swc-win32-ia32-msvc': 13.2.4
|
||||
'@next/swc-win32-x64-msvc': 13.2.4
|
||||
transitivePeerDependencies:
|
||||
- '@babel/core'
|
||||
- babel-plugin-macros
|
||||
@ -4379,6 +4507,12 @@ packages:
|
||||
engines: {node: '>= 0.8.0'}
|
||||
dev: true
|
||||
|
||||
/prettier@2.8.8:
|
||||
resolution: {integrity: sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==}
|
||||
engines: {node: '>=10.13.0'}
|
||||
hasBin: true
|
||||
dev: true
|
||||
|
||||
/prisma@4.13.0:
|
||||
resolution: {integrity: sha512-L9mqjnSmvWIRCYJ9mQkwCtj4+JDYYTdhoyo8hlsHNDXaZLh/b4hR0IoKIBbTKxZuyHQzLopb/+0Rvb69uGV7uA==}
|
||||
engines: {node: '>=14.17'}
|
||||
@ -5574,7 +5708,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
registry.npmmirror.com/@babel/runtime@7.21.0:
|
||||
resolution: {integrity: sha512-xwII0//EObnq89Ji5AKYQaRYiW/nZ3llSv29d49IuxPhKbtJoLP+9QUUZ4nVragQVtaVGeZrpB+ZtG/Pdy/POw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@babel/runtime/-/runtime-7.21.0.tgz}
|
||||
resolution: {integrity: sha512-xwII0//EObnq89Ji5AKYQaRYiW/nZ3llSv29d49IuxPhKbtJoLP+9QUUZ4nVragQVtaVGeZrpB+ZtG/Pdy/POw==, registry: https://registry.npmjs.org/, tarball: https://registry.npmmirror.com/@babel/runtime/-/runtime-7.21.0.tgz}
|
||||
name: '@babel/runtime'
|
||||
version: 7.21.0
|
||||
engines: {node: '>=6.9.0'}
|
||||
@ -5582,155 +5716,8 @@ packages:
|
||||
regenerator-runtime: registry.npmmirror.com/regenerator-runtime@0.13.11
|
||||
dev: false
|
||||
|
||||
registry.npmmirror.com/@next/swc-android-arm-eabi@13.2.4:
|
||||
resolution: {integrity: sha512-DWlalTSkLjDU11MY11jg17O1gGQzpRccM9Oes2yTqj2DpHndajrXHGxj9HGtJ+idq2k7ImUdJVWS2h2l/EDJOw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@next/swc-android-arm-eabi/-/swc-android-arm-eabi-13.2.4.tgz}
|
||||
name: '@next/swc-android-arm-eabi'
|
||||
version: 13.2.4
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm]
|
||||
os: [android]
|
||||
requiresBuild: true
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
registry.npmmirror.com/@next/swc-android-arm64@13.2.4:
|
||||
resolution: {integrity: sha512-sRavmUImUCf332Gy+PjIfLkMhiRX1Ez4SI+3vFDRs1N5eXp+uNzjFUK/oLMMOzk6KFSkbiK/3Wt8+dHQR/flNg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@next/swc-android-arm64/-/swc-android-arm64-13.2.4.tgz}
|
||||
name: '@next/swc-android-arm64'
|
||||
version: 13.2.4
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [android]
|
||||
requiresBuild: true
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
registry.npmmirror.com/@next/swc-darwin-arm64@13.2.4:
|
||||
resolution: {integrity: sha512-S6vBl+OrInP47TM3LlYx65betocKUUlTZDDKzTiRDbsRESeyIkBtZ6Qi5uT2zQs4imqllJznVjFd1bXLx3Aa6A==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@next/swc-darwin-arm64/-/swc-darwin-arm64-13.2.4.tgz}
|
||||
name: '@next/swc-darwin-arm64'
|
||||
version: 13.2.4
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
requiresBuild: true
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
registry.npmmirror.com/@next/swc-darwin-x64@13.2.4:
|
||||
resolution: {integrity: sha512-a6LBuoYGcFOPGd4o8TPo7wmv5FnMr+Prz+vYHopEDuhDoMSHOnC+v+Ab4D7F0NMZkvQjEJQdJS3rqgFhlZmKlw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@next/swc-darwin-x64/-/swc-darwin-x64-13.2.4.tgz}
|
||||
name: '@next/swc-darwin-x64'
|
||||
version: 13.2.4
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
requiresBuild: true
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
registry.npmmirror.com/@next/swc-freebsd-x64@13.2.4:
|
||||
resolution: {integrity: sha512-kkbzKVZGPaXRBPisoAQkh3xh22r+TD+5HwoC5bOkALraJ0dsOQgSMAvzMXKsN3tMzJUPS0tjtRf1cTzrQ0I5vQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@next/swc-freebsd-x64/-/swc-freebsd-x64-13.2.4.tgz}
|
||||
name: '@next/swc-freebsd-x64'
|
||||
version: 13.2.4
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [freebsd]
|
||||
requiresBuild: true
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
registry.npmmirror.com/@next/swc-linux-arm-gnueabihf@13.2.4:
|
||||
resolution: {integrity: sha512-7qA1++UY0fjprqtjBZaOA6cas/7GekpjVsZn/0uHvquuITFCdKGFCsKNBx3S0Rpxmx6WYo0GcmhNRM9ru08BGg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@next/swc-linux-arm-gnueabihf/-/swc-linux-arm-gnueabihf-13.2.4.tgz}
|
||||
name: '@next/swc-linux-arm-gnueabihf'
|
||||
version: 13.2.4
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
requiresBuild: true
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
registry.npmmirror.com/@next/swc-linux-arm64-gnu@13.2.4:
|
||||
resolution: {integrity: sha512-xzYZdAeq883MwXgcwc72hqo/F/dwUxCukpDOkx/j1HTq/J0wJthMGjinN9wH5bPR98Mfeh1MZJ91WWPnZOedOg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-13.2.4.tgz}
|
||||
name: '@next/swc-linux-arm64-gnu'
|
||||
version: 13.2.4
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
requiresBuild: true
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
registry.npmmirror.com/@next/swc-linux-arm64-musl@13.2.4:
|
||||
resolution: {integrity: sha512-8rXr3WfmqSiYkb71qzuDP6I6R2T2tpkmf83elDN8z783N9nvTJf2E7eLx86wu2OJCi4T05nuxCsh4IOU3LQ5xw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-13.2.4.tgz}
|
||||
name: '@next/swc-linux-arm64-musl'
|
||||
version: 13.2.4
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
requiresBuild: true
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
registry.npmmirror.com/@next/swc-linux-x64-gnu@13.2.4:
|
||||
resolution: {integrity: sha512-Ngxh51zGSlYJ4EfpKG4LI6WfquulNdtmHg1yuOYlaAr33KyPJp4HeN/tivBnAHcZkoNy0hh/SbwDyCnz5PFJQQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-13.2.4.tgz}
|
||||
name: '@next/swc-linux-x64-gnu'
|
||||
version: 13.2.4
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
requiresBuild: true
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
registry.npmmirror.com/@next/swc-linux-x64-musl@13.2.4:
|
||||
resolution: {integrity: sha512-gOvwIYoSxd+j14LOcvJr+ekd9fwYT1RyMAHOp7znA10+l40wkFiMONPLWiZuHxfRk+Dy7YdNdDh3ImumvL6VwA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-13.2.4.tgz}
|
||||
name: '@next/swc-linux-x64-musl'
|
||||
version: 13.2.4
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
requiresBuild: true
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
registry.npmmirror.com/@next/swc-win32-arm64-msvc@13.2.4:
|
||||
resolution: {integrity: sha512-q3NJzcfClgBm4HvdcnoEncmztxrA5GXqKeiZ/hADvC56pwNALt3ngDC6t6qr1YW9V/EPDxCYeaX4zYxHciW4Dw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-13.2.4.tgz}
|
||||
name: '@next/swc-win32-arm64-msvc'
|
||||
version: 13.2.4
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
requiresBuild: true
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
registry.npmmirror.com/@next/swc-win32-ia32-msvc@13.2.4:
|
||||
resolution: {integrity: sha512-/eZ5ncmHUYtD2fc6EUmAIZlAJnVT2YmxDsKs1Ourx0ttTtvtma/WKlMV5NoUsyOez0f9ExLyOpeCoz5aj+MPXw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-13.2.4.tgz}
|
||||
name: '@next/swc-win32-ia32-msvc'
|
||||
version: 13.2.4
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [ia32]
|
||||
os: [win32]
|
||||
requiresBuild: true
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
registry.npmmirror.com/@next/swc-win32-x64-msvc@13.2.4:
|
||||
resolution: {integrity: sha512-0MffFmyv7tBLlji01qc0IaPP/LVExzvj7/R5x1Jph1bTAIj4Vu81yFQWHHQAP6r4ff9Ukj1mBK6MDNVXm7Tcvw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-13.2.4.tgz}
|
||||
name: '@next/swc-win32-x64-msvc'
|
||||
version: 13.2.4
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
requiresBuild: true
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
registry.npmmirror.com/@radix-ui/number@1.0.0:
|
||||
resolution: {integrity: sha512-Ofwh/1HX69ZfJRiRBMTy7rgjAzHmwe4kW9C9Y99HTRUcYLUuVT0KESFj15rPjRgKJs20GPq8Bm5aEDJ8DuA3vA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@radix-ui/number/-/number-1.0.0.tgz}
|
||||
resolution: {integrity: sha512-Ofwh/1HX69ZfJRiRBMTy7rgjAzHmwe4kW9C9Y99HTRUcYLUuVT0KESFj15rPjRgKJs20GPq8Bm5aEDJ8DuA3vA==, registry: https://registry.npmjs.org/, tarball: https://registry.npmmirror.com/@radix-ui/number/-/number-1.0.0.tgz}
|
||||
name: '@radix-ui/number'
|
||||
version: 1.0.0
|
||||
dependencies:
|
||||
@ -5738,7 +5725,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
registry.npmmirror.com/@radix-ui/primitive@1.0.0:
|
||||
resolution: {integrity: sha512-3e7rn8FDMin4CgeL7Z/49smCA3rFYY3Ha2rUQ7HRWFadS5iCRw08ZgVT1LaNTCNqgvrUiyczLflrVrF0SRQtNA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@radix-ui/primitive/-/primitive-1.0.0.tgz}
|
||||
resolution: {integrity: sha512-3e7rn8FDMin4CgeL7Z/49smCA3rFYY3Ha2rUQ7HRWFadS5iCRw08ZgVT1LaNTCNqgvrUiyczLflrVrF0SRQtNA==, registry: https://registry.npmjs.org/, tarball: https://registry.npmmirror.com/@radix-ui/primitive/-/primitive-1.0.0.tgz}
|
||||
name: '@radix-ui/primitive'
|
||||
version: 1.0.0
|
||||
dependencies:
|
||||
@ -5746,7 +5733,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
registry.npmmirror.com/@radix-ui/react-compose-refs@1.0.0(react@18.2.0):
|
||||
resolution: {integrity: sha512-0KaSv6sx787/hK3eF53iOkiSLwAGlFMx5lotrqD2pTjB18KbybKoEIgkNZTKC60YECDQTKGTRcDBILwZVqVKvA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@radix-ui/react-compose-refs/-/react-compose-refs-1.0.0.tgz}
|
||||
resolution: {integrity: sha512-0KaSv6sx787/hK3eF53iOkiSLwAGlFMx5lotrqD2pTjB18KbybKoEIgkNZTKC60YECDQTKGTRcDBILwZVqVKvA==, registry: https://registry.npmjs.org/, tarball: https://registry.npmmirror.com/@radix-ui/react-compose-refs/-/react-compose-refs-1.0.0.tgz}
|
||||
id: registry.npmmirror.com/@radix-ui/react-compose-refs/1.0.0
|
||||
name: '@radix-ui/react-compose-refs'
|
||||
version: 1.0.0
|
||||
@ -5758,7 +5745,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
registry.npmmirror.com/@radix-ui/react-context@1.0.0(react@18.2.0):
|
||||
resolution: {integrity: sha512-1pVM9RfOQ+n/N5PJK33kRSKsr1glNxomxONs5c49MliinBY6Yw2Q995qfBUUo0/Mbg05B/sGA0gkgPI7kmSHBg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@radix-ui/react-context/-/react-context-1.0.0.tgz}
|
||||
resolution: {integrity: sha512-1pVM9RfOQ+n/N5PJK33kRSKsr1glNxomxONs5c49MliinBY6Yw2Q995qfBUUo0/Mbg05B/sGA0gkgPI7kmSHBg==, registry: https://registry.npmjs.org/, tarball: https://registry.npmmirror.com/@radix-ui/react-context/-/react-context-1.0.0.tgz}
|
||||
id: registry.npmmirror.com/@radix-ui/react-context/1.0.0
|
||||
name: '@radix-ui/react-context'
|
||||
version: 1.0.0
|
||||
@ -5770,7 +5757,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
registry.npmmirror.com/@radix-ui/react-direction@1.0.0(react@18.2.0):
|
||||
resolution: {integrity: sha512-2HV05lGUgYcA6xgLQ4BKPDmtL+QbIZYH5fCOTAOOcJ5O0QbWS3i9lKaurLzliYUDhORI2Qr3pyjhJh44lKA3rQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@radix-ui/react-direction/-/react-direction-1.0.0.tgz}
|
||||
resolution: {integrity: sha512-2HV05lGUgYcA6xgLQ4BKPDmtL+QbIZYH5fCOTAOOcJ5O0QbWS3i9lKaurLzliYUDhORI2Qr3pyjhJh44lKA3rQ==, registry: https://registry.npmjs.org/, tarball: https://registry.npmmirror.com/@radix-ui/react-direction/-/react-direction-1.0.0.tgz}
|
||||
id: registry.npmmirror.com/@radix-ui/react-direction/1.0.0
|
||||
name: '@radix-ui/react-direction'
|
||||
version: 1.0.0
|
||||
@ -5782,7 +5769,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
registry.npmmirror.com/@radix-ui/react-presence@1.0.0(react-dom@18.2.0)(react@18.2.0):
|
||||
resolution: {integrity: sha512-A+6XEvN01NfVWiKu38ybawfHsBjWum42MRPnEuqPsBZ4eV7e/7K321B5VgYMPv3Xx5An6o1/l9ZuDBgmcmWK3w==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@radix-ui/react-presence/-/react-presence-1.0.0.tgz}
|
||||
resolution: {integrity: sha512-A+6XEvN01NfVWiKu38ybawfHsBjWum42MRPnEuqPsBZ4eV7e/7K321B5VgYMPv3Xx5An6o1/l9ZuDBgmcmWK3w==, registry: https://registry.npmjs.org/, tarball: https://registry.npmmirror.com/@radix-ui/react-presence/-/react-presence-1.0.0.tgz}
|
||||
id: registry.npmmirror.com/@radix-ui/react-presence/1.0.0
|
||||
name: '@radix-ui/react-presence'
|
||||
version: 1.0.0
|
||||
@ -5798,7 +5785,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
registry.npmmirror.com/@radix-ui/react-primitive@1.0.2(react-dom@18.2.0)(react@18.2.0):
|
||||
resolution: {integrity: sha512-zY6G5Qq4R8diFPNwtyoLRZBxzu1Z+SXMlfYpChN7Dv8gvmx9X3qhDqiLWvKseKVJMuedFeU/Sa0Sy/Ia+t06Dw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@radix-ui/react-primitive/-/react-primitive-1.0.2.tgz}
|
||||
resolution: {integrity: sha512-zY6G5Qq4R8diFPNwtyoLRZBxzu1Z+SXMlfYpChN7Dv8gvmx9X3qhDqiLWvKseKVJMuedFeU/Sa0Sy/Ia+t06Dw==, registry: https://registry.npmjs.org/, tarball: https://registry.npmmirror.com/@radix-ui/react-primitive/-/react-primitive-1.0.2.tgz}
|
||||
id: registry.npmmirror.com/@radix-ui/react-primitive/1.0.2
|
||||
name: '@radix-ui/react-primitive'
|
||||
version: 1.0.2
|
||||
@ -5813,7 +5800,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
registry.npmmirror.com/@radix-ui/react-scroll-area@1.0.3(react-dom@18.2.0)(react@18.2.0):
|
||||
resolution: {integrity: sha512-sBX9j8Q+0/jReNObEAveKIGXJtk3xUoSIx4cMKygGtO128QJyVDn01XNOFsyvihKDCTcu7SINzQ2jPAZEhIQtw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@radix-ui/react-scroll-area/-/react-scroll-area-1.0.3.tgz}
|
||||
resolution: {integrity: sha512-sBX9j8Q+0/jReNObEAveKIGXJtk3xUoSIx4cMKygGtO128QJyVDn01XNOFsyvihKDCTcu7SINzQ2jPAZEhIQtw==, registry: https://registry.npmjs.org/, tarball: https://registry.npmmirror.com/@radix-ui/react-scroll-area/-/react-scroll-area-1.0.3.tgz}
|
||||
id: registry.npmmirror.com/@radix-ui/react-scroll-area/1.0.3
|
||||
name: '@radix-ui/react-scroll-area'
|
||||
version: 1.0.3
|
||||
@ -5836,7 +5823,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
registry.npmmirror.com/@radix-ui/react-slot@1.0.1(react@18.2.0):
|
||||
resolution: {integrity: sha512-avutXAFL1ehGvAXtPquu0YK5oz6ctS474iM3vNGQIkswrVhdrS52e3uoMQBzZhNRAIE0jBnUyXWNmSjGHhCFcw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@radix-ui/react-slot/-/react-slot-1.0.1.tgz}
|
||||
resolution: {integrity: sha512-avutXAFL1ehGvAXtPquu0YK5oz6ctS474iM3vNGQIkswrVhdrS52e3uoMQBzZhNRAIE0jBnUyXWNmSjGHhCFcw==, registry: https://registry.npmjs.org/, tarball: https://registry.npmmirror.com/@radix-ui/react-slot/-/react-slot-1.0.1.tgz}
|
||||
id: registry.npmmirror.com/@radix-ui/react-slot/1.0.1
|
||||
name: '@radix-ui/react-slot'
|
||||
version: 1.0.1
|
||||
@ -5849,7 +5836,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
registry.npmmirror.com/@radix-ui/react-use-callback-ref@1.0.0(react@18.2.0):
|
||||
resolution: {integrity: sha512-GZtyzoHz95Rhs6S63D2t/eqvdFCm7I+yHMLVQheKM7nBD8mbZIt+ct1jz4536MDnaOGKIxynJ8eHTkVGVVkoTg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.0.0.tgz}
|
||||
resolution: {integrity: sha512-GZtyzoHz95Rhs6S63D2t/eqvdFCm7I+yHMLVQheKM7nBD8mbZIt+ct1jz4536MDnaOGKIxynJ8eHTkVGVVkoTg==, registry: https://registry.npmjs.org/, tarball: https://registry.npmmirror.com/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.0.0.tgz}
|
||||
id: registry.npmmirror.com/@radix-ui/react-use-callback-ref/1.0.0
|
||||
name: '@radix-ui/react-use-callback-ref'
|
||||
version: 1.0.0
|
||||
@ -5861,7 +5848,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
registry.npmmirror.com/@radix-ui/react-use-layout-effect@1.0.0(react@18.2.0):
|
||||
resolution: {integrity: sha512-6Tpkq+R6LOlmQb1R5NNETLG0B4YP0wc+klfXafpUCj6JGyaUc8il7/kUZ7m59rGbXGczE9Bs+iz2qloqsZBduQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.0.0.tgz}
|
||||
resolution: {integrity: sha512-6Tpkq+R6LOlmQb1R5NNETLG0B4YP0wc+klfXafpUCj6JGyaUc8il7/kUZ7m59rGbXGczE9Bs+iz2qloqsZBduQ==, registry: https://registry.npmjs.org/, tarball: https://registry.npmmirror.com/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.0.0.tgz}
|
||||
id: registry.npmmirror.com/@radix-ui/react-use-layout-effect/1.0.0
|
||||
name: '@radix-ui/react-use-layout-effect'
|
||||
version: 1.0.0
|
||||
@ -5872,18 +5859,8 @@ packages:
|
||||
react: 18.2.0
|
||||
dev: false
|
||||
|
||||
registry.npmmirror.com/fsevents@2.3.2:
|
||||
resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/fsevents/-/fsevents-2.3.2.tgz}
|
||||
name: fsevents
|
||||
version: 2.3.2
|
||||
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
|
||||
os: [darwin]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
registry.npmmirror.com/regenerator-runtime@0.13.11:
|
||||
resolution: {integrity: sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz}
|
||||
resolution: {integrity: sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==, registry: https://registry.npmjs.org/, tarball: https://registry.npmmirror.com/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz}
|
||||
name: regenerator-runtime
|
||||
version: 0.13.11
|
||||
dev: false
|
||||
|
@ -3,4 +3,4 @@ module.exports = {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
}
|
||||
};
|
||||
|
@ -7,7 +7,10 @@ const ClearConversationButton = () => {
|
||||
const conversationStore = useConversationStore();
|
||||
const messageStore = useMessageStore();
|
||||
const [showConfirmModal, setShowConfirmModal] = useState(false);
|
||||
const messageList = messageStore.messageList.filter((message) => message.conversationId === conversationStore.currentConversationId);
|
||||
const messageList = messageStore.messageList.filter(
|
||||
(message) =>
|
||||
message.conversationId === conversationStore.currentConversationId
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -19,7 +22,11 @@ const ClearConversationButton = () => {
|
||||
<Icon.GiBroom className="w-6 h-auto" />
|
||||
</button>
|
||||
|
||||
{showConfirmModal && <ClearConversationConfirmModal close={() => setShowConfirmModal(false)} />}
|
||||
{showConfirmModal && (
|
||||
<ClearConversationConfirmModal
|
||||
close={() => setShowConfirmModal(false)}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@ -13,7 +13,9 @@ const ClearConversationConfirmModal = (props: Props) => {
|
||||
const messageStore = useMessageStore();
|
||||
|
||||
const handleClearMessages = () => {
|
||||
messageStore.clearMessage((item) => item.conversationId !== conversationStore.currentConversationId);
|
||||
messageStore.clearMessage(
|
||||
(item) => item.conversationId !== conversationStore.currentConversationId
|
||||
);
|
||||
close();
|
||||
};
|
||||
|
||||
@ -21,7 +23,9 @@ const ClearConversationConfirmModal = (props: Props) => {
|
||||
<Modal title="Clear messages" className="!w-96" onClose={close}>
|
||||
<div>
|
||||
<div className="w-full flex flex-col justify-start items-start mt-2">
|
||||
<p className="text-gray-500">Are you sure to clear the messages in current conversation?</p>
|
||||
<p className="text-gray-500">
|
||||
Are you sure to clear the messages in current conversation?
|
||||
</p>
|
||||
</div>
|
||||
<div className="w-full flex flex-row justify-end items-center mt-4 space-x-2">
|
||||
<button className="btn btn-outline" onClick={close}>
|
||||
|
@ -4,15 +4,23 @@ import ClearDataConfirmModal from "./ClearDataConfirmModal";
|
||||
|
||||
const ClearDataButton = () => {
|
||||
const { t } = useTranslation();
|
||||
const [showClearDataConfirmModal, setShowClearDataConfirmModal] = useState(false);
|
||||
const [showClearDataConfirmModal, setShowClearDataConfirmModal] =
|
||||
useState(false);
|
||||
|
||||
return (
|
||||
<>
|
||||
<button className="btn btn-error" onClick={() => setShowClearDataConfirmModal(true)}>
|
||||
<button
|
||||
className="btn btn-error"
|
||||
onClick={() => setShowClearDataConfirmModal(true)}
|
||||
>
|
||||
{t("common.clear")}
|
||||
</button>
|
||||
|
||||
{showClearDataConfirmModal && <ClearDataConfirmModal close={() => setShowClearDataConfirmModal(false)} />}
|
||||
{showClearDataConfirmModal && (
|
||||
<ClearDataConfirmModal
|
||||
close={() => setShowClearDataConfirmModal(false)}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@ -23,7 +23,10 @@ const ClearDataConfirmModal = (props: Props) => {
|
||||
<Modal title="Clear all data" className="!w-96" onClose={close}>
|
||||
<div>
|
||||
<div className="w-full flex flex-col justify-start items-start mt-2">
|
||||
<p className="text-gray-500">SQL Chat saves all your data in your local browser. Are you sure to clear all of them?</p>
|
||||
<p className="text-gray-500">
|
||||
SQL Chat saves all your data in your local browser. Are you sure to
|
||||
clear all of them?
|
||||
</p>
|
||||
</div>
|
||||
<div className="w-full flex flex-row justify-end items-center mt-4 space-x-2">
|
||||
<button className="btn btn-outline" onClick={close}>
|
||||
|
@ -21,7 +21,10 @@ export const CodeBlock = (props: Props) => {
|
||||
// Only show execute button in the following situations:
|
||||
// * SQL code;
|
||||
// * Connection setup;
|
||||
const showExecuteButton = currentConnectionCtx?.connection && currentConnectionCtx?.database && language.toUpperCase() === "SQL";
|
||||
const showExecuteButton =
|
||||
currentConnectionCtx?.connection &&
|
||||
currentConnectionCtx?.database &&
|
||||
language.toUpperCase() === "SQL";
|
||||
|
||||
const copyToClipboard = () => {
|
||||
copy(value);
|
||||
@ -67,7 +70,11 @@ export const CodeBlock = (props: Props) => {
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<SyntaxHighlighter language={language.toLowerCase()} style={oneDark} customStyle={{ margin: 0 }}>
|
||||
<SyntaxHighlighter
|
||||
language={language.toLowerCase()}
|
||||
style={oneDark}
|
||||
customStyle={{ margin: 0 }}
|
||||
>
|
||||
{value}
|
||||
</SyntaxHighlighter>
|
||||
</div>
|
||||
|
@ -21,9 +21,12 @@ const ConnectionSidebar = () => {
|
||||
const [state, setState] = useState<State>({
|
||||
showSettingModal: false,
|
||||
});
|
||||
const [isRequestingDatabase, setIsRequestingDatabase] = useState<boolean>(false);
|
||||
const [isRequestingDatabase, setIsRequestingDatabase] =
|
||||
useState<boolean>(false);
|
||||
const currentConnectionCtx = connectionStore.currentConnectionCtx;
|
||||
const databaseList = connectionStore.databaseList.filter((database) => database.connectionId === currentConnectionCtx?.connection.id);
|
||||
const databaseList = connectionStore.databaseList.filter(
|
||||
(database) => database.connectionId === currentConnectionCtx?.connection.id
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const handleWindowResize = () => {
|
||||
@ -47,9 +50,11 @@ const ConnectionSidebar = () => {
|
||||
useEffect(() => {
|
||||
if (currentConnectionCtx?.connection) {
|
||||
setIsRequestingDatabase(true);
|
||||
connectionStore.getOrFetchDatabaseList(currentConnectionCtx.connection).finally(() => {
|
||||
setIsRequestingDatabase(false);
|
||||
});
|
||||
connectionStore
|
||||
.getOrFetchDatabaseList(currentConnectionCtx.connection)
|
||||
.finally(() => {
|
||||
setIsRequestingDatabase(false);
|
||||
});
|
||||
} else {
|
||||
setIsRequestingDatabase(false);
|
||||
}
|
||||
@ -67,8 +72,12 @@ const ConnectionSidebar = () => {
|
||||
return;
|
||||
}
|
||||
|
||||
const databaseList = await connectionStore.getOrFetchDatabaseList(currentConnectionCtx.connection);
|
||||
const database = databaseList.find((database) => database.name === databaseName);
|
||||
const databaseList = await connectionStore.getOrFetchDatabaseList(
|
||||
currentConnectionCtx.connection
|
||||
);
|
||||
const database = databaseList.find(
|
||||
(database) => database.name === databaseName
|
||||
);
|
||||
connectionStore.setCurrentConnectionCtx({
|
||||
connection: currentConnectionCtx.connection,
|
||||
database: database,
|
||||
@ -107,7 +116,8 @@ const ConnectionSidebar = () => {
|
||||
<div className="w-full grow">
|
||||
{isRequestingDatabase && (
|
||||
<div className="w-full h-12 flex flex-row justify-start items-center px-4 sticky top-0 border z-1 mb-4 mt-2 rounded-lg text-sm text-gray-600 dark:text-gray-400">
|
||||
<Icon.BiLoaderAlt className="w-4 h-auto animate-spin mr-1" /> {t("common.loading")}
|
||||
<Icon.BiLoaderAlt className="w-4 h-auto animate-spin mr-1" />{" "}
|
||||
{t("common.loading")}
|
||||
</div>
|
||||
)}
|
||||
{databaseList.length > 0 && (
|
||||
@ -121,7 +131,9 @@ const ConnectionSidebar = () => {
|
||||
value: database.name,
|
||||
};
|
||||
})}
|
||||
onValueChange={(databaseName) => handleDatabaseNameSelect(databaseName)}
|
||||
onValueChange={(databaseName) =>
|
||||
handleDatabaseNameSelect(databaseName)
|
||||
}
|
||||
placeholder={t("connection.select-database") || ""}
|
||||
/>
|
||||
</div>
|
||||
@ -168,7 +180,9 @@ const ConnectionSidebar = () => {
|
||||
</div>
|
||||
</Drawer>
|
||||
|
||||
{state.showSettingModal && <SettingModal close={() => toggleSettingModal(false)} />}
|
||||
{state.showSettingModal && (
|
||||
<SettingModal close={() => toggleSettingModal(false)} />
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@ -14,7 +14,9 @@ const Header = (props: Props) => {
|
||||
const conversationStore = useConversationStore();
|
||||
const isDarkMode = useDarkMode();
|
||||
const currentConversationId = conversationStore.currentConversationId;
|
||||
const title = conversationStore.getConversationById(currentConversationId)?.title || "SQL Chat";
|
||||
const title =
|
||||
conversationStore.getConversationById(currentConversationId)?.title ||
|
||||
"SQL Chat";
|
||||
|
||||
useEffect(() => {
|
||||
document.title = `${title}`;
|
||||
@ -36,14 +38,24 @@ const Header = (props: Props) => {
|
||||
<span className="w-auto text-left block lg:hidden">{title}</span>
|
||||
<GitHubStarBadge className="hidden lg:flex ml-2" />
|
||||
</div>
|
||||
<span className="w-auto text-center h-8 p-1 hidden lg:block">{title}</span>
|
||||
<span className="w-auto text-center h-8 p-1 hidden lg:block">
|
||||
{title}
|
||||
</span>
|
||||
<div className="mr-2 sm:mr-3 relative flex flex-row justify-end items-center">
|
||||
<a
|
||||
href="https://www.bytebase.com?source=sqlchat"
|
||||
className="flex flex-row justify-center items-center h-10 px-3 py-1 rounded-md whitespace-nowrap hover:bg-gray-100 dark:hover:bg-zinc-700"
|
||||
target="_blank"
|
||||
>
|
||||
<img className="h-5 sm:h-6 w-auto" src={isDarkMode ? "/craft-by-bytebase-dark-mode.webp" : "/craft-by-bytebase.webp"} alt="" />
|
||||
<img
|
||||
className="h-5 sm:h-6 w-auto"
|
||||
src={
|
||||
isDarkMode
|
||||
? "/craft-by-bytebase-dark-mode.webp"
|
||||
: "/craft-by-bytebase.webp"
|
||||
}
|
||||
alt=""
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -2,7 +2,12 @@ import { useEffect, useRef, useState } from "react";
|
||||
import { toast } from "react-hot-toast";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import TextareaAutosize from "react-textarea-autosize";
|
||||
import { useConversationStore, useConnectionStore, useMessageStore, useUserStore } from "@/store";
|
||||
import {
|
||||
useConversationStore,
|
||||
useConnectionStore,
|
||||
useMessageStore,
|
||||
useUserStore,
|
||||
} from "@/store";
|
||||
import { CreatorRole } from "@/types";
|
||||
import { generateUUID } from "@/utils";
|
||||
import Icon from "../Icon";
|
||||
@ -34,13 +39,18 @@ const MessageTextarea = (props: Props) => {
|
||||
};
|
||||
|
||||
const handleSend = async () => {
|
||||
let conversation = conversationStore.getConversationById(conversationStore.currentConversationId);
|
||||
let conversation = conversationStore.getConversationById(
|
||||
conversationStore.currentConversationId
|
||||
);
|
||||
if (!conversation) {
|
||||
const currentConnectionCtx = connectionStore.currentConnectionCtx;
|
||||
if (!currentConnectionCtx) {
|
||||
conversation = conversationStore.createConversation();
|
||||
} else {
|
||||
conversation = conversationStore.createConversation(currentConnectionCtx.connection.id, currentConnectionCtx.database?.name);
|
||||
conversation = conversationStore.createConversation(
|
||||
currentConnectionCtx.connection.id,
|
||||
currentConnectionCtx.database?.name
|
||||
);
|
||||
}
|
||||
}
|
||||
if (!value) {
|
||||
|
@ -4,7 +4,13 @@ import { useTranslation } from "react-i18next";
|
||||
import { toast } from "react-hot-toast";
|
||||
import ReactMarkdown from "react-markdown";
|
||||
import remarkGfm from "remark-gfm";
|
||||
import { useConversationStore, useConnectionStore, useMessageStore, useUserStore, useSettingStore } from "@/store";
|
||||
import {
|
||||
useConversationStore,
|
||||
useConnectionStore,
|
||||
useMessageStore,
|
||||
useUserStore,
|
||||
useSettingStore,
|
||||
} from "@/store";
|
||||
import { Message } from "@/types";
|
||||
import Dropdown, { DropdownItem } from "../kit/Dropdown";
|
||||
import Icon from "../Icon";
|
||||
@ -25,7 +31,10 @@ const MessageView = (props: Props) => {
|
||||
const connectionStore = useConnectionStore();
|
||||
const messageStore = useMessageStore();
|
||||
const isCurrentUser = message.creatorId === userStore.currentUser.id;
|
||||
const connection = connectionStore.getConnectionById(conversationStore.getConversationById(message.conversationId)?.connectionId || "");
|
||||
const connection = connectionStore.getConnectionById(
|
||||
conversationStore.getConversationById(message.conversationId)
|
||||
?.connectionId || ""
|
||||
);
|
||||
|
||||
const copyMessage = () => {
|
||||
navigator.clipboard.writeText(message.content);
|
||||
@ -83,9 +92,16 @@ const MessageView = (props: Props) => {
|
||||
<>
|
||||
<div className="flex justify-center items-center mr-2 shrink-0">
|
||||
{connection ? (
|
||||
<EngineIcon className="w-10 h-auto p-1 border dark:border-zinc-700 rounded-full" engine={connection.engineType} />
|
||||
<EngineIcon
|
||||
className="w-10 h-auto p-1 border dark:border-zinc-700 rounded-full"
|
||||
engine={connection.engineType}
|
||||
/>
|
||||
) : (
|
||||
<img className="w-10 h-auto p-1" src="/chat-logo-bot.webp" alt="" />
|
||||
<img
|
||||
className="w-10 h-auto p-1"
|
||||
src="/chat-logo-bot.webp"
|
||||
alt=""
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
{message.status === "LOADING" && message.content === "" ? (
|
||||
@ -97,20 +113,29 @@ const MessageView = (props: Props) => {
|
||||
<div className="w-auto max-w-[calc(100%-2rem)] flex flex-col justify-start items-start">
|
||||
<ReactMarkdown
|
||||
className={`w-auto max-w-full bg-gray-100 dark:bg-zinc-700 px-4 py-2 rounded-lg prose prose-neutral dark:prose-invert ${
|
||||
message.status === "FAILED" && "border border-red-400 bg-red-100 text-red-500"
|
||||
message.status === "FAILED" &&
|
||||
"border border-red-400 bg-red-100 text-red-500"
|
||||
}`}
|
||||
remarkPlugins={[remarkGfm]}
|
||||
components={{
|
||||
pre({ node, className, children, ...props }) {
|
||||
const child = children[0] as ReactElement;
|
||||
const match = /language-(\w+)/.exec(child.props.className || "");
|
||||
const match = /language-(\w+)/.exec(
|
||||
child.props.className || ""
|
||||
);
|
||||
const language = match ? match[1] : "SQL";
|
||||
return (
|
||||
<pre className={`${className || ""} w-full p-0 my-1`} {...props}>
|
||||
<pre
|
||||
className={`${className || ""} w-full p-0 my-1`}
|
||||
{...props}
|
||||
>
|
||||
<CodeBlock
|
||||
key={Math.random()}
|
||||
language={language || "SQL"}
|
||||
value={String(child.props.children).replace(/\n$/, "")}
|
||||
value={String(child.props.children).replace(
|
||||
/\n$/,
|
||||
""
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
</pre>
|
||||
@ -124,7 +149,9 @@ const MessageView = (props: Props) => {
|
||||
{message.content}
|
||||
</ReactMarkdown>
|
||||
<span className="self-end text-sm text-gray-400 pt-1 pr-1">
|
||||
{dayjs(message.createdAt).locale(settingStore.setting.locale).format("lll")}
|
||||
{dayjs(message.createdAt)
|
||||
.locale(settingStore.setting.locale)
|
||||
.format("lll")}
|
||||
</span>
|
||||
</div>
|
||||
<div className="invisible group-hover:visible">
|
||||
|
@ -10,7 +10,10 @@ const ThreeDotsLoader = () => {
|
||||
const theme = settingStore.setting.theme;
|
||||
let appearance = theme;
|
||||
if (theme === "system") {
|
||||
if (window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches) {
|
||||
if (
|
||||
window.matchMedia &&
|
||||
window.matchMedia("(prefers-color-scheme: dark)").matches
|
||||
) {
|
||||
appearance = "dark";
|
||||
} else {
|
||||
appearance = "light";
|
||||
@ -24,7 +27,14 @@ const ThreeDotsLoader = () => {
|
||||
}
|
||||
}, [settingStore.setting.theme]);
|
||||
|
||||
return <ThreeDots wrapperClass="dark:opacity-60" width="24" height="24" color={color} />;
|
||||
return (
|
||||
<ThreeDots
|
||||
wrapperClass="dark:opacity-60"
|
||||
width="24"
|
||||
height="24"
|
||||
color={color}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default ThreeDotsLoader;
|
||||
|
@ -35,9 +35,13 @@ const ConversationView = () => {
|
||||
const [isStickyAtBottom, setIsStickyAtBottom] = useState<boolean>(true);
|
||||
const [showHeaderShadow, setShowHeaderShadow] = useState<boolean>(false);
|
||||
const conversationViewRef = useRef<HTMLDivElement>(null);
|
||||
const currentConversation = conversationStore.getConversationById(conversationStore.currentConversationId);
|
||||
const currentConversation = conversationStore.getConversationById(
|
||||
conversationStore.currentConversationId
|
||||
);
|
||||
const messageList = currentConversation
|
||||
? messageStore.messageList.filter((message) => message.conversationId === currentConversation.id)
|
||||
? messageStore.messageList.filter(
|
||||
(message) => message.conversationId === currentConversation.id
|
||||
)
|
||||
: [];
|
||||
const lastMessage = last(messageList);
|
||||
|
||||
@ -63,13 +67,21 @@ const ConversationView = () => {
|
||||
}
|
||||
setShowHeaderShadow((conversationViewRef.current?.scrollTop || 0) > 0);
|
||||
setIsStickyAtBottom(
|
||||
conversationViewRef.current.scrollTop + conversationViewRef.current.clientHeight >= conversationViewRef.current.scrollHeight
|
||||
conversationViewRef.current.scrollTop +
|
||||
conversationViewRef.current.clientHeight >=
|
||||
conversationViewRef.current.scrollHeight
|
||||
);
|
||||
};
|
||||
conversationViewRef.current?.addEventListener("scroll", handleConversationViewScroll);
|
||||
conversationViewRef.current?.addEventListener(
|
||||
"scroll",
|
||||
handleConversationViewScroll
|
||||
);
|
||||
|
||||
return () => {
|
||||
conversationViewRef.current?.removeEventListener("scroll", handleConversationViewScroll);
|
||||
conversationViewRef.current?.removeEventListener(
|
||||
"scroll",
|
||||
handleConversationViewScroll
|
||||
);
|
||||
};
|
||||
}, []);
|
||||
|
||||
@ -77,7 +89,8 @@ const ConversationView = () => {
|
||||
if (!conversationViewRef.current) {
|
||||
return;
|
||||
}
|
||||
conversationViewRef.current.scrollTop = conversationViewRef.current.scrollHeight;
|
||||
conversationViewRef.current.scrollTop =
|
||||
conversationViewRef.current.scrollHeight;
|
||||
}, [currentConversation, lastMessage?.id]);
|
||||
|
||||
useEffect(() => {
|
||||
@ -86,14 +99,17 @@ const ConversationView = () => {
|
||||
}
|
||||
|
||||
if (lastMessage?.status === "LOADING" && isStickyAtBottom) {
|
||||
conversationViewRef.current.scrollTop = conversationViewRef.current.scrollHeight;
|
||||
conversationViewRef.current.scrollTop =
|
||||
conversationViewRef.current.scrollHeight;
|
||||
}
|
||||
}, [lastMessage?.status, lastMessage?.content, isStickyAtBottom]);
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
currentConversation?.connectionId === connectionStore.currentConnectionCtx?.connection.id &&
|
||||
currentConversation?.databaseName === connectionStore.currentConnectionCtx?.database?.name
|
||||
currentConversation?.connectionId ===
|
||||
connectionStore.currentConnectionCtx?.connection.id &&
|
||||
currentConversation?.databaseName ===
|
||||
connectionStore.currentConnectionCtx?.database?.name
|
||||
) {
|
||||
return;
|
||||
}
|
||||
@ -101,14 +117,18 @@ const ConversationView = () => {
|
||||
// Auto select the first conversation when the current connection changes.
|
||||
const conversationList = conversationStore.conversationList.filter(
|
||||
(conversation) =>
|
||||
conversation.connectionId === connectionStore.currentConnectionCtx?.connection.id &&
|
||||
conversation.databaseName === connectionStore.currentConnectionCtx?.database?.name
|
||||
conversation.connectionId ===
|
||||
connectionStore.currentConnectionCtx?.connection.id &&
|
||||
conversation.databaseName ===
|
||||
connectionStore.currentConnectionCtx?.database?.name
|
||||
);
|
||||
conversationStore.setCurrentConversationId(head(conversationList)?.id);
|
||||
}, [currentConversation, connectionStore.currentConnectionCtx]);
|
||||
|
||||
const sendMessageToCurrentConversation = async () => {
|
||||
const currentConversation = conversationStore.getConversationById(conversationStore.getState().currentConversationId);
|
||||
const currentConversation = conversationStore.getConversationById(
|
||||
conversationStore.getState().currentConversationId
|
||||
);
|
||||
if (!currentConversation) {
|
||||
return;
|
||||
}
|
||||
@ -116,8 +136,14 @@ const ConversationView = () => {
|
||||
return;
|
||||
}
|
||||
|
||||
const messageList = messageStore.getState().messageList.filter((message) => message.conversationId === currentConversation.id);
|
||||
const promptGenerator = getPromptGeneratorOfAssistant(getAssistantById(currentConversation.assistantId)!);
|
||||
const messageList = messageStore
|
||||
.getState()
|
||||
.messageList.filter(
|
||||
(message) => message.conversationId === currentConversation.id
|
||||
);
|
||||
const promptGenerator = getPromptGeneratorOfAssistant(
|
||||
getAssistantById(currentConversation.assistantId)!
|
||||
);
|
||||
let prompt = promptGenerator();
|
||||
let tokens = 0;
|
||||
|
||||
@ -135,7 +161,9 @@ const ConversationView = () => {
|
||||
if (connectionStore.currentConnectionCtx?.database) {
|
||||
let schema = "";
|
||||
try {
|
||||
const tables = await connectionStore.getOrFetchDatabaseSchema(connectionStore.currentConnectionCtx?.database);
|
||||
const tables = await connectionStore.getOrFetchDatabaseSchema(
|
||||
connectionStore.currentConnectionCtx?.database
|
||||
);
|
||||
for (const table of tables) {
|
||||
if (tokens < MAX_TOKENS / 2) {
|
||||
tokens += countTextTokens(schema + table.structure);
|
||||
@ -187,7 +215,8 @@ const ConversationView = () => {
|
||||
|
||||
if (!rawRes.ok) {
|
||||
console.error(rawRes);
|
||||
let errorMessage = "Failed to request message, please check your network.";
|
||||
let errorMessage =
|
||||
"Failed to request message, please check your network.";
|
||||
try {
|
||||
const res = await rawRes.json();
|
||||
errorMessage = res.error.message;
|
||||
@ -253,14 +282,22 @@ const ConversationView = () => {
|
||||
</div>
|
||||
<div className="p-2 w-full h-auto grow max-w-4xl py-1 px-4 sm:px-8 mx-auto">
|
||||
{messageList.length === 0 ? (
|
||||
<EmptyView className="mt-16" sendMessage={sendMessageToCurrentConversation} />
|
||||
<EmptyView
|
||||
className="mt-16"
|
||||
sendMessage={sendMessageToCurrentConversation}
|
||||
/>
|
||||
) : (
|
||||
messageList.map((message) => <MessageView key={message.id} message={message} />)
|
||||
messageList.map((message) => (
|
||||
<MessageView key={message.id} message={message} />
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
<div className="sticky bottom-0 flex flex-row justify-center items-center w-full max-w-4xl py-2 pb-4 px-4 sm:px-8 mx-auto bg-white dark:bg-zinc-800 bg-opacity-80 backdrop-blur">
|
||||
<ClearConversationButton />
|
||||
<MessageTextarea disabled={lastMessage?.status === "LOADING"} sendMessage={sendMessageToCurrentConversation} />
|
||||
<MessageTextarea
|
||||
disabled={lastMessage?.status === "LOADING"}
|
||||
sendMessage={sendMessageToCurrentConversation}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -51,7 +51,8 @@ const CreateConnectionModal = (props: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const connectionStore = useConnectionStore();
|
||||
const [connection, setConnection] = useState<Connection>(defaultConnection);
|
||||
const [showDeleteConnectionModal, setShowDeleteConnectionModal] = useState(false);
|
||||
const [showDeleteConnectionModal, setShowDeleteConnectionModal] =
|
||||
useState(false);
|
||||
const [sslType, setSSLType] = useState<SSLType>("none");
|
||||
const [selectedSSLField, setSelectedSSLField] = useState<SSLFieldType>("ca");
|
||||
const [isRequesting, setIsRequesting] = useState(false);
|
||||
@ -105,7 +106,11 @@ const CreateConnectionModal = (props: Props) => {
|
||||
}
|
||||
|
||||
const file = files[0];
|
||||
if (file.type.startsWith("audio/") || file.type.startsWith("video/") || file.type.startsWith("image/")) {
|
||||
if (
|
||||
file.type.startsWith("audio/") ||
|
||||
file.type.startsWith("video/") ||
|
||||
file.type.startsWith("image/")
|
||||
) {
|
||||
toast.error(`Invalid file type:${file.type}`);
|
||||
return;
|
||||
}
|
||||
@ -177,7 +182,10 @@ const CreateConnectionModal = (props: Props) => {
|
||||
}
|
||||
|
||||
// Set the created connection as the current connection.
|
||||
const databaseList = await connectionStore.getOrFetchDatabaseList(connection, true);
|
||||
const databaseList = await connectionStore.getOrFetchDatabaseList(
|
||||
connection,
|
||||
true
|
||||
);
|
||||
connectionStore.setCurrentConnectionCtx({
|
||||
connection: connection,
|
||||
database: head(databaseList),
|
||||
@ -203,11 +211,19 @@ const CreateConnectionModal = (props: Props) => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<Modal title={isEditing ? t("connection.edit") : t("connection.new")} onClose={close}>
|
||||
<Modal
|
||||
title={isEditing ? t("connection.edit") : t("connection.new")}
|
||||
onClose={close}
|
||||
>
|
||||
<div className="w-full flex flex-col justify-start items-start space-y-3 mt-2">
|
||||
<DataStorageBanner className="rounded-lg bg-white border dark:border-zinc-700 py-2 !justify-start" alwaysShow={true} />
|
||||
<DataStorageBanner
|
||||
className="rounded-lg bg-white border dark:border-zinc-700 py-2 !justify-start"
|
||||
alwaysShow={true}
|
||||
/>
|
||||
<div className="w-full flex flex-col">
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">{t("connection.database-type")}</label>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||
{t("connection.database-type")}
|
||||
</label>
|
||||
<Select
|
||||
className="w-full"
|
||||
value={connection.engineType}
|
||||
@ -216,24 +232,46 @@ const CreateConnectionModal = (props: Props) => {
|
||||
{ value: Engine.PostgreSQL, label: "PostgreSQL" },
|
||||
{ value: Engine.MSSQL, label: "MSSQL" },
|
||||
]}
|
||||
onValueChange={(value) => setPartialConnection({ engineType: value as Engine })}
|
||||
onValueChange={(value) =>
|
||||
setPartialConnection({ engineType: value as Engine })
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div className="w-full flex flex-col">
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">{t("connection.title")}</label>
|
||||
<TextField placeholder="Title" value={connection.title} onChange={(value) => setPartialConnection({ title: value })} />
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||
{t("connection.title")}
|
||||
</label>
|
||||
<TextField
|
||||
placeholder="Title"
|
||||
value={connection.title}
|
||||
onChange={(value) => setPartialConnection({ title: value })}
|
||||
/>
|
||||
</div>
|
||||
<div className="w-full flex flex-col">
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">{t("connection.host")}</label>
|
||||
<TextField placeholder="Connection host" value={connection.host} onChange={(value) => setPartialConnection({ host: value })} />
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||
{t("connection.host")}
|
||||
</label>
|
||||
<TextField
|
||||
placeholder="Connection host"
|
||||
value={connection.host}
|
||||
onChange={(value) => setPartialConnection({ host: value })}
|
||||
/>
|
||||
</div>
|
||||
<div className="w-full flex flex-col">
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">{t("connection.port")}</label>
|
||||
<TextField placeholder="Connection port" value={connection.port} onChange={(value) => setPartialConnection({ port: value })} />
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||
{t("connection.port")}
|
||||
</label>
|
||||
<TextField
|
||||
placeholder="Connection port"
|
||||
value={connection.port}
|
||||
onChange={(value) => setPartialConnection({ port: value })}
|
||||
/>
|
||||
</div>
|
||||
{showDatabaseField && (
|
||||
<div className="w-full flex flex-col">
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">{t("connection.database-name")}</label>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||
{t("connection.database-name")}
|
||||
</label>
|
||||
<TextField
|
||||
placeholder="Connection database"
|
||||
value={connection.database || ""}
|
||||
@ -242,7 +280,9 @@ const CreateConnectionModal = (props: Props) => {
|
||||
</div>
|
||||
)}
|
||||
<div className="w-full flex flex-col">
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">{t("connection.username")}</label>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||
{t("connection.username")}
|
||||
</label>
|
||||
<TextField
|
||||
placeholder="Connection username"
|
||||
value={connection.username || ""}
|
||||
@ -250,7 +290,9 @@ const CreateConnectionModal = (props: Props) => {
|
||||
/>
|
||||
</div>
|
||||
<div className="w-full flex flex-col">
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">{t("connection.password")}</label>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||
{t("connection.password")}
|
||||
</label>
|
||||
<TextField
|
||||
placeholder="Connection password"
|
||||
type="password"
|
||||
@ -259,10 +301,15 @@ const CreateConnectionModal = (props: Props) => {
|
||||
/>
|
||||
</div>
|
||||
<div className="w-full flex flex-col">
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">SSL</label>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||
SSL
|
||||
</label>
|
||||
<div className="w-full flex flex-row justify-start items-start flex-wrap">
|
||||
{SSLTypeOptions.map((option) => (
|
||||
<label key={option.value} className="w-auto flex flex-row justify-start items-center cursor-pointer mr-3 mb-3">
|
||||
<label
|
||||
key={option.value}
|
||||
className="w-auto flex flex-row justify-start items-center cursor-pointer mr-3 mb-3"
|
||||
>
|
||||
<input
|
||||
type="radio"
|
||||
className="radio w-4 h-4 mr-1"
|
||||
@ -279,7 +326,8 @@ const CreateConnectionModal = (props: Props) => {
|
||||
<div className="text-sm space-x-3 mb-2">
|
||||
<span
|
||||
className={`leading-6 pb-1 border-b-2 border-transparent cursor-pointer opacity-60 hover:opacity-80 ${
|
||||
selectedSSLField === "ca" && "!border-indigo-600 !opacity-100"
|
||||
selectedSSLField === "ca" &&
|
||||
"!border-indigo-600 !opacity-100"
|
||||
} `}
|
||||
onClick={() => setSelectedSSLField("ca")}
|
||||
>
|
||||
@ -289,7 +337,8 @@ const CreateConnectionModal = (props: Props) => {
|
||||
<>
|
||||
<span
|
||||
className={`leading-6 pb-1 border-b-2 border-transparent cursor-pointer opacity-60 hover:opacity-80 ${
|
||||
selectedSSLField === "key" && "!border-indigo-600 !opacity-100"
|
||||
selectedSSLField === "key" &&
|
||||
"!border-indigo-600 !opacity-100"
|
||||
}`}
|
||||
onClick={() => setSelectedSSLField("key")}
|
||||
>
|
||||
@ -297,7 +346,8 @@ const CreateConnectionModal = (props: Props) => {
|
||||
</span>
|
||||
<span
|
||||
className={`leading-6 pb-1 border-b-2 border-transparent cursor-pointer opacity-60 hover:opacity-80 ${
|
||||
selectedSSLField === "cert" && "!border-indigo-600 !opacity-100"
|
||||
selectedSSLField === "cert" &&
|
||||
"!border-indigo-600 !opacity-100"
|
||||
}`}
|
||||
onClick={() => setSelectedSSLField("cert")}
|
||||
>
|
||||
@ -311,18 +361,26 @@ const CreateConnectionModal = (props: Props) => {
|
||||
className="w-full border resize-none rounded-lg text-sm p-3"
|
||||
minRows={3}
|
||||
maxRows={3}
|
||||
value={(connection.ssl && connection.ssl[selectedSSLField]) ?? ""}
|
||||
value={
|
||||
(connection.ssl && connection.ssl[selectedSSLField]) ?? ""
|
||||
}
|
||||
onChange={handleSSLValueChange}
|
||||
/>
|
||||
<div
|
||||
className={`${
|
||||
connection.ssl && connection.ssl[selectedSSLField] && "hidden"
|
||||
connection.ssl &&
|
||||
connection.ssl[selectedSSLField] &&
|
||||
"hidden"
|
||||
} absolute top-3 left-4 text-gray-400 text-sm leading-6 pointer-events-none`}
|
||||
>
|
||||
<span className="">Input or </span>
|
||||
<label className="pointer-events-auto border border-dashed px-2 py-1 rounded-lg cursor-pointer hover:border-gray-600 hover:text-gray-600">
|
||||
upload file
|
||||
<input className="hidden" type="file" onChange={handleSSLFileInputChange} />
|
||||
<input
|
||||
className="hidden"
|
||||
type="file"
|
||||
onChange={handleSSLFileInputChange}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
@ -330,14 +388,18 @@ const CreateConnectionModal = (props: Props) => {
|
||||
)}
|
||||
{connection.engineType === Engine.MSSQL && (
|
||||
<div className="w-full flex flex-col">
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">Encrypt</label>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||
Encrypt
|
||||
</label>
|
||||
<div className="w-full flex flex-row justify-start items-start flex-wrap">
|
||||
<label className="flex items-center">
|
||||
<input
|
||||
type="checkbox"
|
||||
className="form-checkbox h-4 w-4 text-indigo-600 transition duration-150 ease-in-out"
|
||||
checked={connection.encrypt}
|
||||
onChange={(e) => setPartialConnection({ encrypt: e.target.checked })}
|
||||
onChange={(e) =>
|
||||
setPartialConnection({ encrypt: e.target.checked })
|
||||
}
|
||||
/>
|
||||
<span className="ml-2 text-sm">Encrypt connection</span>
|
||||
</label>
|
||||
@ -349,7 +411,10 @@ const CreateConnectionModal = (props: Props) => {
|
||||
<div className="modal-action w-full flex flex-row justify-between items-center space-x-2">
|
||||
<div>
|
||||
{isEditing && (
|
||||
<button className="btn btn-outline" onClick={() => setShowDeleteConnectionModal(true)}>
|
||||
<button
|
||||
className="btn btn-outline"
|
||||
onClick={() => setShowDeleteConnectionModal(true)}
|
||||
>
|
||||
Delete
|
||||
</button>
|
||||
)}
|
||||
@ -358,8 +423,14 @@ const CreateConnectionModal = (props: Props) => {
|
||||
<button className="btn btn-outline" onClick={close}>
|
||||
{t("common.close")}
|
||||
</button>
|
||||
<button className="btn" disabled={isRequesting || !allowSave} onClick={handleCreateConnection}>
|
||||
{isRequesting && <Icon.BiLoaderAlt className="w-4 h-auto animate-spin mr-1" />}
|
||||
<button
|
||||
className="btn"
|
||||
disabled={isRequesting || !allowSave}
|
||||
onClick={handleCreateConnection}
|
||||
>
|
||||
{isRequesting && (
|
||||
<Icon.BiLoaderAlt className="w-4 h-auto animate-spin mr-1" />
|
||||
)}
|
||||
{t("common.save")}
|
||||
</button>
|
||||
</div>
|
||||
|
@ -10,7 +10,10 @@ interface Props {
|
||||
const DataStorageBanner = (props: Props) => {
|
||||
const { className, alwaysShow } = props;
|
||||
const { t } = useTranslation();
|
||||
const [hideBanner, setHideBanner] = useLocalStorage("hide-local-storage-banner", false);
|
||||
const [hideBanner, setHideBanner] = useLocalStorage(
|
||||
"hide-local-storage-banner",
|
||||
false
|
||||
);
|
||||
const show = alwaysShow || !hideBanner;
|
||||
|
||||
return (
|
||||
@ -24,7 +27,10 @@ const DataStorageBanner = (props: Props) => {
|
||||
{t("banner.data-storage")}
|
||||
</span>
|
||||
{!alwaysShow && (
|
||||
<button className="absolute right-2 sm:right-4 opacity-60 hover:opacity-100" onClick={() => setHideBanner(true)}>
|
||||
<button
|
||||
className="absolute right-2 sm:right-4 opacity-60 hover:opacity-100"
|
||||
onClick={() => setHideBanner(true)}
|
||||
>
|
||||
<Icon.BiX className="w-6 h-auto" />
|
||||
</button>
|
||||
)}
|
||||
|
@ -1,11 +1,19 @@
|
||||
import { useConversationStore, useConnectionStore, useMessageStore, useUserStore } from "@/store";
|
||||
import {
|
||||
useConversationStore,
|
||||
useConnectionStore,
|
||||
useMessageStore,
|
||||
useUserStore,
|
||||
} from "@/store";
|
||||
import { CreatorRole } from "@/types";
|
||||
import { generateUUID } from "@/utils";
|
||||
import useDarkMode from "@/hooks/useDarkmode";
|
||||
import Icon from "./Icon";
|
||||
|
||||
// examples are used to show some examples to the user.
|
||||
const examples = ["Give me an example schema about employee", "How to create a view in MySQL?"];
|
||||
const examples = [
|
||||
"Give me an example schema about employee",
|
||||
"How to create a view in MySQL?",
|
||||
];
|
||||
|
||||
interface Props {
|
||||
className?: string;
|
||||
@ -21,13 +29,18 @@ const EmptyView = (props: Props) => {
|
||||
const isDarkMode = useDarkMode();
|
||||
|
||||
const handleExampleClick = async (content: string) => {
|
||||
let conversation = conversationStore.getConversationById(conversationStore.currentConversationId);
|
||||
let conversation = conversationStore.getConversationById(
|
||||
conversationStore.currentConversationId
|
||||
);
|
||||
if (!conversation) {
|
||||
const currentConnectionCtx = connectionStore.currentConnectionCtx;
|
||||
if (!currentConnectionCtx) {
|
||||
conversation = conversationStore.createConversation();
|
||||
} else {
|
||||
conversation = conversationStore.createConversation(currentConnectionCtx.connection.id, currentConnectionCtx.database?.name);
|
||||
conversation = conversationStore.createConversation(
|
||||
currentConnectionCtx.connection.id,
|
||||
currentConnectionCtx.database?.name
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -44,9 +57,20 @@ const EmptyView = (props: Props) => {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={`${className || ""} w-full h-full flex flex-col justify-start items-center`}>
|
||||
<div
|
||||
className={`${
|
||||
className || ""
|
||||
} w-full h-full flex flex-col justify-start items-center`}
|
||||
>
|
||||
<div className="w-96 max-w-full font-medium leading-loose mb-8">
|
||||
<img src={isDarkMode ? "/chat-logo-and-text-dark-mode.webp" : "/chat-logo-and-text.webp"} alt="sql-chat-logo" />
|
||||
<img
|
||||
src={
|
||||
isDarkMode
|
||||
? "/chat-logo-and-text-dark-mode.webp"
|
||||
: "/chat-logo-and-text.webp"
|
||||
}
|
||||
alt="sql-chat-logo"
|
||||
/>
|
||||
</div>
|
||||
<div className="w-full grid grid-cols-2 sm:grid-cols-3 gap-4">
|
||||
<div className="w-full flex flex-col justify-start items-center">
|
||||
|
@ -13,7 +13,7 @@ const EngineIcon = (props: Props) => {
|
||||
return <Icon.DiMysql className={className} />;
|
||||
} else if (engine === Engine.PostgreSQL) {
|
||||
return <Icon.DiPostgresql className={className} />;
|
||||
}else if (engine === Engine.MSSQL) {
|
||||
} else if (engine === Engine.MSSQL) {
|
||||
return <Icon.DiMsqlServer className={className} />;
|
||||
} else {
|
||||
return <Icon.DiDatabase className={className} />;
|
||||
|
@ -22,7 +22,9 @@ const DataTableView = (props: Props) => {
|
||||
return rawResults.length === 0 ? (
|
||||
<div className="w-full flex flex-col justify-center items-center py-6 pt-10">
|
||||
<Icon.BsBox2 className="w-7 h-auto opacity-70" />
|
||||
<span className="text-sm font-mono text-gray-500 mt-2">{t("execution.message.no-data")}</span>
|
||||
<span className="text-sm font-mono text-gray-500 mt-2">
|
||||
{t("execution.message.no-data")}
|
||||
</span>
|
||||
</div>
|
||||
) : (
|
||||
<DataTable
|
||||
|
@ -10,7 +10,11 @@ const ExecutionWarningBanner = (props: Props) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<div className={`${className || ""} relative w-full flex flex-row justify-start items-center px-4 py-2 bg-yellow-100 dark:bg-zinc-700`}>
|
||||
<div
|
||||
className={`${
|
||||
className || ""
|
||||
} relative w-full flex flex-row justify-start items-center px-4 py-2 bg-yellow-100 dark:bg-zinc-700`}
|
||||
>
|
||||
<span className="text-sm leading-6 pr-4">
|
||||
<Icon.IoInformationCircleOutline className="inline-block h-5 w-auto -mt-0.5 mr-0.5 opacity-80" />
|
||||
{t("banner.non-select-sql-warning")}
|
||||
|
@ -6,7 +6,13 @@ interface Props {
|
||||
const NotificationView = (props: Props) => {
|
||||
const { message, style } = props;
|
||||
const additionalStyle = style === "error" ? "text-red-500" : "text-gray-500";
|
||||
return <p className={`${additionalStyle} w-full pl-4 mt-4 font-mono text-sm whitespace-pre-wrap`}>{message}</p>;
|
||||
return (
|
||||
<p
|
||||
className={`${additionalStyle} w-full pl-4 mt-4 font-mono text-sm whitespace-pre-wrap`}
|
||||
>
|
||||
{message}
|
||||
</p>
|
||||
);
|
||||
};
|
||||
|
||||
export default NotificationView;
|
||||
|
@ -15,12 +15,15 @@ const GitHubStarBadge = (props: Props) => {
|
||||
const getRepoStarCount = async () => {
|
||||
let starCount = 0;
|
||||
try {
|
||||
const { data } = await axios.get(`https://api.github.com/repos/sqlchat/sqlchat`, {
|
||||
headers: {
|
||||
Accept: "application/vnd.github.v3.star+json",
|
||||
Authorization: "",
|
||||
},
|
||||
});
|
||||
const { data } = await axios.get(
|
||||
`https://api.github.com/repos/sqlchat/sqlchat`,
|
||||
{
|
||||
headers: {
|
||||
Accept: "application/vnd.github.v3.star+json",
|
||||
Authorization: "",
|
||||
},
|
||||
}
|
||||
);
|
||||
starCount = data.stargazers_count as number;
|
||||
} catch (error) {
|
||||
// do nth
|
||||
@ -47,7 +50,11 @@ const GitHubStarBadge = (props: Props) => {
|
||||
<span className="mt-px">Star</span>
|
||||
</span>
|
||||
<div className="h-full block px-2 mt-px font-medium">
|
||||
{isRequesting ? <Icon.BiLoaderAlt className="w-3 h-auto animate-spin opacity-70" /> : stars}
|
||||
{isRequesting ? (
|
||||
<Icon.BiLoaderAlt className="w-3 h-auto animate-spin opacity-70" />
|
||||
) : (
|
||||
stars
|
||||
)}
|
||||
</div>
|
||||
</a>
|
||||
);
|
||||
|
@ -46,7 +46,14 @@ const LocaleSelector = () => {
|
||||
settingStore.setLocale(locale);
|
||||
};
|
||||
|
||||
return <Select className="w-28" value={locale} itemList={localeItemList} onValueChange={handleLocaleChange} />;
|
||||
return (
|
||||
<Select
|
||||
className="w-28"
|
||||
value={locale}
|
||||
itemList={localeItemList}
|
||||
onValueChange={handleLocaleChange}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default LocaleSelector;
|
||||
|
@ -8,7 +8,9 @@ import TextField from "./kit/TextField";
|
||||
const OpenAIApiConfigView = () => {
|
||||
const { t } = useTranslation();
|
||||
const settingStore = useSettingStore();
|
||||
const [openAIApiConfig, setOpenAIApiConfig] = useState(settingStore.setting.openAIApiConfig);
|
||||
const [openAIApiConfig, setOpenAIApiConfig] = useState(
|
||||
settingStore.setting.openAIApiConfig
|
||||
);
|
||||
|
||||
useDebounce(
|
||||
() => {
|
||||
@ -27,7 +29,9 @@ const OpenAIApiConfigView = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<h3 className="pl-4 text-sm text-gray-500">{t("setting.openai-api-configuration.self")}</h3>
|
||||
<h3 className="pl-4 text-sm text-gray-500">
|
||||
{t("setting.openai-api-configuration.self")}
|
||||
</h3>
|
||||
<div className="w-full border border-gray-200 dark:border-zinc-700 p-4 rounded-lg">
|
||||
<div className="flex flex-col">
|
||||
<label className="mb-1">Key</label>
|
||||
|
@ -9,7 +9,10 @@ interface Props {
|
||||
const ProductHuntBanner = (props: Props) => {
|
||||
const { className } = props;
|
||||
const { t } = useTranslation();
|
||||
const [hideBanner, setHideBanner] = useLocalStorage("hide-product-hunt-banner", false);
|
||||
const [hideBanner, setHideBanner] = useLocalStorage(
|
||||
"hide-product-hunt-banner",
|
||||
false
|
||||
);
|
||||
const show = !hideBanner;
|
||||
|
||||
return (
|
||||
@ -25,7 +28,10 @@ const ProductHuntBanner = (props: Props) => {
|
||||
>
|
||||
{t("banner.product-hunt")}
|
||||
</a>
|
||||
<button className="absolute right-2 sm:right-4 opacity-60 hover:opacity-100" onClick={() => setHideBanner(true)}>
|
||||
<button
|
||||
className="absolute right-2 sm:right-4 opacity-60 hover:opacity-100"
|
||||
onClick={() => setHideBanner(true)}
|
||||
>
|
||||
<Icon.BiX className="w-6 h-auto" />
|
||||
</button>
|
||||
</div>
|
||||
|
@ -16,12 +16,17 @@ import ExecutionWarningBanner from "./ExecutionView/ExecutionWarningBanner";
|
||||
const QueryDrawer = () => {
|
||||
const { t } = useTranslation();
|
||||
const queryStore = useQueryStore();
|
||||
const [executionResult, setExecutionResult] = useState<ExecutionResult | undefined>(undefined);
|
||||
const [executionResult, setExecutionResult] = useState<
|
||||
ExecutionResult | undefined
|
||||
>(undefined);
|
||||
const [statement, setStatement] = useState<string>("");
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const context = queryStore.context;
|
||||
const executionMessage = executionResult ? getMessageFromExecutionResult(executionResult) : "";
|
||||
const showExecutionWarningBanner = statement.trim() && !checkStatementIsSelect(statement);
|
||||
const executionMessage = executionResult
|
||||
? getMessageFromExecutionResult(executionResult)
|
||||
: "";
|
||||
const showExecutionWarningBanner =
|
||||
statement.trim() && !checkStatementIsSelect(statement);
|
||||
|
||||
useEffect(() => {
|
||||
if (!queryStore.showDrawer) {
|
||||
@ -84,25 +89,40 @@ const QueryDrawer = () => {
|
||||
const close = () => queryStore.toggleDrawer(false);
|
||||
|
||||
return (
|
||||
<Drawer open={queryStore.showDrawer} anchor="right" className="w-full" onClose={close}>
|
||||
<Drawer
|
||||
open={queryStore.showDrawer}
|
||||
anchor="right"
|
||||
className="w-full"
|
||||
onClose={close}
|
||||
>
|
||||
<div className="dark:text-gray-300 w-screen sm:w-[calc(60vw)] lg:w-[calc(50vw)] 2xl:w-[calc(40vw)] max-w-full flex flex-col justify-start items-start p-4">
|
||||
<button className="w-8 h-8 p-1 bg-zinc-600 text-gray-100 rounded-full hover:opacity-80" onClick={close}>
|
||||
<button
|
||||
className="w-8 h-8 p-1 bg-zinc-600 text-gray-100 rounded-full hover:opacity-80"
|
||||
onClick={close}
|
||||
>
|
||||
<Icon.IoMdClose className="w-full h-auto" />
|
||||
</button>
|
||||
<h3 className="font-bold text-2xl mt-4">{t("execution.title")}</h3>
|
||||
{!context ? (
|
||||
<div className="w-full flex flex-col justify-center items-center py-6 pt-10">
|
||||
<Icon.BiSad className="w-7 h-auto opacity-70" />
|
||||
<span className="text-sm font-mono text-gray-500 mt-2">{t("execution.message.no-connection")}</span>
|
||||
<span className="text-sm font-mono text-gray-500 mt-2">
|
||||
{t("execution.message.no-connection")}
|
||||
</span>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<div className="w-full flex flex-row justify-start items-center mt-4">
|
||||
<span className="opacity-70">{t("connection.self")}: </span>
|
||||
<EngineIcon className="w-6 h-auto" engine={context.connection.engineType} />
|
||||
<EngineIcon
|
||||
className="w-6 h-auto"
|
||||
engine={context.connection.engineType}
|
||||
/>
|
||||
<span>{context.database?.name}</span>
|
||||
</div>
|
||||
{showExecutionWarningBanner && <ExecutionWarningBanner className="rounded-lg mt-4" />}
|
||||
{showExecutionWarningBanner && (
|
||||
<ExecutionWarningBanner className="rounded-lg mt-4" />
|
||||
)}
|
||||
<div className="w-full h-auto mt-4 px-2 flex flex-row justify-between items-end border dark:border-zinc-700 rounded-lg overflow-clip">
|
||||
<TextareaAutosize
|
||||
className="w-full h-full outline-none border-none bg-transparent leading-6 pl-2 py-2 resize-none hide-scrollbar text-sm font-mono break-all whitespace-pre-wrap"
|
||||
@ -126,15 +146,22 @@ const QueryDrawer = () => {
|
||||
{isLoading ? (
|
||||
<div className="w-full flex flex-col justify-center items-center py-6 pt-10">
|
||||
<Icon.BiLoaderAlt className="w-7 h-auto opacity-70 animate-spin" />
|
||||
<span className="text-sm font-mono text-gray-500 mt-2">{t("execution.message.executing")}</span>
|
||||
<span className="text-sm font-mono text-gray-500 mt-2">
|
||||
{t("execution.message.executing")}
|
||||
</span>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
{executionResult ? (
|
||||
executionMessage ? (
|
||||
<NotificationView message={executionMessage} style={executionResult?.error ? "error" : "info"} />
|
||||
<NotificationView
|
||||
message={executionMessage}
|
||||
style={executionResult?.error ? "error" : "info"}
|
||||
/>
|
||||
) : (
|
||||
<DataTableView rawResults={executionResult?.rawResult || []} />
|
||||
<DataTableView
|
||||
rawResults={executionResult?.rawResult || []}
|
||||
/>
|
||||
)
|
||||
) : (
|
||||
<></>
|
||||
|
@ -11,7 +11,10 @@ interface Props {
|
||||
const QuotaOverflowBanner = (props: Props) => {
|
||||
const { className } = props;
|
||||
const { t } = useTranslation();
|
||||
const [hideBanner, setHideBanner] = useLocalStorage("hide-quota-overflow-banner", false);
|
||||
const [hideBanner, setHideBanner] = useLocalStorage(
|
||||
"hide-quota-overflow-banner",
|
||||
false
|
||||
);
|
||||
const [showSettingModal, setShowSettingModal] = useState(false);
|
||||
const show = !hideBanner;
|
||||
|
||||
@ -24,16 +27,24 @@ const QuotaOverflowBanner = (props: Props) => {
|
||||
>
|
||||
<div className="text-sm leading-6 pr-4 cursor-pointer">
|
||||
{t("banner.quota-overflow")}{" "}
|
||||
<button className="ml-1 underline hover:opacity-80" onClick={() => setShowSettingModal(true)}>
|
||||
<button
|
||||
className="ml-1 underline hover:opacity-80"
|
||||
onClick={() => setShowSettingModal(true)}
|
||||
>
|
||||
{t("banner.use-my-key")}
|
||||
</button>
|
||||
</div>
|
||||
<button className="absolute right-2 sm:right-4 opacity-60 hover:opacity-100" onClick={() => setHideBanner(true)}>
|
||||
<button
|
||||
className="absolute right-2 sm:right-4 opacity-60 hover:opacity-100"
|
||||
onClick={() => setHideBanner(true)}
|
||||
>
|
||||
<Icon.BiX className="w-6 h-auto" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{showSettingModal && <SettingModal close={() => setShowSettingModal(false)} />}
|
||||
{showSettingModal && (
|
||||
<SettingModal close={() => setShowSettingModal(false)} />
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@ -30,7 +30,9 @@ const SettingModal = (props: Props) => {
|
||||
<WeChatQRCodeView />
|
||||
</div>
|
||||
|
||||
<h3 className="pl-4 text-sm text-gray-500">{t("setting.basic.self")}</h3>
|
||||
<h3 className="pl-4 text-sm text-gray-500">
|
||||
{t("setting.basic.self")}
|
||||
</h3>
|
||||
<div className="w-full border border-gray-200 dark:border-zinc-700 p-4 rounded-lg space-y-2">
|
||||
<div className="w-full flex flex-row justify-between items-center gap-2">
|
||||
<span>{t("setting.basic.language")}</span>
|
||||
|
@ -22,7 +22,8 @@ const ConnectionList = () => {
|
||||
showSettingModal: false,
|
||||
showUpdateConversationModal: false,
|
||||
});
|
||||
const [editConnectionModalContext, setEditConnectionModalContext] = useState<Connection>();
|
||||
const [editConnectionModalContext, setEditConnectionModalContext] =
|
||||
useState<Connection>();
|
||||
const connectionList = connectionStore.connectionList;
|
||||
const currentConnectionCtx = connectionStore.currentConnectionCtx;
|
||||
|
||||
@ -35,7 +36,9 @@ const ConnectionList = () => {
|
||||
};
|
||||
|
||||
const handleConnectionSelect = async (connection: Connection) => {
|
||||
const databaseList = await connectionStore.getOrFetchDatabaseList(connection);
|
||||
const databaseList = await connectionStore.getOrFetchDatabaseList(
|
||||
connection
|
||||
);
|
||||
connectionStore.setCurrentConnectionCtx({
|
||||
connection,
|
||||
database: head(databaseList),
|
||||
@ -53,7 +56,10 @@ const ConnectionList = () => {
|
||||
return (
|
||||
<>
|
||||
<button
|
||||
className={`w-full h-14 rounded-l-lg p-2 mt-1 group ${currentConnectionCtx === undefined && "bg-gray-100 dark:bg-zinc-700 shadow"}`}
|
||||
className={`w-full h-14 rounded-l-lg p-2 mt-1 group ${
|
||||
currentConnectionCtx === undefined &&
|
||||
"bg-gray-100 dark:bg-zinc-700 shadow"
|
||||
}`}
|
||||
onClick={() => connectionStore.setCurrentConnectionCtx(undefined)}
|
||||
>
|
||||
<img src="/chat-logo-bot.webp" className="w-7 h-auto mx-auto" alt="" />
|
||||
@ -62,7 +68,8 @@ const ConnectionList = () => {
|
||||
<Tooltip key={connection.id} title={connection.title} side="right">
|
||||
<button
|
||||
className={`relative w-full h-14 rounded-l-lg p-2 mt-2 group ${
|
||||
currentConnectionCtx?.connection.id === connection.id && "bg-gray-100 dark:bg-zinc-700 shadow"
|
||||
currentConnectionCtx?.connection.id === connection.id &&
|
||||
"bg-gray-100 dark:bg-zinc-700 shadow"
|
||||
}`}
|
||||
onClick={() => handleConnectionSelect(connection)}
|
||||
>
|
||||
@ -75,7 +82,10 @@ const ConnectionList = () => {
|
||||
>
|
||||
<Icon.FiEdit3 className="w-3.5 h-auto dark:text-gray-300" />
|
||||
</span>
|
||||
<EngineIcon engine={connection.engineType} className="w-auto h-full mx-auto dark:text-gray-300" />
|
||||
<EngineIcon
|
||||
engine={connection.engineType}
|
||||
className="w-auto h-full mx-auto dark:text-gray-300"
|
||||
/>
|
||||
</button>
|
||||
</Tooltip>
|
||||
))}
|
||||
@ -89,7 +99,10 @@ const ConnectionList = () => {
|
||||
</Tooltip>
|
||||
|
||||
{state.showCreateConnectionModal && (
|
||||
<CreateConnectionModal connection={editConnectionModalContext} close={() => toggleCreateConnectionModal(false)} />
|
||||
<CreateConnectionModal
|
||||
connection={editConnectionModalContext}
|
||||
close={() => toggleCreateConnectionModal(false)}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
@ -1,6 +1,10 @@
|
||||
import { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useConversationStore, useConnectionStore, useLayoutStore } from "@/store";
|
||||
import {
|
||||
useConversationStore,
|
||||
useConnectionStore,
|
||||
useLayoutStore,
|
||||
} from "@/store";
|
||||
import { Conversation } from "@/types";
|
||||
import Dropdown, { DropdownItem } from "../kit/Dropdown";
|
||||
import Icon from "../Icon";
|
||||
@ -18,7 +22,8 @@ const ConversationList = () => {
|
||||
const [state, setState] = useState<State>({
|
||||
showUpdateConversationModal: false,
|
||||
});
|
||||
const [updateConversationModalContext, setUpdateConversationModalContext] = useState<Conversation>();
|
||||
const [updateConversationModalContext, setUpdateConversationModalContext] =
|
||||
useState<Conversation>();
|
||||
const currentConnectionCtx = connectionStore.currentConnectionCtx;
|
||||
const conversationList = conversationStore.conversationList.filter(
|
||||
(conversation) =>
|
||||
@ -37,7 +42,10 @@ const ConversationList = () => {
|
||||
if (!currentConnectionCtx) {
|
||||
conversationStore.createConversation();
|
||||
} else {
|
||||
conversationStore.createConversation(currentConnectionCtx.connection.id, currentConnectionCtx.database?.name);
|
||||
conversationStore.createConversation(
|
||||
currentConnectionCtx.connection.id,
|
||||
currentConnectionCtx.database?.name
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@ -69,7 +77,8 @@ const ConversationList = () => {
|
||||
<div
|
||||
key={conversation.id}
|
||||
className={`w-full mt-2 first:mt-4 py-3 pl-4 pr-2 rounded-lg flex flex-row justify-start items-center cursor-pointer dark:text-gray-300 border border-transparent group hover:bg-white dark:hover:bg-zinc-800 ${
|
||||
conversation.id === conversationStore.currentConversationId && "bg-white dark:bg-zinc-800 border-gray-200 font-medium"
|
||||
conversation.id === conversationStore.currentConversationId &&
|
||||
"bg-white dark:bg-zinc-800 border-gray-200 font-medium"
|
||||
}`}
|
||||
onClick={() => handleConversationSelect(conversation)}
|
||||
>
|
||||
@ -78,7 +87,9 @@ const ConversationList = () => {
|
||||
) : (
|
||||
<Icon.IoChatbubbleOutline className="w-5 h-auto mr-1.5 opacity-80 shrink-0" />
|
||||
)}
|
||||
<span className="truncate grow">{conversation.title || "SQL Chat"}</span>
|
||||
<span className="truncate grow">
|
||||
{conversation.title || "SQL Chat"}
|
||||
</span>
|
||||
<Dropdown
|
||||
tigger={
|
||||
<button className="w-4 h-4 shrink-0 group-hover:visible invisible flex justify-center items-center text-gray-400 hover:text-gray-500">
|
||||
@ -114,7 +125,10 @@ const ConversationList = () => {
|
||||
</button>
|
||||
|
||||
{updateConversationModalContext && state.showUpdateConversationModal && (
|
||||
<UpdateConversationModal close={() => toggleUpdateConversationModal(false)} conversation={updateConversationModalContext} />
|
||||
<UpdateConversationModal
|
||||
close={() => toggleUpdateConversationModal(false)}
|
||||
conversation={updateConversationModalContext}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
@ -32,7 +32,14 @@ const ThemeSelector = () => {
|
||||
settingStore.setTheme(theme);
|
||||
};
|
||||
|
||||
return <Select className="w-auto min-w-[120px]" value={theme} itemList={themeItemList} onValueChange={handleThemeChange} />;
|
||||
return (
|
||||
<Select
|
||||
className="w-auto min-w-[120px]"
|
||||
value={theme}
|
||||
itemList={themeItemList}
|
||||
onValueChange={handleThemeChange}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default ThemeSelector;
|
||||
|
@ -14,7 +14,10 @@ const ThemeSwitch = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<button className="w-10 h-10 p-1 rounded-full flex flex-row justify-center items-center hover:bg-gray-100" onClick={handleThemeChange}>
|
||||
<button
|
||||
className="w-10 h-10 p-1 rounded-full flex flex-row justify-center items-center hover:bg-gray-100"
|
||||
onClick={handleThemeChange}
|
||||
>
|
||||
<Icon.IoSunny className="text-gray-600 w-6 h-auto" />
|
||||
</button>
|
||||
);
|
||||
|
@ -27,7 +27,9 @@ const UpdateConversationModal = (props: Props) => {
|
||||
label: assistant.name,
|
||||
};
|
||||
});
|
||||
const currentAssistant = assistantList.find((assistant) => assistant.id === assistantId);
|
||||
const currentAssistant = assistantList.find(
|
||||
(assistant) => assistant.id === assistantId
|
||||
);
|
||||
|
||||
const handleSaveEdit = () => {
|
||||
const formatedTitle = title.trim();
|
||||
@ -46,17 +48,30 @@ const UpdateConversationModal = (props: Props) => {
|
||||
return (
|
||||
<Modal title={t("conversation.update")} onClose={close}>
|
||||
<div className="w-full flex flex-col justify-start items-start mt-2">
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">{t("conversation.title")}</label>
|
||||
<TextField placeholder={t("conversation.conversation-title") || ""} value={title} onChange={(value) => setTitle(value)} />
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||
{t("conversation.title")}
|
||||
</label>
|
||||
<TextField
|
||||
placeholder={t("conversation.conversation-title") || ""}
|
||||
value={title}
|
||||
onChange={(value) => setTitle(value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="w-full flex flex-col justify-start items-start mt-2">
|
||||
<label className="text-sm font-medium text-gray-700 mb-1 flex flex-row justify-start items-center">
|
||||
{t("assistant.self")} <BetaBadge />
|
||||
</label>
|
||||
<Select className="w-full" value={assistantId} itemList={assistantItems} onValueChange={(value) => setAssistantId(value)} />
|
||||
<Select
|
||||
className="w-full"
|
||||
value={assistantId}
|
||||
itemList={assistantItems}
|
||||
onValueChange={(value) => setAssistantId(value)}
|
||||
/>
|
||||
{currentAssistant && (
|
||||
<div className="w-full flex flex-col justify-start items-start">
|
||||
<p className="block text-sm text-gray-700 mt-2 mx-3">{currentAssistant.description}</p>
|
||||
<p className="block text-sm text-gray-700 mt-2 mx-3">
|
||||
{currentAssistant.description}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
<a
|
||||
@ -64,7 +79,8 @@ const UpdateConversationModal = (props: Props) => {
|
||||
href="https://github.com/sqlchat/sqlchat/tree/main/assistants"
|
||||
target="_blank"
|
||||
>
|
||||
{t("assistant.create-your-bot")} <Icon.FiExternalLink className="inline-block -mt-0.5" />
|
||||
{t("assistant.create-your-bot")}{" "}
|
||||
<Icon.FiExternalLink className="inline-block -mt-0.5" />
|
||||
</a>
|
||||
</div>
|
||||
<div className="w-full flex flex-row justify-end items-center mt-4 space-x-2">
|
||||
|
@ -19,7 +19,9 @@ const Modal = (props: Props) => {
|
||||
className || ""
|
||||
} flex flex-col bg-white dark:bg-zinc-800 rounded-xl p-4 fixed top-[50%] left-[50%] h-auto max-h-[85vh] w-[90vw] max-w-[90vw] sm:max-w-lg translate-x-[-50%] translate-y-[-50%] z-100 outline-none`}
|
||||
>
|
||||
<p className="text-lg pl-1 text-black dark:text-gray-300 font-medium mb-2">{title}</p>
|
||||
<p className="text-lg pl-1 text-black dark:text-gray-300 font-medium mb-2">
|
||||
{title}
|
||||
</p>
|
||||
<button
|
||||
className="absolute top-3 right-3 outline-none w-8 h-8 p-1 bg-zinc-600 rounded-full text-gray-300 hover:opacity-80"
|
||||
aria-label="Close"
|
||||
@ -27,7 +29,9 @@ const Modal = (props: Props) => {
|
||||
>
|
||||
<Icon.IoClose className="w-full h-auto" />
|
||||
</button>
|
||||
<div className="w-full px-1 h-[calc(100%-36px)] flex flex-col justify-start items-start overflow-y-auto">{children}</div>
|
||||
<div className="w-full px-1 h-[calc(100%-36px)] flex flex-col justify-start items-start overflow-y-auto">
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
</ModalUI>
|
||||
);
|
||||
|
@ -16,7 +16,9 @@ const Popover = (props: Props) => {
|
||||
<PopoverUI.Portal>
|
||||
<PopoverUI.Content
|
||||
asChild
|
||||
className={`${className || ""} z-[999] p-2 bg-white dark:bg-zinc-700 drop-shadow rounded-lg`}
|
||||
className={`${
|
||||
className || ""
|
||||
} z-[999] p-2 bg-white dark:bg-zinc-700 drop-shadow rounded-lg`}
|
||||
sideOffset={5}
|
||||
>
|
||||
{children}
|
||||
|
@ -1,5 +1,5 @@
|
||||
import * as SelectUI from "@radix-ui/react-select";
|
||||
import * as ScrollArea from '@radix-ui/react-scroll-area';
|
||||
import * as ScrollArea from "@radix-ui/react-scroll-area";
|
||||
import React from "react";
|
||||
import Icon from "../Icon";
|
||||
|
||||
@ -37,18 +37,34 @@ const Select = (props: Props) => {
|
||||
}}
|
||||
position="popper"
|
||||
>
|
||||
<ScrollArea.Root className={`${itemList.length > 7 ? "h-80 overflow-hidden" : "max-h-80 overflow-auto"} border dark:border-zinc-800 rounded-lg drop-shadow-lg`} type="auto">
|
||||
<SelectUI.Viewport asChild className="bg-white dark:bg-zinc-700 p-1 rounded-lg">
|
||||
<ScrollArea.Root
|
||||
className={`${
|
||||
itemList.length > 7
|
||||
? "h-80 overflow-hidden"
|
||||
: "max-h-80 overflow-auto"
|
||||
} border dark:border-zinc-800 rounded-lg drop-shadow-lg`}
|
||||
type="auto"
|
||||
>
|
||||
<SelectUI.Viewport
|
||||
asChild
|
||||
className="bg-white dark:bg-zinc-700 p-1 rounded-lg"
|
||||
>
|
||||
<ScrollArea.Viewport className="w-full h-full">
|
||||
<SelectUI.Group>
|
||||
{placeholder && <SelectUI.Label className="w-full px-3 mt-2 mb-2 text-sm text-gray-400">{placeholder}</SelectUI.Label>}
|
||||
<SelectUI.Group>
|
||||
{placeholder && (
|
||||
<SelectUI.Label className="w-full px-3 mt-2 mb-2 text-sm text-gray-400">
|
||||
{placeholder}
|
||||
</SelectUI.Label>
|
||||
)}
|
||||
{itemList.map((item) => (
|
||||
<SelectUI.Item
|
||||
className="w-full px-3 py-2 whitespace-nowrap truncate text-ellipsis overflow-x-hidden text-sm rounded-lg flex flex-row justify-between items-center cursor-pointer hover:bg-gray-100 dark:hover:bg-zinc-800"
|
||||
key={item.label}
|
||||
value={item.value}
|
||||
>
|
||||
<SelectUI.ItemText className="truncate">{item.label}</SelectUI.ItemText>
|
||||
<SelectUI.ItemText className="truncate">
|
||||
{item.label}
|
||||
</SelectUI.ItemText>
|
||||
<SelectUI.ItemIndicator className="w-5 h-auto">
|
||||
<Icon.BiCheck className="w-full h-auto" />
|
||||
</SelectUI.ItemIndicator>
|
||||
|
@ -19,11 +19,16 @@ const getDefaultProps = () => ({
|
||||
});
|
||||
|
||||
const TextField = (props: Props) => {
|
||||
const { value, disabled, className, placeholder, type, onChange } = { ...getDefaultProps(), ...props };
|
||||
const { value, disabled, className, placeholder, type, onChange } = {
|
||||
...getDefaultProps(),
|
||||
...props,
|
||||
};
|
||||
|
||||
return (
|
||||
<input
|
||||
className={`${className || ""} w-full border px-3 py-2 rounded-lg dark:border-zinc-700 dark:bg-zinc-800 focus:outline-2`}
|
||||
className={`${
|
||||
className || ""
|
||||
} w-full border px-3 py-2 rounded-lg dark:border-zinc-700 dark:bg-zinc-800 focus:outline-2`}
|
||||
type={type}
|
||||
disabled={disabled}
|
||||
placeholder={placeholder}
|
||||
|
@ -5,10 +5,17 @@ import mssql from "./mssql";
|
||||
|
||||
export interface Connector {
|
||||
testConnection: () => Promise<boolean>;
|
||||
execute: (databaseName: string, statement: string) => Promise<ExecutionResult>;
|
||||
execute: (
|
||||
databaseName: string,
|
||||
statement: string
|
||||
) => Promise<ExecutionResult>;
|
||||
getDatabases: () => Promise<string[]>;
|
||||
getTables: (databaseName: string) => Promise<string[]>;
|
||||
getTableStructure: (databaseName: string, tableName: string, structureFetched: (tableName: string, structure: string) => void) => Promise<void>;
|
||||
getTableStructure: (
|
||||
databaseName: string,
|
||||
tableName: string,
|
||||
structureFetched: (tableName: string, structure: string) => void
|
||||
) => Promise<void>;
|
||||
}
|
||||
|
||||
export const newConnector = (connection: Connection): Connector => {
|
||||
|
@ -4,7 +4,9 @@ import { Connector } from "..";
|
||||
|
||||
const systemDatabases = ["master", "tempdb", "model", "msdb"];
|
||||
|
||||
const getMSSQLConnection = async (connection: Connection): Promise<ConnectionPool> => {
|
||||
const getMSSQLConnection = async (
|
||||
connection: Connection
|
||||
): Promise<ConnectionPool> => {
|
||||
const connectionOptions: any = {
|
||||
server: connection.host,
|
||||
port: parseInt(connection.port),
|
||||
@ -32,7 +34,11 @@ const testConnection = async (connection: Connection): Promise<boolean> => {
|
||||
return true;
|
||||
};
|
||||
|
||||
const execute = async (connection: Connection, databaseName: string, statement: string): Promise<any> => {
|
||||
const execute = async (
|
||||
connection: Connection,
|
||||
databaseName: string,
|
||||
statement: string
|
||||
): Promise<any> => {
|
||||
const pool = await getMSSQLConnection(connection);
|
||||
const request = pool.request();
|
||||
const result = await request.query(`USE ${databaseName}; ${statement}`);
|
||||
@ -48,7 +54,11 @@ const execute = async (connection: Connection, databaseName: string, statement:
|
||||
const getDatabases = async (connection: Connection): Promise<string[]> => {
|
||||
const pool = await getMSSQLConnection(connection);
|
||||
const request = pool.request();
|
||||
const result = await request.query(`SELECT name FROM sys.databases WHERE name NOT IN ('${systemDatabases.join("','")}');`);
|
||||
const result = await request.query(
|
||||
`SELECT name FROM sys.databases WHERE name NOT IN ('${systemDatabases.join(
|
||||
"','"
|
||||
)}');`
|
||||
);
|
||||
await pool.close();
|
||||
const databaseList = [];
|
||||
for (const row of result.recordset) {
|
||||
@ -59,7 +69,10 @@ const getDatabases = async (connection: Connection): Promise<string[]> => {
|
||||
return databaseList;
|
||||
};
|
||||
|
||||
const getTables = async (connection: Connection, databaseName: string): Promise<string[]> => {
|
||||
const getTables = async (
|
||||
connection: Connection,
|
||||
databaseName: string
|
||||
): Promise<string[]> => {
|
||||
const pool = await getMSSQLConnection(connection);
|
||||
const request = pool.request();
|
||||
const result = await request.query(
|
||||
@ -75,7 +88,12 @@ const getTables = async (connection: Connection, databaseName: string): Promise<
|
||||
return tableList;
|
||||
};
|
||||
|
||||
const getTableStructure = async (connection: Connection, databaseName: string, tableName: string, structureFetched: (tableName: string,structure: string) => void): Promise<void> => {
|
||||
const getTableStructure = async (
|
||||
connection: Connection,
|
||||
databaseName: string,
|
||||
tableName: string,
|
||||
structureFetched: (tableName: string, structure: string) => void
|
||||
): Promise<void> => {
|
||||
const pool = await getMSSQLConnection(connection);
|
||||
const request = pool.request();
|
||||
const { recordset } = await request.query(
|
||||
@ -86,21 +104,32 @@ const getTableStructure = async (connection: Connection, databaseName: string, t
|
||||
// Transform to standard schema string.
|
||||
for (const row of recordset) {
|
||||
columnList.push(
|
||||
`${row["COLUMN_NAME"]} ${row["DATA_TYPE"].toUpperCase()} ${String(row["IS_NULLABLE"]).toUpperCase() === "NO" ? "NOT NULL" : ""}`
|
||||
`${row["COLUMN_NAME"]} ${row["DATA_TYPE"].toUpperCase()} ${
|
||||
String(row["IS_NULLABLE"]).toUpperCase() === "NO" ? "NOT NULL" : ""
|
||||
}`
|
||||
);
|
||||
}
|
||||
structureFetched(tableName, `CREATE TABLE [${tableName}] (
|
||||
structureFetched(
|
||||
tableName,
|
||||
`CREATE TABLE [${tableName}] (
|
||||
${columnList.join(",\n")}
|
||||
);`);
|
||||
);`
|
||||
);
|
||||
};
|
||||
|
||||
const newConnector = (connection: Connection): Connector => {
|
||||
return {
|
||||
testConnection: () => testConnection(connection),
|
||||
execute: (databaseName: string, statement: string) => execute(connection, databaseName, statement),
|
||||
execute: (databaseName: string, statement: string) =>
|
||||
execute(connection, databaseName, statement),
|
||||
getDatabases: () => getDatabases(connection),
|
||||
getTables: (databaseName: string) => getTables(connection, databaseName),
|
||||
getTableStructure: (databaseName: string, tableName: string, structureFetched: (tableName: string, structure: string) => void) => getTableStructure(connection, databaseName, tableName, structureFetched),
|
||||
getTableStructure: (
|
||||
databaseName: string,
|
||||
tableName: string,
|
||||
structureFetched: (tableName: string, structure: string) => void
|
||||
) =>
|
||||
getTableStructure(connection, databaseName, tableName, structureFetched),
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -3,9 +3,16 @@ import mysql, { RowDataPacket } from "mysql2/promise";
|
||||
import { Connection, ExecutionResult } from "@/types";
|
||||
import { Connector } from "..";
|
||||
|
||||
const systemDatabases = ["information_schema", "mysql", "performance_schema", "sys"];
|
||||
const systemDatabases = [
|
||||
"information_schema",
|
||||
"mysql",
|
||||
"performance_schema",
|
||||
"sys",
|
||||
];
|
||||
|
||||
const getMySQLConnection = async (connection: Connection): Promise<mysql.Connection> => {
|
||||
const getMySQLConnection = async (
|
||||
connection: Connection
|
||||
): Promise<mysql.Connection> => {
|
||||
const connectionOptions: ConnectionOptions = {
|
||||
host: connection.host,
|
||||
port: parseInt(connection.port),
|
||||
@ -30,7 +37,11 @@ const testConnection = async (connection: Connection): Promise<boolean> => {
|
||||
return true;
|
||||
};
|
||||
|
||||
const execute = async (connection: Connection, databaseName: string, statement: string): Promise<any> => {
|
||||
const execute = async (
|
||||
connection: Connection,
|
||||
databaseName: string,
|
||||
statement: string
|
||||
): Promise<any> => {
|
||||
connection.database = databaseName;
|
||||
const conn = await getMySQLConnection(connection);
|
||||
const [rows] = await conn.execute(statement);
|
||||
@ -64,7 +75,10 @@ const getDatabases = async (connection: Connection): Promise<string[]> => {
|
||||
return databaseList;
|
||||
};
|
||||
|
||||
const getTables = async (connection: Connection, databaseName: string): Promise<string[]> => {
|
||||
const getTables = async (
|
||||
connection: Connection,
|
||||
databaseName: string
|
||||
): Promise<string[]> => {
|
||||
const conn = await getMySQLConnection(connection);
|
||||
const [rows] = await conn.query<RowDataPacket[]>(
|
||||
`SELECT TABLE_NAME as table_name FROM information_schema.tables WHERE TABLE_SCHEMA=? AND TABLE_TYPE='BASE TABLE';`,
|
||||
@ -80,9 +94,16 @@ const getTables = async (connection: Connection, databaseName: string): Promise<
|
||||
return tableList;
|
||||
};
|
||||
|
||||
const getTableStructure = async (connection: Connection, databaseName: string, tableName: string, structureFetched: (tableName: string,structure: string) => void): Promise<void> => {
|
||||
const getTableStructure = async (
|
||||
connection: Connection,
|
||||
databaseName: string,
|
||||
tableName: string,
|
||||
structureFetched: (tableName: string, structure: string) => void
|
||||
): Promise<void> => {
|
||||
const conn = await getMySQLConnection(connection);
|
||||
const [rows] = await conn.query<RowDataPacket[]>(`SHOW CREATE TABLE \`${databaseName}\`.\`${tableName}\`;`);
|
||||
const [rows] = await conn.query<RowDataPacket[]>(
|
||||
`SHOW CREATE TABLE \`${databaseName}\`.\`${tableName}\`;`
|
||||
);
|
||||
conn.destroy();
|
||||
if (rows.length !== 1) {
|
||||
throw new Error("Unexpected number of rows.");
|
||||
@ -93,10 +114,16 @@ const getTableStructure = async (connection: Connection, databaseName: string, t
|
||||
const newConnector = (connection: Connection): Connector => {
|
||||
return {
|
||||
testConnection: () => testConnection(connection),
|
||||
execute: (databaseName: string, statement: string) => execute(connection, databaseName, statement),
|
||||
execute: (databaseName: string, statement: string) =>
|
||||
execute(connection, databaseName, statement),
|
||||
getDatabases: () => getDatabases(connection),
|
||||
getTables: (databaseName: string) => getTables(connection, databaseName),
|
||||
getTableStructure: (databaseName: string, tableName: string, structureFetched: (tableName: string, structure: string) => void) => getTableStructure(connection, databaseName, tableName, structureFetched),
|
||||
getTableStructure: (
|
||||
databaseName: string,
|
||||
tableName: string,
|
||||
structureFetched: (tableName: string, structure: string) => void
|
||||
) =>
|
||||
getTableStructure(connection, databaseName, tableName, structureFetched),
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -27,7 +27,11 @@ const testConnection = async (connection: Connection): Promise<boolean> => {
|
||||
return true;
|
||||
};
|
||||
|
||||
const execute = async (connection: Connection, databaseName: string, statement: string): Promise<any> => {
|
||||
const execute = async (
|
||||
connection: Connection,
|
||||
databaseName: string,
|
||||
statement: string
|
||||
): Promise<any> => {
|
||||
connection.database = databaseName;
|
||||
const client = newPostgresClient(connection);
|
||||
await client.connect();
|
||||
@ -65,7 +69,10 @@ const getDatabases = async (connection: Connection): Promise<string[]> => {
|
||||
return databaseList;
|
||||
};
|
||||
|
||||
const getTables = async (connection: Connection, databaseName: string): Promise<string[]> => {
|
||||
const getTables = async (
|
||||
connection: Connection,
|
||||
databaseName: string
|
||||
): Promise<string[]> => {
|
||||
connection.database = databaseName;
|
||||
const client = newPostgresClient(connection);
|
||||
await client.connect();
|
||||
@ -83,7 +90,12 @@ const getTables = async (connection: Connection, databaseName: string): Promise<
|
||||
return tableList;
|
||||
};
|
||||
|
||||
const getTableStructure = async (connection: Connection, databaseName: string, tableName: string, structureFetched: (tableName: string,structure: string) => void): Promise<void> => {
|
||||
const getTableStructure = async (
|
||||
connection: Connection,
|
||||
databaseName: string,
|
||||
tableName: string,
|
||||
structureFetched: (tableName: string, structure: string) => void
|
||||
): Promise<void> => {
|
||||
connection.database = databaseName;
|
||||
const client = newPostgresClient(connection);
|
||||
await client.connect();
|
||||
@ -96,21 +108,32 @@ const getTableStructure = async (connection: Connection, databaseName: string, t
|
||||
// TODO(steven): transform it to standard schema string.
|
||||
for (const row of rows) {
|
||||
columnList.push(
|
||||
`${row["column_name"]} ${row["data_type"].toUpperCase()} ${String(row["is_nullable"]).toUpperCase() === "NO" ? "NOT NULL" : ""}`
|
||||
`${row["column_name"]} ${row["data_type"].toUpperCase()} ${
|
||||
String(row["is_nullable"]).toUpperCase() === "NO" ? "NOT NULL" : ""
|
||||
}`
|
||||
);
|
||||
}
|
||||
structureFetched(tableName, `CREATE TABLE \`${tableName}\` (
|
||||
structureFetched(
|
||||
tableName,
|
||||
`CREATE TABLE \`${tableName}\` (
|
||||
${columnList.join(",\n")}
|
||||
);`);
|
||||
);`
|
||||
);
|
||||
};
|
||||
|
||||
const newConnector = (connection: Connection): Connector => {
|
||||
return {
|
||||
testConnection: () => testConnection(connection),
|
||||
execute: (databaseName: string, statement: string) => execute(connection, databaseName, statement),
|
||||
execute: (databaseName: string, statement: string) =>
|
||||
execute(connection, databaseName, statement),
|
||||
getDatabases: () => getDatabases(connection),
|
||||
getTables: (databaseName: string) => getTables(connection, databaseName),
|
||||
getTableStructure: (databaseName: string, tableName: string, structureFetched: (tableName: string, structure: string) => void) => getTableStructure(connection, databaseName, tableName, structureFetched),
|
||||
getTableStructure: (
|
||||
databaseName: string,
|
||||
tableName: string,
|
||||
structureFetched: (tableName: string, structure: string) => void
|
||||
) =>
|
||||
getTableStructure(connection, databaseName, tableName, structureFetched),
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -52,7 +52,10 @@ function MyApp({ Component, pageProps }: AppProps) {
|
||||
const theme = settingStore.setting.theme;
|
||||
let currentAppearance = theme;
|
||||
if (theme === "system") {
|
||||
if (window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches) {
|
||||
if (
|
||||
window.matchMedia &&
|
||||
window.matchMedia("(prefers-color-scheme: dark)").matches
|
||||
) {
|
||||
currentAppearance = "dark";
|
||||
} else {
|
||||
currentAppearance = "light";
|
||||
|
@ -1,4 +1,8 @@
|
||||
import { createParser, ParsedEvent, ReconnectInterval } from "eventsource-parser";
|
||||
import {
|
||||
createParser,
|
||||
ParsedEvent,
|
||||
ReconnectInterval,
|
||||
} from "eventsource-parser";
|
||||
import { NextRequest } from "next/server";
|
||||
import { API_KEY } from "@/env";
|
||||
import { openAIApiEndpoint, openAIApiKey } from "@/utils";
|
||||
@ -26,7 +30,9 @@ const handler = async (req: NextRequest) => {
|
||||
const reqBody = await req.json();
|
||||
const openAIApiConfig = reqBody.openAIApiConfig;
|
||||
const apiKey = openAIApiConfig?.key || openAIApiKey;
|
||||
const apiEndpoint = getApiEndpoint(openAIApiConfig?.endpoint || openAIApiEndpoint);
|
||||
const apiEndpoint = getApiEndpoint(
|
||||
openAIApiConfig?.endpoint || openAIApiEndpoint
|
||||
);
|
||||
const res = await fetch(apiEndpoint, {
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
|
@ -16,18 +16,20 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
const connector = newConnector(connection);
|
||||
const tableStructures: Table[] = [];
|
||||
const rawTableNameList = await connector.getTables(db);
|
||||
const structureFetched = (tableName:string, structure:string) => {
|
||||
const structureFetched = (tableName: string, structure: string) => {
|
||||
tableStructures.push({
|
||||
name: tableName,
|
||||
structure,
|
||||
});
|
||||
}
|
||||
Promise.all(rawTableNameList.map(async (tableName) =>
|
||||
connector.getTableStructure(db, tableName, structureFetched)
|
||||
)).then(() => {
|
||||
};
|
||||
Promise.all(
|
||||
rawTableNameList.map(async (tableName) =>
|
||||
connector.getTableStructure(db, tableName, structureFetched)
|
||||
)
|
||||
).then(() => {
|
||||
res.status(200).json({
|
||||
data: tableStructures,
|
||||
});
|
||||
});
|
||||
});
|
||||
} catch (error: any) {
|
||||
res.status(400).json({
|
||||
|
@ -4,7 +4,10 @@ import { Conversation, Message } from "@/types";
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
export default async function handler(
|
||||
req: NextApiRequest,
|
||||
res: NextApiResponse
|
||||
) {
|
||||
if (req.method !== "POST") {
|
||||
return res.status(405).json([]);
|
||||
}
|
||||
|
@ -6,12 +6,18 @@ import React from "react";
|
||||
|
||||
// Use dynamic import to avoid page hydrated.
|
||||
// reference: https://github.com/pmndrs/zustand/issues/1145#issuecomment-1316431268
|
||||
const ConnectionSidebar = dynamic(() => import("@/components/ConnectionSidebar"), {
|
||||
ssr: false,
|
||||
});
|
||||
const ConversationView = dynamic(() => import("@/components/ConversationView"), {
|
||||
ssr: false,
|
||||
});
|
||||
const ConnectionSidebar = dynamic(
|
||||
() => import("@/components/ConnectionSidebar"),
|
||||
{
|
||||
ssr: false,
|
||||
}
|
||||
);
|
||||
const ConversationView = dynamic(
|
||||
() => import("@/components/ConversationView"),
|
||||
{
|
||||
ssr: false,
|
||||
}
|
||||
);
|
||||
const QueryDrawer = dynamic(() => import("@/components/QueryDrawer"), {
|
||||
ssr: false,
|
||||
});
|
||||
@ -20,14 +26,31 @@ const IndexPage: NextPage = () => {
|
||||
return (
|
||||
<div>
|
||||
<Head>
|
||||
<title>SQL Chat - Chat-based SQL Client and Editor for the next decade</title>
|
||||
<meta name="description" content="Chat-based SQL Client and Editor for the next decade" />
|
||||
<title>
|
||||
SQL Chat - Chat-based SQL Client and Editor for the next decade
|
||||
</title>
|
||||
<meta
|
||||
name="description"
|
||||
content="Chat-based SQL Client and Editor for the next decade"
|
||||
/>
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
<meta name="og:title" property="og:title" content="SQL Chat" />
|
||||
<meta name="og:description" property="og:description" content="Chat-based SQL Client and Editor for the next decade" />
|
||||
<meta name="og:image" property="og:image" content="https://www.sqlchat.ai/chat-logo-and-text.webp" />
|
||||
<meta
|
||||
name="og:description"
|
||||
property="og:description"
|
||||
content="Chat-based SQL Client and Editor for the next decade"
|
||||
/>
|
||||
<meta
|
||||
name="og:image"
|
||||
property="og:image"
|
||||
content="https://www.sqlchat.ai/chat-logo-and-text.webp"
|
||||
/>
|
||||
<meta name="og:type" property="og:type" content="website" />
|
||||
<meta name="og:url" property="og:url" content="https://www.sqlchat.ai" />
|
||||
<meta
|
||||
name="og:url"
|
||||
property="og:url"
|
||||
content="https://www.sqlchat.ai"
|
||||
/>
|
||||
</Head>
|
||||
|
||||
<h1 className="sr-only">SQL Chat</h1>
|
||||
@ -38,7 +61,11 @@ const IndexPage: NextPage = () => {
|
||||
<QueryDrawer />
|
||||
</main>
|
||||
|
||||
<Script defer data-domain="sqlchat.ai" src="https://plausible.io/js/script.js" />
|
||||
<Script
|
||||
defer
|
||||
data-domain="sqlchat.ai"
|
||||
src="https://plausible.io/js/script.js"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -5,11 +5,13 @@ import * as customAssistantList from "../../assistants";
|
||||
export const GeneralBotId = "general-bot";
|
||||
export const SQLChatBotId = "sql-chat-bot";
|
||||
|
||||
export const assistantList: Assistant[] = Object.keys(customAssistantList).map((name) => {
|
||||
return {
|
||||
...((customAssistantList as any)[name].default as Assistant),
|
||||
};
|
||||
});
|
||||
export const assistantList: Assistant[] = Object.keys(customAssistantList).map(
|
||||
(name) => {
|
||||
return {
|
||||
...((customAssistantList as any)[name].default as Assistant),
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
export const getAssistantById = (id: Id) => {
|
||||
const assistant = assistantList.find((assistant) => assistant.id === id);
|
||||
|
@ -26,11 +26,22 @@ interface ConnectionState {
|
||||
databaseList: Database[];
|
||||
currentConnectionCtx?: ConnectionContext;
|
||||
createConnection: (connection: Connection) => Connection;
|
||||
setCurrentConnectionCtx: (connectionCtx: ConnectionContext | undefined) => void;
|
||||
getOrFetchDatabaseList: (connection: Connection, skipCache?: boolean) => Promise<Database[]>;
|
||||
getOrFetchDatabaseSchema: (database: Database, skipCache?: boolean) => Promise<Table[]>;
|
||||
setCurrentConnectionCtx: (
|
||||
connectionCtx: ConnectionContext | undefined
|
||||
) => void;
|
||||
getOrFetchDatabaseList: (
|
||||
connection: Connection,
|
||||
skipCache?: boolean
|
||||
) => Promise<Database[]>;
|
||||
getOrFetchDatabaseSchema: (
|
||||
database: Database,
|
||||
skipCache?: boolean
|
||||
) => Promise<Table[]>;
|
||||
getConnectionById: (connectionId: string) => Connection | undefined;
|
||||
updateConnection: (connectionId: string, connection: Partial<Connection>) => void;
|
||||
updateConnection: (
|
||||
connectionId: string,
|
||||
connection: Partial<Connection>
|
||||
) => void;
|
||||
clearConnection: (filter: (connection: Connection) => boolean) => void;
|
||||
}
|
||||
|
||||
@ -55,12 +66,21 @@ export const useConnectionStore = create<ConnectionState>()(
|
||||
...state,
|
||||
currentConnectionCtx: connectionCtx,
|
||||
})),
|
||||
getOrFetchDatabaseList: async (connection: Connection, skipCache = false) => {
|
||||
getOrFetchDatabaseList: async (
|
||||
connection: Connection,
|
||||
skipCache = false
|
||||
) => {
|
||||
const state = get();
|
||||
|
||||
if (!skipCache) {
|
||||
if (state.databaseList.some((database) => database.connectionId === connection.id)) {
|
||||
return state.databaseList.filter((database) => database.connectionId === connection.id);
|
||||
if (
|
||||
state.databaseList.some(
|
||||
(database) => database.connectionId === connection.id
|
||||
)
|
||||
) {
|
||||
return state.databaseList.filter(
|
||||
(database) => database.connectionId === connection.id
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -83,27 +103,45 @@ export const useConnectionStore = create<ConnectionState>()(
|
||||
...state,
|
||||
databaseList,
|
||||
}));
|
||||
return databaseList.filter((database) => database.connectionId === connection.id);
|
||||
return databaseList.filter(
|
||||
(database) => database.connectionId === connection.id
|
||||
);
|
||||
},
|
||||
getOrFetchDatabaseSchema: async (database: Database, skipCache = false) => {
|
||||
getOrFetchDatabaseSchema: async (
|
||||
database: Database,
|
||||
skipCache = false
|
||||
) => {
|
||||
const state = get();
|
||||
|
||||
if (!skipCache) {
|
||||
const db = state.databaseList.find((db) => db.connectionId === database.connectionId && db.name === database.name);
|
||||
if (db !== undefined && Array.isArray(db.tableList) && db.tableList.length !== 0) {
|
||||
const db = state.databaseList.find(
|
||||
(db) =>
|
||||
db.connectionId === database.connectionId &&
|
||||
db.name === database.name
|
||||
);
|
||||
if (
|
||||
db !== undefined &&
|
||||
Array.isArray(db.tableList) &&
|
||||
db.tableList.length !== 0
|
||||
) {
|
||||
return db.tableList;
|
||||
}
|
||||
}
|
||||
|
||||
const connection = state.connectionList.find((connection) => connection.id === database.connectionId);
|
||||
const connection = state.connectionList.find(
|
||||
(connection) => connection.id === database.connectionId
|
||||
);
|
||||
if (!connection) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const { data: result } = await axios.post<ResponseObject<Table[]>>("/api/connection/db_schema", {
|
||||
connection,
|
||||
db: database.name,
|
||||
});
|
||||
const { data: result } = await axios.post<ResponseObject<Table[]>>(
|
||||
"/api/connection/db_schema",
|
||||
{
|
||||
connection,
|
||||
db: database.name,
|
||||
}
|
||||
);
|
||||
if (result.message) {
|
||||
throw result.message;
|
||||
}
|
||||
@ -112,19 +150,29 @@ export const useConnectionStore = create<ConnectionState>()(
|
||||
set((state) => ({
|
||||
...state,
|
||||
databaseList: state.databaseList.map((item) =>
|
||||
item.connectionId === database.connectionId && item.name === database.name ? { ...item, tableList: fetchedTableList } : item
|
||||
item.connectionId === database.connectionId &&
|
||||
item.name === database.name
|
||||
? { ...item, tableList: fetchedTableList }
|
||||
: item
|
||||
),
|
||||
}));
|
||||
|
||||
return fetchedTableList;
|
||||
},
|
||||
getConnectionById: (connectionId: string) => {
|
||||
return get().connectionList.find((connection) => connection.id === connectionId);
|
||||
return get().connectionList.find(
|
||||
(connection) => connection.id === connectionId
|
||||
);
|
||||
},
|
||||
updateConnection: (connectionId: string, connection: Partial<Connection>) => {
|
||||
updateConnection: (
|
||||
connectionId: string,
|
||||
connection: Partial<Connection>
|
||||
) => {
|
||||
set((state) => ({
|
||||
...state,
|
||||
connectionList: state.connectionList.map((item) => (item.id === connectionId ? { ...item, ...connection } : item)),
|
||||
connectionList: state.connectionList.map((item) =>
|
||||
item.id === connectionId ? { ...item, ...connection } : item
|
||||
),
|
||||
}));
|
||||
},
|
||||
clearConnection: (filter: (connection: Connection) => boolean) => {
|
||||
|
@ -18,10 +18,18 @@ interface ConversationState {
|
||||
getState: () => ConversationState;
|
||||
conversationList: Conversation[];
|
||||
currentConversationId?: Id;
|
||||
createConversation: (connectionId?: Id, databaseName?: string) => Conversation;
|
||||
createConversation: (
|
||||
connectionId?: Id,
|
||||
databaseName?: string
|
||||
) => Conversation;
|
||||
setCurrentConversationId: (conversationId: Id | undefined) => void;
|
||||
getConversationById: (conversationId: Id | undefined) => Conversation | undefined;
|
||||
updateConversation: (conversationId: Id, conversation: Partial<Conversation>) => void;
|
||||
getConversationById: (
|
||||
conversationId: Id | undefined
|
||||
) => Conversation | undefined;
|
||||
updateConversation: (
|
||||
conversationId: Id,
|
||||
conversation: Partial<Conversation>
|
||||
) => void;
|
||||
clearConversation: (filter: (conversation: Conversation) => boolean) => void;
|
||||
}
|
||||
|
||||
@ -45,14 +53,22 @@ export const useConversationStore = create<ConversationState>()(
|
||||
}));
|
||||
return conversation;
|
||||
},
|
||||
setCurrentConversationId: (conversation: Id | undefined) => set(() => ({ currentConversationId: conversation })),
|
||||
setCurrentConversationId: (conversation: Id | undefined) =>
|
||||
set(() => ({ currentConversationId: conversation })),
|
||||
getConversationById: (conversationId: Id | undefined) => {
|
||||
return get().conversationList.find((item) => item.id === conversationId);
|
||||
return get().conversationList.find(
|
||||
(item) => item.id === conversationId
|
||||
);
|
||||
},
|
||||
updateConversation: (conversationId: Id, conversation: Partial<Conversation>) => {
|
||||
updateConversation: (
|
||||
conversationId: Id,
|
||||
conversation: Partial<Conversation>
|
||||
) => {
|
||||
set((state) => ({
|
||||
...state,
|
||||
conversationList: state.conversationList.map((item) => (item.id === conversationId ? { ...item, ...conversation } : item)),
|
||||
conversationList: state.conversationList.map((item) =>
|
||||
item.id === conversationId ? { ...item, ...conversation } : item
|
||||
),
|
||||
}));
|
||||
},
|
||||
clearConversation: (filter: (conversation: Conversation) => boolean) => {
|
||||
|
@ -15,14 +15,18 @@ export const useMessageStore = create<MessageState>()(
|
||||
(set, get) => ({
|
||||
messageList: [],
|
||||
getState: () => get(),
|
||||
addMessage: (message: Message) => set((state) => ({ messageList: [...state.messageList, message] })),
|
||||
addMessage: (message: Message) =>
|
||||
set((state) => ({ messageList: [...state.messageList, message] })),
|
||||
updateMessage: (messageId: Id, message: Partial<Message>) => {
|
||||
set((state) => ({
|
||||
...state,
|
||||
messageList: state.messageList.map((item) => (item.id === messageId ? { ...item, ...message } : item)),
|
||||
messageList: state.messageList.map((item) =>
|
||||
item.id === messageId ? { ...item, ...message } : item
|
||||
),
|
||||
}));
|
||||
},
|
||||
clearMessage: (filter: (message: Message) => boolean) => set((state) => ({ messageList: state.messageList.filter(filter) })),
|
||||
clearMessage: (filter: (message: Message) => boolean) =>
|
||||
set((state) => ({ messageList: state.messageList.filter(filter) })),
|
||||
}),
|
||||
{
|
||||
name: "message-storage",
|
||||
|
@ -54,7 +54,8 @@ export const useSettingStore = create<SettingState>()(
|
||||
}),
|
||||
{
|
||||
name: "setting-storage",
|
||||
merge: (persistedState, currentState) => merge(currentState, persistedState),
|
||||
merge: (persistedState, currentState) =>
|
||||
merge(currentState, persistedState),
|
||||
}
|
||||
)
|
||||
);
|
||||
|
@ -1,6 +1,8 @@
|
||||
import { ExecutionResult } from "@/types";
|
||||
|
||||
export const getMessageFromExecutionResult = (result: ExecutionResult): string => {
|
||||
export const getMessageFromExecutionResult = (
|
||||
result: ExecutionResult
|
||||
): string => {
|
||||
if (result.error) {
|
||||
return result.error;
|
||||
}
|
||||
|
@ -4,7 +4,8 @@ import { encode } from "@nem035/gpt-3-encoder";
|
||||
export const openAIApiKey = process.env.OPENAI_API_KEY;
|
||||
|
||||
// openAIApiEndpoint is the API endpoint for OpenAI API. Defaults to https://api.openai.com.
|
||||
export const openAIApiEndpoint = process.env.OPENAI_API_ENDPOINT || "https://api.openai.com";
|
||||
export const openAIApiEndpoint =
|
||||
process.env.OPENAI_API_ENDPOINT || "https://api.openai.com";
|
||||
|
||||
export const countTextTokens = (text: string) => {
|
||||
return encode(text).length;
|
||||
|
@ -1,6 +1,9 @@
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
module.exports = {
|
||||
content: ["./src/pages/**/*.{js,ts,jsx,tsx}", "./src/components/**/*.{js,ts,jsx,tsx}"],
|
||||
content: [
|
||||
"./src/pages/**/*.{js,ts,jsx,tsx}",
|
||||
"./src/components/**/*.{js,ts,jsx,tsx}",
|
||||
],
|
||||
darkMode: "class",
|
||||
theme: {
|
||||
extend: {
|
||||
|
Reference in New Issue
Block a user