feat: add categories and icons (#107)
* feat: add search data type * feat: add search store * feat: search result add category * chore: UI adjustments * chore: UI adjustments * chore: add css duration * feat: add type icon & add categories * feat: add doc click open url * chore: remove debug * chore: optimize page details
4
.env
@@ -1,3 +1,3 @@
|
|||||||
COCO_SERVER_URL=https://coco.infini.cloud # http://localhost:2900
|
COCO_SERVER_URL=https://infini.tpddns.cn:27200 #https://coco.infini.cloud # http://localhost:2900
|
||||||
|
|
||||||
COCO_WEBSOCKET_URL=wss://coco.infini.cloud/ws # ws://localhost:2900/ws
|
COCO_WEBSOCKET_URL=wss://infini.tpddns.cn:27200/ws #wss://coco.infini.cloud/ws # ws://localhost:2900/ws
|
||||||
2
.vscode/settings.json
vendored
@@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"cSpell.words": [
|
"cSpell.words": [
|
||||||
|
"ahooks",
|
||||||
"autolaunch",
|
"autolaunch",
|
||||||
"Avenir",
|
"Avenir",
|
||||||
"callout",
|
"callout",
|
||||||
@@ -30,6 +31,7 @@
|
|||||||
"tailwindcss",
|
"tailwindcss",
|
||||||
"tauri",
|
"tauri",
|
||||||
"titlebar",
|
"titlebar",
|
||||||
|
"tpddns",
|
||||||
"traptitech",
|
"traptitech",
|
||||||
"unlisten",
|
"unlisten",
|
||||||
"unlistener",
|
"unlistener",
|
||||||
|
|||||||
@@ -21,6 +21,7 @@
|
|||||||
"@tauri-apps/plugin-shell": ">=2.0.0",
|
"@tauri-apps/plugin-shell": ">=2.0.0",
|
||||||
"@tauri-apps/plugin-websocket": "~2",
|
"@tauri-apps/plugin-websocket": "~2",
|
||||||
"@tauri-apps/plugin-window": "2.0.0-alpha.1",
|
"@tauri-apps/plugin-window": "2.0.0-alpha.1",
|
||||||
|
"ahooks": "^3.8.4",
|
||||||
"axios": "^1.7.7",
|
"axios": "^1.7.7",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"dotenv": "^16.4.7",
|
"dotenv": "^16.4.7",
|
||||||
|
|||||||
62
pnpm-lock.yaml
generated
@@ -41,6 +41,9 @@ importers:
|
|||||||
'@tauri-apps/plugin-window':
|
'@tauri-apps/plugin-window':
|
||||||
specifier: 2.0.0-alpha.1
|
specifier: 2.0.0-alpha.1
|
||||||
version: 2.0.0-alpha.1
|
version: 2.0.0-alpha.1
|
||||||
|
ahooks:
|
||||||
|
specifier: ^3.8.4
|
||||||
|
version: 3.8.4(react@18.3.1)
|
||||||
axios:
|
axios:
|
||||||
specifier: ^1.7.7
|
specifier: ^1.7.7
|
||||||
version: 1.7.7
|
version: 1.7.7
|
||||||
@@ -551,46 +554,55 @@ packages:
|
|||||||
resolution: {integrity: sha512-0KXvIJQMOImLCVCz9uvvdPgfyWo93aHHp8ui3FrtOP57svqrF/roSSR5pjqL2hcMp0ljeGlU4q9o/rQaAQ3AYA==}
|
resolution: {integrity: sha512-0KXvIJQMOImLCVCz9uvvdPgfyWo93aHHp8ui3FrtOP57svqrF/roSSR5pjqL2hcMp0ljeGlU4q9o/rQaAQ3AYA==}
|
||||||
cpu: [arm]
|
cpu: [arm]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
'@rollup/rollup-linux-arm-musleabihf@4.24.0':
|
'@rollup/rollup-linux-arm-musleabihf@4.24.0':
|
||||||
resolution: {integrity: sha512-it2BW6kKFVh8xk/BnHfakEeoLPv8STIISekpoF+nBgWM4d55CZKc7T4Dx1pEbTnYm/xEKMgy1MNtYuoA8RFIWw==}
|
resolution: {integrity: sha512-it2BW6kKFVh8xk/BnHfakEeoLPv8STIISekpoF+nBgWM4d55CZKc7T4Dx1pEbTnYm/xEKMgy1MNtYuoA8RFIWw==}
|
||||||
cpu: [arm]
|
cpu: [arm]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [musl]
|
||||||
|
|
||||||
'@rollup/rollup-linux-arm64-gnu@4.24.0':
|
'@rollup/rollup-linux-arm64-gnu@4.24.0':
|
||||||
resolution: {integrity: sha512-i0xTLXjqap2eRfulFVlSnM5dEbTVque/3Pi4g2y7cxrs7+a9De42z4XxKLYJ7+OhE3IgxvfQM7vQc43bwTgPwA==}
|
resolution: {integrity: sha512-i0xTLXjqap2eRfulFVlSnM5dEbTVque/3Pi4g2y7cxrs7+a9De42z4XxKLYJ7+OhE3IgxvfQM7vQc43bwTgPwA==}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
'@rollup/rollup-linux-arm64-musl@4.24.0':
|
'@rollup/rollup-linux-arm64-musl@4.24.0':
|
||||||
resolution: {integrity: sha512-9E6MKUJhDuDh604Qco5yP/3qn3y7SLXYuiC0Rpr89aMScS2UAmK1wHP2b7KAa1nSjWJc/f/Lc0Wl1L47qjiyQw==}
|
resolution: {integrity: sha512-9E6MKUJhDuDh604Qco5yP/3qn3y7SLXYuiC0Rpr89aMScS2UAmK1wHP2b7KAa1nSjWJc/f/Lc0Wl1L47qjiyQw==}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [musl]
|
||||||
|
|
||||||
'@rollup/rollup-linux-powerpc64le-gnu@4.24.0':
|
'@rollup/rollup-linux-powerpc64le-gnu@4.24.0':
|
||||||
resolution: {integrity: sha512-2XFFPJ2XMEiF5Zi2EBf4h73oR1V/lycirxZxHZNc93SqDN/IWhYYSYj8I9381ikUFXZrz2v7r2tOVk2NBwxrWw==}
|
resolution: {integrity: sha512-2XFFPJ2XMEiF5Zi2EBf4h73oR1V/lycirxZxHZNc93SqDN/IWhYYSYj8I9381ikUFXZrz2v7r2tOVk2NBwxrWw==}
|
||||||
cpu: [ppc64]
|
cpu: [ppc64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
'@rollup/rollup-linux-riscv64-gnu@4.24.0':
|
'@rollup/rollup-linux-riscv64-gnu@4.24.0':
|
||||||
resolution: {integrity: sha512-M3Dg4hlwuntUCdzU7KjYqbbd+BLq3JMAOhCKdBE3TcMGMZbKkDdJ5ivNdehOssMCIokNHFOsv7DO4rlEOfyKpg==}
|
resolution: {integrity: sha512-M3Dg4hlwuntUCdzU7KjYqbbd+BLq3JMAOhCKdBE3TcMGMZbKkDdJ5ivNdehOssMCIokNHFOsv7DO4rlEOfyKpg==}
|
||||||
cpu: [riscv64]
|
cpu: [riscv64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
'@rollup/rollup-linux-s390x-gnu@4.24.0':
|
'@rollup/rollup-linux-s390x-gnu@4.24.0':
|
||||||
resolution: {integrity: sha512-mjBaoo4ocxJppTorZVKWFpy1bfFj9FeCMJqzlMQGjpNPY9JwQi7OuS1axzNIk0nMX6jSgy6ZURDZ2w0QW6D56g==}
|
resolution: {integrity: sha512-mjBaoo4ocxJppTorZVKWFpy1bfFj9FeCMJqzlMQGjpNPY9JwQi7OuS1axzNIk0nMX6jSgy6ZURDZ2w0QW6D56g==}
|
||||||
cpu: [s390x]
|
cpu: [s390x]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
'@rollup/rollup-linux-x64-gnu@4.24.0':
|
'@rollup/rollup-linux-x64-gnu@4.24.0':
|
||||||
resolution: {integrity: sha512-ZXFk7M72R0YYFN5q13niV0B7G8/5dcQ9JDp8keJSfr3GoZeXEoMHP/HlvqROA3OMbMdfr19IjCeNAnPUG93b6A==}
|
resolution: {integrity: sha512-ZXFk7M72R0YYFN5q13niV0B7G8/5dcQ9JDp8keJSfr3GoZeXEoMHP/HlvqROA3OMbMdfr19IjCeNAnPUG93b6A==}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
'@rollup/rollup-linux-x64-musl@4.24.0':
|
'@rollup/rollup-linux-x64-musl@4.24.0':
|
||||||
resolution: {integrity: sha512-w1i+L7kAXZNdYl+vFvzSZy8Y1arS7vMgIy8wusXJzRrPyof5LAb02KGr1PD2EkRcl73kHulIID0M501lN+vobQ==}
|
resolution: {integrity: sha512-w1i+L7kAXZNdYl+vFvzSZy8Y1arS7vMgIy8wusXJzRrPyof5LAb02KGr1PD2EkRcl73kHulIID0M501lN+vobQ==}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [musl]
|
||||||
|
|
||||||
'@rollup/rollup-win32-arm64-msvc@4.24.0':
|
'@rollup/rollup-win32-arm64-msvc@4.24.0':
|
||||||
resolution: {integrity: sha512-VXBrnPWgBpVDCVY6XF3LEW0pOU51KbaHhccHw6AS6vBWIC60eqsH19DAeeObl+g8nKAz04QFdl/Cefta0xQtUQ==}
|
resolution: {integrity: sha512-VXBrnPWgBpVDCVY6XF3LEW0pOU51KbaHhccHw6AS6vBWIC60eqsH19DAeeObl+g8nKAz04QFdl/Cefta0xQtUQ==}
|
||||||
@@ -649,24 +661,28 @@ packages:
|
|||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
'@tauri-apps/cli-linux-arm64-musl@2.0.3':
|
'@tauri-apps/cli-linux-arm64-musl@2.0.3':
|
||||||
resolution: {integrity: sha512-I4MVD7nf6lLLRmNQPpe5beEIFM6q7Zkmh77ROA5BNu/+vHNL5kiTMD+bmd10ZL2r753A6pO7AvqkIxcBuIl0tg==}
|
resolution: {integrity: sha512-I4MVD7nf6lLLRmNQPpe5beEIFM6q7Zkmh77ROA5BNu/+vHNL5kiTMD+bmd10ZL2r753A6pO7AvqkIxcBuIl0tg==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [musl]
|
||||||
|
|
||||||
'@tauri-apps/cli-linux-x64-gnu@2.0.3':
|
'@tauri-apps/cli-linux-x64-gnu@2.0.3':
|
||||||
resolution: {integrity: sha512-C6Jkx2zZGKkoi+sg5FK9GoH/0EvAaOgrZfF5azV5EALGba46g7VpWcZgp9zFUd7K2IzTi+0OOY8TQ2OVfKZgew==}
|
resolution: {integrity: sha512-C6Jkx2zZGKkoi+sg5FK9GoH/0EvAaOgrZfF5azV5EALGba46g7VpWcZgp9zFUd7K2IzTi+0OOY8TQ2OVfKZgew==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
'@tauri-apps/cli-linux-x64-musl@2.0.3':
|
'@tauri-apps/cli-linux-x64-musl@2.0.3':
|
||||||
resolution: {integrity: sha512-qi4ghmTfSAl+EEUDwmwI9AJUiOLNSmU1RgiGgcPRE+7A/W+Am9UnxYySAiRbB/gJgTl9sj/pqH5Y9duP1/sqHg==}
|
resolution: {integrity: sha512-qi4ghmTfSAl+EEUDwmwI9AJUiOLNSmU1RgiGgcPRE+7A/W+Am9UnxYySAiRbB/gJgTl9sj/pqH5Y9duP1/sqHg==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [musl]
|
||||||
|
|
||||||
'@tauri-apps/cli-win32-arm64-msvc@2.0.3':
|
'@tauri-apps/cli-win32-arm64-msvc@2.0.3':
|
||||||
resolution: {integrity: sha512-UXxHkYmFesC97qVmZre4vY7oDxRDtC2OeKNv0bH+iSnuUp/ROxzJYGyaelnv9Ybvgl4YVqDCnxgB28qMM938TA==}
|
resolution: {integrity: sha512-UXxHkYmFesC97qVmZre4vY7oDxRDtC2OeKNv0bH+iSnuUp/ROxzJYGyaelnv9Ybvgl4YVqDCnxgB28qMM938TA==}
|
||||||
@@ -901,6 +917,12 @@ packages:
|
|||||||
engines: {node: '>=0.4.0'}
|
engines: {node: '>=0.4.0'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
|
ahooks@3.8.4:
|
||||||
|
resolution: {integrity: sha512-39wDEw2ZHvypaT14EpMMk4AzosHWt0z9bulY0BeDsvc9PqJEV+Kjh/4TZfftSsotBMq52iYIOFPd3PR56e0ZJg==}
|
||||||
|
engines: {node: '>=8.0.0'}
|
||||||
|
peerDependencies:
|
||||||
|
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||||
|
|
||||||
ansi-regex@5.0.1:
|
ansi-regex@5.0.1:
|
||||||
resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
|
resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
@@ -1428,6 +1450,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==}
|
resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
|
intersection-observer@0.12.2:
|
||||||
|
resolution: {integrity: sha512-7m1vEcPCxXYI8HqnL8CKI6siDyD+eIWSwgB3DZA+ZTogxk9I4CDnj4wilt9x/+/QbHI4YG5YZNmC6458/e9Ktg==}
|
||||||
|
|
||||||
is-alphabetical@2.0.1:
|
is-alphabetical@2.0.1:
|
||||||
resolution: {integrity: sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==}
|
resolution: {integrity: sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==}
|
||||||
|
|
||||||
@@ -1478,6 +1503,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==}
|
resolution: {integrity: sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
|
js-cookie@3.0.5:
|
||||||
|
resolution: {integrity: sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==}
|
||||||
|
engines: {node: '>=14'}
|
||||||
|
|
||||||
js-tokens@4.0.0:
|
js-tokens@4.0.0:
|
||||||
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
|
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
|
||||||
|
|
||||||
@@ -1867,6 +1896,9 @@ packages:
|
|||||||
peerDependencies:
|
peerDependencies:
|
||||||
react: ^18.3.1
|
react: ^18.3.1
|
||||||
|
|
||||||
|
react-fast-compare@3.2.2:
|
||||||
|
resolution: {integrity: sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==}
|
||||||
|
|
||||||
react-hotkeys-hook@4.5.1:
|
react-hotkeys-hook@4.5.1:
|
||||||
resolution: {integrity: sha512-scAEJOh3Irm0g95NIn6+tQVf/OICCjsQsC9NBHfQws/Vxw4sfq1tDQut5fhTEvPraXhu/sHxRd9lOtxzyYuNAg==}
|
resolution: {integrity: sha512-scAEJOh3Irm0g95NIn6+tQVf/OICCjsQsC9NBHfQws/Vxw4sfq1tDQut5fhTEvPraXhu/sHxRd9lOtxzyYuNAg==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -1947,6 +1979,9 @@ packages:
|
|||||||
remark-stringify@11.0.0:
|
remark-stringify@11.0.0:
|
||||||
resolution: {integrity: sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==}
|
resolution: {integrity: sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==}
|
||||||
|
|
||||||
|
resize-observer-polyfill@1.5.1:
|
||||||
|
resolution: {integrity: sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==}
|
||||||
|
|
||||||
resolve@1.22.8:
|
resolve@1.22.8:
|
||||||
resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==}
|
resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
@@ -1978,6 +2013,10 @@ packages:
|
|||||||
scheduler@0.23.2:
|
scheduler@0.23.2:
|
||||||
resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==}
|
resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==}
|
||||||
|
|
||||||
|
screenfull@5.2.0:
|
||||||
|
resolution: {integrity: sha512-9BakfsO2aUQN2K9Fdbj87RJIEZ82Q9IGim7FqM5OsebfoFC6ZHXgDq/KvniuLTPdeM8wY2o6Dj3WQ7KeQCj3cA==}
|
||||||
|
engines: {node: '>=0.10.0'}
|
||||||
|
|
||||||
semver@6.3.1:
|
semver@6.3.1:
|
||||||
resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==}
|
resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
@@ -2985,6 +3024,19 @@ snapshots:
|
|||||||
|
|
||||||
acorn@8.14.0: {}
|
acorn@8.14.0: {}
|
||||||
|
|
||||||
|
ahooks@3.8.4(react@18.3.1):
|
||||||
|
dependencies:
|
||||||
|
'@babel/runtime': 7.25.9
|
||||||
|
dayjs: 1.11.13
|
||||||
|
intersection-observer: 0.12.2
|
||||||
|
js-cookie: 3.0.5
|
||||||
|
lodash: 4.17.21
|
||||||
|
react: 18.3.1
|
||||||
|
react-fast-compare: 3.2.2
|
||||||
|
resize-observer-polyfill: 1.5.1
|
||||||
|
screenfull: 5.2.0
|
||||||
|
tslib: 2.8.0
|
||||||
|
|
||||||
ansi-regex@5.0.1: {}
|
ansi-regex@5.0.1: {}
|
||||||
|
|
||||||
ansi-regex@6.1.0: {}
|
ansi-regex@6.1.0: {}
|
||||||
@@ -3570,6 +3622,8 @@ snapshots:
|
|||||||
|
|
||||||
internmap@2.0.3: {}
|
internmap@2.0.3: {}
|
||||||
|
|
||||||
|
intersection-observer@0.12.2: {}
|
||||||
|
|
||||||
is-alphabetical@2.0.1: {}
|
is-alphabetical@2.0.1: {}
|
||||||
|
|
||||||
is-alphanumerical@2.0.1:
|
is-alphanumerical@2.0.1:
|
||||||
@@ -3611,6 +3665,8 @@ snapshots:
|
|||||||
|
|
||||||
jiti@1.21.6: {}
|
jiti@1.21.6: {}
|
||||||
|
|
||||||
|
js-cookie@3.0.5: {}
|
||||||
|
|
||||||
js-tokens@4.0.0: {}
|
js-tokens@4.0.0: {}
|
||||||
|
|
||||||
jsesc@3.0.2: {}
|
jsesc@3.0.2: {}
|
||||||
@@ -4223,6 +4279,8 @@ snapshots:
|
|||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
scheduler: 0.23.2
|
scheduler: 0.23.2
|
||||||
|
|
||||||
|
react-fast-compare@3.2.2: {}
|
||||||
|
|
||||||
react-hotkeys-hook@4.5.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
|
react-hotkeys-hook@4.5.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
|
||||||
dependencies:
|
dependencies:
|
||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
@@ -4349,6 +4407,8 @@ snapshots:
|
|||||||
mdast-util-to-markdown: 2.1.2
|
mdast-util-to-markdown: 2.1.2
|
||||||
unified: 11.0.5
|
unified: 11.0.5
|
||||||
|
|
||||||
|
resize-observer-polyfill@1.5.1: {}
|
||||||
|
|
||||||
resolve@1.22.8:
|
resolve@1.22.8:
|
||||||
dependencies:
|
dependencies:
|
||||||
is-core-module: 2.15.1
|
is-core-module: 2.15.1
|
||||||
@@ -4400,6 +4460,8 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
loose-envify: 1.4.0
|
loose-envify: 1.4.0
|
||||||
|
|
||||||
|
screenfull@5.2.0: {}
|
||||||
|
|
||||||
semver@6.3.1: {}
|
semver@6.3.1: {}
|
||||||
|
|
||||||
shebang-command@2.0.0:
|
shebang-command@2.0.0:
|
||||||
|
|||||||
@@ -83,7 +83,6 @@ pub fn run() {
|
|||||||
// show_panel,
|
// show_panel,
|
||||||
// hide_panel,
|
// hide_panel,
|
||||||
// close_panel
|
// close_panel
|
||||||
shortcut::check_shortcut_available,
|
|
||||||
])
|
])
|
||||||
.setup(|app| {
|
.setup(|app| {
|
||||||
init(app.app_handle());
|
init(app.app_handle());
|
||||||
@@ -168,7 +167,7 @@ fn hide_coco(app: tauri::AppHandle) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn handle_open_coco(app: &AppHandle) {
|
fn handle_open_coco(app: &AppHandle) {
|
||||||
println!("Open Coco menu clicked!");
|
// println!("Open Coco menu clicked!");
|
||||||
|
|
||||||
if let Some(window) = app.get_window("main") {
|
if let Some(window) = app.get_window("main") {
|
||||||
window.show().unwrap();
|
window.show().unwrap();
|
||||||
@@ -179,7 +178,7 @@ fn handle_open_coco(app: &AppHandle) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn handle_hide_coco(app: &AppHandle) {
|
fn handle_hide_coco(app: &AppHandle) {
|
||||||
println!("Hide Coco menu clicked!");
|
// println!("Hide Coco menu clicked!");
|
||||||
|
|
||||||
if let Some(window) = app.get_window("main") {
|
if let Some(window) = app.get_window("main") {
|
||||||
if let Err(err) = window.hide() {
|
if let Err(err) = window.hide() {
|
||||||
@@ -196,7 +195,7 @@ fn handle_hide_coco(app: &AppHandle) {
|
|||||||
fn switch_tray_icon(app: tauri::AppHandle, is_dark_mode: bool) {
|
fn switch_tray_icon(app: tauri::AppHandle, is_dark_mode: bool) {
|
||||||
let app_handle = app.app_handle();
|
let app_handle = app.app_handle();
|
||||||
|
|
||||||
println!("is_dark_mode: {}", is_dark_mode);
|
// println!("is_dark_mode: {}", is_dark_mode);
|
||||||
|
|
||||||
const DARK_ICON_PATH: &[u8] = include_bytes!("../icons/dark@2x.png");
|
const DARK_ICON_PATH: &[u8] = include_bytes!("../icons/dark@2x.png");
|
||||||
const LIGHT_ICON_PATH: &[u8] = include_bytes!("../icons/light@2x.png");
|
const LIGHT_ICON_PATH: &[u8] = include_bytes!("../icons/light@2x.png");
|
||||||
|
|||||||
@@ -160,31 +160,3 @@ pub fn _get_shortcut<R: Runtime>(app: &AppHandle<R>) -> String {
|
|||||||
),
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
|
||||||
pub async fn check_shortcut_available(key: String) -> bool {
|
|
||||||
// 这里可以实现系统级的快捷键检查
|
|
||||||
// 可以检查是否与其他应用的全局快捷键冲突
|
|
||||||
// 返回 true 表示可用,false 表示已被占用
|
|
||||||
|
|
||||||
// 简单实现示例:
|
|
||||||
!is_system_shortcut(&key)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_system_shortcut(key: &str) -> bool {
|
|
||||||
let system_shortcuts = vec![
|
|
||||||
"Command+C",
|
|
||||||
"Command+V",
|
|
||||||
"Command+X",
|
|
||||||
"Command+A",
|
|
||||||
"Command+Z",
|
|
||||||
"Control+C",
|
|
||||||
"Control+V",
|
|
||||||
"Control+X",
|
|
||||||
"Control+A",
|
|
||||||
"Control+Z",
|
|
||||||
// 添加更多系统快捷键
|
|
||||||
];
|
|
||||||
|
|
||||||
system_shortcuts.contains(&key)
|
|
||||||
}
|
|
||||||
|
|||||||
BIN
src/assets/app-icon.png
Normal file
|
After Width: | Height: | Size: 460 KiB |
BIN
src/assets/hugo_site/blog.png
Normal file
|
After Width: | Height: | Size: 753 B |
BIN
src/assets/hugo_site/icon.png
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
BIN
src/assets/hugo_site/news.png
Normal file
|
After Width: | Height: | Size: 621 B |
BIN
src/assets/hugo_site/web.png
Normal file
|
After Width: | Height: | Size: 710 B |
BIN
src/assets/hugo_site/web_page.png
Normal file
|
After Width: | Height: | Size: 439 B |
BIN
src/assets/images/file_efault.png
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
BIN
src/assets/images/source_default.png
Normal file
|
After Width: | Height: | Size: 3.9 KiB |
BIN
src/assets/images/source_default_dark.png
Normal file
|
After Width: | Height: | Size: 3.0 KiB |
@@ -1,28 +1,67 @@
|
|||||||
import { useEffect, useRef, useState } from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
import { CircleAlert, Bolt, X } from "lucide-react";
|
import {
|
||||||
|
CircleAlert,
|
||||||
|
Bolt,
|
||||||
|
X,
|
||||||
|
SquareArrowRight,
|
||||||
|
// UserRoundPen,
|
||||||
|
} from "lucide-react";
|
||||||
|
|
||||||
import { isTauri } from "@tauri-apps/api/core";
|
import { isTauri } from "@tauri-apps/api/core";
|
||||||
import { open } from "@tauri-apps/plugin-shell";
|
import { open } from "@tauri-apps/plugin-shell";
|
||||||
|
|
||||||
import { useAppStore } from "@/stores/appStore";
|
import { useAppStore } from "@/stores/appStore";
|
||||||
|
import { useSearchStore } from "@/stores/searchStore";
|
||||||
|
import source_default_img from "@/assets/images/source_default.png";
|
||||||
|
import source_default_dark_img from "@/assets/images/source_default_dark.png";
|
||||||
|
import file_efault_img from "@/assets/images/file_efault.png";
|
||||||
|
import { useTheme } from "@/contexts/ThemeContext";
|
||||||
|
|
||||||
|
type ISearchData = Record<string, any[]>;
|
||||||
|
|
||||||
interface DropdownListProps {
|
interface DropdownListProps {
|
||||||
selected: (item: any) => void;
|
selected: (item: any) => void;
|
||||||
suggests: any[];
|
suggests: any[];
|
||||||
|
SearchData: ISearchData;
|
||||||
IsError: boolean;
|
IsError: boolean;
|
||||||
isSearchComplete: boolean;
|
isSearchComplete: boolean;
|
||||||
|
isChatMode: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
function DropdownList({ selected, suggests, IsError }: DropdownListProps) {
|
function DropdownList({
|
||||||
|
selected,
|
||||||
|
suggests,
|
||||||
|
SearchData,
|
||||||
|
IsError,
|
||||||
|
isChatMode,
|
||||||
|
}: DropdownListProps) {
|
||||||
|
let globalIndex = 0;
|
||||||
|
const globalItemIndexMap: any[] = [];
|
||||||
|
// const letterFirstIndex: any = {
|
||||||
|
// a: 0,
|
||||||
|
// s: 0,
|
||||||
|
// d: 0,
|
||||||
|
// f: 0,
|
||||||
|
// };
|
||||||
|
|
||||||
|
const { theme } = useTheme();
|
||||||
|
|
||||||
const connector_data = useAppStore((state) => state.connector_data);
|
const connector_data = useAppStore((state) => state.connector_data);
|
||||||
const datasourceData = useAppStore((state) => state.datasourceData);
|
const datasourceData = useAppStore((state) => state.datasourceData);
|
||||||
const endpoint_http = useAppStore((state) => state.endpoint_http);
|
const endpoint_http = useAppStore((state) => state.endpoint_http);
|
||||||
|
const setSourceData = useSearchStore((state) => state.setSourceData);
|
||||||
|
|
||||||
const [showError, setShowError] = useState<boolean>(IsError);
|
const [showError, setShowError] = useState<boolean>(IsError);
|
||||||
const [selectedItem, setSelectedItem] = useState<number | null>(null);
|
const [selectedItem, setSelectedItem] = useState<number | null>(null);
|
||||||
|
const [selectedName, setSelectedName] = useState<string>("");
|
||||||
const [showIndex, setShowIndex] = useState<boolean>(false);
|
const [showIndex, setShowIndex] = useState<boolean>(false);
|
||||||
const containerRef = useRef<HTMLDivElement>(null);
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
const itemRefs = useRef<(HTMLDivElement | null)[]>([]);
|
const itemRefs = useRef<(HTMLDivElement | null)[]>([]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
isChatMode && setSelectedItem(null);
|
||||||
|
}, [isChatMode]);
|
||||||
|
|
||||||
const handleOpenURL = async (url: string) => {
|
const handleOpenURL = async (url: string) => {
|
||||||
if (!url) return;
|
if (!url) return;
|
||||||
try {
|
try {
|
||||||
@@ -46,9 +85,12 @@ function DropdownList({ selected, suggests, IsError }: DropdownListProps) {
|
|||||||
|
|
||||||
if (e.key === "ArrowUp") {
|
if (e.key === "ArrowUp") {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
setSelectedItem((prev) =>
|
setSelectedItem((prev) => {
|
||||||
prev === null || prev === 0 ? suggests.length - 1 : prev - 1
|
const res =
|
||||||
);
|
prev === null || prev === 0 ? suggests.length - 1 : prev - 1;
|
||||||
|
|
||||||
|
return res;
|
||||||
|
});
|
||||||
} else if (e.key === "ArrowDown") {
|
} else if (e.key === "ArrowDown") {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
setSelectedItem((prev) =>
|
setSelectedItem((prev) =>
|
||||||
@@ -56,12 +98,22 @@ function DropdownList({ selected, suggests, IsError }: DropdownListProps) {
|
|||||||
);
|
);
|
||||||
} else if (e.key === "Meta") {
|
} else if (e.key === "Meta") {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
if (selectedItem !== null) {
|
||||||
|
const item = globalItemIndexMap[selectedItem];
|
||||||
|
setSelectedName(item?._source?.source?.name);
|
||||||
|
}
|
||||||
setShowIndex(true);
|
setShowIndex(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (e.key === "ArrowRight" && selectedItem !== null) {
|
||||||
|
e.preventDefault();
|
||||||
|
const item = globalItemIndexMap[selectedItem];
|
||||||
|
goToTwoPage(item);
|
||||||
|
}
|
||||||
|
|
||||||
if (e.key === "Enter" && selectedItem !== null) {
|
if (e.key === "Enter" && selectedItem !== null) {
|
||||||
// console.log("Enter key pressed", selectedItem);
|
// console.log("Enter key pressed", selectedItem);
|
||||||
const item = suggests[selectedItem];
|
const item = globalItemIndexMap[selectedItem];
|
||||||
if (item?._source?.url) {
|
if (item?._source?.url) {
|
||||||
handleOpenURL(item?._source?.url);
|
handleOpenURL(item?._source?.url);
|
||||||
} else {
|
} else {
|
||||||
@@ -71,7 +123,7 @@ function DropdownList({ selected, suggests, IsError }: DropdownListProps) {
|
|||||||
|
|
||||||
if (e.key >= "0" && e.key <= "9" && showIndex) {
|
if (e.key >= "0" && e.key <= "9" && showIndex) {
|
||||||
// console.log(`number ${e.key}`);
|
// console.log(`number ${e.key}`);
|
||||||
const item = suggests[parseInt(e.key, 10)];
|
const item = globalItemIndexMap[parseInt(e.key, 10)];
|
||||||
if (item?._source?.url) {
|
if (item?._source?.url) {
|
||||||
handleOpenURL(item?._source?.url);
|
handleOpenURL(item?._source?.url);
|
||||||
} else {
|
} else {
|
||||||
@@ -108,26 +160,87 @@ function DropdownList({ selected, suggests, IsError }: DropdownListProps) {
|
|||||||
}
|
}
|
||||||
}, [selectedItem]);
|
}, [selectedItem]);
|
||||||
|
|
||||||
function getIcon(_source: any) {
|
function findConnectorIcon(item: any) {
|
||||||
const id = _source?.source?.id || "";
|
const id = item?._source?.source?.id || "";
|
||||||
|
|
||||||
const result = datasourceData.find((item: any) => item._source.id === id);
|
const result_source = datasourceData.find(
|
||||||
|
(data: any) => data._source.id === id
|
||||||
const connector_id = result?._source?.connector?.id;
|
|
||||||
|
|
||||||
const result1 = connector_data.find(
|
|
||||||
(item: any) => item._source.id === connector_id
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const icons = result1?._source?.assets?.icons || {};
|
const connector_id = result_source?._source?.connector?.id;
|
||||||
|
|
||||||
if (icons[_source.icon]?.includes("http")) {
|
const result_connector = connector_data.find(
|
||||||
return icons[_source.icon];
|
(data: any) => data._source.id === connector_id
|
||||||
|
);
|
||||||
|
|
||||||
|
return result_connector?._source;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTypeIcon(item: any) {
|
||||||
|
const connectorSource = findConnectorIcon(item);
|
||||||
|
const icons = connectorSource?.icon;
|
||||||
|
|
||||||
|
if (!icons) {
|
||||||
|
return theme === "dark" ? source_default_dark_img : source_default_img;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (icons?.includes("http")) {
|
||||||
|
return icons;
|
||||||
} else {
|
} else {
|
||||||
return endpoint_http + icons[_source.icon];
|
return endpoint_http + icons;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getIcon(item: any) {
|
||||||
|
const connectorSource = findConnectorIcon(item);
|
||||||
|
const icons = connectorSource?.assets?.icons || {};
|
||||||
|
|
||||||
|
const selectedIcon = icons[item?._source?.icon];
|
||||||
|
|
||||||
|
if (!selectedIcon) {
|
||||||
|
return file_efault_img;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selectedIcon?.includes("http")) {
|
||||||
|
return selectedIcon;
|
||||||
|
} else {
|
||||||
|
return endpoint_http + selectedIcon;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getRichIcon(item: any) {
|
||||||
|
const connectorSource = findConnectorIcon(item);
|
||||||
|
const icons = connectorSource?.assets?.icons || {};
|
||||||
|
|
||||||
|
const selectedIcon = icons[item?._source?.rich_categories?.[0]?.icon];
|
||||||
|
|
||||||
|
if (!selectedIcon) {
|
||||||
|
return theme === "dark" ? source_default_dark_img : source_default_img;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selectedIcon?.includes("http")) {
|
||||||
|
return selectedIcon;
|
||||||
|
} else {
|
||||||
|
return endpoint_http + selectedIcon;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function goToTwoPage(item: any) {
|
||||||
|
setSourceData(item);
|
||||||
|
selected && selected(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
// function numberToLetter(num: number): string {
|
||||||
|
// const mapping = ["A", "S", "D", "F"];
|
||||||
|
// if (num >= 0 && num < mapping.length) {
|
||||||
|
// const letter = mapping[num];
|
||||||
|
// letterFirstIndex[letter.toLocaleLowerCase()] = globalIndex
|
||||||
|
// return letter
|
||||||
|
// } else {
|
||||||
|
// return "";
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
ref={containerRef}
|
ref={containerRef}
|
||||||
@@ -147,14 +260,37 @@ function DropdownList({ selected, suggests, IsError }: DropdownListProps) {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
<div className="p-2 text-xs text-[#999] dark:text-[#666]">Results</div>
|
{Object.entries(SearchData).map(([sourceName, items]) => (
|
||||||
{suggests?.map((item, index) => {
|
<div key={sourceName}>
|
||||||
const isSelected = selectedItem === index;
|
{items.length > 2 ? (
|
||||||
|
<div className="p-2 text-xs text-[#999] dark:text-[#666] flex items-center gap-2.5 relative">
|
||||||
|
<img className="w-4 h-4" src={getTypeIcon(items[0])} alt="icon" />
|
||||||
|
{sourceName}
|
||||||
|
<div className="flex-1 border-b border-b-[#e6e6e6] dark:border-b-[#272626]"></div>
|
||||||
|
<SquareArrowRight
|
||||||
|
className="w-4 h-4 cursor-pointer"
|
||||||
|
onClick={() => goToTwoPage(items[0])}
|
||||||
|
/>
|
||||||
|
{showIndex && sourceName === selectedName ? (
|
||||||
|
<div
|
||||||
|
className={`absolute right-2
|
||||||
|
w-4 h-4 flex items-end justify-center font-normal text-xs text-[#333] leading-[14px] bg-[#ccc] dark:bg-[#6B6B6B] rounded-md shadow-[-6px_0px_6px_2px_#fff] dark:shadow-[-6px_0px_6px_2px_#000]`}
|
||||||
|
>
|
||||||
|
→
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
{items.map((item: any) => {
|
||||||
|
const isSelected = selectedItem === globalIndex;
|
||||||
|
const currentIndex = globalIndex;
|
||||||
|
globalItemIndexMap.push(item);
|
||||||
|
globalIndex++;
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={item._id}
|
key={item._id}
|
||||||
ref={(el) => (itemRefs.current[index] = el)}
|
ref={(el) => (itemRefs.current[currentIndex] = el)}
|
||||||
onMouseEnter={() => setSelectedItem(index)}
|
onMouseEnter={() => setSelectedItem(currentIndex)}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (item?._source?.url) {
|
if (item?._source?.url) {
|
||||||
handleOpenURL(item?._source?.url);
|
handleOpenURL(item?._source?.url);
|
||||||
@@ -164,29 +300,79 @@ function DropdownList({ selected, suggests, IsError }: DropdownListProps) {
|
|||||||
}}
|
}}
|
||||||
className={`w-full px-2 py-2.5 text-sm flex items-center justify-between rounded-lg transition-colors ${
|
className={`w-full px-2 py-2.5 text-sm flex items-center justify-between rounded-lg transition-colors ${
|
||||||
isSelected
|
isSelected
|
||||||
? "bg-[rgba(0,0,0,0.1)] dark:bg-[rgba(255,255,255,0.1)] hover:bg-[rgba(0,0,0,0.1)] dark:hover:bg-[rgba(255,255,255,0.1)]"
|
? "text-white bg-[#950599] hover:bg-[#950599]"
|
||||||
: ""
|
: "text-[#333] dark:text-[#d8d8d8]"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<div className="flex gap-2 items-center justify-start max-w-[450px]">
|
||||||
|
<img className="w-5 h-5" src={getIcon(item)} alt="icon" />
|
||||||
|
<span
|
||||||
|
className={`text-sm truncate text-left ${
|
||||||
|
isSelected ? "font-medium" : ""
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<div className="flex gap-2 items-center">
|
|
||||||
<img
|
|
||||||
className="w-5 h-5"
|
|
||||||
src={getIcon(item?._source)}
|
|
||||||
alt="icon"
|
|
||||||
/>
|
|
||||||
<span className="text-[#333] dark:text-[#d8d8d8] truncate w-80 text-left">
|
|
||||||
{item?._source?.title}
|
{item?._source?.title}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-2 items-center relative">
|
<div className="flex-1 min-w-[180px] h-full text-[12px] flex gap-2 items-center justify-end relative">
|
||||||
<span className="text-sm text-[#666] dark:text-[#666] truncate w-52 text-right">
|
<span
|
||||||
{item?._source?.source?.name}
|
className={`text-[12px] truncate ${
|
||||||
</span>
|
isSelected
|
||||||
{showIndex && index < 10 ? (
|
? "text-[#DCDCDC]"
|
||||||
<div
|
: "text-[#999] dark:text-[#666]"
|
||||||
className={`absolute right-0 w-4 h-4 flex items-center justify-center font-normal text-xs text-[#333] leading-[14px] bg-[#ccc] dark:bg-[#6B6B6B] shadow-[-6px_0px_6px_2px_#e6e6e6] dark:shadow-[-6px_0px_6px_2px_#000] rounded-md`}
|
}`}
|
||||||
>
|
>
|
||||||
{index}
|
{(item?._source?.category || "") +
|
||||||
|
(item?._source?.subcategory
|
||||||
|
? `/${item?._source?.subcategory}`
|
||||||
|
: "")}
|
||||||
|
</span>
|
||||||
|
{item?._source?.rich_categories ? (
|
||||||
|
<div className="truncate flex gap-2">
|
||||||
|
<img
|
||||||
|
className="w-4 h-4 cursor-pointer"
|
||||||
|
src={getRichIcon(item)}
|
||||||
|
alt="icon"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
goToTwoPage(item);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{item?._source?.rich_categories?.map((rich_item: any) => (
|
||||||
|
<span
|
||||||
|
className={`${
|
||||||
|
isSelected ? "text-[#C8C8C8]" : "text-[#666]"
|
||||||
|
} text-right mr-1`}
|
||||||
|
>
|
||||||
|
{rich_item?.label}
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
{isSelected ? (
|
||||||
|
<div
|
||||||
|
className={`absolute ${
|
||||||
|
showIndex && currentIndex < 10 ? "right-7" : "right-0"
|
||||||
|
} w-4 h-4 flex items-end justify-center font-normal text-xs text-[#333] leading-[14px] bg-[#ccc] dark:bg-[#6B6B6B] rounded-md ${
|
||||||
|
isSelected
|
||||||
|
? "shadow-[-6px_0px_6px_2px_#950599]"
|
||||||
|
: "shadow-[-6px_0px_6px_2px_#fff] dark:shadow-[-6px_0px_6px_2px_#000]"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
↩︎
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
{showIndex && currentIndex < 10 ? (
|
||||||
|
<div
|
||||||
|
className={`absolute right-0 w-4 h-4 flex items-center justify-center font-normal text-xs text-[#333] leading-[14px] bg-[#ccc] dark:bg-[#6B6B6B] rounded-md ${
|
||||||
|
isSelected
|
||||||
|
? "shadow-[-6px_0px_6px_2px_#950599]"
|
||||||
|
: "shadow-[-6px_0px_6px_2px_#fff] dark:shadow-[-6px_0px_6px_2px_#000]"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{currentIndex}
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
@@ -194,6 +380,8 @@ function DropdownList({ selected, suggests, IsError }: DropdownListProps) {
|
|||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,12 @@ import {
|
|||||||
CornerDownLeft,
|
CornerDownLeft,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
|
|
||||||
import logoImg from "@/assets/32x32.png";
|
import logoImg from "@/assets/app-icon.png";
|
||||||
|
import source_default_img from "@/assets/images/source_default.png";
|
||||||
|
import source_default_dark_img from "@/assets/images/source_default_dark.png";
|
||||||
|
import { useSearchStore } from "@/stores/searchStore";
|
||||||
|
import { useAppStore } from "@/stores/appStore";
|
||||||
|
import { useTheme } from "@/contexts/ThemeContext";
|
||||||
|
|
||||||
interface FooterProps {
|
interface FooterProps {
|
||||||
isChat: boolean;
|
isChat: boolean;
|
||||||
@@ -13,16 +18,58 @@ interface FooterProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function Footer({ name }: FooterProps) {
|
export default function Footer({ name }: FooterProps) {
|
||||||
|
const sourceData = useSearchStore((state) => state.sourceData);
|
||||||
|
const connector_data = useAppStore((state) => state.connector_data);
|
||||||
|
const datasourceData = useAppStore((state) => state.datasourceData);
|
||||||
|
const endpoint_http = useAppStore((state) => state.endpoint_http);
|
||||||
|
|
||||||
|
const { theme } = useTheme();
|
||||||
|
|
||||||
|
function findConnectorIcon(item: any) {
|
||||||
|
const id = item?._source?.source?.id || "";
|
||||||
|
|
||||||
|
const result_source = datasourceData.find(
|
||||||
|
(data: any) => data._source.id === id
|
||||||
|
);
|
||||||
|
|
||||||
|
const connector_id = result_source?._source?.connector?.id;
|
||||||
|
|
||||||
|
const result_connector = connector_data.find(
|
||||||
|
(data: any) => data._source.id === connector_id
|
||||||
|
);
|
||||||
|
|
||||||
|
return result_connector?._source;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTypeIcon(item: any) {
|
||||||
|
const connectorSource = findConnectorIcon(item);
|
||||||
|
const icons = connectorSource?.icon;
|
||||||
|
|
||||||
|
if (!icons) {
|
||||||
|
return theme === "dark" ? source_default_dark_img : source_default_img;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (icons?.includes("http")) {
|
||||||
|
return icons;
|
||||||
|
} else {
|
||||||
|
return endpoint_http + icons;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
data-tauri-drag-region
|
data-tauri-drag-region
|
||||||
className="px-4 z-999 mx-[1px] h-10 absolute bottom-0 left-0 right-0 border-t border-gray-200 dark:border-gray-700 flex items-center justify-between rounded-xl rounded-t-none overflow-hidden"
|
className="px-4 z-999 mx-[1px] h-10 absolute bottom-0 left-0 right-0 border-t border-gray-200 dark:border-gray-700 flex items-center justify-between rounded-xl rounded-t-none overflow-hidden"
|
||||||
>
|
>
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<div className="flex items-center space-x-4">
|
<div className="flex items-center space-x-2">
|
||||||
|
{sourceData?._source?.source?.name ? (
|
||||||
|
<img className="w-5 h-5" src={getTypeIcon(sourceData)} alt="icon" />
|
||||||
|
) : (
|
||||||
<img src={logoImg} className="w-5 h-5" />
|
<img src={logoImg} className="w-5 h-5" />
|
||||||
|
)}
|
||||||
<span className="text-xs text-gray-500 dark:text-gray-400">
|
<span className="text-xs text-gray-500 dark:text-gray-400">
|
||||||
Version 1.0.0
|
{sourceData?._source?.source?.name || "Version 1.0.0"}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -36,12 +83,18 @@ export default function Footer({ name }: FooterProps) {
|
|||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<div className="gap-1 flex items-center text-[#666] dark:text-[#666] text-sm">
|
<div className="gap-1 flex items-center text-[#666] dark:text-[#666] text-sm">
|
||||||
<span className="mr-1.5 ">Quick open</span>
|
<span className="mr-1.5 ">Quick open</span>
|
||||||
<Command className="w-5 h-5 p-1 border rounded-[6px] dark:text-[#666] dark:border-[rgba(255,255,255,0.15)]" />
|
<kbd className="docsearch-modal-footer-commands-key pr-1">
|
||||||
<ArrowDown01 className="w-5 h-5 p-1 border rounded-[6px] dark:text-[#666] dark:border-[rgba(255,255,255,0.15)]" />
|
<Command className="w-3 h-3" />
|
||||||
|
</kbd>
|
||||||
|
<kbd className="docsearch-modal-footer-commands-key pr-1">
|
||||||
|
<ArrowDown01 className="w-3 h-3" />
|
||||||
|
</kbd>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center text-[#666] dark:text-[#666] text-sm">
|
<div className="flex items-center text-[#666] dark:text-[#666] text-sm">
|
||||||
<span className="mr-1.5 ">Open</span>
|
<span className="mr-1.5 ">Open</span>
|
||||||
<CornerDownLeft className="w-5 h-5 p-1 border rounded-[6px] dark:text-[#666] dark:border-[rgba(255,255,255,0.15)]" />
|
<kbd className="docsearch-modal-footer-commands-key pr-1">
|
||||||
|
<CornerDownLeft className="w-3 h-3" />
|
||||||
|
</kbd>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,4 +1,12 @@
|
|||||||
import { Library, Mic, Send, Plus, AudioLines, Image } from "lucide-react";
|
import {
|
||||||
|
Library,
|
||||||
|
Mic,
|
||||||
|
Send,
|
||||||
|
Plus,
|
||||||
|
AudioLines,
|
||||||
|
Image,
|
||||||
|
SquareArrowLeft,
|
||||||
|
} from "lucide-react";
|
||||||
import { useRef, useState, useEffect, useCallback } from "react";
|
import { useRef, useState, useEffect, useCallback } from "react";
|
||||||
import { listen } from "@tauri-apps/api/event";
|
import { listen } from "@tauri-apps/api/event";
|
||||||
import { isTauri } from "@tauri-apps/api/core";
|
import { isTauri } from "@tauri-apps/api/core";
|
||||||
@@ -8,6 +16,7 @@ import AutoResizeTextarea from "./AutoResizeTextarea";
|
|||||||
import { useChatStore } from "@/stores/chatStore";
|
import { useChatStore } from "@/stores/chatStore";
|
||||||
import StopIcon from "@/icons/Stop";
|
import StopIcon from "@/icons/Stop";
|
||||||
import { useAppStore } from "@/stores/appStore";
|
import { useAppStore } from "@/stores/appStore";
|
||||||
|
import { useSearchStore } from "@/stores/searchStore";
|
||||||
|
|
||||||
interface ChatInputProps {
|
interface ChatInputProps {
|
||||||
onSend: (message: string) => void;
|
onSend: (message: string) => void;
|
||||||
@@ -32,6 +41,13 @@ export default function ChatInput({
|
|||||||
}: ChatInputProps) {
|
}: ChatInputProps) {
|
||||||
const showTooltip = useAppStore((state) => state.showTooltip);
|
const showTooltip = useAppStore((state) => state.showTooltip);
|
||||||
|
|
||||||
|
const sourceData = useSearchStore((state) => state.sourceData);
|
||||||
|
const setSourceData = useSearchStore((state) => state.setSourceData);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setSourceData(undefined);
|
||||||
|
}, []);
|
||||||
|
|
||||||
const inputRef = useRef<HTMLInputElement>(null);
|
const inputRef = useRef<HTMLInputElement>(null);
|
||||||
const textareaRef = useRef<{ reset: () => void; focus: () => void }>(null);
|
const textareaRef = useRef<{ reset: () => void; focus: () => void }>(null);
|
||||||
|
|
||||||
@@ -65,11 +81,14 @@ export default function ChatInput({
|
|||||||
case "KeyI":
|
case "KeyI":
|
||||||
handleToggleFocus();
|
handleToggleFocus();
|
||||||
break;
|
break;
|
||||||
|
case "ArrowLeft":
|
||||||
|
setSourceData(undefined);
|
||||||
|
break;
|
||||||
case "KeyM":
|
case "KeyM":
|
||||||
console.log("KeyM");
|
console.log("KeyM");
|
||||||
break;
|
break;
|
||||||
case "Enter":
|
case "Enter":
|
||||||
isChatMode && handleSubmit();
|
isChatMode && (curChatEnd ? handleSubmit() : disabledChange());
|
||||||
break;
|
break;
|
||||||
case "KeyO":
|
case "KeyO":
|
||||||
console.log("KeyO");
|
console.log("KeyO");
|
||||||
@@ -139,7 +158,7 @@ export default function ChatInput({
|
|||||||
|
|
||||||
const [countdown, setCountdown] = useState(5);
|
const [countdown, setCountdown] = useState(5);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (connected) return
|
if (connected) return;
|
||||||
if (countdown <= 0) {
|
if (countdown <= 0) {
|
||||||
ReconnectClick();
|
ReconnectClick();
|
||||||
return;
|
return;
|
||||||
@@ -153,14 +172,21 @@ export default function ChatInput({
|
|||||||
}, [countdown, connected]);
|
}, [countdown, connected]);
|
||||||
|
|
||||||
const ReconnectClick = () => {
|
const ReconnectClick = () => {
|
||||||
setCountdown(5)
|
setCountdown(5);
|
||||||
reconnect()
|
reconnect();
|
||||||
}
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full relative">
|
<div className="w-full relative">
|
||||||
<div className="p-[12px] flex items-center dark:text-[#D8D8D8] bg-[#ededed] dark:bg-[#202126] rounded transition-all relative">
|
<div className="p-2 flex items-center dark:text-[#D8D8D8] bg-[#ededed] dark:bg-[#202126] rounded transition-all relative">
|
||||||
<div className="flex flex-wrap gap-2 flex-1 items-center relative">
|
<div className="flex flex-wrap gap-2 flex-1 items-center relative">
|
||||||
|
{!isChatMode && sourceData ? (
|
||||||
|
<SquareArrowLeft
|
||||||
|
className="w-4 h-4 text-[#000] dark:text-[#d8d8d8] cursor-pointer"
|
||||||
|
onClick={() => setSourceData(undefined)}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
|
||||||
{isChatMode ? (
|
{isChatMode ? (
|
||||||
<AutoResizeTextarea
|
<AutoResizeTextarea
|
||||||
ref={textareaRef}
|
ref={textareaRef}
|
||||||
@@ -190,11 +216,20 @@ export default function ChatInput({
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
{showTooltip && isCommandPressed && !isChatMode && sourceData ? (
|
||||||
|
<div
|
||||||
|
className={`absolute left-0 w-4 h-4 flex items-center justify-center font-normal text-xs text-[#333] leading-[14px] bg-[#ccc] dark:bg-[#6B6B6B] rounded-md shadow-[-6px_0px_6px_2px_#ededed] dark:shadow-[-6px_0px_6px_2px_#202126]`}
|
||||||
|
>
|
||||||
|
←
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
{showTooltip && isCommandPressed ? (
|
{showTooltip && isCommandPressed ? (
|
||||||
<div
|
<div
|
||||||
className={`absolute bg-black bg-opacity-70 text-white font-bold px-2 py-0.5 rounded-md text-xs transition-opacity duration-200`}
|
className={`absolute ${
|
||||||
|
!isChatMode && sourceData ? "left-7" : ""
|
||||||
|
} w-4 h-4 flex items-center justify-center font-normal text-xs text-[#333] leading-[14px] bg-[#ccc] dark:bg-[#6B6B6B] rounded-md shadow-[-6px_0px_6px_2px_#ededed] dark:shadow-[-6px_0px_6px_2px_#202126]`}
|
||||||
>
|
>
|
||||||
⌘ + I
|
I
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
@@ -237,24 +272,27 @@ export default function ChatInput({
|
|||||||
|
|
||||||
{showTooltip && isChatMode && isCommandPressed ? (
|
{showTooltip && isChatMode && isCommandPressed ? (
|
||||||
<div
|
<div
|
||||||
className={`absolute right-16 bg-black bg-opacity-70 text-white font-bold px-2 py-0.5 rounded-md text-xs transition-opacity duration-200`}
|
className={`absolute right-10 w-4 h-4 flex items-center justify-center font-normal text-xs text-[#333] leading-[14px] bg-[#ccc] dark:bg-[#6B6B6B] rounded-md shadow-[-6px_0px_6px_2px_#fff] dark:shadow-[-6px_0px_6px_2px_#000]`}
|
||||||
>
|
>
|
||||||
⌘ + M
|
M
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
{showTooltip && isChatMode && isCommandPressed ? (
|
{showTooltip && isChatMode && isCommandPressed ? (
|
||||||
<div
|
<div
|
||||||
className={`absolute right-1 bg-black bg-opacity-70 text-white font-bold px-2 py-0.5 rounded-md text-xs transition-opacity duration-200`}
|
className={`absolute right-3 w-4 h-4 flex items-end justify-center font-normal text-xs text-[#333] leading-[14px] bg-[#ccc] dark:bg-[#6B6B6B] rounded-md shadow-[-6px_0px_6px_2px_#fff] dark:shadow-[-6px_0px_6px_2px_#000]`}
|
||||||
>
|
>
|
||||||
⌘ + ↩︎
|
↩︎
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
{!connected && isChatMode ? (
|
{!connected && isChatMode ? (
|
||||||
<div className="absolute top-0 right-0 bottom-0 left-0 px-2 py-4 bg-red-500/10 rounded-md font-normal text-xs text-gray-400 flex items-center gap-4">
|
<div className="absolute top-0 right-0 bottom-0 left-0 px-2 py-4 bg-red-500/10 rounded-md font-normal text-xs text-gray-400 flex items-center gap-4">
|
||||||
Unable to connect to the server
|
Unable to connect to the server
|
||||||
<div className="w-[96px] h-[24px] bg-[#0061FF] rounded-[12px] font-normal text-xs text-white flex items-center justify-center cursor-pointer" onClick={ReconnectClick}>
|
<div
|
||||||
|
className="w-[96px] h-[24px] bg-[#0061FF] rounded-[12px] font-normal text-xs text-white flex items-center justify-center cursor-pointer"
|
||||||
|
onClick={ReconnectClick}
|
||||||
|
>
|
||||||
Reconnect ({countdown})
|
Reconnect ({countdown})
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -280,16 +318,16 @@ export default function ChatInput({
|
|||||||
</button>
|
</button>
|
||||||
{showTooltip && isCommandPressed ? (
|
{showTooltip && isCommandPressed ? (
|
||||||
<div
|
<div
|
||||||
className={`absolute left-2 bg-black bg-opacity-70 text-white font-bold px-2 py-0.5 rounded-md text-xs transition-opacity duration-200`}
|
className={`absolute left-2 w-4 h-4 flex items-center justify-center font-normal text-xs text-[#333] leading-[14px] bg-[#ccc] dark:bg-[#6B6B6B] rounded-md shadow-[-6px_0px_6px_2px_#fff] dark:shadow-[-6px_0px_6px_2px_#000]`}
|
||||||
>
|
>
|
||||||
⌘ + O
|
O
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
{showTooltip && isCommandPressed ? (
|
{showTooltip && isCommandPressed ? (
|
||||||
<div
|
<div
|
||||||
className={`absolute left-16 bg-black bg-opacity-70 text-white font-bold px-2 py-0.5 rounded-md text-xs transition-opacity duration-200`}
|
className={`absolute left-16 w-4 h-4 flex items-center justify-center font-normal text-xs text-[#333] leading-[14px] bg-[#ccc] dark:bg-[#6B6B6B] rounded-md shadow-[-6px_0px_6px_2px_#fff] dark:shadow-[-6px_0px_6px_2px_#000]`}
|
||||||
>
|
>
|
||||||
⌘ + U
|
U
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
@@ -306,27 +344,27 @@ export default function ChatInput({
|
|||||||
</button>
|
</button>
|
||||||
{showTooltip && isCommandPressed ? (
|
{showTooltip && isCommandPressed ? (
|
||||||
<div
|
<div
|
||||||
className={`absolute left-0 bg-black bg-opacity-70 text-white font-bold px-2 py-0.5 rounded-md text-xs transition-opacity duration-200`}
|
className={`absolute left-0 w-4 h-4 flex items-center justify-center font-normal text-xs text-[#333] leading-[14px] bg-[#ccc] dark:bg-[#6B6B6B] rounded-md shadow-[-6px_0px_6px_2px_#fff] dark:shadow-[-6px_0px_6px_2px_#000]`}
|
||||||
>
|
>
|
||||||
⌘ + N
|
N
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
{showTooltip && isCommandPressed ? (
|
{showTooltip && isCommandPressed ? (
|
||||||
<div
|
<div
|
||||||
className={`absolute left-14 bg-black bg-opacity-70 text-white font-bold px-2 py-0.5 rounded-md text-xs transition-opacity duration-200`}
|
className={`absolute left-6 w-4 h-4 flex items-center justify-center font-normal text-xs text-[#333] leading-[14px] bg-[#ccc] dark:bg-[#6B6B6B] rounded-md shadow-[-6px_0px_6px_2px_#fff] dark:shadow-[-6px_0px_6px_2px_#000]`}
|
||||||
>
|
>
|
||||||
⌘ + G
|
G
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="relative w-24 flex justify-end items-center">
|
<div className="relative w-16 flex justify-end items-center">
|
||||||
{showTooltip && isCommandPressed ? (
|
{showTooltip && isCommandPressed ? (
|
||||||
<div
|
<div
|
||||||
className={`absolute left-0 z-10 bg-black bg-opacity-70 text-white font-bold px-2 py-0.5 rounded-md text-xs transition-opacity duration-200`}
|
className={`absolute left-1 z-10 w-4 h-4 flex items-center justify-center font-normal text-xs text-[#333] leading-[14px] bg-[#ccc] dark:bg-[#6B6B6B] rounded-md shadow-[-6px_0px_6px_2px_#fff] dark:shadow-[-6px_0px_6px_2px_#000]`}
|
||||||
>
|
>
|
||||||
⌘ + T
|
T
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
<ChatSwitch
|
<ChatSwitch
|
||||||
@@ -334,6 +372,7 @@ export default function ChatInput({
|
|||||||
onChange={(value) => {
|
onChange={(value) => {
|
||||||
value && disabledChange();
|
value && disabledChange();
|
||||||
changeMode(value);
|
changeMode(value);
|
||||||
|
setSourceData(undefined);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -8,6 +8,9 @@ import Footer from "./Footer";
|
|||||||
import { tauriFetch } from "@/api/tauriFetchClient";
|
import { tauriFetch } from "@/api/tauriFetchClient";
|
||||||
import noDataImg from "@/assets/coconut-tree.png";
|
import noDataImg from "@/assets/coconut-tree.png";
|
||||||
import { useAppStore } from "@/stores/appStore";
|
import { useAppStore } from "@/stores/appStore";
|
||||||
|
// import { res_search } from "@/mock/index";
|
||||||
|
import { SearchResults } from "../SearchChat/SearchResults";
|
||||||
|
import { useSearchStore } from "@/stores/searchStore";
|
||||||
|
|
||||||
interface SearchProps {
|
interface SearchProps {
|
||||||
changeInput: (val: string) => void;
|
changeInput: (val: string) => void;
|
||||||
@@ -18,8 +21,11 @@ interface SearchProps {
|
|||||||
function Search({ isChatMode, input }: SearchProps) {
|
function Search({ isChatMode, input }: SearchProps) {
|
||||||
const appStore = useAppStore();
|
const appStore = useAppStore();
|
||||||
|
|
||||||
|
const sourceData = useSearchStore((state) => state.sourceData);
|
||||||
|
|
||||||
const [IsError, setIsError] = useState<boolean>(false);
|
const [IsError, setIsError] = useState<boolean>(false);
|
||||||
const [suggests, setSuggests] = useState<any[]>([]);
|
const [suggests, setSuggests] = useState<any[]>([]);
|
||||||
|
const [SearchData, setSearchData] = useState<any>({});
|
||||||
const [isSearchComplete, setIsSearchComplete] = useState(false);
|
const [isSearchComplete, setIsSearchComplete] = useState(false);
|
||||||
const [selectedItem, setSelectedItem] = useState<any>();
|
const [selectedItem, setSelectedItem] = useState<any>();
|
||||||
|
|
||||||
@@ -55,13 +61,18 @@ function Search({ isChatMode, input }: SearchProps) {
|
|||||||
const getSuggest = async () => {
|
const getSuggest = async () => {
|
||||||
if (!input) return;
|
if (!input) return;
|
||||||
//
|
//
|
||||||
// const list = [];
|
// mock
|
||||||
// for (let i = 0; i < input.length; i++) {
|
// let list = res_search?.hits?.hits;
|
||||||
// list.push({
|
|
||||||
// _source: { url: `https://www.google.com/search?q=${i}`, _id: i },
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
// setSuggests(list);
|
// setSuggests(list);
|
||||||
|
// const search_data = list.reduce((acc: any, item) => {
|
||||||
|
// const name = item._source.source.name;
|
||||||
|
// if (!acc[name]) {
|
||||||
|
// acc[name] = [];
|
||||||
|
// }
|
||||||
|
// acc[name].push(item);
|
||||||
|
// return acc;
|
||||||
|
// }, {});
|
||||||
|
// setSearchData(search_data);
|
||||||
// return;
|
// return;
|
||||||
//
|
//
|
||||||
try {
|
try {
|
||||||
@@ -72,10 +83,20 @@ function Search({ isChatMode, input }: SearchProps) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
console.log("_suggest", input, response);
|
console.log("_suggest", input, response);
|
||||||
const data = response.data?.hits?.hits || [];
|
let data = response.data?.hits?.hits || [];
|
||||||
setSuggests(data);
|
setSuggests(data);
|
||||||
setIsError(false);
|
const search_data = data.reduce((acc: any, item: any) => {
|
||||||
|
const name = item?._source?.source?.name;
|
||||||
|
if (!acc[name]) {
|
||||||
|
acc[name] = [];
|
||||||
|
}
|
||||||
|
acc[name].push(item);
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
setSearchData(search_data);
|
||||||
|
|
||||||
|
setIsError(false);
|
||||||
setIsSearchComplete(true);
|
setIsSearchComplete(true);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
setSuggests([]);
|
setSuggests([]);
|
||||||
@@ -95,7 +116,7 @@ function Search({ isChatMode, input }: SearchProps) {
|
|||||||
const debouncedSearch = useCallback(debounce(getSuggest, 300), [input]);
|
const debouncedSearch = useCallback(debounce(getSuggest, 300), [input]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
!isChatMode && debouncedSearch();
|
!isChatMode && !sourceData && debouncedSearch();
|
||||||
if (!input) setSuggests([]);
|
if (!input) setSuggests([]);
|
||||||
}, [input]);
|
}, [input]);
|
||||||
|
|
||||||
@@ -103,12 +124,18 @@ function Search({ isChatMode, input }: SearchProps) {
|
|||||||
<div ref={mainWindowRef} className={`h-[500px] pb-10 w-full relative`}>
|
<div ref={mainWindowRef} className={`h-[500px] pb-10 w-full relative`}>
|
||||||
{/* Search Results Panel */}
|
{/* Search Results Panel */}
|
||||||
{suggests.length > 0 ? (
|
{suggests.length > 0 ? (
|
||||||
|
sourceData ? (
|
||||||
|
<SearchResults input={input} isChatMode={isChatMode} />
|
||||||
|
) : (
|
||||||
<DropdownList
|
<DropdownList
|
||||||
suggests={suggests}
|
suggests={suggests}
|
||||||
|
SearchData={SearchData}
|
||||||
IsError={IsError}
|
IsError={IsError}
|
||||||
isSearchComplete={isSearchComplete}
|
isSearchComplete={isSearchComplete}
|
||||||
|
isChatMode={isChatMode}
|
||||||
selected={(item) => setSelectedItem(item)}
|
selected={(item) => setSelectedItem(item)}
|
||||||
/>
|
/>
|
||||||
|
)
|
||||||
) : (
|
) : (
|
||||||
<div
|
<div
|
||||||
data-tauri-drag-region
|
data-tauri-drag-region
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ const ChatSwitch: React.FC<ChatSwitchProps> = ({ isChatMode, onChange }) => {
|
|||||||
role="switch"
|
role="switch"
|
||||||
aria-checked={isChatMode}
|
aria-checked={isChatMode}
|
||||||
className={`relative flex items-center justify-between w-10 h-[18px] rounded-full cursor-pointer transition-colors duration-300 ${
|
className={`relative flex items-center justify-between w-10 h-[18px] rounded-full cursor-pointer transition-colors duration-300 ${
|
||||||
isChatMode ? "bg-[#0072ff]" : "bg-[#6000FF]"
|
isChatMode ? "bg-[#0072ff]" : "bg-[#950599]"
|
||||||
}`}
|
}`}
|
||||||
onClick={handleToggle}
|
onClick={handleToggle}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1,78 +1,138 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { Calendar, User, Clock } from "lucide-react";
|
|
||||||
|
import { useAppStore } from "@/stores/appStore";
|
||||||
|
import {formatter} from "@/utils/index"
|
||||||
|
import source_default_img from "@/assets/images/source_default.png";
|
||||||
|
import source_default_dark_img from "@/assets/images/source_default_dark.png";
|
||||||
|
import { useTheme } from "@/contexts/ThemeContext";
|
||||||
|
|
||||||
interface DocumentDetailProps {
|
interface DocumentDetailProps {
|
||||||
documentId?: string;
|
document: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DocumentDetail: React.FC<DocumentDetailProps> = ({
|
export const DocumentDetail: React.FC<DocumentDetailProps> = ({ document }) => {
|
||||||
documentId,
|
const connector_data = useAppStore((state) => state.connector_data);
|
||||||
}) => {
|
const datasourceData = useAppStore((state) => state.datasourceData);
|
||||||
if (!documentId) {
|
const endpoint_http = useAppStore((state) => state.endpoint_http);
|
||||||
return (
|
|
||||||
<div className="h-full flex items-center justify-center text-gray-400 dark:text-gray-500">
|
const { theme } = useTheme();
|
||||||
请选择一个文档查看详情
|
|
||||||
</div>
|
function findConnectorIcon(item: any) {
|
||||||
|
const id = item?._source?.source?.id || "";
|
||||||
|
|
||||||
|
const result_source = datasourceData.find(
|
||||||
|
(data: any) => data._source.id === id
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const connector_id = result_source?._source?.connector?.id;
|
||||||
|
|
||||||
|
const result_connector = connector_data.find(
|
||||||
|
(data: any) => data._source.id === connector_id
|
||||||
|
);
|
||||||
|
|
||||||
|
return result_connector?._source;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTypeIcon(item: any) {
|
||||||
|
const connectorSource = findConnectorIcon(item);
|
||||||
|
const icons = connectorSource?.icon;
|
||||||
|
|
||||||
|
if (!icons) {
|
||||||
|
return theme === "dark" ? source_default_dark_img : source_default_img;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (icons?.includes("http")) {
|
||||||
|
return icons;
|
||||||
|
} else {
|
||||||
|
return endpoint_http + icons;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="p-8 space-y-8">
|
<div className="p-4">
|
||||||
<div className="space-y-6">
|
<div className="font-normal text-xs text-[#666] dark:text-[#999] mb-2">
|
||||||
<h2 className="text-2xl font-semibold text-gray-900 dark:text-gray-100">
|
Details
|
||||||
产品需求规划文档
|
|
||||||
</h2>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<div className="flex items-center gap-6 text-sm text-gray-500 dark:text-gray-400">
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<Calendar className="w-4 h-4" />
|
|
||||||
<span>2024-02-20</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<User className="w-4 h-4" />
|
|
||||||
<span>张小明</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center gap-6 text-sm text-gray-500 dark:text-gray-400">
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<Clock className="w-4 h-4" />
|
|
||||||
<span>最近更新于 2小时前</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<img
|
{/* <div className="mb-4">
|
||||||
|
<iframe
|
||||||
|
src={document?._source?.metadata?.web_view_link}
|
||||||
|
style={{ width: "100%", height: "500px" }}
|
||||||
|
title="Text Preview"
|
||||||
|
/>
|
||||||
|
</div> */}
|
||||||
|
|
||||||
|
{/* <img
|
||||||
src="https://images.unsplash.com/photo-1664575602276-acd073f104c1"
|
src="https://images.unsplash.com/photo-1664575602276-acd073f104c1"
|
||||||
alt="Document preview"
|
alt="Document preview"
|
||||||
className="w-full aspect-video object-cover rounded-xl shadow-md"
|
className="w-full aspect-video object-cover rounded-xl shadow-md"
|
||||||
|
/> */}
|
||||||
|
|
||||||
|
<div className="py-4 mt-4">
|
||||||
|
<div className="flex justify-between flex-wrap font-normal text-xs mb-2.5">
|
||||||
|
<div className="text-[#666]">Name</div>
|
||||||
|
<div className="text-[#333] dark:text-[#D8D8D8] text-right w-60 break-words">
|
||||||
|
{document?._source?.title || "-"}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex justify-between flex-wrap font-normal text-xs mb-2.5">
|
||||||
|
<div className="text-[#666]">Source</div>
|
||||||
|
<div className="text-[#333] dark:text-[#D8D8D8] flex justify-end text-right w-56 break-words">
|
||||||
|
<img
|
||||||
|
className="w-4 h-4 mr-1"
|
||||||
|
src={getTypeIcon(document)}
|
||||||
|
alt="icon"
|
||||||
/>
|
/>
|
||||||
|
{document?._source?.source?.name || "-"}
|
||||||
<div className="prose prose-gray dark:prose-invert max-w-none">
|
</div>
|
||||||
<h3 className="text-lg font-medium text-gray-900 dark:text-gray-100">
|
</div>
|
||||||
文档概述
|
{/* <div className="flex justify-between font-normal text-xs mb-2.5">
|
||||||
</h3>
|
<div className="text-[#666]">Where</div>
|
||||||
<p className="text-gray-600 dark:text-gray-300 leading-relaxed">
|
<div className="text-[#333] dark:text-[#D8D8D8] text-right w-56 break-words">
|
||||||
本文档详细说明了2024年Q1的产品规划方向和具体功能需求。包含了用户研究结果、
|
-
|
||||||
竞品分析、功能优先级排序等重要内容。产品团队可以基于此文档进行后续的设计和开发工作。
|
</div>
|
||||||
</p>
|
</div> */}
|
||||||
|
{document?._source?.updated ? (
|
||||||
<h3 className="text-lg font-medium text-gray-900 dark:text-gray-100 mt-6">
|
<div className="flex justify-between flex-wrap font-normal text-xs mb-2.5">
|
||||||
主要内容
|
<div className="text-[#666]">Updated at</div>
|
||||||
</h3>
|
<div className="text-[#333] dark:text-[#D8D8D8] text-right w-56 break-words">
|
||||||
<ul className="list-disc pl-4 text-gray-600 dark:text-gray-300 space-y-2">
|
{document?._source?.updated || "-"}
|
||||||
<li>用户痛点分析与解决方案</li>
|
</div>
|
||||||
<li>核心功能详细说明</li>
|
</div>
|
||||||
<li>交互流程设计</li>
|
) : null}
|
||||||
<li>技术可行性评估</li>
|
{document?._source?.last_updated_by?.user?.username ? (
|
||||||
<li>项目时间节点规划</li>
|
<div className="flex justify-between flex-wrap font-normal text-xs mb-2.5">
|
||||||
</ul>
|
<div className="text-[#666]">Update by</div>
|
||||||
|
<div className="text-[#333] dark:text-[#D8D8D8] text-right w-56 break-words">
|
||||||
<p className="text-gray-600 dark:text-gray-300 leading-relaxed mt-6">
|
{document?._source?.last_updated_by?.user?.username || "-"}
|
||||||
通过实施本文档中规划的功能,我们期望能够提升用户体验,增强产品竞争力,
|
</div>
|
||||||
实现Q1的业务增长目标。
|
</div>
|
||||||
</p>
|
) : null}
|
||||||
|
{document?._source?.owner?.username ? (
|
||||||
|
<div className="flex justify-between flex-wrap font-normal text-xs mb-2.5">
|
||||||
|
<div className="text-[#666]">Created by</div>
|
||||||
|
<div className="text-[#333] dark:text-[#D8D8D8] text-right w-56 break-words">
|
||||||
|
{document?._source?.owner?.username || "-"}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
{document?._source?.type ? (
|
||||||
|
<div className="flex justify-between flex-wrap font-normal text-xs mb-2.5">
|
||||||
|
<div className="text-[#666]">Type</div>
|
||||||
|
<div className="text-[#333] dark:text-[#D8D8D8] text-right w-56 break-words">
|
||||||
|
{document?._source?.type || "-"}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
{document?._source?.size ? (
|
||||||
|
<div className="flex justify-between flex-wrap font-normal text-xs mb-2.5">
|
||||||
|
<div className="text-[#666]">Size</div>
|
||||||
|
<div className="text-[#333] dark:text-[#D8D8D8] text-right w-56 break-words">
|
||||||
|
{formatter.bytes(document?._source?.size || 0)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,91 +1,269 @@
|
|||||||
import React from 'react';
|
import React, { useState, useRef, useEffect } from "react";
|
||||||
import { FileText, Image, FileCode, Users, User, Globe } from 'lucide-react';
|
import { useInfiniteScroll } from "ahooks";
|
||||||
|
import { isTauri } from "@tauri-apps/api/core";
|
||||||
|
import { open } from "@tauri-apps/plugin-shell";
|
||||||
|
|
||||||
interface Document {
|
import { useAppStore } from "@/stores/appStore";
|
||||||
id: string;
|
import { tauriFetch } from "@/api/tauriFetchClient";
|
||||||
title: string;
|
import { useSearchStore } from "@/stores/searchStore";
|
||||||
type: 'text' | 'image' | 'code';
|
import { SearchHeader } from "./SearchHeader";
|
||||||
owner: 'personal' | 'team' | 'public';
|
import file_efault_img from "@/assets/images/file_efault.png";
|
||||||
description: string;
|
import noDataImg from "@/assets/coconut-tree.png";
|
||||||
date: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const documents: Document[] = [
|
|
||||||
{
|
|
||||||
id: '1',
|
|
||||||
title: '产品需求规划文档.doc',
|
|
||||||
type: 'text',
|
|
||||||
owner: 'team',
|
|
||||||
description: '2024年Q1产品规划及功能需求文档,包含详细的功能描述和交互设计说明。',
|
|
||||||
date: '2024-02-20'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: '2',
|
|
||||||
title: 'UI设计规范.fig',
|
|
||||||
type: 'image',
|
|
||||||
owner: 'public',
|
|
||||||
description: '最新的设计系统规范文档,包含组件库使用说明和设计标准。',
|
|
||||||
date: '2024-02-19'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: '3',
|
|
||||||
title: 'API接口文档.ts',
|
|
||||||
type: 'code',
|
|
||||||
owner: 'personal',
|
|
||||||
description: 'TypeScript版本的API接口定义文档,包含所有接口的请求和响应类型。',
|
|
||||||
date: '2024-02-18'
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const getIcon = (type: Document['type']) => {
|
|
||||||
switch (type) {
|
|
||||||
case 'image':
|
|
||||||
return <Image className="w-5 h-5 text-blue-500 dark:text-blue-400" />;
|
|
||||||
case 'code':
|
|
||||||
return <FileCode className="w-5 h-5 text-green-500 dark:text-green-400" />;
|
|
||||||
default:
|
|
||||||
return <FileText className="w-5 h-5 text-purple-500 dark:text-purple-400" />;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const getOwnerIcon = (owner: Document['owner']) => {
|
|
||||||
switch (owner) {
|
|
||||||
case 'team':
|
|
||||||
return <Users className="w-4 h-4 text-blue-500 dark:text-blue-400" />;
|
|
||||||
case 'public':
|
|
||||||
return <Globe className="w-4 h-4 text-green-500 dark:text-green-400" />;
|
|
||||||
default:
|
|
||||||
return <User className="w-4 h-4 text-gray-500 dark:text-gray-400" />;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
interface DocumentListProps {
|
interface DocumentListProps {
|
||||||
onSelectDocument: (id: string) => void;
|
onSelectDocument: (id: string) => void;
|
||||||
|
getDocDetail: (detail: any) => void;
|
||||||
|
input: string;
|
||||||
|
isChatMode: boolean;
|
||||||
selectedId?: string;
|
selectedId?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DocumentList: React.FC<DocumentListProps> = ({ onSelectDocument, selectedId }) => {
|
const PAGE_SIZE = 20;
|
||||||
|
|
||||||
|
export const DocumentList: React.FC<DocumentListProps> = ({
|
||||||
|
input,
|
||||||
|
getDocDetail,
|
||||||
|
isChatMode,
|
||||||
|
}) => {
|
||||||
|
const connector_data = useAppStore((state) => state.connector_data);
|
||||||
|
const datasourceData = useAppStore((state) => state.datasourceData);
|
||||||
|
const sourceData = useSearchStore((state) => state.sourceData);
|
||||||
|
const endpoint_http = useAppStore((state) => state.endpoint_http);
|
||||||
|
|
||||||
|
const [selectedItem, setSelectedItem] = useState<number | null>(null);
|
||||||
|
const [total, setTotal] = useState(0);
|
||||||
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
|
const itemRefs = useRef<(HTMLDivElement | null)[]>([]);
|
||||||
|
|
||||||
|
const { data, loading } = useInfiniteScroll(
|
||||||
|
async (d) => {
|
||||||
|
const page = d ? Math.ceil(d.list.length / PAGE_SIZE) + 1 : 1;
|
||||||
|
|
||||||
|
const from = (page - 1) * PAGE_SIZE;
|
||||||
|
|
||||||
|
let url = `/query/_search?query=${input}&datasource=${sourceData?._source?.source?.id}&from=${from}&size=${PAGE_SIZE}`;
|
||||||
|
|
||||||
|
if (sourceData?._source?.rich_categories) {
|
||||||
|
url = `/query/_search?query=${input}&rich_category=${sourceData?._source?.rich_categories[0]?.key}&from=${from}&size=${PAGE_SIZE}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await tauriFetch({
|
||||||
|
url,
|
||||||
|
method: "GET",
|
||||||
|
});
|
||||||
|
|
||||||
|
const list = response.data?.hits?.hits || [];
|
||||||
|
const total = response.data?.hits?.total?.value || 0;
|
||||||
|
|
||||||
|
console.log("doc", url, response.data?.hits)
|
||||||
|
|
||||||
|
setTotal(total);
|
||||||
|
|
||||||
|
getDocDetail(list[0] || {});
|
||||||
|
|
||||||
|
return {
|
||||||
|
list,
|
||||||
|
hasMore: from + list.length < total,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to fetch documents:", error);
|
||||||
|
return {
|
||||||
|
list: [],
|
||||||
|
hasMore: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
target: containerRef,
|
||||||
|
isNoMore: (d) => (d?.list.length || 0) >= total,
|
||||||
|
reloadDeps: [input, JSON.stringify(sourceData)],
|
||||||
|
onBefore: () => {
|
||||||
|
setTimeout(() => {
|
||||||
|
const parentRef = containerRef.current;
|
||||||
|
if (parentRef && parentRef.childElementCount > 10) {
|
||||||
|
const itemHeight = (parentRef.firstChild as HTMLElement)?.offsetHeight || 80;
|
||||||
|
parentRef.scrollTo({
|
||||||
|
top: (parentRef.lastChild as HTMLElement)?.offsetTop - itemHeight,
|
||||||
|
behavior: 'instant',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onFinally: (data) => onFinally(data, containerRef),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const onFinally = (data: any, ref: any) => {
|
||||||
|
if (data?.page === 1) return;
|
||||||
|
const parentRef = ref.current;
|
||||||
|
if (!parentRef) return;
|
||||||
|
const itemHeight = parentRef.firstChild?.offsetHeight || 80;
|
||||||
|
parentRef.scrollTo({
|
||||||
|
top:
|
||||||
|
parentRef.lastChild?.offsetTop - (data?.list?.length + 1) * itemHeight,
|
||||||
|
behavior: 'instant',
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
function findConnectorIcon(item: any) {
|
||||||
|
const id = item?._source?.source?.id || "";
|
||||||
|
|
||||||
|
const result_source = datasourceData.find(
|
||||||
|
(data: any) => data._source.id === id
|
||||||
|
);
|
||||||
|
|
||||||
|
const connector_id = result_source?._source?.connector?.id;
|
||||||
|
|
||||||
|
const result_connector = connector_data.find(
|
||||||
|
(data: any) => data._source.id === connector_id
|
||||||
|
);
|
||||||
|
|
||||||
|
return result_connector?._source;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getIcon(item: any) {
|
||||||
|
const connectorSource = findConnectorIcon(item);
|
||||||
|
const icons = connectorSource?.assets?.icons || {};
|
||||||
|
|
||||||
|
const selectedIcon = icons[item?._source?.icon];
|
||||||
|
|
||||||
|
if (!selectedIcon) {
|
||||||
|
return file_efault_img;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selectedIcon?.includes("http")) {
|
||||||
|
return selectedIcon;
|
||||||
|
} else {
|
||||||
|
return endpoint_http + selectedIcon;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onMouseEnter(index: number, item: any) {
|
||||||
|
getDocDetail(item);
|
||||||
|
setSelectedItem(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setSelectedItem(null);
|
||||||
|
}, [isChatMode, input]);
|
||||||
|
|
||||||
|
const handleOpenURL = async (url: string) => {
|
||||||
|
if (!url) return;
|
||||||
|
try {
|
||||||
|
if (isTauri()) {
|
||||||
|
await open(url);
|
||||||
|
// console.log("URL opened in default browser");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to open URL:", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleKeyDown = (e: KeyboardEvent) => {
|
||||||
|
if (!data?.list?.length) return;
|
||||||
|
|
||||||
|
if (e.key === "ArrowUp") {
|
||||||
|
e.preventDefault();
|
||||||
|
setSelectedItem((prev) => (prev === null || prev === 0 ? 0 : prev - 1));
|
||||||
|
} else if (e.key === "ArrowDown") {
|
||||||
|
e.preventDefault();
|
||||||
|
setSelectedItem((prev) =>
|
||||||
|
prev === null ? 0 : prev === data?.list?.length - 1 ? prev : prev + 1
|
||||||
|
);
|
||||||
|
} else if (e.key === "Meta") {
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e.key === "Enter" && selectedItem !== null) {
|
||||||
|
const item = data?.list?.[selectedItem];
|
||||||
|
if (item?._source?.url) {
|
||||||
|
handleOpenURL(item?._source?.url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
window.addEventListener("keydown", handleKeyDown);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener("keydown", handleKeyDown);
|
||||||
|
};
|
||||||
|
}, [selectedItem]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (selectedItem !== null && itemRefs.current[selectedItem]) {
|
||||||
|
itemRefs.current[selectedItem]?.scrollIntoView({
|
||||||
|
behavior: "smooth",
|
||||||
|
block: "nearest",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [selectedItem]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-1 py-2">
|
<div className="w-[50%] border-r border-gray-200 dark:border-gray-700 flex flex-col h-full">
|
||||||
{documents.map((doc) => (
|
<div className="px-2 flex-shrink-0">
|
||||||
<button
|
<SearchHeader total={total} />
|
||||||
key={doc.id}
|
</div>
|
||||||
onClick={() => onSelectDocument(doc.id)}
|
|
||||||
className={`w-full flex items-start px-4 py-3 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700/50 transition-colors ${
|
<div
|
||||||
selectedId === doc.id ? 'bg-blue-50 dark:bg-blue-900/50' : ''
|
ref={containerRef}
|
||||||
|
className="flex-1 overflow-y-auto custom-scrollbar"
|
||||||
|
>
|
||||||
|
{data?.list.map((item: any, index: number) => {
|
||||||
|
const isSelected = selectedItem === index;
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={item._id + index}
|
||||||
|
ref={(el) => (itemRefs.current[index] = el)}
|
||||||
|
onMouseEnter={() => onMouseEnter(index, item)}
|
||||||
|
onClick={() => {
|
||||||
|
if (item?._source?.url) {
|
||||||
|
handleOpenURL(item?._source?.url);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
className={`w-full px-2 py-2.5 text-sm flex items-center gap-3 rounded-lg transition-colors cursor-pointer ${
|
||||||
|
isSelected
|
||||||
|
? "text-white bg-[#950599] hover:bg-[#950599]"
|
||||||
|
: "text-[#333] dark:text-[#d8d8d8]"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<span className="mr-3 mt-0.5">{getIcon(doc.type)}</span>
|
<div className="flex gap-2 items-center flex-1 min-w-0">
|
||||||
<div className="flex-1 text-left">
|
<img
|
||||||
<div className="flex items-center gap-2">
|
className="w-5 h-5 flex-shrink-0"
|
||||||
<span className="text-sm font-medium text-gray-900 dark:text-gray-100">{doc.title}</span>
|
src={getIcon(item)}
|
||||||
<span className="mt-0.5">{getOwnerIcon(doc.owner)}</span>
|
alt="icon"
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
className={`text-sm truncate ${
|
||||||
|
isSelected ? "font-medium" : ""
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{item?._source?.title}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-xs text-gray-500 dark:text-gray-400 mt-1 line-clamp-2">{doc.description}</p>
|
|
||||||
<span className="text-xs text-gray-400 dark:text-gray-500 mt-1 block">{doc.date}</span>
|
|
||||||
</div>
|
</div>
|
||||||
</button>
|
);
|
||||||
))}
|
})}
|
||||||
|
|
||||||
|
{loading && (
|
||||||
|
<div className="flex justify-center py-4">
|
||||||
|
<span>Loading...</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{!loading && data?.list.length === 0 && (
|
||||||
|
<div
|
||||||
|
data-tauri-drag-region
|
||||||
|
className="h-full w-full flex flex-col items-center"
|
||||||
|
>
|
||||||
|
<img src={noDataImg} alt="no-data" className="w-16 h-16 mt-24" />
|
||||||
|
<div className="mt-4 text-sm text-[#999] dark:text-[#666]">
|
||||||
|
No Results
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -118,7 +118,7 @@ function Search({ isTransitioned, isChatMode, input }: SearchProps) {
|
|||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
{selectedItem ? <SearchResults /> : null}
|
{selectedItem ? <SearchResults input={input} isChatMode={isChatMode} /> : null}
|
||||||
|
|
||||||
{suggests.length > 0 || selectedItem ? (
|
{suggests.length > 0 || selectedItem ? (
|
||||||
<Footer isChat={false} name={selectedItem?.source} />
|
<Footer isChat={false} name={selectedItem?.source} />
|
||||||
|
|||||||
@@ -58,15 +58,19 @@ const typeOptions: FilterOption[] = [
|
|||||||
{ id: "code", label: "Code" },
|
{ id: "code", label: "Code" },
|
||||||
];
|
];
|
||||||
|
|
||||||
export const SearchHeader: React.FC = () => {
|
interface SearchHeaderProps {
|
||||||
|
total: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SearchHeader: React.FC<SearchHeaderProps> = ({ total }) => {
|
||||||
const [typeFilter, setTypeFilter] = useState("all");
|
const [typeFilter, setTypeFilter] = useState("all");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center justify-between py-1">
|
<div className="flex items-center justify-between py-1">
|
||||||
<div className="text-xs text-gray-600 dark:text-gray-400">
|
<div className="text-xs text-gray-600 dark:text-gray-400">
|
||||||
Find
|
Found
|
||||||
<span className="px-1 font-medium text-gray-900 dark:text-gray-100">
|
<span className="px-1 font-medium text-gray-900 dark:text-gray-100">
|
||||||
200
|
{total}
|
||||||
</span>
|
</span>
|
||||||
results
|
results
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,32 +1,38 @@
|
|||||||
import React, { useState } from "react";
|
import { useState } from "react";
|
||||||
import { SearchHeader } from "./SearchHeader";
|
|
||||||
import { DocumentList } from "./DocumentList";
|
import { DocumentList } from "./DocumentList";
|
||||||
import { DocumentDetail } from "./DocumentDetail";
|
import { DocumentDetail } from "./DocumentDetail";
|
||||||
|
|
||||||
export const SearchResults: React.FC = () => {
|
interface SearchResultsProps {
|
||||||
|
input: string;
|
||||||
|
isChatMode: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function SearchResults({ input, isChatMode }: SearchResultsProps) {
|
||||||
const [selectedDocumentId, setSelectedDocumentId] = useState("1");
|
const [selectedDocumentId, setSelectedDocumentId] = useState("1");
|
||||||
|
|
||||||
|
const [detailData, setDetailData] = useState<any>({});
|
||||||
|
|
||||||
|
function getDocDetail(detail: any) {
|
||||||
|
setDetailData(detail)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="max-h-[458px] w-full p-2 flex flex-col rounded-xl overflow-y-auto overflow-hidden custom-scrollbar focus:outline-none">
|
<div className="h-[458px] w-full p-2 pr-0 flex flex-col rounded-xl focus:outline-none">
|
||||||
<div className="flex">
|
<div className="h-full flex">
|
||||||
{/* Left Panel */}
|
{/* Left Panel */}
|
||||||
<div className="w-[50%] border-r border-gray-200 dark:border-gray-700 flex flex-col overflow-hidden">
|
|
||||||
<div className="px-4 flex-shrink-0">
|
|
||||||
<SearchHeader />
|
|
||||||
</div>
|
|
||||||
<div className="overflow-y-auto flex-1 custom-scrollbar">
|
|
||||||
<DocumentList
|
<DocumentList
|
||||||
onSelectDocument={setSelectedDocumentId}
|
onSelectDocument={setSelectedDocumentId}
|
||||||
selectedId={selectedDocumentId}
|
selectedId={selectedDocumentId}
|
||||||
|
input={input}
|
||||||
|
getDocDetail={getDocDetail}
|
||||||
|
isChatMode={isChatMode}
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Right Panel */}
|
{/* Right Panel */}
|
||||||
<div className="flex-1 overflow-y-auto custom-scrollbar">
|
<div className="flex-1 overflow-y-auto custom-scrollbar">
|
||||||
<DocumentDetail documentId={selectedDocumentId} />
|
<DocumentDetail document={detailData}/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|||||||
30
src/hooks/useInfiniteScroll.ts
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
import { useEffect, useRef } from "react";
|
||||||
|
|
||||||
|
const useInfiniteScroll = (callback: () => void) => {
|
||||||
|
const loaderRef = useRef<HTMLDivElement | null>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const observer = new IntersectionObserver(
|
||||||
|
(entries) => {
|
||||||
|
if (entries[0].isIntersecting) {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ threshold: 1.0 }
|
||||||
|
);
|
||||||
|
|
||||||
|
if (loaderRef.current) {
|
||||||
|
observer.observe(loaderRef.current);
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
if (loaderRef.current) {
|
||||||
|
observer.unobserve(loaderRef.current);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, [callback]);
|
||||||
|
|
||||||
|
return loaderRef;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useInfiniteScroll;
|
||||||
@@ -27,6 +27,19 @@ const RESERVED_SHORTCUTS = [
|
|||||||
["Command", "M"],
|
["Command", "M"],
|
||||||
["Command", "Enter"],
|
["Command", "Enter"],
|
||||||
["Command", "ArrowLeft"],
|
["Command", "ArrowLeft"],
|
||||||
|
["Command", "ArrowRight"],
|
||||||
|
["Command", "ArrowUp"],
|
||||||
|
["Command", "ArrowDown"],
|
||||||
|
["Command", "0"],
|
||||||
|
["Command", "1"],
|
||||||
|
["Command", "2"],
|
||||||
|
["Command", "3"],
|
||||||
|
["Command", "4"],
|
||||||
|
["Command", "5"],
|
||||||
|
["Command", "6"],
|
||||||
|
["Command", "7"],
|
||||||
|
["Command", "8"],
|
||||||
|
["Command", "9"],
|
||||||
];
|
];
|
||||||
|
|
||||||
export function useShortcutEditor(shortcut: Shortcut, onChange: (shortcut: Shortcut) => void) {
|
export function useShortcutEditor(shortcut: Shortcut, onChange: (shortcut: Shortcut) => void) {
|
||||||
|
|||||||
55
src/main.css
@@ -7,12 +7,53 @@
|
|||||||
--background: #ffffff;
|
--background: #ffffff;
|
||||||
--foreground: #09090b;
|
--foreground: #09090b;
|
||||||
--border: #e3e3e7;
|
--border: #e3e3e7;
|
||||||
|
--docsearch-primary-color: rgb(149, 5, 153);
|
||||||
|
--docsearch-text-color: rgb(28, 30, 33);
|
||||||
|
--docsearch-spacing: 12px;
|
||||||
|
--docsearch-icon-stroke-width: 1.4;
|
||||||
|
--docsearch-highlight-color: var(--docsearch-primary-color);
|
||||||
|
--docsearch-muted-color: rgb(150, 159, 175);
|
||||||
|
--docsearch-modal-container-background: rgba(101, 108, 133, .8);
|
||||||
|
--docsearch-modal-width: 560px;
|
||||||
|
--docsearch-modal-height: 600px;
|
||||||
|
--docsearch-modal-background: rgb(245, 246, 247);
|
||||||
|
--docsearch-modal-shadow: inset 1px 1px 0 0 rgba(255, 255, 255, .5), 0 3px 8px 0 rgba(85, 90, 100, 1);
|
||||||
|
--docsearch-searchbox-height: 56px;
|
||||||
|
--docsearch-searchbox-background: rgb(235, 237, 240);
|
||||||
|
--docsearch-searchbox-focus-background: #fff;
|
||||||
|
--docsearch-searchbox-shadow: inset 0 0 0 2px var(--docsearch-primary-color);
|
||||||
|
--docsearch-hit-height: 56px;
|
||||||
|
--docsearch-hit-color: rgb(68, 73, 80);
|
||||||
|
--docsearch-hit-active-color: #fff;
|
||||||
|
--docsearch-hit-background: #fff;
|
||||||
|
--docsearch-hit-shadow: 0 1px 3px 0 rgb(212, 217, 225);
|
||||||
|
--docsearch-key-gradient: linear-gradient(-225deg, rgb(213, 219, 228) 0%, rgb(248, 248, 248) 100%);
|
||||||
|
--docsearch-key-shadow: inset 0 -2px 0 0 rgb(205, 205, 230), inset 0 0 1px 1px #fff, 0 1px 2px 1px rgba(30, 35, 90, .4);
|
||||||
|
--docsearch-footer-height: 44px;
|
||||||
|
--docsearch-footer-background: #fff;
|
||||||
|
--docsearch-footer-shadow: 0 -1px 0 0 rgb(224, 227, 232), 0 -3px 6px 0 rgba(69, 98, 155, .12);
|
||||||
|
--docsearch-icon-color: rgb(21, 21, 21);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark {
|
.dark {
|
||||||
--background: #09090b;
|
--background: #09090b;
|
||||||
--foreground: #f9f9f9;
|
--foreground: #f9f9f9;
|
||||||
--border: #27272a;
|
--border: #27272a;
|
||||||
|
--docsearch-text-color: rgb(245, 246, 247);
|
||||||
|
--docsearch-modal-container-background: rgba(9, 10, 17, .8);
|
||||||
|
--docsearch-modal-background: rgb(21, 23, 42);
|
||||||
|
--docsearch-modal-shadow: inset 1px 1px 0 0 rgb(44, 46, 64), 0 3px 8px 0 rgb(0, 3, 9);
|
||||||
|
--docsearch-searchbox-background: rgb(9, 10, 17);
|
||||||
|
--docsearch-searchbox-focus-background: #000;
|
||||||
|
--docsearch-hit-color: rgb(190, 195, 201);
|
||||||
|
--docsearch-hit-shadow: none;
|
||||||
|
--docsearch-hit-background: rgb(9, 10, 17);
|
||||||
|
--docsearch-key-gradient: linear-gradient(-26.5deg, rgb(86, 88, 114) 0%, rgb(49, 53, 91) 100%);
|
||||||
|
--docsearch-key-shadow: inset 0 -2px 0 0 rgb(40, 45, 85), inset 0 0 1px 1px rgb(81, 87, 125), 0 2px 2px 0 rgba(3, 4, 9, .3);
|
||||||
|
--docsearch-footer-background: rgb(30, 33, 54);
|
||||||
|
--docsearch-footer-shadow: inset 0 1px 0 0 rgba(73, 76, 106, .5), 0 -4px 8px 0 rgba(0, 0, 0, .2);
|
||||||
|
--docsearch-muted-color: rgb(127, 132, 151);
|
||||||
|
--docsearch-icon-color: rgb(255, 255, 255);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -32,7 +73,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.input-body {
|
.input-body {
|
||||||
@apply rounded-xl overflow-hidden
|
@apply rounded-xl overflow-hidden;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -142,6 +183,18 @@
|
|||||||
background-color: #f79c42;
|
background-color: #f79c42;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.docsearch-modal-footer-commands-key {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border-radius: 4px;
|
||||||
|
border: 0px;
|
||||||
|
padding: 2px;
|
||||||
|
background: var(--docsearch-key-gradient);
|
||||||
|
/* box-shadow: var(--docsearch-key-shadow); */
|
||||||
|
color: var(--docsearch-muted-color);
|
||||||
|
}
|
||||||
|
|
||||||
.user-select{
|
.user-select{
|
||||||
-webkit-touch-callout: none;
|
-webkit-touch-callout: none;
|
||||||
-webkit-user-select: none;
|
-webkit-user-select: none;
|
||||||
|
|||||||
263
src/mock/index.ts
Normal file
@@ -0,0 +1,263 @@
|
|||||||
|
// mock
|
||||||
|
export const res_search = {
|
||||||
|
took: 2590,
|
||||||
|
timed_out: false,
|
||||||
|
_shards: {
|
||||||
|
total: 1,
|
||||||
|
successful: 1,
|
||||||
|
skipped: 0,
|
||||||
|
failed: 0
|
||||||
|
},
|
||||||
|
hits: {
|
||||||
|
total: {
|
||||||
|
value: 253,
|
||||||
|
relation: "eq"
|
||||||
|
},
|
||||||
|
max_score: 32.709457,
|
||||||
|
hits: [
|
||||||
|
{
|
||||||
|
_index: "coco_document",
|
||||||
|
_type: "_doc",
|
||||||
|
_id: "3ac857ef30d101b1e5880b53b1438b1a",
|
||||||
|
_score: 32.709457,
|
||||||
|
_source: {
|
||||||
|
icon: "web",
|
||||||
|
id: "3ac857ef30d101b1e5880b53b1438b1a",
|
||||||
|
source: {
|
||||||
|
name: "hugo_site",
|
||||||
|
type: "connector"
|
||||||
|
},
|
||||||
|
type: "web_page",
|
||||||
|
category: "Aggregation",
|
||||||
|
subcategory: "Metric",
|
||||||
|
title: "Avg aggregation",
|
||||||
|
content: "",
|
||||||
|
author: "liaosy",
|
||||||
|
url: "https://pizza.rs/docs/references/aggregation/avg/",
|
||||||
|
tags: [
|
||||||
|
"avg",
|
||||||
|
"aggregation"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
_index: "coco_document",
|
||||||
|
_type: "_doc",
|
||||||
|
_id: "2485a744c5dae1278a01c04d39bf60a6",
|
||||||
|
_score: 32.37022,
|
||||||
|
_source: {
|
||||||
|
icon: "web",
|
||||||
|
id: "2485a744c5dae1278a01c04d39bf60a6",
|
||||||
|
source: {
|
||||||
|
name: "hugo_site",
|
||||||
|
type: "connector"
|
||||||
|
},
|
||||||
|
type: "web_page",
|
||||||
|
title: "auto_generate_doc_id",
|
||||||
|
content: "",
|
||||||
|
author: "liaosy",
|
||||||
|
url: "https://infinilabs.cn/docs/latest/gateway/references/filters/auto_generate_doc_id/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
_index: "coco_document",
|
||||||
|
_type: "_doc",
|
||||||
|
_id: "15aa340fa9ddfcfbf793b8707a4fa16b",
|
||||||
|
_score: 21.983166,
|
||||||
|
_source: {
|
||||||
|
icon: "web",
|
||||||
|
id: "15aa340fa9ddfcfbf793b8707a4fa16b",
|
||||||
|
source: {
|
||||||
|
name: "hugo_site",
|
||||||
|
type: "connector"
|
||||||
|
},
|
||||||
|
type: "web_page",
|
||||||
|
category: "Overview",
|
||||||
|
subcategory: "Architecture",
|
||||||
|
title: "Architecture",
|
||||||
|
content: "",
|
||||||
|
author: "yangfan",
|
||||||
|
url: "https://pizza.rs/docs/overview/architecture/",
|
||||||
|
tags: [
|
||||||
|
"architecture"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
_index: "coco_document",
|
||||||
|
_type: "_doc",
|
||||||
|
_id: "f96b1af318d62a43a44f54731409ff52",
|
||||||
|
_score: 21.887964,
|
||||||
|
_source: {
|
||||||
|
icon: "web",
|
||||||
|
id: "f96b1af318d62a43a44f54731409ff52",
|
||||||
|
source: {
|
||||||
|
name: "hugo_site",
|
||||||
|
type: "connector"
|
||||||
|
},
|
||||||
|
type: "web_page",
|
||||||
|
title: "Introducing Coco AI in Two Minutes - A Quick Start Video 🥥",
|
||||||
|
content: "",
|
||||||
|
author: "yangfan",
|
||||||
|
url: "https://blog.infinilabs.com/posts/2024/a-quick-start-viideo-to-introduce-coco-ai-in-two-minutes/",
|
||||||
|
tags: [
|
||||||
|
"Coco AI",
|
||||||
|
"Search",
|
||||||
|
"Gen-AI",
|
||||||
|
"Enterprise"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
_index: "coco_document",
|
||||||
|
_type: "_doc",
|
||||||
|
_id: "3a806937f9e7fe55905a7f71d111e523",
|
||||||
|
_score: 21.286049,
|
||||||
|
_source: {
|
||||||
|
icon: "web",
|
||||||
|
id: "3a806937f9e7fe55905a7f71d111e523",
|
||||||
|
source: {
|
||||||
|
name: "hugo_site",
|
||||||
|
type: "connector"
|
||||||
|
},
|
||||||
|
type: "web_page",
|
||||||
|
category: "Catalog",
|
||||||
|
subcategory: "Namespace",
|
||||||
|
title: "Create a namespace",
|
||||||
|
content: "",
|
||||||
|
author: "yangfan",
|
||||||
|
url: "https://pizza.rs/docs/references/namespace/create/",
|
||||||
|
tags: [
|
||||||
|
"create",
|
||||||
|
"namespace"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
_index: "coco_document",
|
||||||
|
_type: "_doc",
|
||||||
|
_id: "04f48643c2c52b872c149e077765f8cb",
|
||||||
|
_score: 21.286049,
|
||||||
|
_source: {
|
||||||
|
icon: "web",
|
||||||
|
id: "04f48643c2c52b872c149e077765f8cb",
|
||||||
|
source: {
|
||||||
|
name: "hugo_site",
|
||||||
|
type: "connector"
|
||||||
|
},
|
||||||
|
type: "web_page",
|
||||||
|
category: "Document",
|
||||||
|
subcategory: "Index",
|
||||||
|
title: "Create a document",
|
||||||
|
content: "",
|
||||||
|
author: "zouwenan",
|
||||||
|
url: "https://pizza.rs/docs/references/document/create/",
|
||||||
|
tags: [
|
||||||
|
"create",
|
||||||
|
"index"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
_index: "coco_document",
|
||||||
|
_type: "_doc",
|
||||||
|
_id: "d101818b1e6d2eb23ca2f813ef3a9648",
|
||||||
|
_score: 21.213268,
|
||||||
|
_source: {
|
||||||
|
icon: "web",
|
||||||
|
id: "d101818b1e6d2eb23ca2f813ef3a9648",
|
||||||
|
source: {
|
||||||
|
name: "hugo_site",
|
||||||
|
type: "connector"
|
||||||
|
},
|
||||||
|
type: "web_page",
|
||||||
|
category: "Catalog",
|
||||||
|
subcategory: "Collection",
|
||||||
|
title: "Create a collection",
|
||||||
|
content: " ",
|
||||||
|
author: "zouwenan",
|
||||||
|
url: "https://pizza.rs/docs/references/collection/create/",
|
||||||
|
tags: [
|
||||||
|
"create",
|
||||||
|
"collection"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
_index: "coco_document",
|
||||||
|
_type: "_doc",
|
||||||
|
_id: "ee2228755039808c199e6812d09c745e",
|
||||||
|
_score: 20.967154,
|
||||||
|
_source: {
|
||||||
|
icon: "web",
|
||||||
|
id: "ee2228755039808c199e6812d09c745e",
|
||||||
|
source: {
|
||||||
|
name: "hugo_site",
|
||||||
|
type: "connector"
|
||||||
|
},
|
||||||
|
type: "web_page",
|
||||||
|
category: "Document",
|
||||||
|
subcategory: "Index",
|
||||||
|
title: "Delete a document",
|
||||||
|
content: "",
|
||||||
|
author: "zouwenan",
|
||||||
|
url: "https://pizza.rs/docs/references/document/delete/",
|
||||||
|
tags: [
|
||||||
|
"delete",
|
||||||
|
"index"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
_index: "coco_document",
|
||||||
|
_type: "_doc",
|
||||||
|
_id: "60584d12aba0ff569e7b79a7be168810",
|
||||||
|
_score: 20.967154,
|
||||||
|
_source: {
|
||||||
|
icon: "web",
|
||||||
|
id: "60584d12aba0ff569e7b79a7be168810",
|
||||||
|
source: {
|
||||||
|
name: "hugo_site",
|
||||||
|
type: "connector"
|
||||||
|
},
|
||||||
|
type: "web_page",
|
||||||
|
category: "Catalog",
|
||||||
|
subcategory: "Namespace",
|
||||||
|
title: "Delete a namespace",
|
||||||
|
content: "",
|
||||||
|
author: "zouwenan",
|
||||||
|
url: "https://pizza.rs/docs/references/namespace/delete/",
|
||||||
|
tags: [
|
||||||
|
"delete",
|
||||||
|
"namespace"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
_index: "coco_document",
|
||||||
|
_type: "_doc",
|
||||||
|
_id: "73e31d0feeb8d3d97e4b06a98de54672",
|
||||||
|
_score: 20.934437,
|
||||||
|
_source: {
|
||||||
|
icon: "web",
|
||||||
|
id: "73e31d0feeb8d3d97e4b06a98de54672",
|
||||||
|
source: {
|
||||||
|
name: "hugo_site",
|
||||||
|
type: "connector"
|
||||||
|
},
|
||||||
|
type: "web_page",
|
||||||
|
category: "Document",
|
||||||
|
subcategory: "Index",
|
||||||
|
title: "Replace a document",
|
||||||
|
content: " ",
|
||||||
|
author: "medcl",
|
||||||
|
url: "https://pizza.rs/docs/references/document/replace/",
|
||||||
|
tags: [
|
||||||
|
"replace",
|
||||||
|
"index"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -99,7 +99,7 @@ export default function DesktopApp() {
|
|||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
data-tauri-drag-region
|
data-tauri-drag-region
|
||||||
className={`p-[7px] pb-0 absolute w-full flex items-center justify-center transition-all duration-500 ${
|
className={`p-2 pb-0 absolute w-full flex items-center justify-center transition-all duration-500 ${
|
||||||
isTransitioned
|
isTransitioned
|
||||||
? "top-[500px] h-[90px] border-t"
|
? "top-[500px] h-[90px] border-t"
|
||||||
: "top-0 h-[90px] border-b"
|
: "top-0 h-[90px] border-b"
|
||||||
@@ -123,7 +123,7 @@ export default function DesktopApp() {
|
|||||||
data-tauri-drag-region
|
data-tauri-drag-region
|
||||||
className={`absolute w-full transition-opacity duration-500 ${
|
className={`absolute w-full transition-opacity duration-500 ${
|
||||||
isTransitioned ? "opacity-0 pointer-events-none" : "opacity-100"
|
isTransitioned ? "opacity-0 pointer-events-none" : "opacity-100"
|
||||||
} bottom-0 h-[500px] user-select`}
|
} bottom-0 h-[500px] `}
|
||||||
>
|
>
|
||||||
<Search
|
<Search
|
||||||
key="Search"
|
key="Search"
|
||||||
@@ -141,7 +141,7 @@ export default function DesktopApp() {
|
|||||||
: "-top-[506px] opacity-0 pointer-events-none"
|
: "-top-[506px] opacity-0 pointer-events-none"
|
||||||
} h-[500px]`}
|
} h-[500px]`}
|
||||||
>
|
>
|
||||||
{isTransitioned ? (
|
{isTransitioned && isChatMode ? (
|
||||||
<ChatAI
|
<ChatAI
|
||||||
ref={chatAIRef}
|
ref={chatAIRef}
|
||||||
key="ChatAI"
|
key="ChatAI"
|
||||||
|
|||||||
22
src/stores/searchStore.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import { create } from "zustand";
|
||||||
|
import { persist } from "zustand/middleware";
|
||||||
|
|
||||||
|
export type ISearchStore = {
|
||||||
|
sourceData: any;
|
||||||
|
setSourceData: (sourceData: any) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useSearchStore = create<ISearchStore>()(
|
||||||
|
persist(
|
||||||
|
(set) => ({
|
||||||
|
sourceData: undefined,
|
||||||
|
setSourceData: (sourceData: any) => set({ sourceData }),
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
name: "search-store",
|
||||||
|
partialize: (state) => ({
|
||||||
|
sourceData: state.sourceData,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
@@ -81,3 +81,19 @@ export const authWitheGithub = (uid: string) => {
|
|||||||
|
|
||||||
location.href = `${authorizeUrl}?client_id=${"Ov23li4IcdbbWp2RgLTN"}&redirect_uri=${"http://localhost:1420/login"}`;
|
location.href = `${authorizeUrl}?client_id=${"Ov23li4IcdbbWp2RgLTN"}&redirect_uri=${"http://localhost:1420/login"}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const unitArr = ["B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"] as const;
|
||||||
|
|
||||||
|
export const formatter = {
|
||||||
|
bytes: (value: number): string => {
|
||||||
|
if (!Number.isFinite(value) || value <= 0) {
|
||||||
|
return "0B";
|
||||||
|
}
|
||||||
|
|
||||||
|
const index = Math.floor(Math.log(value) / Math.log(1024));
|
||||||
|
const size = (value / Math.pow(1024, index)).toFixed(1);
|
||||||
|
|
||||||
|
return size + (unitArr[index] ?? "B")
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -47,6 +47,11 @@ export default defineConfig(async () => ({
|
|||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
secure: false,
|
secure: false,
|
||||||
},
|
},
|
||||||
|
"/connector": {
|
||||||
|
target: process.env.COCO_SERVER_URL,
|
||||||
|
changeOrigin: true,
|
||||||
|
secure: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
build: {
|
build: {
|
||||||
|
|||||||