mirror of
https://github.com/infinilabs/coco-app.git
synced 2025-12-16 19:47:43 +01:00
refactor: web components (#331)
* refactor: web components * chore: web component * chore: web * chore: web * docs: update notes
This commit is contained in:
1
.vscode/settings.json
vendored
1
.vscode/settings.json
vendored
@@ -57,6 +57,7 @@
|
|||||||
"uuidv",
|
"uuidv",
|
||||||
"VITE",
|
"VITE",
|
||||||
"walkdir",
|
"walkdir",
|
||||||
|
"wavesurfer",
|
||||||
"webviews",
|
"webviews",
|
||||||
"xzvf",
|
"xzvf",
|
||||||
"yuque",
|
"yuque",
|
||||||
|
|||||||
@@ -19,6 +19,8 @@ Information about release notes of Coco Server is provided here.
|
|||||||
|
|
||||||
### Improvements
|
### Improvements
|
||||||
|
|
||||||
|
- refactor: web components #331
|
||||||
|
|
||||||
## 0.3.0 (2025-03-31)
|
## 0.3.0 (2025-03-31)
|
||||||
|
|
||||||
### Breaking changes
|
### Breaking changes
|
||||||
|
|||||||
@@ -6,8 +6,8 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"build": "tsc && vite build",
|
"build": "tsc && vite build",
|
||||||
"build:web": "tsc && tsup",
|
"build:web": "cross-env BUILD_TARGET=web tsc && cross-env BUILD_TARGET=web tsup --format esm",
|
||||||
"publish:web": "cd dist/search-chat && npm publish",
|
"publish:web": "cd out/search-chat && npm publish",
|
||||||
"publish:web:beta": "cd dist/search-chat && npm publish --tag beta",
|
"publish:web:beta": "cd dist/search-chat && npm publish --tag beta",
|
||||||
"publish:web:alpha": "cd dist/search-chat && npm publish --tag alpha",
|
"publish:web:alpha": "cd dist/search-chat && npm publish --tag alpha",
|
||||||
"publish:web:rc": "cd dist/search-chat && npm publish --tag rc",
|
"publish:web:rc": "cd dist/search-chat && npm publish --tag rc",
|
||||||
@@ -34,6 +34,7 @@
|
|||||||
"@tauri-apps/plugin-window": "2.0.0-alpha.1",
|
"@tauri-apps/plugin-window": "2.0.0-alpha.1",
|
||||||
"@wavesurfer/react": "^1.0.9",
|
"@wavesurfer/react": "^1.0.9",
|
||||||
"ahooks": "^3.8.4",
|
"ahooks": "^3.8.4",
|
||||||
|
"axios": "^1.8.4",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"dayjs": "^1.11.13",
|
"dayjs": "^1.11.13",
|
||||||
"dotenv": "^16.4.7",
|
"dotenv": "^16.4.7",
|
||||||
@@ -76,6 +77,7 @@
|
|||||||
"@types/react-window": "^1.8.8",
|
"@types/react-window": "^1.8.8",
|
||||||
"@vitejs/plugin-react": "^4.3.4",
|
"@vitejs/plugin-react": "^4.3.4",
|
||||||
"autoprefixer": "^10.4.21",
|
"autoprefixer": "^10.4.21",
|
||||||
|
"cross-env": "^7.0.3",
|
||||||
"immer": "^10.1.1",
|
"immer": "^10.1.1",
|
||||||
"postcss": "^8.5.3",
|
"postcss": "^8.5.3",
|
||||||
"release-it": "^18.1.2",
|
"release-it": "^18.1.2",
|
||||||
|
|||||||
169
pnpm-lock.yaml
generated
169
pnpm-lock.yaml
generated
@@ -56,6 +56,9 @@ importers:
|
|||||||
ahooks:
|
ahooks:
|
||||||
specifier: ^3.8.4
|
specifier: ^3.8.4
|
||||||
version: 3.8.4(react@18.3.1)
|
version: 3.8.4(react@18.3.1)
|
||||||
|
axios:
|
||||||
|
specifier: ^1.8.4
|
||||||
|
version: 1.8.4
|
||||||
clsx:
|
clsx:
|
||||||
specifier: ^2.1.1
|
specifier: ^2.1.1
|
||||||
version: 2.1.1
|
version: 2.1.1
|
||||||
@@ -177,6 +180,9 @@ importers:
|
|||||||
autoprefixer:
|
autoprefixer:
|
||||||
specifier: ^10.4.21
|
specifier: ^10.4.21
|
||||||
version: 10.4.21(postcss@8.5.3)
|
version: 10.4.21(postcss@8.5.3)
|
||||||
|
cross-env:
|
||||||
|
specifier: ^7.0.3
|
||||||
|
version: 7.0.3
|
||||||
immer:
|
immer:
|
||||||
specifier: ^10.1.1
|
specifier: ^10.1.1
|
||||||
version: 10.1.1
|
version: 10.1.1
|
||||||
@@ -1418,6 +1424,9 @@ packages:
|
|||||||
async-retry@1.3.3:
|
async-retry@1.3.3:
|
||||||
resolution: {integrity: sha512-wfr/jstw9xNi/0teMHrRW7dsz3Lt5ARhYNZ2ewpadnhaIp5mbALhOAP+EAdsC7t4Z6wqsDVv9+W6gm1Dk9mEyw==}
|
resolution: {integrity: sha512-wfr/jstw9xNi/0teMHrRW7dsz3Lt5ARhYNZ2ewpadnhaIp5mbALhOAP+EAdsC7t4Z6wqsDVv9+W6gm1Dk9mEyw==}
|
||||||
|
|
||||||
|
asynckit@0.4.0:
|
||||||
|
resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
|
||||||
|
|
||||||
atomically@2.0.3:
|
atomically@2.0.3:
|
||||||
resolution: {integrity: sha512-kU6FmrwZ3Lx7/7y3hPS5QnbJfaohcIul5fGqf7ok+4KklIEk9tJ0C2IQPdacSbVUWv6zVHXEBWoWd6NrVMT7Cw==}
|
resolution: {integrity: sha512-kU6FmrwZ3Lx7/7y3hPS5QnbJfaohcIul5fGqf7ok+4KklIEk9tJ0C2IQPdacSbVUWv6zVHXEBWoWd6NrVMT7Cw==}
|
||||||
|
|
||||||
@@ -1428,6 +1437,9 @@ packages:
|
|||||||
peerDependencies:
|
peerDependencies:
|
||||||
postcss: ^8.1.0
|
postcss: ^8.1.0
|
||||||
|
|
||||||
|
axios@1.8.4:
|
||||||
|
resolution: {integrity: sha512-eBSYY4Y68NNlHbHBMdeDmKNtDgXWhQsJcGqzO3iLUM0GraQFSS9cVgPX5I9b3lbdFKyYoAEGAZF1DwhTaljNAw==}
|
||||||
|
|
||||||
bail@2.0.2:
|
bail@2.0.2:
|
||||||
resolution: {integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==}
|
resolution: {integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==}
|
||||||
|
|
||||||
@@ -1478,6 +1490,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==}
|
resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
|
|
||||||
|
call-bind-apply-helpers@1.0.2:
|
||||||
|
resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==}
|
||||||
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
callsites@3.1.0:
|
callsites@3.1.0:
|
||||||
resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==}
|
resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==}
|
||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
@@ -1565,6 +1581,10 @@ packages:
|
|||||||
color-name@1.1.4:
|
color-name@1.1.4:
|
||||||
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
|
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
|
||||||
|
|
||||||
|
combined-stream@1.0.8:
|
||||||
|
resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==}
|
||||||
|
engines: {node: '>= 0.8'}
|
||||||
|
|
||||||
comma-separated-tokens@2.0.3:
|
comma-separated-tokens@2.0.3:
|
||||||
resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==}
|
resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==}
|
||||||
|
|
||||||
@@ -1618,6 +1638,11 @@ packages:
|
|||||||
typescript:
|
typescript:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
cross-env@7.0.3:
|
||||||
|
resolution: {integrity: sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==}
|
||||||
|
engines: {node: '>=10.14', npm: '>=6', yarn: '>=1'}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
cross-spawn@7.0.6:
|
cross-spawn@7.0.6:
|
||||||
resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
|
resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
|
||||||
engines: {node: '>= 8'}
|
engines: {node: '>= 8'}
|
||||||
@@ -1828,6 +1853,10 @@ packages:
|
|||||||
delaunator@5.0.1:
|
delaunator@5.0.1:
|
||||||
resolution: {integrity: sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw==}
|
resolution: {integrity: sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw==}
|
||||||
|
|
||||||
|
delayed-stream@1.0.0:
|
||||||
|
resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
|
||||||
|
engines: {node: '>=0.4.0'}
|
||||||
|
|
||||||
dequal@2.0.3:
|
dequal@2.0.3:
|
||||||
resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==}
|
resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==}
|
||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
@@ -1852,6 +1881,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==}
|
resolution: {integrity: sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
|
dunder-proto@1.0.1:
|
||||||
|
resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==}
|
||||||
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
eastasianwidth@0.2.0:
|
eastasianwidth@0.2.0:
|
||||||
resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==}
|
resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==}
|
||||||
|
|
||||||
@@ -1878,6 +1911,22 @@ packages:
|
|||||||
error-ex@1.3.2:
|
error-ex@1.3.2:
|
||||||
resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==}
|
resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==}
|
||||||
|
|
||||||
|
es-define-property@1.0.1:
|
||||||
|
resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==}
|
||||||
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
es-errors@1.3.0:
|
||||||
|
resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==}
|
||||||
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
es-object-atoms@1.1.1:
|
||||||
|
resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==}
|
||||||
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
es-set-tostringtag@2.1.0:
|
||||||
|
resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==}
|
||||||
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
esbuild@0.21.5:
|
esbuild@0.21.5:
|
||||||
resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==}
|
resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
@@ -1969,10 +2018,23 @@ packages:
|
|||||||
resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==}
|
resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
|
|
||||||
|
follow-redirects@1.15.9:
|
||||||
|
resolution: {integrity: sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==}
|
||||||
|
engines: {node: '>=4.0'}
|
||||||
|
peerDependencies:
|
||||||
|
debug: '*'
|
||||||
|
peerDependenciesMeta:
|
||||||
|
debug:
|
||||||
|
optional: true
|
||||||
|
|
||||||
foreground-child@3.3.1:
|
foreground-child@3.3.1:
|
||||||
resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==}
|
resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==}
|
||||||
engines: {node: '>=14'}
|
engines: {node: '>=14'}
|
||||||
|
|
||||||
|
form-data@4.0.2:
|
||||||
|
resolution: {integrity: sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==}
|
||||||
|
engines: {node: '>= 6'}
|
||||||
|
|
||||||
fraction.js@4.3.7:
|
fraction.js@4.3.7:
|
||||||
resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==}
|
resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==}
|
||||||
|
|
||||||
@@ -1995,6 +2057,14 @@ packages:
|
|||||||
resolution: {integrity: sha512-vpeMIQKxczTD/0s2CdEWHcb0eeJe6TFjxb+J5xgX7hScxqrGuyjmv4c1D4A/gelKfyox0gJJwIHF+fLjeaM8kQ==}
|
resolution: {integrity: sha512-vpeMIQKxczTD/0s2CdEWHcb0eeJe6TFjxb+J5xgX7hScxqrGuyjmv4c1D4A/gelKfyox0gJJwIHF+fLjeaM8kQ==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
|
get-intrinsic@1.3.0:
|
||||||
|
resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==}
|
||||||
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
get-proto@1.0.1:
|
||||||
|
resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==}
|
||||||
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
get-stream@8.0.1:
|
get-stream@8.0.1:
|
||||||
resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==}
|
resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==}
|
||||||
engines: {node: '>=16'}
|
engines: {node: '>=16'}
|
||||||
@@ -2048,6 +2118,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-s3Fq41ZVh7vbbe2PN3nrW7yC7U7MFVc5c98/iTl9c2GawNMKx/J648KQRW6WKkuU8GIbbh2IXfIRQjOZnXcTnw==}
|
resolution: {integrity: sha512-s3Fq41ZVh7vbbe2PN3nrW7yC7U7MFVc5c98/iTl9c2GawNMKx/J648KQRW6WKkuU8GIbbh2IXfIRQjOZnXcTnw==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
|
gopd@1.2.0:
|
||||||
|
resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==}
|
||||||
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
graceful-fs@4.2.10:
|
graceful-fs@4.2.10:
|
||||||
resolution: {integrity: sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==}
|
resolution: {integrity: sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==}
|
||||||
|
|
||||||
@@ -2057,6 +2131,14 @@ packages:
|
|||||||
hachure-fill@0.5.2:
|
hachure-fill@0.5.2:
|
||||||
resolution: {integrity: sha512-3GKBOn+m2LX9iq+JC1064cSFprJY4jL1jCXTcpnfER5HYE2l/4EfWSGzkPa/ZDBmYI0ZOEj5VHV/eKnPGkHuOg==}
|
resolution: {integrity: sha512-3GKBOn+m2LX9iq+JC1064cSFprJY4jL1jCXTcpnfER5HYE2l/4EfWSGzkPa/ZDBmYI0ZOEj5VHV/eKnPGkHuOg==}
|
||||||
|
|
||||||
|
has-symbols@1.1.0:
|
||||||
|
resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==}
|
||||||
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
has-tostringtag@1.0.2:
|
||||||
|
resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==}
|
||||||
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
hasown@2.0.2:
|
hasown@2.0.2:
|
||||||
resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
|
resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
@@ -2433,6 +2515,10 @@ packages:
|
|||||||
engines: {node: '>= 18'}
|
engines: {node: '>= 18'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
|
math-intrinsics@1.1.0:
|
||||||
|
resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==}
|
||||||
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
mdast-util-find-and-replace@3.0.2:
|
mdast-util-find-and-replace@3.0.2:
|
||||||
resolution: {integrity: sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==}
|
resolution: {integrity: sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==}
|
||||||
|
|
||||||
@@ -4653,6 +4739,8 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
retry: 0.13.1
|
retry: 0.13.1
|
||||||
|
|
||||||
|
asynckit@0.4.0: {}
|
||||||
|
|
||||||
atomically@2.0.3:
|
atomically@2.0.3:
|
||||||
dependencies:
|
dependencies:
|
||||||
stubborn-fs: 1.2.5
|
stubborn-fs: 1.2.5
|
||||||
@@ -4668,6 +4756,14 @@ snapshots:
|
|||||||
postcss: 8.5.3
|
postcss: 8.5.3
|
||||||
postcss-value-parser: 4.2.0
|
postcss-value-parser: 4.2.0
|
||||||
|
|
||||||
|
axios@1.8.4:
|
||||||
|
dependencies:
|
||||||
|
follow-redirects: 1.15.9
|
||||||
|
form-data: 4.0.2
|
||||||
|
proxy-from-env: 1.1.0
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- debug
|
||||||
|
|
||||||
bail@2.0.2: {}
|
bail@2.0.2: {}
|
||||||
|
|
||||||
balanced-match@1.0.2: {}
|
balanced-match@1.0.2: {}
|
||||||
@@ -4720,6 +4816,11 @@ snapshots:
|
|||||||
|
|
||||||
cac@6.7.14: {}
|
cac@6.7.14: {}
|
||||||
|
|
||||||
|
call-bind-apply-helpers@1.0.2:
|
||||||
|
dependencies:
|
||||||
|
es-errors: 1.3.0
|
||||||
|
function-bind: 1.1.2
|
||||||
|
|
||||||
callsites@3.1.0: {}
|
callsites@3.1.0: {}
|
||||||
|
|
||||||
camelcase-css@2.0.1: {}
|
camelcase-css@2.0.1: {}
|
||||||
@@ -4794,6 +4895,10 @@ snapshots:
|
|||||||
|
|
||||||
color-name@1.1.4: {}
|
color-name@1.1.4: {}
|
||||||
|
|
||||||
|
combined-stream@1.0.8:
|
||||||
|
dependencies:
|
||||||
|
delayed-stream: 1.0.0
|
||||||
|
|
||||||
comma-separated-tokens@2.0.3: {}
|
comma-separated-tokens@2.0.3: {}
|
||||||
|
|
||||||
commander@4.1.1: {}
|
commander@4.1.1: {}
|
||||||
@@ -4841,6 +4946,10 @@ snapshots:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
typescript: 5.8.2
|
typescript: 5.8.2
|
||||||
|
|
||||||
|
cross-env@7.0.3:
|
||||||
|
dependencies:
|
||||||
|
cross-spawn: 7.0.6
|
||||||
|
|
||||||
cross-spawn@7.0.6:
|
cross-spawn@7.0.6:
|
||||||
dependencies:
|
dependencies:
|
||||||
path-key: 3.1.1
|
path-key: 3.1.1
|
||||||
@@ -5068,6 +5177,8 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
robust-predicates: 3.0.2
|
robust-predicates: 3.0.2
|
||||||
|
|
||||||
|
delayed-stream@1.0.0: {}
|
||||||
|
|
||||||
dequal@2.0.3: {}
|
dequal@2.0.3: {}
|
||||||
|
|
||||||
devlop@1.1.0:
|
devlop@1.1.0:
|
||||||
@@ -5088,6 +5199,12 @@ snapshots:
|
|||||||
|
|
||||||
dotenv@16.4.7: {}
|
dotenv@16.4.7: {}
|
||||||
|
|
||||||
|
dunder-proto@1.0.1:
|
||||||
|
dependencies:
|
||||||
|
call-bind-apply-helpers: 1.0.2
|
||||||
|
es-errors: 1.3.0
|
||||||
|
gopd: 1.2.0
|
||||||
|
|
||||||
eastasianwidth@0.2.0: {}
|
eastasianwidth@0.2.0: {}
|
||||||
|
|
||||||
electron-to-chromium@1.5.123: {}
|
electron-to-chromium@1.5.123: {}
|
||||||
@@ -5106,6 +5223,21 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
is-arrayish: 0.2.1
|
is-arrayish: 0.2.1
|
||||||
|
|
||||||
|
es-define-property@1.0.1: {}
|
||||||
|
|
||||||
|
es-errors@1.3.0: {}
|
||||||
|
|
||||||
|
es-object-atoms@1.1.1:
|
||||||
|
dependencies:
|
||||||
|
es-errors: 1.3.0
|
||||||
|
|
||||||
|
es-set-tostringtag@2.1.0:
|
||||||
|
dependencies:
|
||||||
|
es-errors: 1.3.0
|
||||||
|
get-intrinsic: 1.3.0
|
||||||
|
has-tostringtag: 1.0.2
|
||||||
|
hasown: 2.0.2
|
||||||
|
|
||||||
esbuild@0.21.5:
|
esbuild@0.21.5:
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@esbuild/aix-ppc64': 0.21.5
|
'@esbuild/aix-ppc64': 0.21.5
|
||||||
@@ -5247,11 +5379,20 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
to-regex-range: 5.0.1
|
to-regex-range: 5.0.1
|
||||||
|
|
||||||
|
follow-redirects@1.15.9: {}
|
||||||
|
|
||||||
foreground-child@3.3.1:
|
foreground-child@3.3.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
cross-spawn: 7.0.6
|
cross-spawn: 7.0.6
|
||||||
signal-exit: 4.1.0
|
signal-exit: 4.1.0
|
||||||
|
|
||||||
|
form-data@4.0.2:
|
||||||
|
dependencies:
|
||||||
|
asynckit: 0.4.0
|
||||||
|
combined-stream: 1.0.8
|
||||||
|
es-set-tostringtag: 2.1.0
|
||||||
|
mime-types: 2.1.35
|
||||||
|
|
||||||
fraction.js@4.3.7: {}
|
fraction.js@4.3.7: {}
|
||||||
|
|
||||||
fs.realpath@1.0.0: {}
|
fs.realpath@1.0.0: {}
|
||||||
@@ -5265,6 +5406,24 @@ snapshots:
|
|||||||
|
|
||||||
get-east-asian-width@1.3.0: {}
|
get-east-asian-width@1.3.0: {}
|
||||||
|
|
||||||
|
get-intrinsic@1.3.0:
|
||||||
|
dependencies:
|
||||||
|
call-bind-apply-helpers: 1.0.2
|
||||||
|
es-define-property: 1.0.1
|
||||||
|
es-errors: 1.3.0
|
||||||
|
es-object-atoms: 1.1.1
|
||||||
|
function-bind: 1.1.2
|
||||||
|
get-proto: 1.0.1
|
||||||
|
gopd: 1.2.0
|
||||||
|
has-symbols: 1.1.0
|
||||||
|
hasown: 2.0.2
|
||||||
|
math-intrinsics: 1.1.0
|
||||||
|
|
||||||
|
get-proto@1.0.1:
|
||||||
|
dependencies:
|
||||||
|
dunder-proto: 1.0.1
|
||||||
|
es-object-atoms: 1.1.1
|
||||||
|
|
||||||
get-stream@8.0.1: {}
|
get-stream@8.0.1: {}
|
||||||
|
|
||||||
get-stream@9.0.1:
|
get-stream@9.0.1:
|
||||||
@@ -5336,12 +5495,20 @@ snapshots:
|
|||||||
slash: 5.1.0
|
slash: 5.1.0
|
||||||
unicorn-magic: 0.1.0
|
unicorn-magic: 0.1.0
|
||||||
|
|
||||||
|
gopd@1.2.0: {}
|
||||||
|
|
||||||
graceful-fs@4.2.10: {}
|
graceful-fs@4.2.10: {}
|
||||||
|
|
||||||
graceful-fs@4.2.11: {}
|
graceful-fs@4.2.11: {}
|
||||||
|
|
||||||
hachure-fill@0.5.2: {}
|
hachure-fill@0.5.2: {}
|
||||||
|
|
||||||
|
has-symbols@1.1.0: {}
|
||||||
|
|
||||||
|
has-tostringtag@1.0.2:
|
||||||
|
dependencies:
|
||||||
|
has-symbols: 1.1.0
|
||||||
|
|
||||||
hasown@2.0.2:
|
hasown@2.0.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
function-bind: 1.1.2
|
function-bind: 1.1.2
|
||||||
@@ -5707,6 +5874,8 @@ snapshots:
|
|||||||
|
|
||||||
marked@15.0.7: {}
|
marked@15.0.7: {}
|
||||||
|
|
||||||
|
math-intrinsics@1.1.0: {}
|
||||||
|
|
||||||
mdast-util-find-and-replace@3.0.2:
|
mdast-util-find-and-replace@3.0.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/mdast': 4.0.4
|
'@types/mdast': 4.0.4
|
||||||
|
|||||||
93
src/api/axiosRequest.ts
Normal file
93
src/api/axiosRequest.ts
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
import axios from "axios";
|
||||||
|
|
||||||
|
import {
|
||||||
|
handleChangeRequestHeader,
|
||||||
|
handleConfigureAuth,
|
||||||
|
// handleAuthError,
|
||||||
|
// handleGeneralError,
|
||||||
|
handleNetworkError,
|
||||||
|
} from "./tools";
|
||||||
|
|
||||||
|
type Fn = (data: FcResponse<any>) => unknown;
|
||||||
|
|
||||||
|
interface IAnyObj {
|
||||||
|
[index: string]: unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface FcResponse<T> {
|
||||||
|
errno: string;
|
||||||
|
errmsg: string;
|
||||||
|
data: T;
|
||||||
|
}
|
||||||
|
|
||||||
|
axios.interceptors.request.use((config) => {
|
||||||
|
config = handleChangeRequestHeader(config);
|
||||||
|
config = handleConfigureAuth(config);
|
||||||
|
// console.log("config", config);
|
||||||
|
return config;
|
||||||
|
});
|
||||||
|
|
||||||
|
axios.interceptors.response.use(
|
||||||
|
(response) => {
|
||||||
|
if (response.status !== 200) return Promise.reject(response.data);
|
||||||
|
// handleAuthError(response.data.errno);
|
||||||
|
// handleGeneralError(response.data.errno, response.data.errmsg);
|
||||||
|
return response;
|
||||||
|
},
|
||||||
|
(err) => {
|
||||||
|
handleNetworkError(err?.response?.status);
|
||||||
|
return Promise.reject(err?.response);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export const Get = <T>(
|
||||||
|
url: string,
|
||||||
|
params: IAnyObj = {},
|
||||||
|
clearFn?: Fn
|
||||||
|
): Promise<[any, FcResponse<T> | undefined]> =>
|
||||||
|
new Promise((resolve) => {
|
||||||
|
const appStore = JSON.parse(localStorage.getItem("app-store") || "{}");
|
||||||
|
// console.log("baseURL", appStore.state?.endpoint_http)
|
||||||
|
|
||||||
|
let baseURL = appStore.state?.endpoint_http;
|
||||||
|
axios
|
||||||
|
.get(baseURL + url, { params })
|
||||||
|
.then((result) => {
|
||||||
|
let res: FcResponse<T>;
|
||||||
|
if (clearFn !== undefined) {
|
||||||
|
res = clearFn(result?.data) as unknown as FcResponse<T>;
|
||||||
|
} else {
|
||||||
|
res = result?.data as FcResponse<T>;
|
||||||
|
}
|
||||||
|
resolve([null, res as FcResponse<T>]);
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
resolve([err, undefined]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
export const Post = <T>(
|
||||||
|
url: string,
|
||||||
|
data: IAnyObj,
|
||||||
|
params: IAnyObj = {},
|
||||||
|
headers: IAnyObj = {}
|
||||||
|
): Promise<[any, FcResponse<T> | undefined]> => {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
const appStore = JSON.parse(localStorage.getItem("app-store") || "{}");
|
||||||
|
// console.log("baseURL", appStore.state?.endpoint_http)
|
||||||
|
|
||||||
|
let baseURL = appStore.state?.endpoint_http;
|
||||||
|
|
||||||
|
axios
|
||||||
|
.post(baseURL + url, data, {
|
||||||
|
params,
|
||||||
|
headers,
|
||||||
|
} as any)
|
||||||
|
.then((result) => {
|
||||||
|
resolve([null, result.data as FcResponse<T>]);
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
resolve([err, undefined]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
73
src/api/tools.ts
Normal file
73
src/api/tools.ts
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
export const handleChangeRequestHeader = (config: any) => {
|
||||||
|
config["xxxx"] = "xxx";
|
||||||
|
return config;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const handleConfigureAuth = (config: any) => {
|
||||||
|
// config.headers["X-API-TOKEN"] = localStorage.getItem("token") || "";
|
||||||
|
|
||||||
|
const headersStr = localStorage.getItem("headers") || "{}";
|
||||||
|
const headers = JSON.parse(headersStr);
|
||||||
|
// console.log("headers:", headers);
|
||||||
|
|
||||||
|
config.headers = {
|
||||||
|
...config.headers,
|
||||||
|
...headers,
|
||||||
|
}
|
||||||
|
// console.log("config.headers", config.headers)
|
||||||
|
return config;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const handleNetworkError = (errStatus?: number): void => {
|
||||||
|
const networkErrMap: any = {
|
||||||
|
"400": "Bad Request", // token invalid
|
||||||
|
"401": "Unauthorized, please login again",
|
||||||
|
"403": "Access Denied",
|
||||||
|
"404": "Resource Not Found",
|
||||||
|
"405": "Method Not Allowed",
|
||||||
|
"408": "Request Timeout",
|
||||||
|
"500": "Internal Server Error",
|
||||||
|
"501": "Not Implemented",
|
||||||
|
"502": "Bad Gateway",
|
||||||
|
"503": "Service Unavailable",
|
||||||
|
"504": "Gateway Timeout",
|
||||||
|
"505": "HTTP Version Not Supported",
|
||||||
|
};
|
||||||
|
if (errStatus) {
|
||||||
|
console.error(networkErrMap[errStatus] ?? `Other Connection Error --${errStatus}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.error("Unable to connect to server!");
|
||||||
|
};
|
||||||
|
|
||||||
|
export const handleAuthError = (errno: string): boolean => {
|
||||||
|
const authErrMap: any = {
|
||||||
|
"10031": "Login expired, please login again", // token invalid
|
||||||
|
"10032": "Session timeout, please login again", // token expired
|
||||||
|
"10033": "Account not bound to role, please contact administrator",
|
||||||
|
"10034": "User not registered, please contact administrator",
|
||||||
|
"10035": "Unable to get third-party platform user with code",
|
||||||
|
"10036": "Account not linked to employee, please contact administrator",
|
||||||
|
"10037": "Account is invalid",
|
||||||
|
"10038": "Account not found",
|
||||||
|
};
|
||||||
|
|
||||||
|
if (authErrMap.hasOwnProperty(errno)) {
|
||||||
|
console.error(authErrMap[errno]);
|
||||||
|
// Authorization error, logout account
|
||||||
|
// logout();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const handleGeneralError = (errno: string, errmsg: string): boolean => {
|
||||||
|
if (errno !== "0") {
|
||||||
|
console.error(errmsg);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
BIN
src/assets/images/logo-dark.png
Normal file
BIN
src/assets/images/logo-dark.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.4 KiB |
BIN
src/assets/images/logo-light.png
Normal file
BIN
src/assets/images/logo-light.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.4 KiB |
@@ -21,6 +21,7 @@ import { ChatHeader } from "./ChatHeader";
|
|||||||
import { ChatContent } from "./ChatContent";
|
import { ChatContent } from "./ChatContent";
|
||||||
import ConnectPrompt from "./ConnectPrompt";
|
import ConnectPrompt from "./ConnectPrompt";
|
||||||
import type { Chat } from "./types";
|
import type { Chat } from "./types";
|
||||||
|
import PrevSuggestion from "@/components/ChatMessage/PrevSuggestion";
|
||||||
|
|
||||||
interface ChatAIProps {
|
interface ChatAIProps {
|
||||||
isTransitioned: boolean;
|
isTransitioned: boolean;
|
||||||
@@ -33,6 +34,7 @@ interface ChatAIProps {
|
|||||||
clearChatPage?: () => void;
|
clearChatPage?: () => void;
|
||||||
isChatPage?: boolean;
|
isChatPage?: boolean;
|
||||||
getFileUrl: (path: string) => string;
|
getFileUrl: (path: string) => string;
|
||||||
|
showChatHistory?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ChatAIRef {
|
export interface ChatAIRef {
|
||||||
@@ -56,6 +58,7 @@ const ChatAI = memo(
|
|||||||
clearChatPage,
|
clearChatPage,
|
||||||
isChatPage = false,
|
isChatPage = false,
|
||||||
getFileUrl,
|
getFileUrl,
|
||||||
|
showChatHistory = true,
|
||||||
},
|
},
|
||||||
ref
|
ref
|
||||||
) => {
|
) => {
|
||||||
@@ -89,7 +92,9 @@ const ChatAI = memo(
|
|||||||
|
|
||||||
const [Question, setQuestion] = useState<string>("");
|
const [Question, setQuestion] = useState<string>("");
|
||||||
|
|
||||||
const [websocketSessionId, setWebsocketSessionId] = useState('');
|
const [showPrevSuggestion, setShowPrevSuggestion] = useState(true);
|
||||||
|
|
||||||
|
const [websocketSessionId, setWebsocketSessionId] = useState("");
|
||||||
|
|
||||||
const onWebsocketSessionId = useCallback((sessionId: string) => {
|
const onWebsocketSessionId = useCallback((sessionId: string) => {
|
||||||
setWebsocketSessionId(sessionId);
|
setWebsocketSessionId(sessionId);
|
||||||
@@ -119,16 +124,21 @@ const ChatAI = memo(
|
|||||||
|
|
||||||
const dealMsgRef = useRef<((msg: string) => void) | null>(null);
|
const dealMsgRef = useRef<((msg: string) => void) | null>(null);
|
||||||
|
|
||||||
const clientId = isChatPage ? "standalone" : "popup"
|
const clientId = isChatPage ? "standalone" : "popup";
|
||||||
const { errorShow, setErrorShow, reconnect, disconnectWS, updateDealMsg } =
|
const {
|
||||||
useWebSocket({
|
errorShow,
|
||||||
clientId,
|
setErrorShow,
|
||||||
connected,
|
reconnect,
|
||||||
setConnected,
|
disconnectWS,
|
||||||
currentService,
|
updateDealMsg,
|
||||||
dealMsgRef,
|
} = useWebSocket({
|
||||||
onWebsocketSessionId,
|
clientId,
|
||||||
});
|
connected,
|
||||||
|
setConnected,
|
||||||
|
currentService,
|
||||||
|
dealMsgRef,
|
||||||
|
onWebsocketSessionId,
|
||||||
|
});
|
||||||
|
|
||||||
const {
|
const {
|
||||||
chatClose,
|
chatClose,
|
||||||
@@ -161,7 +171,7 @@ const ChatAI = memo(
|
|||||||
setTimedoutShow,
|
setTimedoutShow,
|
||||||
(chat) => cancelChat(chat || activeChat),
|
(chat) => cancelChat(chat || activeChat),
|
||||||
setLoadingStep,
|
setLoadingStep,
|
||||||
handlers,
|
handlers
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -189,16 +199,28 @@ const ChatAI = memo(
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
const init = useCallback(
|
const init = useCallback(
|
||||||
(value: string) => {
|
async (value: string) => {
|
||||||
if (!isLogin) return;
|
try {
|
||||||
if (!curChatEnd) return;
|
console.log("init", isLogin, curChatEnd, activeChat?._id);
|
||||||
if (!activeChat?._id) {
|
if (!isLogin || !curChatEnd) return;
|
||||||
createNewChat(value, activeChat, websocketSessionId);
|
setShowPrevSuggestion(false);
|
||||||
} else {
|
if (!activeChat?._id) {
|
||||||
handleSendMessage(value, activeChat, websocketSessionId);
|
await createNewChat(value, activeChat, websocketSessionId);
|
||||||
|
} else {
|
||||||
|
await handleSendMessage(value, activeChat, websocketSessionId);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to initialize chat:', error);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[isLogin, curChatEnd, activeChat, createNewChat, handleSendMessage, websocketSessionId]
|
[
|
||||||
|
isLogin,
|
||||||
|
curChatEnd,
|
||||||
|
activeChat,
|
||||||
|
createNewChat,
|
||||||
|
handleSendMessage,
|
||||||
|
websocketSessionId,
|
||||||
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
const { createWin } = useWindows();
|
const { createWin } = useWindows();
|
||||||
@@ -207,14 +229,17 @@ const ChatAI = memo(
|
|||||||
}, [createChatWindow, createWin]);
|
}, [createChatWindow, createWin]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
setCurChatEnd(true);
|
||||||
return () => {
|
return () => {
|
||||||
if (messageTimeoutRef.current) {
|
if (messageTimeoutRef.current) {
|
||||||
clearTimeout(messageTimeoutRef.current);
|
clearTimeout(messageTimeoutRef.current);
|
||||||
}
|
}
|
||||||
chatClose(activeChat);
|
Promise.resolve().then(() => {
|
||||||
setActiveChat(undefined);
|
chatClose(activeChat);
|
||||||
setCurChatEnd(true);
|
setActiveChat(undefined);
|
||||||
disconnectWS();
|
setCurChatEnd(true);
|
||||||
|
disconnectWS();
|
||||||
|
});
|
||||||
};
|
};
|
||||||
}, [chatClose, setCurChatEnd]);
|
}, [chatClose, setCurChatEnd]);
|
||||||
|
|
||||||
@@ -240,17 +265,20 @@ const ChatAI = memo(
|
|||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
const deleteChat = useCallback((chatId: string) => {
|
const deleteChat = useCallback(
|
||||||
setChats((prev) => prev.filter((chat) => chat._id !== chatId));
|
(chatId: string) => {
|
||||||
if (activeChat?._id === chatId) {
|
setChats((prev) => prev.filter((chat) => chat._id !== chatId));
|
||||||
const remainingChats = chats.filter((chat) => chat._id !== chatId);
|
if (activeChat?._id === chatId) {
|
||||||
if (remainingChats.length > 0) {
|
const remainingChats = chats.filter((chat) => chat._id !== chatId);
|
||||||
setActiveChat(remainingChats[0]);
|
if (remainingChats.length > 0) {
|
||||||
} else {
|
setActiveChat(remainingChats[0]);
|
||||||
init("");
|
} else {
|
||||||
|
init("");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
}, [activeChat, chats, init, setActiveChat]);
|
[activeChat, chats, init, setActiveChat]
|
||||||
|
);
|
||||||
|
|
||||||
const handleOutsideClick = useCallback((e: MouseEvent) => {
|
const handleOutsideClick = useCallback((e: MouseEvent) => {
|
||||||
const sidebar = document.querySelector("[data-sidebar]");
|
const sidebar = document.querySelector("[data-sidebar]");
|
||||||
@@ -297,9 +325,9 @@ const ChatAI = memo(
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
data-tauri-drag-region
|
data-tauri-drag-region
|
||||||
className={`h-full flex flex-col rounded-xl overflow-hidden`}
|
className={`h-full flex flex-col rounded-xl relative`}
|
||||||
>
|
>
|
||||||
{!setIsSidebarOpen && (
|
{showChatHistory && !setIsSidebarOpen && (
|
||||||
<ChatSidebar
|
<ChatSidebar
|
||||||
isSidebarOpen={isSidebarOpenChat}
|
isSidebarOpen={isSidebarOpenChat}
|
||||||
chats={chats}
|
chats={chats}
|
||||||
@@ -320,6 +348,7 @@ const ChatAI = memo(
|
|||||||
reconnect={reconnect}
|
reconnect={reconnect}
|
||||||
isChatPage={isChatPage}
|
isChatPage={isChatPage}
|
||||||
setIsLogin={setIsLoginChat}
|
setIsLogin={setIsLoginChat}
|
||||||
|
showChatHistory={showChatHistory}
|
||||||
/>
|
/>
|
||||||
{isLogin ? (
|
{isLogin ? (
|
||||||
<ChatContent
|
<ChatContent
|
||||||
@@ -335,12 +364,16 @@ const ChatAI = memo(
|
|||||||
timedoutShow={timedoutShow}
|
timedoutShow={timedoutShow}
|
||||||
errorShow={errorShow}
|
errorShow={errorShow}
|
||||||
Question={Question}
|
Question={Question}
|
||||||
handleSendMessage={(value) => handleSendMessage(value, activeChat)}
|
handleSendMessage={(value) =>
|
||||||
|
handleSendMessage(value, activeChat)
|
||||||
|
}
|
||||||
getFileUrl={getFileUrl}
|
getFileUrl={getFileUrl}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<ConnectPrompt />
|
<ConnectPrompt />
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{showPrevSuggestion ? <PrevSuggestion sendMessage={init} /> : null}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,6 +29,8 @@ import { useChatStore } from "@/stores/chatStore";
|
|||||||
import type { Chat } from "./types";
|
import type { Chat } from "./types";
|
||||||
import { useConnectStore } from "@/stores/connectStore";
|
import { useConnectStore } from "@/stores/connectStore";
|
||||||
import platformAdapter from "@/utils/platformAdapter";
|
import platformAdapter from "@/utils/platformAdapter";
|
||||||
|
import { list_coco_servers } from "@/commands";
|
||||||
|
|
||||||
interface ChatHeaderProps {
|
interface ChatHeaderProps {
|
||||||
onCreateNewChat: () => void;
|
onCreateNewChat: () => void;
|
||||||
onOpenChatAI: () => void;
|
onOpenChatAI: () => void;
|
||||||
@@ -38,6 +40,7 @@ interface ChatHeaderProps {
|
|||||||
reconnect: (server?: IServer) => void;
|
reconnect: (server?: IServer) => void;
|
||||||
setIsLogin: (isLogin: boolean) => void;
|
setIsLogin: (isLogin: boolean) => void;
|
||||||
isChatPage?: boolean;
|
isChatPage?: boolean;
|
||||||
|
showChatHistory?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ChatHeader({
|
export function ChatHeader({
|
||||||
@@ -48,6 +51,7 @@ export function ChatHeader({
|
|||||||
reconnect,
|
reconnect,
|
||||||
setIsLogin,
|
setIsLogin,
|
||||||
isChatPage = false,
|
isChatPage = false,
|
||||||
|
showChatHistory,
|
||||||
}: ChatHeaderProps) {
|
}: ChatHeaderProps) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
@@ -63,34 +67,39 @@ export function ChatHeader({
|
|||||||
const currentService = useConnectStore((state) => state.currentService);
|
const currentService = useConnectStore((state) => state.currentService);
|
||||||
const setCurrentService = useConnectStore((state) => state.setCurrentService);
|
const setCurrentService = useConnectStore((state) => state.setCurrentService);
|
||||||
|
|
||||||
const fetchServers = useCallback(async (resetSelection: boolean) => {
|
const isTauri = useAppStore((state) => state.isTauri);
|
||||||
platformAdapter.invokeBackend("list_coco_servers")
|
|
||||||
.then((res: any) => {
|
|
||||||
const enabledServers = (res as IServer[]).filter(
|
|
||||||
(server) => server.enabled !== false
|
|
||||||
);
|
|
||||||
//console.log("list_coco_servers", enabledServers);
|
|
||||||
setServerList(enabledServers);
|
|
||||||
|
|
||||||
if (resetSelection && enabledServers.length > 0) {
|
const fetchServers = useCallback(
|
||||||
const currentServiceExists = enabledServers.find(
|
async (resetSelection: boolean) => {
|
||||||
(server) => server.id === currentService?.id
|
list_coco_servers()
|
||||||
|
.then((res: any) => {
|
||||||
|
const enabledServers = (res as IServer[]).filter(
|
||||||
|
(server) => server.enabled !== false
|
||||||
);
|
);
|
||||||
|
//console.log("list_coco_servers", enabledServers);
|
||||||
|
setServerList(enabledServers);
|
||||||
|
|
||||||
if (currentServiceExists) {
|
if (resetSelection && enabledServers.length > 0) {
|
||||||
switchServer(currentServiceExists);
|
const currentServiceExists = enabledServers.find(
|
||||||
} else {
|
(server) => server.id === currentService?.id
|
||||||
switchServer(enabledServers[enabledServers.length - 1]);
|
);
|
||||||
|
|
||||||
|
if (currentServiceExists) {
|
||||||
|
switchServer(currentServiceExists);
|
||||||
|
} else {
|
||||||
|
switchServer(enabledServers[enabledServers.length - 1]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
})
|
.catch((err: any) => {
|
||||||
.catch((err: any) => {
|
console.error(err);
|
||||||
console.error(err);
|
});
|
||||||
});
|
},
|
||||||
}, [currentService?.id]);
|
[currentService?.id]
|
||||||
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchServers(true);
|
isTauri && fetchServers(true);
|
||||||
|
|
||||||
const unlisten = platformAdapter.listenEvent("login_or_logout", (event) => {
|
const unlisten = platformAdapter.listenEvent("login_or_logout", (event) => {
|
||||||
console.log("Login or Logout:", currentService, event);
|
console.log("Login or Logout:", currentService, event);
|
||||||
@@ -103,8 +112,6 @@ export function ChatHeader({
|
|||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const switchServer = async (server: IServer) => {
|
const switchServer = async (server: IServer) => {
|
||||||
if (!server) return;
|
if (!server) return;
|
||||||
try {
|
try {
|
||||||
@@ -149,52 +156,60 @@ export function ChatHeader({
|
|||||||
data-tauri-drag-region
|
data-tauri-drag-region
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<button
|
{isTauri && (
|
||||||
data-sidebar-button
|
<button
|
||||||
onClick={(e) => {
|
data-sidebar-button
|
||||||
e.stopPropagation();
|
onClick={(e) => {
|
||||||
setIsSidebarOpen();
|
e.stopPropagation();
|
||||||
}}
|
setIsSidebarOpen();
|
||||||
className="p-2 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-800"
|
}}
|
||||||
>
|
className="p-2 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-800"
|
||||||
<HistoryIcon />
|
>
|
||||||
</button>
|
<HistoryIcon />
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
|
||||||
<Menu>
|
<Menu>
|
||||||
<MenuButton className="flex items-center gap-1 rounded-full bg-white dark:bg-[#202126] p-1 text-sm/6 font-semibold text-gray-800 dark:text-white border border-gray-200 dark:border-gray-700 hover:bg-gray-50 dark:hover:bg-gray-700 focus:outline-none">
|
<MenuButton className="px-2 flex items-center gap-1 rounded-full bg-white dark:bg-[#202126] p-1 text-sm/6 font-semibold text-gray-800 dark:text-white border border-gray-200 dark:border-gray-700 hover:bg-gray-50 dark:hover:bg-gray-700 focus:outline-none">
|
||||||
<img
|
<img
|
||||||
src={logoImg}
|
src={logoImg}
|
||||||
className="w-4 h-4"
|
className="w-4 h-4"
|
||||||
alt={t("assistant.message.logo")}
|
alt={t("assistant.message.logo")}
|
||||||
/>
|
/>
|
||||||
Coco AI
|
Coco AI
|
||||||
<ChevronDownIcon className="size-4 text-gray-500 dark:text-gray-400" />
|
{showChatHistory && isTauri ? (
|
||||||
|
<ChevronDownIcon className="size-4 text-gray-500 dark:text-gray-400" />
|
||||||
|
) : null}
|
||||||
</MenuButton>
|
</MenuButton>
|
||||||
|
|
||||||
<MenuItems
|
{showChatHistory && isTauri ? (
|
||||||
transition
|
<MenuItems
|
||||||
anchor="bottom end"
|
transition
|
||||||
className="w-28 origin-top-right rounded-xl bg-white dark:bg-[#202126] p-1 text-sm/6 text-gray-800 dark:text-white shadow-lg border border-gray-200 dark:border-gray-700 focus:outline-none data-[closed]:scale-95 data-[closed]:opacity-0"
|
anchor="bottom end"
|
||||||
>
|
className="w-28 origin-top-right rounded-xl bg-white dark:bg-[#202126] p-1 text-sm/6 text-gray-800 dark:text-white shadow-lg border border-gray-200 dark:border-gray-700 focus:outline-none data-[closed]:scale-95 data-[closed]:opacity-0"
|
||||||
<MenuItem>
|
>
|
||||||
<button className="group flex w-full items-center gap-2 rounded-lg py-1.5 px-3 hover:bg-gray-100 dark:hover:bg-gray-700">
|
<MenuItem>
|
||||||
<img
|
<button className="group flex w-full items-center gap-2 rounded-lg py-1.5 px-3 hover:bg-gray-100 dark:hover:bg-gray-700">
|
||||||
src={logoImg}
|
<img
|
||||||
className="w-4 h-4"
|
src={logoImg}
|
||||||
alt={t("assistant.message.logo")}
|
className="w-4 h-4"
|
||||||
/>
|
alt={t("assistant.message.logo")}
|
||||||
Coco AI
|
/>
|
||||||
</button>
|
Coco AI
|
||||||
</MenuItem>
|
</button>
|
||||||
</MenuItems>
|
</MenuItem>
|
||||||
|
</MenuItems>
|
||||||
|
) : null}
|
||||||
</Menu>
|
</Menu>
|
||||||
|
|
||||||
<button
|
{showChatHistory && isTauri ? (
|
||||||
onClick={onCreateNewChat}
|
<button
|
||||||
className="p-2 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-800"
|
onClick={onCreateNewChat}
|
||||||
>
|
className="p-2 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-800"
|
||||||
<MessageSquarePlus className="h-4 w-4" />
|
>
|
||||||
</button>
|
<MessageSquarePlus className="h-4 w-4" />
|
||||||
|
</button>
|
||||||
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
@@ -205,7 +220,7 @@ export function ChatHeader({
|
|||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center gap-2">
|
{isTauri ? <div className="flex items-center gap-2">
|
||||||
<button
|
<button
|
||||||
onClick={togglePin}
|
onClick={togglePin}
|
||||||
className={`${isPinned ? "text-blue-500" : ""}`}
|
className={`${isPinned ? "text-blue-500" : ""}`}
|
||||||
@@ -315,7 +330,7 @@ export function ChatHeader({
|
|||||||
<WindowsFullIcon className="rotate-30 scale-x-[-1]" />
|
<WindowsFullIcon className="rotate-30 scale-x-[-1]" />
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>: <div/>}
|
||||||
</header>
|
</header>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,11 +25,14 @@ export const ChatSidebar: React.FC<ChatSidebarProps> = ({
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
data-sidebar
|
data-sidebar
|
||||||
className={`fixed inset-y-0 left-0 z-50 w-64 transform transition-all duration-300 ease-in-out
|
className={`
|
||||||
${isSidebarOpen ? "translate-x-0" : "-translate-x-[calc(100%)]"}
|
h-[calc(100%+90px)] absolute top-0 left-0 z-10 w-64
|
||||||
md:relative md:translate-x-0 bg-gray-100 dark:bg-gray-800
|
transform transition-all duration-300 ease-in-out
|
||||||
border-r border-gray-200 dark:border-gray-700 rounded-tl-xl rounded-bl-xl
|
${isSidebarOpen ? "translate-x-0" : "-translate-x-full"}
|
||||||
overflow-hidden`}
|
bg-gray-100 dark:bg-gray-800
|
||||||
|
border-r border-gray-200 dark:border-gray-700 rounded-tl-xl rounded-bl-xl
|
||||||
|
overflow-hidden
|
||||||
|
`}
|
||||||
>
|
>
|
||||||
<Sidebar
|
<Sidebar
|
||||||
chats={chats}
|
chats={chats}
|
||||||
|
|||||||
@@ -1,13 +1,15 @@
|
|||||||
import { useConnectStore } from "@/stores/connectStore";
|
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
import { filesize } from "filesize";
|
import { filesize } from "filesize";
|
||||||
import { Files, Trash2, X } from "lucide-react";
|
import { Files, Trash2, X } from "lucide-react";
|
||||||
import { useEffect, useMemo, useState } from "react";
|
import { useEffect, useMemo, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import Checkbox from "../Common/Checkbox";
|
|
||||||
import FileIcon from "../Common/Icons/FileIcon";
|
import { useConnectStore } from "@/stores/connectStore";
|
||||||
|
import Checkbox from "@/components/Common/Checkbox";
|
||||||
|
import FileIcon from "@/components/Common/Icons/FileIcon";
|
||||||
import { delete_attachment, get_attachment } from "@/commands";
|
import { delete_attachment, get_attachment } from "@/commands";
|
||||||
import { AttachmentHit } from "@/types/commands";
|
import { AttachmentHit } from "@/types/commands";
|
||||||
|
import { useAppStore } from "@/stores/appStore";
|
||||||
|
|
||||||
interface SessionFileProps {
|
interface SessionFileProps {
|
||||||
sessionId: string;
|
sessionId: string;
|
||||||
@@ -16,6 +18,8 @@ interface SessionFileProps {
|
|||||||
const SessionFile = (props: SessionFileProps) => {
|
const SessionFile = (props: SessionFileProps) => {
|
||||||
const { sessionId } = props;
|
const { sessionId } = props;
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const isTauri = useAppStore((state) => state.isTauri);
|
||||||
const currentService = useConnectStore((state) => state.currentService);
|
const currentService = useConnectStore((state) => state.currentService);
|
||||||
const [uploadedFiles, setUploadedFiles] = useState<AttachmentHit[]>([]);
|
const [uploadedFiles, setUploadedFiles] = useState<AttachmentHit[]>([]);
|
||||||
const [visible, setVisible] = useState(false);
|
const [visible, setVisible] = useState(false);
|
||||||
@@ -32,14 +36,20 @@ const SessionFile = (props: SessionFileProps) => {
|
|||||||
}, [sessionId]);
|
}, [sessionId]);
|
||||||
|
|
||||||
const getUploadedFiles = async () => {
|
const getUploadedFiles = async () => {
|
||||||
const response = await get_attachment({ serverId, sessionId });
|
if (isTauri) {
|
||||||
|
const response = await get_attachment({ serverId, sessionId });
|
||||||
|
|
||||||
setUploadedFiles(response.hits.hits);
|
setUploadedFiles(response.hits.hits);
|
||||||
|
} else {
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDelete = async (id: string) => {
|
const handleDelete = async (id: string) => {
|
||||||
const result = await delete_attachment({ serverId, id });
|
let result;
|
||||||
|
if (isTauri) {
|
||||||
|
result = await delete_attachment({ serverId, id });
|
||||||
|
} else {
|
||||||
|
}
|
||||||
if (!result) return;
|
if (!result) return;
|
||||||
|
|
||||||
getUploadedFiles();
|
getUploadedFiles();
|
||||||
|
|||||||
65
src/components/ChatMessage/PrevSuggestion.tsx
Normal file
65
src/components/ChatMessage/PrevSuggestion.tsx
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
import { MoveRight } from "lucide-react";
|
||||||
|
import { FC, useEffect, useState } from "react";
|
||||||
|
|
||||||
|
import { Get } from "@/api/axiosRequest";
|
||||||
|
import { useAppStore } from "@/stores/appStore";
|
||||||
|
|
||||||
|
interface PrevSuggestionProps {
|
||||||
|
sendMessage: (message: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const PrevSuggestion: FC<PrevSuggestionProps> = (props) => {
|
||||||
|
const { sendMessage } = props;
|
||||||
|
|
||||||
|
const isTauri = useAppStore((state) => state.isTauri);
|
||||||
|
|
||||||
|
const headersStr = localStorage.getItem("headers") || "{}";
|
||||||
|
const headers = JSON.parse(headersStr);
|
||||||
|
const id = headers["APP-INTEGRATION-ID"] || "cvkm9hmhpcemufsg3vug";
|
||||||
|
console.log("id", id);
|
||||||
|
|
||||||
|
const [list, setList] = useState<string[]>([]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isTauri) getList();
|
||||||
|
}, [id]);
|
||||||
|
|
||||||
|
const getList = async () => {
|
||||||
|
if (!id) return;
|
||||||
|
|
||||||
|
const url = `/integration/${id}/chat/_suggest`;
|
||||||
|
|
||||||
|
const [error, res] = await Get(`/integration/${id}/chat/_suggest`);
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
console.error(url, error);
|
||||||
|
return setList([]);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("res", res);
|
||||||
|
|
||||||
|
setList(Array.isArray(res) ? res : []);
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log("id", id);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ul className="absolute left-2 bottom-2 flex flex-col gap-2">
|
||||||
|
{list.map((item) => {
|
||||||
|
return (
|
||||||
|
<li
|
||||||
|
key={item}
|
||||||
|
className="flex items-center self-start gap-2 px-3 py-2 leading-4 text-sm text-[#333] dark:text-[#d8d8d8] rounded-xl border border-black/15 dark:border-white/15 hover:!border-[#0072ff] hover:!text-[#0072ff] transition cursor-pointer"
|
||||||
|
onClick={() => sendMessage(item)}
|
||||||
|
>
|
||||||
|
{item}
|
||||||
|
|
||||||
|
<MoveRight className="size-4" />
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</ul>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default PrevSuggestion;
|
||||||
24
src/components/Common/Copyright/index.tsx
Normal file
24
src/components/Common/Copyright/index.tsx
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import { useThemeStore } from "@/stores/themeStore";
|
||||||
|
import logoLight from "@/assets/images/logo-light.png";
|
||||||
|
import logoDark from "@/assets/images/logo-dark.png";
|
||||||
|
|
||||||
|
const Copyright = () => {
|
||||||
|
const isDark = useThemeStore((state) => state.isDark);
|
||||||
|
|
||||||
|
const renderLogo = () => {
|
||||||
|
return (
|
||||||
|
<a href="https://coco.rs/" target="_blank">
|
||||||
|
<img src={isDark ? logoDark : logoLight} alt="Logo" className="h-4" />
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex items-center gap-[6px] text-xs text-[#666] dark:text-[#999]">
|
||||||
|
Powered by
|
||||||
|
{renderLogo()}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Copyright;
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import { File } from "lucide-react";
|
import { File } from "lucide-react";
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
import IconWrapper from "./IconWrapper";
|
import IconWrapper from "./IconWrapper";
|
||||||
import ThemedIcon from "./ThemedIcon";
|
import ThemedIcon from "./ThemedIcon";
|
||||||
@@ -11,7 +12,7 @@ interface ItemIconProps {
|
|||||||
onClick?: React.MouseEventHandler<HTMLDivElement>;
|
onClick?: React.MouseEventHandler<HTMLDivElement>;
|
||||||
}
|
}
|
||||||
|
|
||||||
function ItemIcon({
|
const ItemIcon = React.memo(function ItemIcon({
|
||||||
item,
|
item,
|
||||||
className = "w-5 h-5 flex-shrink-0",
|
className = "w-5 h-5 flex-shrink-0",
|
||||||
onClick = () => {},
|
onClick = () => {},
|
||||||
@@ -34,7 +35,7 @@ function ItemIcon({
|
|||||||
|
|
||||||
let selectedIcon = icons[item?.icon];
|
let selectedIcon = icons[item?.icon];
|
||||||
if (!selectedIcon) {
|
if (!selectedIcon) {
|
||||||
selectedIcon=item?.icon
|
selectedIcon = item?.icon;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!selectedIcon) {
|
if (!selectedIcon) {
|
||||||
@@ -65,6 +66,6 @@ function ItemIcon({
|
|||||||
</IconWrapper>
|
</IconWrapper>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
|
|
||||||
export default ItemIcon;
|
export default ItemIcon;
|
||||||
|
|||||||
@@ -20,6 +20,19 @@ function TypeIcon({
|
|||||||
const endpoint_http = useAppStore((state) => state.endpoint_http);
|
const endpoint_http = useAppStore((state) => state.endpoint_http);
|
||||||
const connectorSource = useFindConnectorIcon(item);
|
const connectorSource = useFindConnectorIcon(item);
|
||||||
|
|
||||||
|
if (item?.source?.icon) {
|
||||||
|
if (
|
||||||
|
item?.source?.icon.startsWith("http://") ||
|
||||||
|
item?.source?.icon.startsWith("https://")
|
||||||
|
) {
|
||||||
|
return (
|
||||||
|
<IconWrapper className={className} onClick={onClick}>
|
||||||
|
<img className={className} src={item?.source?.icon} alt="icon" />
|
||||||
|
</IconWrapper>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// If the icon is a valid base64-encoded image
|
// If the icon is a valid base64-encoded image
|
||||||
const isBase64 = connectorSource?.icon?.startsWith("data:image/");
|
const isBase64 = connectorSource?.icon?.startsWith("data:image/");
|
||||||
if (isBase64) {
|
if (isBase64) {
|
||||||
|
|||||||
@@ -2,21 +2,24 @@ import { ArrowDown01, Command, CornerDownLeft } from "lucide-react";
|
|||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
|
|
||||||
import logoImg from "@/assets/icon.svg";
|
|
||||||
import { useSearchStore } from "@/stores/searchStore";
|
|
||||||
import TypeIcon from "@/components/Common/Icons/TypeIcon";
|
import TypeIcon from "@/components/Common/Icons/TypeIcon";
|
||||||
import { useAppStore } from "@/stores/appStore";
|
import Copyright from "@/components/Common/Copyright";
|
||||||
import { isMac } from "@/utils/platform";
|
import { isMac } from "@/utils/platform";
|
||||||
import PinOffIcon from "@/icons/PinOff";
|
import PinOffIcon from "@/icons/PinOff";
|
||||||
import PinIcon from "@/icons/Pin";
|
import PinIcon from "@/icons/Pin";
|
||||||
|
import logoImg from "@/assets/icon.svg";
|
||||||
|
import { useAppStore } from "@/stores/appStore";
|
||||||
|
import { useSearchStore } from "@/stores/searchStore";
|
||||||
import { useUpdateStore } from "@/stores/updateStore";
|
import { useUpdateStore } from "@/stores/updateStore";
|
||||||
|
|
||||||
interface FooterProps {
|
interface FooterProps {
|
||||||
|
isTauri: boolean;
|
||||||
openSetting: () => void;
|
openSetting: () => void;
|
||||||
setWindowAlwaysOnTop: (isPinned: boolean) => Promise<void>;
|
setWindowAlwaysOnTop: (isPinned: boolean) => Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Footer({
|
export default function Footer({
|
||||||
|
isTauri,
|
||||||
openSetting,
|
openSetting,
|
||||||
setWindowAlwaysOnTop,
|
setWindowAlwaysOnTop,
|
||||||
}: FooterProps) {
|
}: FooterProps) {
|
||||||
@@ -41,46 +44,53 @@ export default function Footer({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
data-tauri-drag-region
|
data-tauri-drag-region={isTauri}
|
||||||
className="px-4 z-999 mx-[1px] h-8 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-8 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">
|
{isTauri ? (
|
||||||
<div className="flex items-center space-x-2">
|
<div className="flex items-center">
|
||||||
{sourceData?.source?.name ? (
|
<div className="flex items-center space-x-2">
|
||||||
<TypeIcon item={sourceData} className="w-4 h-4" />
|
{sourceData?.source?.name ? (
|
||||||
) : (
|
<TypeIcon item={sourceData} className="w-4 h-4" />
|
||||||
<img
|
|
||||||
src={logoImg}
|
|
||||||
className="w-4 h-4 cursor-pointer"
|
|
||||||
onClick={openSetting}
|
|
||||||
alt={t("search.footer.logoAlt")}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<div className="relative text-xs text-gray-500 dark:text-gray-400">
|
|
||||||
{updateInfo?.available ? (
|
|
||||||
<div className="cursor-pointer" onClick={() => setVisible(true)}>
|
|
||||||
<span>{t("search.footer.updateAvailable")}</span>
|
|
||||||
<span className="absolute top-0 -right-2 size-1.5 bg-[#FF3434] rounded-full"></span>
|
|
||||||
</div>
|
|
||||||
) : (
|
) : (
|
||||||
sourceData?.source?.name ||
|
<img
|
||||||
t("search.footer.version", {
|
src={logoImg}
|
||||||
version: process.env.VERSION || "v1.0.0",
|
className="w-4 h-4 cursor-pointer"
|
||||||
})
|
onClick={openSetting}
|
||||||
|
alt={t("search.footer.logoAlt")}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
<div className="relative text-xs text-gray-500 dark:text-gray-400">
|
||||||
|
{updateInfo?.available ? (
|
||||||
|
<div
|
||||||
|
className="cursor-pointer"
|
||||||
|
onClick={() => setVisible(true)}
|
||||||
|
>
|
||||||
|
<span>{t("search.footer.updateAvailable")}</span>
|
||||||
|
<span className="absolute top-0 -right-2 size-1.5 bg-[#FF3434] rounded-full"></span>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
sourceData?.source?.name ||
|
||||||
|
t("search.footer.version", {
|
||||||
|
version: process.env.VERSION || "v1.0.0",
|
||||||
|
})
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
onClick={togglePin}
|
onClick={togglePin}
|
||||||
className={clsx({
|
className={clsx({
|
||||||
"text-blue-500": isPinned,
|
"text-blue-500": isPinned,
|
||||||
"pl-2": updateInfo?.available,
|
"pl-2": updateInfo?.available,
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
{isPinned ? <PinIcon /> : <PinOffIcon />}
|
{isPinned ? <PinIcon /> : <PinOffIcon />}
|
||||||
</button>
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
) : (
|
||||||
|
<Copyright />
|
||||||
|
)}
|
||||||
|
|
||||||
<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-xs">
|
<div className="gap-1 flex items-center text-[#666] dark:text-[#666] text-xs">
|
||||||
@@ -2,11 +2,14 @@ import { Command } from "lucide-react";
|
|||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
import { isMac } from "@/utils/platform";
|
import { isMac } from "@/utils/platform";
|
||||||
|
import { useShortcutsStore } from "@/stores/shortcutsStore";
|
||||||
import noDataImg from "@/assets/coconut-tree.png";
|
import noDataImg from "@/assets/coconut-tree.png";
|
||||||
|
|
||||||
export const NoResults = () => {
|
export const NoResults = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const modeSwitch = useShortcutsStore((state) => state.modeSwitch);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
data-tauri-drag-region
|
data-tauri-drag-region
|
||||||
@@ -30,7 +33,7 @@ export const NoResults = () => {
|
|||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
<span className="ml-1 w-5 h-5 rounded-[6px] border border-[#D8D8D8] flex justify-center items-center">
|
<span className="ml-1 w-5 h-5 rounded-[6px] border border-[#D8D8D8] flex justify-center items-center">
|
||||||
T
|
{modeSwitch}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -1,49 +1,49 @@
|
|||||||
import {Menu, MenuButton,} from "@headlessui/react";
|
import { Menu, MenuButton } from "@headlessui/react";
|
||||||
|
import { OctagonAlert, X } from "lucide-react";
|
||||||
// import { Settings, LogOut, User, ChevronUp, Home } from "lucide-react";
|
// import { Settings, LogOut, User, ChevronUp, Home } from "lucide-react";
|
||||||
// import { Link } from "react-router-dom";
|
// import { Link } from "react-router-dom";
|
||||||
import logoImg from "../assets/icon.svg";
|
|
||||||
import {useAppStore} from "@/stores/appStore";
|
import logoImg from "@/assets/icon.svg";
|
||||||
import {OctagonAlert, X} from 'lucide-react';
|
import { useAppStore } from "@/stores/appStore";
|
||||||
|
|
||||||
const Footer = () => {
|
const Footer = () => {
|
||||||
|
const error = useAppStore((state) => state.error);
|
||||||
|
const setError = useAppStore((state) => state.setError);
|
||||||
|
|
||||||
const error = useAppStore((state) => state.error);
|
return (
|
||||||
const setError = useAppStore((state) => state.setError);
|
<div className="fixed bottom-0 left-0 right-0 bg-white dark:bg-gray-800 border-t dark:border-gray-700">
|
||||||
|
{/* Move the warning message outside the border */}
|
||||||
|
{error && (
|
||||||
|
<div className="fixed bottom-6 left-0 right-0 bg-yellow-100 dark:bg-yellow-900 border-l-4 border-yellow-500 rounded-lg shadow-lg p-4 m-4">
|
||||||
|
<div className="flex items-center space-x-4">
|
||||||
|
<OctagonAlert size={32} color="red" className="mr-2" />
|
||||||
|
<span className="text-xs text-red-500 dark:text-red-400 flex-1">
|
||||||
|
{error}
|
||||||
|
</span>
|
||||||
|
<X
|
||||||
|
className="cursor-pointer ml-2"
|
||||||
|
onClick={() => setError("")}
|
||||||
|
size={32}
|
||||||
|
color="gray"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
return (
|
<div className="max-w-6xl mx-auto px-4 h-8 flex items-center justify-between">
|
||||||
<div className="fixed bottom-0 left-0 right-0 bg-white dark:bg-gray-800 border-t dark:border-gray-700">
|
<Menu as="div" className="relative">
|
||||||
{/* Move the warning message outside the border */}
|
<MenuButton className="h-7 flex items-center space-x-2 px-1 py-1.5 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors">
|
||||||
{error && (
|
<img
|
||||||
<div
|
src={logoImg}
|
||||||
className="fixed bottom-6 left-0 right-0 bg-yellow-100 dark:bg-yellow-900 border-l-4 border-yellow-500 rounded-lg shadow-lg p-4 m-4">
|
className="w-5 h-5 text-gray-600 dark:text-gray-400"
|
||||||
<div className="flex items-center space-x-4">
|
/>
|
||||||
<OctagonAlert size={32} color="red" className="mr-2"/>
|
<span className="text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||||
<span className="text-xs text-red-500 dark:text-red-400 flex-1">{error}</span>
|
|
||||||
<X
|
|
||||||
className="cursor-pointer ml-2"
|
|
||||||
onClick={() => setError("")}
|
|
||||||
size={32}
|
|
||||||
color="gray"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div className="max-w-6xl mx-auto px-4 h-8 flex items-center justify-between">
|
|
||||||
<Menu as="div" className="relative">
|
|
||||||
<MenuButton
|
|
||||||
className="h-7 flex items-center space-x-2 px-1 py-1.5 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors">
|
|
||||||
<img
|
|
||||||
src={logoImg}
|
|
||||||
className="w-5 h-5 text-gray-600 dark:text-gray-400"
|
|
||||||
/>
|
|
||||||
<span className="text-sm font-medium text-gray-700 dark:text-gray-300">
|
|
||||||
Coco
|
Coco
|
||||||
</span>
|
</span>
|
||||||
{/* <ChevronUp className="w-4 h-4 text-gray-500 dark:text-gray-400" /> */}
|
{/* <ChevronUp className="w-4 h-4 text-gray-500 dark:text-gray-400" /> */}
|
||||||
</MenuButton>
|
</MenuButton>
|
||||||
|
|
||||||
{/* <MenuItems className="absolute bottom-full mb-2 left-0 w-64 rounded-lg bg-white dark:bg-gray-800 shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none">
|
{/* <MenuItems className="absolute bottom-full mb-2 left-0 w-64 rounded-lg bg-white dark:bg-gray-800 shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none">
|
||||||
<div className="p-1">
|
<div className="p-1">
|
||||||
<MenuItem>
|
<MenuItem>
|
||||||
{({ active }) => (
|
{({ active }) => (
|
||||||
@@ -104,21 +104,20 @@ const Footer = () => {
|
|||||||
</MenuItem>
|
</MenuItem>
|
||||||
</div>
|
</div>
|
||||||
</MenuItems> */}
|
</MenuItems> */}
|
||||||
</Menu>
|
</Menu>
|
||||||
|
|
||||||
|
<div className="flex items-center space-x-4">
|
||||||
<div className="flex items-center space-x-4">
|
|
||||||
<span className="text-xs text-gray-500 dark:text-gray-400">
|
<span className="text-xs text-gray-500 dark:text-gray-400">
|
||||||
Version {process.env.VERSION || "v1.0.0"}
|
Version {process.env.VERSION || "v1.0.0"}
|
||||||
</span>
|
</span>
|
||||||
{/* <div className="h-4 w-px bg-gray-200 dark:bg-gray-700" />
|
{/* <div className="h-4 w-px bg-gray-200 dark:bg-gray-700" />
|
||||||
<button className="text-xs text-gray-500 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white transition-colors">
|
<button className="text-xs text-gray-500 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white transition-colors">
|
||||||
Check for Updates
|
Check for Updates
|
||||||
</button> */}
|
</button> */}
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Footer;
|
export default Footer;
|
||||||
@@ -6,13 +6,14 @@ interface AutoResizeTextareaProps {
|
|||||||
setInput: (value: string) => void;
|
setInput: (value: string) => void;
|
||||||
handleKeyDown?: (e: React.KeyboardEvent<HTMLTextAreaElement>) => void;
|
handleKeyDown?: (e: React.KeyboardEvent<HTMLTextAreaElement>) => void;
|
||||||
connected: boolean;
|
connected: boolean;
|
||||||
|
chatPlaceholder?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Forward ref to allow parent to interact with this component
|
// Forward ref to allow parent to interact with this component
|
||||||
const AutoResizeTextarea = forwardRef<
|
const AutoResizeTextarea = forwardRef<
|
||||||
{ reset: () => void; focus: () => void },
|
{ reset: () => void; focus: () => void },
|
||||||
AutoResizeTextareaProps
|
AutoResizeTextareaProps
|
||||||
>(({ input, setInput, handleKeyDown, connected }, ref) => {
|
>(({ input, setInput, handleKeyDown, connected, chatPlaceholder }, ref) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const textareaRef = useRef<HTMLTextAreaElement>(null);
|
const textareaRef = useRef<HTMLTextAreaElement>(null);
|
||||||
|
|
||||||
@@ -34,8 +35,10 @@ const AutoResizeTextarea = forwardRef<
|
|||||||
autoCapitalize="none"
|
autoCapitalize="none"
|
||||||
spellCheck="false"
|
spellCheck="false"
|
||||||
className="text-base flex-1 outline-none min-w-[200px] text-[#333] dark:text-[#d8d8d8] placeholder-text-xs placeholder-[#999] dark:placeholder-gray-500 bg-transparent"
|
className="text-base flex-1 outline-none min-w-[200px] text-[#333] dark:text-[#d8d8d8] placeholder-text-xs placeholder-[#999] dark:placeholder-gray-500 bg-transparent"
|
||||||
placeholder={connected ? t('search.textarea.placeholder') : ""}
|
placeholder={
|
||||||
aria-label={t('search.textarea.ariaLabel')}
|
connected ? chatPlaceholder || t("search.textarea.placeholder") : ""
|
||||||
|
}
|
||||||
|
aria-label={t("search.textarea.ariaLabel")}
|
||||||
value={input}
|
value={input}
|
||||||
onChange={(e) => setInput(e.target.value)}
|
onChange={(e) => setInput(e.target.value)}
|
||||||
onKeyDown={(e) => handleKeyDown?.(e)}
|
onKeyDown={(e) => handleKeyDown?.(e)}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ interface State {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface ContextMenuProps {
|
interface ContextMenuProps {
|
||||||
hideCoco: () => Promise<void>;
|
hideCoco?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ContextMenu = ({ hideCoco }: ContextMenuProps) => {
|
const ContextMenu = ({ hideCoco }: ContextMenuProps) => {
|
||||||
@@ -54,7 +54,7 @@ const ContextMenu = ({ hideCoco }: ContextMenuProps) => {
|
|||||||
|
|
||||||
setVisibleContextMenu(false);
|
setVisibleContextMenu(false);
|
||||||
|
|
||||||
hideCoco();
|
hideCoco && hideCoco();
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { ArrowBigLeft, Search, Send, Brain } from "lucide-react";
|
|||||||
import { useCallback, useEffect, useRef, useState } from "react";
|
import { useCallback, useEffect, useRef, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
|
import { useKeyPress } from "ahooks";
|
||||||
|
|
||||||
import ChatSwitch from "@/components/Common/ChatSwitch";
|
import ChatSwitch from "@/components/Common/ChatSwitch";
|
||||||
import AutoResizeTextarea from "./AutoResizeTextarea";
|
import AutoResizeTextarea from "./AutoResizeTextarea";
|
||||||
@@ -12,12 +13,11 @@ import { useSearchStore } from "@/stores/searchStore";
|
|||||||
import { metaOrCtrlKey } from "@/utils/keyboardUtils";
|
import { metaOrCtrlKey } from "@/utils/keyboardUtils";
|
||||||
import SearchPopover from "./SearchPopover";
|
import SearchPopover from "./SearchPopover";
|
||||||
// import AudioRecording from "../AudioRecording";
|
// import AudioRecording from "../AudioRecording";
|
||||||
import { hide_coco } from "@/commands";
|
|
||||||
import { DataSource } from "@/types/commands";
|
import { DataSource } from "@/types/commands";
|
||||||
// import InputExtra from "./InputExtra";
|
// import InputExtra from "./InputExtra";
|
||||||
// import { useConnectStore } from "@/stores/connectStore";
|
// import { useConnectStore } from "@/stores/connectStore";
|
||||||
import { useShortcutsStore } from "@/stores/shortcutsStore";
|
import { useShortcutsStore } from "@/stores/shortcutsStore";
|
||||||
import { useKeyPress } from "ahooks";
|
import Copyright from "@/components/Common/Copyright";
|
||||||
|
|
||||||
interface ChatInputProps {
|
interface ChatInputProps {
|
||||||
onSend: (message: string) => void;
|
onSend: (message: string) => void;
|
||||||
@@ -46,6 +46,11 @@ interface ChatInputProps {
|
|||||||
}) => Promise<string | string[] | null>;
|
}) => Promise<string | string[] | null>;
|
||||||
getFileMetadata: (path: string) => Promise<any>;
|
getFileMetadata: (path: string) => Promise<any>;
|
||||||
getFileIcon: (path: string, size: number) => Promise<string>;
|
getFileIcon: (path: string, size: number) => Promise<string>;
|
||||||
|
hideCoco?: () => void;
|
||||||
|
hasFeature?: string[];
|
||||||
|
hasModules?: string[];
|
||||||
|
searchPlaceholder?: string;
|
||||||
|
chatPlaceholder?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function ChatInput({
|
export default function ChatInput({
|
||||||
@@ -64,16 +69,12 @@ export default function ChatInput({
|
|||||||
isChatPage = false,
|
isChatPage = false,
|
||||||
getDataSourcesByServer,
|
getDataSourcesByServer,
|
||||||
setupWindowFocusListener,
|
setupWindowFocusListener,
|
||||||
}: // checkScreenPermission,
|
hasFeature = ["think", "search", "think_icon", "search_icon"],
|
||||||
// requestScreenPermission,
|
hideCoco,
|
||||||
// getScreenMonitors,
|
hasModules = [],
|
||||||
// getScreenWindows,
|
searchPlaceholder,
|
||||||
// captureMonitorScreenshot,
|
chatPlaceholder,
|
||||||
// captureWindowScreenshot,
|
}: ChatInputProps) {
|
||||||
// openFileDialog,
|
|
||||||
// getFileMetadata,
|
|
||||||
// getFileIcon,
|
|
||||||
ChatInputProps) {
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const showTooltip = useAppStore(
|
const showTooltip = useAppStore(
|
||||||
@@ -156,6 +157,7 @@ ChatInputProps) {
|
|||||||
|
|
||||||
const handleSubmit = useCallback(() => {
|
const handleSubmit = useCallback(() => {
|
||||||
const trimmedValue = inputValue.trim();
|
const trimmedValue = inputValue.trim();
|
||||||
|
console.log("handleSubmit", trimmedValue, disabled);
|
||||||
if (trimmedValue && !disabled) {
|
if (trimmedValue && !disabled) {
|
||||||
changeInput("");
|
changeInput("");
|
||||||
onSend(trimmedValue);
|
onSend(trimmedValue);
|
||||||
@@ -168,7 +170,7 @@ ChatInputProps) {
|
|||||||
if (inputValue) {
|
if (inputValue) {
|
||||||
changeInput("");
|
changeInput("");
|
||||||
} else if (!isPinned) {
|
} else if (!isPinned) {
|
||||||
hide_coco();
|
hideCoco && hideCoco();
|
||||||
}
|
}
|
||||||
}, [inputValue, isPinned]);
|
}, [inputValue, isPinned]);
|
||||||
|
|
||||||
@@ -295,12 +297,17 @@ ChatInputProps) {
|
|||||||
changeInput(value);
|
changeInput(value);
|
||||||
}}
|
}}
|
||||||
connected={connected}
|
connected={connected}
|
||||||
handleKeyDown={(e) => {
|
handleKeyDown={(e: React.KeyboardEvent<HTMLTextAreaElement>) => {
|
||||||
if (e.key === "Enter") {
|
if (e.key === "Enter") {
|
||||||
|
if (e.nativeEvent.isComposing) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
console.log("handleKeyDown", e.nativeEvent.isComposing);
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
handleSubmit();
|
handleSubmit();
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
chatPlaceholder={chatPlaceholder}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<input
|
<input
|
||||||
@@ -311,7 +318,9 @@ ChatInputProps) {
|
|||||||
autoCapitalize="none"
|
autoCapitalize="none"
|
||||||
spellCheck="false"
|
spellCheck="false"
|
||||||
className="text-base font-normal flex-1 outline-none min-w-[200px] text-[#333] dark:text-[#d8d8d8] placeholder-text-xs placeholder-[#999] dark:placeholder-gray-500 bg-transparent"
|
className="text-base font-normal flex-1 outline-none min-w-[200px] text-[#333] dark:text-[#d8d8d8] placeholder-text-xs placeholder-[#999] dark:placeholder-gray-500 bg-transparent"
|
||||||
placeholder={t("search.input.searchPlaceholder")}
|
placeholder={
|
||||||
|
searchPlaceholder || t("search.input.searchPlaceholder")
|
||||||
|
}
|
||||||
value={inputValue}
|
value={inputValue}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
onSend(e.target.value);
|
onSend(e.target.value);
|
||||||
@@ -390,7 +399,7 @@ ChatInputProps) {
|
|||||||
<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">
|
||||||
{t("search.input.connectionError")}
|
{t("search.input.connectionError")}
|
||||||
<div
|
<div
|
||||||
className="h-[24px] px-2 bg-[#0061FF] rounded-[12px] font-normal text-xs text-white flex items-center justify-center cursor-pointer"
|
className="px-1 h-[24px] text-[#0061FF] font-normal text-xs flex items-center justify-center cursor-pointer underline"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
reconnect();
|
reconnect();
|
||||||
setReconnectCountdown(10);
|
setReconnectCountdown(10);
|
||||||
@@ -424,38 +433,47 @@ ChatInputProps) {
|
|||||||
/>
|
/>
|
||||||
)} */}
|
)} */}
|
||||||
|
|
||||||
<button
|
{hasFeature.includes("think") && (
|
||||||
className={clsx(
|
<button
|
||||||
"flex items-center gap-1 p-1 h-6 rounded-lg transition hover:bg-[#EDEDED] dark:hover:bg-[#202126]",
|
className={clsx(
|
||||||
{
|
"flex items-center gap-1 p-1 h-6 rounded-lg transition hover:bg-[#EDEDED] dark:hover:bg-[#202126]",
|
||||||
"!bg-[rgba(0,114,255,0.3)]": isDeepThinkActive,
|
{
|
||||||
}
|
"!bg-[rgba(0,114,255,0.3)]": isDeepThinkActive,
|
||||||
)}
|
|
||||||
onClick={DeepThinkClick}
|
|
||||||
>
|
|
||||||
<Brain
|
|
||||||
className={`size-4 ${
|
|
||||||
isDeepThinkActive
|
|
||||||
? "text-[#0072FF] dark:text-[#0072FF]"
|
|
||||||
: "text-[#333] dark:text-white"
|
|
||||||
}`}
|
|
||||||
/>
|
|
||||||
{isDeepThinkActive && (
|
|
||||||
<span
|
|
||||||
className={
|
|
||||||
isDeepThinkActive ? "text-[#0072FF]" : "dark:text-white"
|
|
||||||
}
|
}
|
||||||
>
|
)}
|
||||||
{t("search.input.deepThink")}
|
onClick={DeepThinkClick}
|
||||||
</span>
|
>
|
||||||
)}
|
<Brain
|
||||||
</button>
|
className={`size-4 ${
|
||||||
|
isDeepThinkActive
|
||||||
|
? "text-[#0072FF] dark:text-[#0072FF]"
|
||||||
|
: "text-[#333] dark:text-white"
|
||||||
|
}`}
|
||||||
|
/>
|
||||||
|
{isDeepThinkActive && (
|
||||||
|
<span
|
||||||
|
className={
|
||||||
|
isDeepThinkActive ? "text-[#0072FF]" : "dark:text-white"
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{t("search.input.deepThink")}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
|
||||||
<SearchPopover
|
{hasFeature.includes("search") && (
|
||||||
isSearchActive={isSearchActive}
|
<SearchPopover
|
||||||
setIsSearchActive={setIsSearchActive}
|
isSearchActive={isSearchActive}
|
||||||
getDataSourcesByServer={getDataSourcesByServer}
|
setIsSearchActive={setIsSearchActive}
|
||||||
/>
|
getDataSourcesByServer={getDataSourcesByServer}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{!hasFeature.includes("search") && !hasFeature.includes("think") ? (
|
||||||
|
<div className="px-2">
|
||||||
|
<Copyright />
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div
|
<div
|
||||||
@@ -464,7 +482,7 @@ ChatInputProps) {
|
|||||||
></div>
|
></div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{isChatPage ? null : (
|
{isChatPage || hasModules?.length !== 2 ? null : (
|
||||||
<div className="relative w-16 flex justify-end items-center">
|
<div className="relative w-16 flex justify-end items-center">
|
||||||
{showTooltip && modifierKeyPressed ? (
|
{showTooltip && modifierKeyPressed ? (
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -2,13 +2,13 @@ import { useEffect, useState, useCallback, useRef } from "react";
|
|||||||
import { debounce } from "lodash-es";
|
import { debounce } from "lodash-es";
|
||||||
|
|
||||||
import DropdownList from "./DropdownList";
|
import DropdownList from "./DropdownList";
|
||||||
import Footer from "./Footer";
|
|
||||||
import { SearchResults } from "@/components/Search/SearchResults";
|
import { SearchResults } from "@/components/Search/SearchResults";
|
||||||
import { useSearchStore } from "@/stores/searchStore";
|
import { useSearchStore } from "@/stores/searchStore";
|
||||||
import ContextMenu from "./ContextMenu";
|
import ContextMenu from "./ContextMenu";
|
||||||
import { NoResults } from "./NoResults";
|
import { NoResults } from "@/components/Common/UI/NoResults";
|
||||||
|
import Footer from "@/components/Common/UI/Footer";
|
||||||
interface SearchProps {
|
interface SearchProps {
|
||||||
|
isTauri: boolean;
|
||||||
changeInput: (val: string) => void;
|
changeInput: (val: string) => void;
|
||||||
isChatMode: boolean;
|
isChatMode: boolean;
|
||||||
input: string;
|
input: string;
|
||||||
@@ -18,12 +18,13 @@ interface SearchProps {
|
|||||||
size: number,
|
size: number,
|
||||||
queryStrings: any
|
queryStrings: any
|
||||||
) => Promise<any>;
|
) => Promise<any>;
|
||||||
hideCoco: () => Promise<any>;
|
hideCoco?: () => void;
|
||||||
openSetting: () => void;
|
openSetting: () => void;
|
||||||
setWindowAlwaysOnTop: (isPinned: boolean) => Promise<void>;
|
setWindowAlwaysOnTop: (isPinned: boolean) => Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
function Search({
|
function Search({
|
||||||
|
isTauri,
|
||||||
isChatMode,
|
isChatMode,
|
||||||
input,
|
input,
|
||||||
querySearch,
|
querySearch,
|
||||||
@@ -81,7 +82,7 @@ function Search({
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
ref={mainWindowRef}
|
ref={mainWindowRef}
|
||||||
className={`h-[calc(100vh-90px)] pb-10 w-full relative`}
|
className={`h-full pb-10 w-full relative`}
|
||||||
>
|
>
|
||||||
{/* Search Results Panel */}
|
{/* Search Results Panel */}
|
||||||
{suggests.length > 0 ? (
|
{suggests.length > 0 ? (
|
||||||
@@ -105,6 +106,7 @@ function Search({
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<Footer
|
<Footer
|
||||||
|
isTauri={isTauri}
|
||||||
openSetting={openSetting}
|
openSetting={openSetting}
|
||||||
setWindowAlwaysOnTop={setWindowAlwaysOnTop}
|
setWindowAlwaysOnTop={setWindowAlwaysOnTop}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ interface SearchListItemProps {
|
|||||||
showListRight?: boolean;
|
showListRight?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const SearchListItem: React.FC<SearchListItemProps> = ({
|
const SearchListItem: React.FC<SearchListItemProps> = React.memo(({
|
||||||
item,
|
item,
|
||||||
isSelected,
|
isSelected,
|
||||||
currentIndex,
|
currentIndex,
|
||||||
@@ -68,6 +68,6 @@ const SearchListItem: React.FC<SearchListItemProps> = ({
|
|||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
});
|
||||||
|
|
||||||
export default SearchListItem;
|
export default SearchListItem;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useState, useEffect, useCallback, useMemo } from "react";
|
import { useState, useEffect, useCallback, useMemo, useRef } from "react";
|
||||||
import { Popover, PopoverButton, PopoverPanel } from "@headlessui/react";
|
import { Popover, PopoverButton, PopoverPanel } from "@headlessui/react";
|
||||||
import { ChevronDownIcon, RefreshCw, Layers, Globe } from "lucide-react";
|
import { ChevronDownIcon, RefreshCw, Layers, Globe } from "lucide-react";
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
@@ -8,7 +8,7 @@ import TypeIcon from "@/components/Common/Icons/TypeIcon";
|
|||||||
import { useConnectStore } from "@/stores/connectStore";
|
import { useConnectStore } from "@/stores/connectStore";
|
||||||
import { useSearchStore } from "@/stores/searchStore";
|
import { useSearchStore } from "@/stores/searchStore";
|
||||||
import { DataSource } from "@/types/commands";
|
import { DataSource } from "@/types/commands";
|
||||||
import Checkbox from "../Common/Checkbox";
|
import Checkbox from "@/components/Common/Checkbox";
|
||||||
|
|
||||||
interface SearchPopoverProps {
|
interface SearchPopoverProps {
|
||||||
isSearchActive: boolean;
|
isSearchActive: boolean;
|
||||||
@@ -30,18 +30,26 @@ export default function SearchPopover({
|
|||||||
|
|
||||||
const currentService = useConnectStore((state) => state.currentService);
|
const currentService = useConnectStore((state) => state.currentService);
|
||||||
|
|
||||||
|
const [showDataSource, setShowDataSource] = useState(false);
|
||||||
|
|
||||||
const getDataSourceList = useCallback(async () => {
|
const getDataSourceList = useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
const res: DataSource[] = await getDataSourcesByServer(
|
const res: DataSource[] = await getDataSourcesByServer(
|
||||||
currentService?.id
|
currentService?.id
|
||||||
);
|
);
|
||||||
const data = [
|
if (res?.length === 0) {
|
||||||
{
|
setDataSourceList([]);
|
||||||
id: "all",
|
return;
|
||||||
name: "search.input.searchPopover.allScope",
|
}
|
||||||
},
|
const data = res?.length
|
||||||
...(res || []),
|
? [
|
||||||
];
|
{
|
||||||
|
id: "all",
|
||||||
|
name: "search.input.searchPopover.allScope",
|
||||||
|
},
|
||||||
|
...res,
|
||||||
|
]
|
||||||
|
: [];
|
||||||
setDataSourceList(data);
|
setDataSourceList(data);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setDataSourceList([]);
|
setDataSourceList([]);
|
||||||
@@ -49,6 +57,29 @@ export default function SearchPopover({
|
|||||||
}
|
}
|
||||||
}, [currentService?.id]);
|
}, [currentService?.id]);
|
||||||
|
|
||||||
|
const popoverRef = useRef<HTMLDivElement>(null);
|
||||||
|
const buttonRef = useRef<HTMLButtonElement>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!showDataSource) return;
|
||||||
|
|
||||||
|
const handleClickOutside = (event: MouseEvent) => {
|
||||||
|
if (
|
||||||
|
popoverRef.current &&
|
||||||
|
!popoverRef.current.contains(event.target as Node) &&
|
||||||
|
buttonRef.current &&
|
||||||
|
!buttonRef.current.contains(event.target as Node)
|
||||||
|
) {
|
||||||
|
setShowDataSource(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
document.addEventListener("mousedown", handleClickOutside);
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener("mousedown", handleClickOutside);
|
||||||
|
};
|
||||||
|
}, [showDataSource]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (dataSourceList.length > 0) {
|
if (dataSourceList.length > 0) {
|
||||||
onSelectDataSource("all", true, true);
|
onSelectDataSource("all", true, true);
|
||||||
@@ -111,8 +142,16 @@ export default function SearchPopover({
|
|||||||
</span>
|
</span>
|
||||||
|
|
||||||
{dataSourceList?.length > 0 && (
|
{dataSourceList?.length > 0 && (
|
||||||
<Popover>
|
<Popover className="relative">
|
||||||
<PopoverButton as="span" className={clsx("flex items-center")}>
|
<PopoverButton
|
||||||
|
as="span"
|
||||||
|
ref={buttonRef}
|
||||||
|
className={clsx("flex items-center")}
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
setShowDataSource((prev) => !prev);
|
||||||
|
}}
|
||||||
|
>
|
||||||
<ChevronDownIcon
|
<ChevronDownIcon
|
||||||
className={clsx("size-5", [
|
className={clsx("size-5", [
|
||||||
isSearchActive
|
isSearchActive
|
||||||
@@ -122,79 +161,82 @@ export default function SearchPopover({
|
|||||||
/>
|
/>
|
||||||
</PopoverButton>
|
</PopoverButton>
|
||||||
|
|
||||||
<PopoverPanel
|
{showDataSource ? (
|
||||||
anchor="top start"
|
<PopoverPanel
|
||||||
className="min-w-[220px] bg-white dark:bg-[#202126] rounded-lg shadow-lg border border-gray-200 dark:border-gray-700"
|
static
|
||||||
>
|
ref={popoverRef}
|
||||||
<div
|
className="absolute z-50 left-0 bottom-6 min-w-[220px] max-h-[400px] overflow-y-auto bg-white dark:bg-[#202126] rounded-lg shadow-lg border border-gray-200 dark:border-gray-700"
|
||||||
className="text-sm px-[12px] py-[18px]"
|
|
||||||
onClick={(e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<div className="flex justify-between mb-[18px]">
|
<div
|
||||||
<span>{t("search.input.searchPopover.title")}</span>
|
className="text-sm px-[12px] py-[18px]"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="flex justify-between mb-[18px]">
|
||||||
|
<span>{t("search.input.searchPopover.title")}</span>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
setIsRefreshDataSource(true);
|
setIsRefreshDataSource(true);
|
||||||
|
|
||||||
getDataSourceList();
|
getDataSourceList();
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
setIsRefreshDataSource(false);
|
setIsRefreshDataSource(false);
|
||||||
}, 1000);
|
}, 1000);
|
||||||
}}
|
}}
|
||||||
className="size-[24px] flex justify-center items-center rounded-lg border border-black/10 dark:border-white/10 cursor-pointer"
|
className="size-[24px] flex justify-center items-center rounded-lg border border-black/10 dark:border-white/10 cursor-pointer"
|
||||||
>
|
>
|
||||||
<RefreshCw
|
<RefreshCw
|
||||||
className={`size-3 text-[#0287FF] transition-transform duration-1000 ${
|
className={`size-3 text-[#0287FF] transition-transform duration-1000 ${
|
||||||
isRefreshDataSource ? "animate-spin" : ""
|
isRefreshDataSource ? "animate-spin" : ""
|
||||||
}`}
|
}`}
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<ul className="flex flex-col gap-[16px]">
|
||||||
|
{dataSourceList?.map((item, index) => {
|
||||||
|
const { id, name } = item;
|
||||||
|
|
||||||
|
const isAll = index === 0;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<li
|
||||||
|
key={id}
|
||||||
|
className="flex justify-between items-center"
|
||||||
|
>
|
||||||
|
<div className="flex items-center gap-[8px]">
|
||||||
|
{isAll ? (
|
||||||
|
<Layers className="size-[16px] text-[#0287FF]" />
|
||||||
|
) : (
|
||||||
|
<TypeIcon item={item} className="size-[16px]" />
|
||||||
|
)}
|
||||||
|
|
||||||
|
<span>{isAll && name ? t(name) : name}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex justify-center items-center size-[24px]">
|
||||||
|
<Checkbox
|
||||||
|
checked={
|
||||||
|
isAll
|
||||||
|
? sourceDataIds.length ===
|
||||||
|
dataSourceList.length - 1
|
||||||
|
: sourceDataIds?.includes(id)
|
||||||
|
}
|
||||||
|
indeterminate={isAll}
|
||||||
|
onChange={(value) =>
|
||||||
|
onSelectDataSource(id, value, isAll)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<ul className="flex flex-col gap-[16px]">
|
</PopoverPanel>
|
||||||
{dataSourceList?.map((item, index) => {
|
) : null}
|
||||||
const { id, name } = item;
|
|
||||||
|
|
||||||
const isAll = index === 0;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<li
|
|
||||||
key={id}
|
|
||||||
className="flex justify-between items-center"
|
|
||||||
>
|
|
||||||
<div className="flex items-center gap-[8px]">
|
|
||||||
{isAll ? (
|
|
||||||
<Layers className="size-[16px] text-[#0287FF]" />
|
|
||||||
) : (
|
|
||||||
<TypeIcon item={item} className="size-[16px]" />
|
|
||||||
)}
|
|
||||||
|
|
||||||
<span>{isAll && name ? t(name) : name}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex justify-center items-center size-[24px]">
|
|
||||||
<Checkbox
|
|
||||||
checked={
|
|
||||||
isAll
|
|
||||||
? sourceDataIds.length ===
|
|
||||||
dataSourceList.length - 1
|
|
||||||
: sourceDataIds?.includes(id)
|
|
||||||
}
|
|
||||||
indeterminate={isAll}
|
|
||||||
onChange={(value) =>
|
|
||||||
onSelectDataSource(id, value, isAll)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</PopoverPanel>
|
|
||||||
</Popover>
|
</Popover>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
|||||||
420
src/components/SearchChat/index.tsx
Normal file
420
src/components/SearchChat/index.tsx
Normal file
@@ -0,0 +1,420 @@
|
|||||||
|
import {
|
||||||
|
useEffect,
|
||||||
|
useRef,
|
||||||
|
useCallback,
|
||||||
|
useReducer,
|
||||||
|
Suspense,
|
||||||
|
memo,
|
||||||
|
} from "react";
|
||||||
|
import clsx from "clsx";
|
||||||
|
|
||||||
|
import Search from "@/components/Search/Search";
|
||||||
|
import InputBox from "@/components/Search/InputBox";
|
||||||
|
import ChatAI, { ChatAIRef } from "@/components/Assistant/Chat";
|
||||||
|
import UpdateApp from "@/components/UpdateApp";
|
||||||
|
import { isLinux, isWin } from "@/utils/platform";
|
||||||
|
import { appReducer, initialAppState } from "@/reducers/appReducer";
|
||||||
|
import { useWindowEvents } from "@/hooks/useWindowEvents";
|
||||||
|
import { useAppStore } from "@/stores/appStore";
|
||||||
|
import { useAuthStore } from "@/stores/authStore";
|
||||||
|
import platformAdapter from "@/utils/platformAdapter";
|
||||||
|
import { useStartupStore } from "@/stores/startupStore";
|
||||||
|
import { DataSource } from "@/types/commands";
|
||||||
|
import { useThemeStore } from "@/stores/themeStore";
|
||||||
|
import { Get } from "@/api/axiosRequest";
|
||||||
|
|
||||||
|
interface SearchChatProps {
|
||||||
|
isTauri?: boolean;
|
||||||
|
hasModules?: string[];
|
||||||
|
defaultModule?: "search" | "chat";
|
||||||
|
|
||||||
|
hasFeature?: string[];
|
||||||
|
showChatHistory?: boolean;
|
||||||
|
|
||||||
|
theme?: "auto" | "light" | "dark";
|
||||||
|
searchPlaceholder?: string;
|
||||||
|
chatPlaceholder?: string;
|
||||||
|
|
||||||
|
hideCoco?: () => void;
|
||||||
|
setIsPinned?: (value: boolean) => void;
|
||||||
|
querySearch: (input: string) => Promise<any>;
|
||||||
|
queryDocuments: (
|
||||||
|
from: number,
|
||||||
|
size: number,
|
||||||
|
queryStrings: any
|
||||||
|
) => Promise<any>;
|
||||||
|
}
|
||||||
|
|
||||||
|
function SearchChat({
|
||||||
|
isTauri = true,
|
||||||
|
hasModules = ["search", "chat"],
|
||||||
|
defaultModule = "search",
|
||||||
|
hasFeature = ["think", "search", "think_active", "search_active"],
|
||||||
|
theme,
|
||||||
|
hideCoco,
|
||||||
|
querySearch,
|
||||||
|
queryDocuments,
|
||||||
|
searchPlaceholder,
|
||||||
|
chatPlaceholder,
|
||||||
|
showChatHistory,
|
||||||
|
setIsPinned,
|
||||||
|
}: SearchChatProps) {
|
||||||
|
const customInitialState = {
|
||||||
|
...initialAppState,
|
||||||
|
isDeepThinkActive: hasFeature.includes("think_active"),
|
||||||
|
isSearchActive: hasFeature.includes("search_active"),
|
||||||
|
};
|
||||||
|
|
||||||
|
const [state, dispatch] = useReducer(appReducer, customInitialState);
|
||||||
|
const {
|
||||||
|
isChatMode,
|
||||||
|
input,
|
||||||
|
isTransitioned,
|
||||||
|
isSearchActive,
|
||||||
|
isDeepThinkActive,
|
||||||
|
isTyping,
|
||||||
|
} = state;
|
||||||
|
|
||||||
|
useWindowEvents();
|
||||||
|
|
||||||
|
const initializeListeners = useAppStore((state) => state.initializeListeners);
|
||||||
|
const initializeListeners_auth = useAuthStore(
|
||||||
|
(state) => state.initializeListeners
|
||||||
|
);
|
||||||
|
|
||||||
|
const setTheme = useThemeStore((state) => state.setTheme);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let mounted = true;
|
||||||
|
|
||||||
|
const init = async () => {
|
||||||
|
if (!mounted) return;
|
||||||
|
await initializeListeners();
|
||||||
|
await initializeListeners_auth();
|
||||||
|
await platformAdapter.invokeBackend("get_app_search_source");
|
||||||
|
if (theme && mounted) {
|
||||||
|
setTheme(theme);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
init();
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
mounted = false;
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const chatAIRef = useRef<ChatAIRef>(null);
|
||||||
|
|
||||||
|
const changeMode = useCallback(async (value: boolean) => {
|
||||||
|
dispatch({ type: "SET_CHAT_MODE", payload: value });
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleSendMessage = useCallback(
|
||||||
|
async (value: string) => {
|
||||||
|
dispatch({ type: "SET_INPUT", payload: value });
|
||||||
|
if (isChatMode) {
|
||||||
|
chatAIRef.current?.init(value);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[isChatMode]
|
||||||
|
);
|
||||||
|
|
||||||
|
const cancelChat = useCallback(() => {
|
||||||
|
chatAIRef.current?.cancelChat();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const reconnect = useCallback(() => {
|
||||||
|
chatAIRef.current?.reconnect();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const setInput = useCallback((value: string) => {
|
||||||
|
dispatch({ type: "SET_INPUT", payload: value });
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const toggleSearchActive = useCallback(() => {
|
||||||
|
dispatch({ type: "TOGGLE_SEARCH_ACTIVE" });
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const toggleDeepThinkActive = useCallback(() => {
|
||||||
|
dispatch({ type: "TOGGLE_DEEP_THINK_ACTIVE" });
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const LoadingFallback = () => (
|
||||||
|
<div className="flex items-center justify-center h-full">loading...</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
const getFileUrl = useCallback((path: string) => {
|
||||||
|
return platformAdapter.convertFileSrc(path);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const openSetting = useCallback(() => {
|
||||||
|
return platformAdapter.emitEvent("open_settings", "");
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const setWindowAlwaysOnTop = useCallback(async (isPinned: boolean) => {
|
||||||
|
setIsPinned && setIsPinned(isPinned);
|
||||||
|
return platformAdapter.setAlwaysOnTop(isPinned);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const getDataSourcesByServer = useCallback(
|
||||||
|
async (serverId: string): Promise<DataSource[]> => {
|
||||||
|
if (isTauri) {
|
||||||
|
return platformAdapter.invokeBackend("get_datasources_by_server", {
|
||||||
|
id: serverId,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
const [error, response]: any = await Get("/datasource/_search");
|
||||||
|
if (error) {
|
||||||
|
console.error("_search", error);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
const res = response?.hits?.hits?.map((item: any) => {
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
id: item._source.id,
|
||||||
|
name: item._source.name,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
return res || [];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
|
const setupWindowFocusListener = useCallback(async (callback: () => void) => {
|
||||||
|
return platformAdapter.listenEvent("tauri://focus", callback);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const checkScreenPermission = useCallback(async () => {
|
||||||
|
return platformAdapter.checkScreenRecordingPermission();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const requestScreenPermission = useCallback(() => {
|
||||||
|
return platformAdapter.requestScreenRecordingPermission();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const getScreenMonitors = useCallback(async () => {
|
||||||
|
return platformAdapter.getScreenshotableMonitors();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const getScreenWindows = useCallback(async () => {
|
||||||
|
return platformAdapter.getScreenshotableWindows();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const captureMonitorScreenshot = useCallback(async (id: number) => {
|
||||||
|
return platformAdapter.captureMonitorScreenshot(id);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const captureWindowScreenshot = useCallback(async (id: number) => {
|
||||||
|
return platformAdapter.captureWindowScreenshot(id);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const openFileDialog = useCallback(async (options: { multiple: boolean }) => {
|
||||||
|
return platformAdapter.openFileDialog(options);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const getFileMetadata = useCallback(async (path: string) => {
|
||||||
|
return platformAdapter.getFileMetadata(path);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const getFileIcon = useCallback(async (path: string, size: number) => {
|
||||||
|
return platformAdapter.getFileIcon(path, size);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const checkUpdate = useCallback(async () => {
|
||||||
|
return platformAdapter.checkUpdate();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const relaunchApp = useCallback(async () => {
|
||||||
|
return platformAdapter.relaunchApp();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const defaultStartupWindow = useStartupStore((state) => {
|
||||||
|
return state.defaultStartupWindow;
|
||||||
|
});
|
||||||
|
const setDefaultStartupWindow = useStartupStore((state) => {
|
||||||
|
return state.setDefaultStartupWindow;
|
||||||
|
});
|
||||||
|
|
||||||
|
const showCocoListenRef = useRef<(() => void) | undefined>();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (hasModules?.length === 1 && hasModules?.includes("chat")) {
|
||||||
|
changeMode(true);
|
||||||
|
} else {
|
||||||
|
changeMode(defaultModule === "chat");
|
||||||
|
}
|
||||||
|
|
||||||
|
let unlistenChangeStartupStore: (() => void) | undefined;
|
||||||
|
|
||||||
|
const setupListener = async () => {
|
||||||
|
try {
|
||||||
|
unlistenChangeStartupStore = await platformAdapter.listenEvent(
|
||||||
|
"change-startup-store",
|
||||||
|
({ payload }) => {
|
||||||
|
if (
|
||||||
|
payload &&
|
||||||
|
typeof payload === "object" &&
|
||||||
|
"defaultStartupWindow" in payload
|
||||||
|
) {
|
||||||
|
const startupWindow = payload.defaultStartupWindow;
|
||||||
|
if (
|
||||||
|
startupWindow === "searchMode" ||
|
||||||
|
startupWindow === "chatMode"
|
||||||
|
) {
|
||||||
|
setDefaultStartupWindow(startupWindow);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error setting up change-startup-store listener:", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
setupListener();
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
if (unlistenChangeStartupStore) {
|
||||||
|
unlistenChangeStartupStore();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const setupShowCocoListener = async () => {
|
||||||
|
if (showCocoListenRef.current) {
|
||||||
|
showCocoListenRef.current();
|
||||||
|
showCocoListenRef.current = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const unlisten = await platformAdapter.listenEvent("show-coco", () => {
|
||||||
|
changeMode(defaultStartupWindow === "chatMode");
|
||||||
|
});
|
||||||
|
|
||||||
|
showCocoListenRef.current = unlisten;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error setting up show-coco listener:", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
setupShowCocoListener();
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
if (showCocoListenRef.current) {
|
||||||
|
showCocoListenRef.current();
|
||||||
|
showCocoListenRef.current = undefined;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, [defaultStartupWindow, changeMode]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
data-tauri-drag-region={isTauri}
|
||||||
|
className={clsx(
|
||||||
|
"size-full m-auto overflow-hidden relative bg-no-repeat bg-cover bg-center bg-white dark:bg-black",
|
||||||
|
[
|
||||||
|
isTransitioned
|
||||||
|
? "bg-chat_bg_light dark:bg-chat_bg_dark"
|
||||||
|
: "bg-search_bg_light dark:bg-search_bg_dark",
|
||||||
|
],
|
||||||
|
{
|
||||||
|
"rounded-xl": !isWin,
|
||||||
|
"border border-[#E6E6E6] dark:border-[#272626]": isLinux,
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
data-tauri-drag-region={isTauri}
|
||||||
|
className={`p-2 pb-0 absolute w-full flex items-center justify-center transition-all duration-500 ${
|
||||||
|
isTransitioned
|
||||||
|
? "top-[calc(100%-90px)] h-[90px] border-t"
|
||||||
|
: "top-0 h-[90px] border-b"
|
||||||
|
} border-[#E6E6E6] dark:border-[#272626]`}
|
||||||
|
>
|
||||||
|
<InputBox
|
||||||
|
isChatMode={isChatMode}
|
||||||
|
inputValue={input}
|
||||||
|
onSend={handleSendMessage}
|
||||||
|
disabled={isTyping}
|
||||||
|
disabledChange={cancelChat}
|
||||||
|
changeMode={changeMode}
|
||||||
|
changeInput={setInput}
|
||||||
|
reconnect={reconnect}
|
||||||
|
isSearchActive={isSearchActive}
|
||||||
|
setIsSearchActive={toggleSearchActive}
|
||||||
|
isDeepThinkActive={isDeepThinkActive}
|
||||||
|
setIsDeepThinkActive={toggleDeepThinkActive}
|
||||||
|
hasFeature={hasFeature}
|
||||||
|
getDataSourcesByServer={getDataSourcesByServer}
|
||||||
|
setupWindowFocusListener={setupWindowFocusListener}
|
||||||
|
checkScreenPermission={checkScreenPermission}
|
||||||
|
requestScreenPermission={requestScreenPermission}
|
||||||
|
getScreenMonitors={getScreenMonitors}
|
||||||
|
getScreenWindows={getScreenWindows}
|
||||||
|
captureMonitorScreenshot={captureMonitorScreenshot}
|
||||||
|
captureWindowScreenshot={captureWindowScreenshot}
|
||||||
|
openFileDialog={openFileDialog}
|
||||||
|
getFileMetadata={getFileMetadata}
|
||||||
|
getFileIcon={getFileIcon}
|
||||||
|
hasModules={hasModules}
|
||||||
|
searchPlaceholder={searchPlaceholder}
|
||||||
|
chatPlaceholder={chatPlaceholder}
|
||||||
|
hideCoco={hideCoco}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
data-tauri-drag-region={isTauri}
|
||||||
|
className={`absolute w-full transition-opacity duration-500 ${
|
||||||
|
isTransitioned ? "opacity-0 pointer-events-none" : "opacity-100"
|
||||||
|
} bottom-0 h-[calc(100%-90px)] `}
|
||||||
|
>
|
||||||
|
<Suspense fallback={<LoadingFallback />}>
|
||||||
|
<Search
|
||||||
|
key="Search"
|
||||||
|
isTauri={isTauri}
|
||||||
|
input={input}
|
||||||
|
isChatMode={isChatMode}
|
||||||
|
changeInput={setInput}
|
||||||
|
querySearch={querySearch}
|
||||||
|
queryDocuments={queryDocuments}
|
||||||
|
hideCoco={hideCoco}
|
||||||
|
openSetting={openSetting}
|
||||||
|
setWindowAlwaysOnTop={setWindowAlwaysOnTop}
|
||||||
|
/>
|
||||||
|
</Suspense>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
data-tauri-drag-region={isTauri}
|
||||||
|
className={`absolute w-full transition-all duration-500 select-auto ${
|
||||||
|
isTransitioned
|
||||||
|
? "top-0 opacity-100 pointer-events-auto"
|
||||||
|
: "-top-[506px] opacity-0 pointer-events-none"
|
||||||
|
} h-[calc(100%-90px)]`}
|
||||||
|
>
|
||||||
|
{isTransitioned && isChatMode ? (
|
||||||
|
<Suspense fallback={<LoadingFallback />}>
|
||||||
|
<ChatAI
|
||||||
|
ref={chatAIRef}
|
||||||
|
key="ChatAI"
|
||||||
|
isTransitioned={isTransitioned}
|
||||||
|
changeInput={setInput}
|
||||||
|
isSearchActive={isSearchActive}
|
||||||
|
isDeepThinkActive={isDeepThinkActive}
|
||||||
|
getFileUrl={getFileUrl}
|
||||||
|
showChatHistory={showChatHistory}
|
||||||
|
/>
|
||||||
|
</Suspense>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<UpdateApp checkUpdate={checkUpdate} relaunchApp={relaunchApp} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default memo(SearchChat);
|
||||||
@@ -1,11 +1,12 @@
|
|||||||
import { ModifierKey, useShortcutsStore } from "@/stores/shortcutsStore";
|
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { formatKey } from "@/utils/keyboardUtils";
|
|
||||||
import SettingsItem from "@/components/Settings/SettingsItem";
|
|
||||||
import { Command } from "lucide-react";
|
import { Command } from "lucide-react";
|
||||||
import { ChangeEvent, useEffect } from "react";
|
import { ChangeEvent, useEffect } from "react";
|
||||||
import { emit } from "@tauri-apps/api/event";
|
import { emit } from "@tauri-apps/api/event";
|
||||||
|
|
||||||
|
import { formatKey } from "@/utils/keyboardUtils";
|
||||||
|
import SettingsItem from "@/components/Settings/SettingsItem";
|
||||||
import { isMac } from "@/utils/platform";
|
import { isMac } from "@/utils/platform";
|
||||||
|
import { ModifierKey, useShortcutsStore } from "@/stores/shortcutsStore";
|
||||||
|
|
||||||
export const modifierKeys: ModifierKey[] = isMac
|
export const modifierKeys: ModifierKey[] = isMac
|
||||||
? ["meta", "ctrl"]
|
? ["meta", "ctrl"]
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ const UpdateApp = ({ checkUpdate, relaunchApp }: UpdateAppProps) => {
|
|||||||
|
|
||||||
state.loading = true;
|
state.loading = true;
|
||||||
|
|
||||||
await updateInfo?.downloadAndInstall((progress) => {
|
await updateInfo?.downloadAndInstall((progress: any) => {
|
||||||
switch (progress.event) {
|
switch (progress.event) {
|
||||||
case "Started":
|
case "Started":
|
||||||
state.total = progress.data.contentLength;
|
state.total = progress.data.contentLength;
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import { useCallback } from "react";
|
import { useCallback } from "react";
|
||||||
import { isTauri } from "@tauri-apps/api/core";
|
|
||||||
|
|
||||||
import type { Chat } from "@/components/Assistant/types";
|
import type { Chat } from "@/components/Assistant/types";
|
||||||
import { close_session_chat, cancel_session_chat, session_chat_history, new_chat, send_message, open_session_chat, chat_history } from "@/commands"
|
import { close_session_chat, cancel_session_chat, session_chat_history, new_chat, send_message, open_session_chat, chat_history } from "@/commands"
|
||||||
|
import { useAppStore } from "@/stores/appStore";
|
||||||
|
import { Get, Post } from "@/api/axiosRequest";
|
||||||
|
|
||||||
export function useChatActions(
|
export function useChatActions(
|
||||||
currentServiceId: string | undefined,
|
currentServiceId: string | undefined,
|
||||||
@@ -19,14 +20,27 @@ export function useChatActions(
|
|||||||
changeInput?: (val: string) => void,
|
changeInput?: (val: string) => void,
|
||||||
websocketSessionId?: string,
|
websocketSessionId?: string,
|
||||||
) {
|
) {
|
||||||
|
const isTauri = useAppStore((state) => state.isTauri);
|
||||||
|
|
||||||
const chatClose = useCallback(async (activeChat?: Chat) => {
|
const chatClose = useCallback(async (activeChat?: Chat) => {
|
||||||
if (!activeChat?._id || !currentServiceId) return;
|
if (!activeChat?._id) return;
|
||||||
try {
|
try {
|
||||||
let response: any = await close_session_chat({
|
let response: any
|
||||||
serverId: currentServiceId,
|
if (isTauri) {
|
||||||
sessionId: activeChat?._id,
|
if (!currentServiceId) return;
|
||||||
});
|
response = await close_session_chat({
|
||||||
response = JSON.parse(response || "");
|
serverId: currentServiceId,
|
||||||
|
sessionId: activeChat?._id,
|
||||||
|
});
|
||||||
|
response = JSON.parse(response || "");
|
||||||
|
} else {
|
||||||
|
const [error, res] = await Post(`/chat/${activeChat?._id}/_close`, {})
|
||||||
|
if (error) {
|
||||||
|
console.error('_close', error);
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response = res
|
||||||
|
}
|
||||||
console.log("_close", response);
|
console.log("_close", response);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("chatClose:", error);
|
console.error("chatClose:", error);
|
||||||
@@ -35,13 +49,24 @@ export function useChatActions(
|
|||||||
|
|
||||||
const cancelChat = useCallback(async (activeChat?: Chat) => {
|
const cancelChat = useCallback(async (activeChat?: Chat) => {
|
||||||
setCurChatEnd(true);
|
setCurChatEnd(true);
|
||||||
if (!activeChat?._id || !currentServiceId) return;
|
if (!activeChat?._id) return;
|
||||||
try {
|
try {
|
||||||
let response: any = await cancel_session_chat({
|
let response: any
|
||||||
serverId: currentServiceId,
|
if (isTauri) {
|
||||||
sessionId: activeChat?._id,
|
if (!currentServiceId) return;
|
||||||
});
|
response = await cancel_session_chat({
|
||||||
response = JSON.parse(response || "");
|
serverId: currentServiceId,
|
||||||
|
sessionId: activeChat?._id,
|
||||||
|
});
|
||||||
|
response = JSON.parse(response || "");
|
||||||
|
} else {
|
||||||
|
const [error, res] = await Post(`/chat/${activeChat?._id}/_cancel`, {})
|
||||||
|
if (error) {
|
||||||
|
console.error('_cancel', error);
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response = res
|
||||||
|
}
|
||||||
console.log("_cancel", response);
|
console.log("_cancel", response);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("cancelChat:", error);
|
console.error("cancelChat:", error);
|
||||||
@@ -52,15 +77,29 @@ export function useChatActions(
|
|||||||
chat: Chat,
|
chat: Chat,
|
||||||
callback?: (chat: Chat) => void
|
callback?: (chat: Chat) => void
|
||||||
) => {
|
) => {
|
||||||
if (!chat?._id || !currentServiceId) return;
|
if (!chat?._id) return;
|
||||||
try {
|
try {
|
||||||
let response: any = await session_chat_history({
|
let response: any
|
||||||
serverId: currentServiceId,
|
if (isTauri) {
|
||||||
sessionId: chat?._id,
|
if (!currentServiceId) return;
|
||||||
from: 0,
|
response = await session_chat_history({
|
||||||
size: 20,
|
serverId: currentServiceId,
|
||||||
});
|
sessionId: chat?._id,
|
||||||
response = JSON.parse(response || "");
|
from: 0,
|
||||||
|
size: 20,
|
||||||
|
});
|
||||||
|
response = JSON.parse(response || "");
|
||||||
|
} else {
|
||||||
|
const [error, res] = await Get(`/chat/${chat?._id}/_history`, {
|
||||||
|
from: 0,
|
||||||
|
size: 20,
|
||||||
|
})
|
||||||
|
if (error) {
|
||||||
|
console.error('_cancel', error);
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response = res
|
||||||
|
}
|
||||||
const hits = response?.hits?.hits || [];
|
const hits = response?.hits?.hits || [];
|
||||||
const updatedChat: Chat = {
|
const updatedChat: Chat = {
|
||||||
...chat,
|
...chat,
|
||||||
@@ -76,29 +115,50 @@ export function useChatActions(
|
|||||||
|
|
||||||
const createNewChat = useCallback(
|
const createNewChat = useCallback(
|
||||||
async (value: string = "", activeChat?: Chat, id?: string) => {
|
async (value: string = "", activeChat?: Chat, id?: string) => {
|
||||||
setTimedoutShow(false);
|
|
||||||
setErrorShow(false);
|
|
||||||
chatClose(activeChat);
|
|
||||||
clearAllChunkData();
|
|
||||||
setQuestion(value);
|
|
||||||
if (!currentServiceId) return;
|
|
||||||
try {
|
try {
|
||||||
if (!(websocketSessionId || id)){
|
setTimedoutShow(false);
|
||||||
|
setErrorShow(false);
|
||||||
|
await chatClose(activeChat);
|
||||||
|
clearAllChunkData();
|
||||||
|
setQuestion(value);
|
||||||
|
if (!(websocketSessionId || id)) {
|
||||||
setErrorShow(true);
|
setErrorShow(true);
|
||||||
console.error("websocketSessionId", websocketSessionId, id);
|
console.error("websocketSessionId", websocketSessionId, id);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
console.log("sourceDataIds", sourceDataIds, websocketSessionId, id);
|
console.log("sourceDataIds", sourceDataIds, websocketSessionId, id);
|
||||||
let response: any = await new_chat({
|
let response: any
|
||||||
serverId: currentServiceId,
|
if (isTauri) {
|
||||||
websocketId: websocketSessionId || id,
|
if (!currentServiceId) return;
|
||||||
message: value,
|
response = await new_chat({
|
||||||
queryParams: {
|
serverId: currentServiceId,
|
||||||
|
websocketId: websocketSessionId || id,
|
||||||
|
message: value,
|
||||||
|
queryParams: {
|
||||||
|
search: isSearchActive,
|
||||||
|
deep_thinking: isDeepThinkActive,
|
||||||
|
datasource: sourceDataIds?.join(",") || "",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
} else {
|
||||||
|
console.log('websocketSessionId', websocketSessionId, id)
|
||||||
|
const [error, res] = await Post('/chat/_new', {
|
||||||
|
message: value,
|
||||||
|
}, {
|
||||||
search: isSearchActive,
|
search: isSearchActive,
|
||||||
deep_thinking: isDeepThinkActive,
|
deep_thinking: isDeepThinkActive,
|
||||||
datasource: sourceDataIds?.join(",") || "",
|
datasource: sourceDataIds?.join(",") || "",
|
||||||
},
|
}, {
|
||||||
});
|
"WEBSOCKET-SESSION-ID": websocketSessionId || id,
|
||||||
|
})
|
||||||
|
if (error) {
|
||||||
|
setErrorShow(true);
|
||||||
|
console.error('_new', error);
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response = res
|
||||||
|
}
|
||||||
console.log("_new", response);
|
console.log("_new", response);
|
||||||
const newChat: Chat = response;
|
const newChat: Chat = response;
|
||||||
curIdRef.current = response?.payload?.id;
|
curIdRef.current = response?.payload?.id;
|
||||||
@@ -124,27 +184,49 @@ export function useChatActions(
|
|||||||
|
|
||||||
const sendMessage = useCallback(
|
const sendMessage = useCallback(
|
||||||
async (content: string, newChat: Chat, id?: string) => {
|
async (content: string, newChat: Chat, id?: string) => {
|
||||||
if (!newChat?._id || !currentServiceId || !content) return;
|
if (!newChat?._id || !content) return;
|
||||||
|
|
||||||
clearAllChunkData();
|
clearAllChunkData();
|
||||||
try {
|
try {
|
||||||
if (!(websocketSessionId || id)){
|
if (!(websocketSessionId || id)) {
|
||||||
setErrorShow(true);
|
setErrorShow(true);
|
||||||
console.error("websocketSessionId", websocketSessionId, id);
|
console.error("websocketSessionId", websocketSessionId, id);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let response: any = await send_message({
|
let response: any
|
||||||
serverId: currentServiceId,
|
if (isTauri) {
|
||||||
websocketId: websocketSessionId || id,
|
if (!currentServiceId) return;
|
||||||
sessionId: newChat?._id,
|
response = await send_message({
|
||||||
queryParams: {
|
serverId: currentServiceId,
|
||||||
|
websocketId: websocketSessionId || id,
|
||||||
|
sessionId: newChat?._id,
|
||||||
|
queryParams: {
|
||||||
|
search: isSearchActive,
|
||||||
|
deep_thinking: isDeepThinkActive,
|
||||||
|
datasource: sourceDataIds?.join(",") || "",
|
||||||
|
},
|
||||||
|
message: content,
|
||||||
|
});
|
||||||
|
response = JSON.parse(response || "");
|
||||||
|
} else {
|
||||||
|
console.log('websocketSessionId', websocketSessionId, id)
|
||||||
|
const [error, res] = await Post(`/chat/${newChat?._id}/_send`, {
|
||||||
|
message: content
|
||||||
|
}, {
|
||||||
search: isSearchActive,
|
search: isSearchActive,
|
||||||
deep_thinking: isDeepThinkActive,
|
deep_thinking: isDeepThinkActive,
|
||||||
datasource: sourceDataIds?.join(",") || "",
|
datasource: sourceDataIds?.join(",") || "",
|
||||||
},
|
}, {
|
||||||
message: content,
|
"WEBSOCKET-SESSION-ID": websocketSessionId || id,
|
||||||
});
|
})
|
||||||
response = JSON.parse(response || "");
|
|
||||||
|
if (error) {
|
||||||
|
setErrorShow(true);
|
||||||
|
console.error('_cancel', error);
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response = res
|
||||||
|
}
|
||||||
console.log("_send", response);
|
console.log("_send", response);
|
||||||
curIdRef.current = response[0]?._id;
|
curIdRef.current = response[0]?._id;
|
||||||
|
|
||||||
@@ -178,13 +260,25 @@ export function useChatActions(
|
|||||||
);
|
);
|
||||||
|
|
||||||
const openSessionChat = useCallback(async (chat: Chat) => {
|
const openSessionChat = useCallback(async (chat: Chat) => {
|
||||||
if (!chat?._id || !currentServiceId) return;
|
if (!chat?._id) return;
|
||||||
try {
|
try {
|
||||||
let response: any = await open_session_chat({
|
let response: any
|
||||||
serverId: currentServiceId,
|
if (isTauri) {
|
||||||
sessionId: chat?._id,
|
if (!currentServiceId) return;
|
||||||
});
|
response = await open_session_chat({
|
||||||
response = JSON.parse(response || "");
|
serverId: currentServiceId,
|
||||||
|
sessionId: chat?._id,
|
||||||
|
});
|
||||||
|
response = JSON.parse(response || "");
|
||||||
|
} else {
|
||||||
|
const [error, res] = await Post(`/chat/${chat?._id}/_open`, {})
|
||||||
|
if (error) {
|
||||||
|
console.error('_open', error);
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
response = res
|
||||||
|
}
|
||||||
|
|
||||||
console.log("_open", response);
|
console.log("_open", response);
|
||||||
return response;
|
return response;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -194,14 +288,27 @@ export function useChatActions(
|
|||||||
}, [currentServiceId]);
|
}, [currentServiceId]);
|
||||||
|
|
||||||
const getChatHistory = useCallback(async () => {
|
const getChatHistory = useCallback(async () => {
|
||||||
if (!currentServiceId) return [];
|
|
||||||
try {
|
try {
|
||||||
let response: any = await chat_history({
|
let response: any
|
||||||
serverId: currentServiceId,
|
if (isTauri) {
|
||||||
from: 0,
|
if (!currentServiceId) return [];
|
||||||
size: 20,
|
response = await chat_history({
|
||||||
});
|
serverId: currentServiceId,
|
||||||
response = JSON.parse(response || "");
|
from: 0,
|
||||||
|
size: 20,
|
||||||
|
});
|
||||||
|
response = JSON.parse(response || "");
|
||||||
|
} else {
|
||||||
|
const [error, res] = await Get(`/chat/_history`, {
|
||||||
|
from: 0,
|
||||||
|
size: 20,
|
||||||
|
})
|
||||||
|
if (error) {
|
||||||
|
console.error('_history', error);
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
response = res
|
||||||
|
}
|
||||||
console.log("_history", response);
|
console.log("_history", response);
|
||||||
const hits = response?.hits?.hits || [];
|
const hits = response?.hits?.hits || [];
|
||||||
return hits;
|
return hits;
|
||||||
@@ -212,7 +319,7 @@ export function useChatActions(
|
|||||||
}, [currentServiceId]);
|
}, [currentServiceId]);
|
||||||
|
|
||||||
const createChatWindow = useCallback(async (createWin: any) => {
|
const createChatWindow = useCallback(async (createWin: any) => {
|
||||||
if (isTauri()) {
|
if (isTauri) {
|
||||||
createWin && createWin({
|
createWin && createWin({
|
||||||
label: "chat",
|
label: "chat",
|
||||||
title: "Coco Chat",
|
title: "Coco Chat",
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import { isTauri } from "@tauri-apps/api/core";
|
|
||||||
import { listen } from "@tauri-apps/api/event";
|
|
||||||
|
|
||||||
import { hide_coco } from "@/commands"
|
import platformAdapter from "@/utils/platformAdapter";
|
||||||
|
|
||||||
const useEscape = () => {
|
const useEscape = () => {
|
||||||
const handleEscape = async (event: KeyboardEvent) => {
|
const handleEscape = async (event: KeyboardEvent) => {
|
||||||
@@ -12,16 +10,14 @@ const useEscape = () => {
|
|||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
// Hide the Tauri app window when 'Esc' is pressed
|
// Hide the Tauri app window when 'Esc' is pressed
|
||||||
await hide_coco()
|
await platformAdapter.invokeBackend("hide_coco");
|
||||||
|
|
||||||
console.log("App window hidden successfully.");
|
console.log("App window hidden successfully.");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isTauri()) return;
|
const unlisten = platformAdapter.listenEvent("tauri://focus", () => {
|
||||||
|
|
||||||
const unlisten = listen("tauri://focus", () => {
|
|
||||||
// Add event listener for keydown
|
// Add event listener for keydown
|
||||||
window.addEventListener("keydown", handleEscape);
|
window.addEventListener("keydown", handleEscape);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,19 +1,26 @@
|
|||||||
import { useConnectStore } from "@/stores/connectStore";
|
import { useConnectStore } from "@/stores/connectStore";
|
||||||
|
import { useAppStore } from "@/stores/appStore";
|
||||||
|
|
||||||
export function useFindConnectorIcon(item: any) {
|
export function useFindConnectorIcon(item: any) {
|
||||||
const connector_data = useConnectStore((state) => state.connector_data);
|
const connector_data = useConnectStore((state) => state.connector_data);
|
||||||
const datasourceData = useConnectStore((state) => state.datasourceData);
|
const datasourceData = useConnectStore((state) => state.datasourceData);
|
||||||
const currentService = useConnectStore((state) => state.currentService);
|
const currentService = useConnectStore((state) => state.currentService);
|
||||||
|
const isTauri = useAppStore((state) => state.isTauri);
|
||||||
|
|
||||||
|
let currentServiceId = currentService?.id;
|
||||||
|
if (!isTauri) {
|
||||||
|
currentServiceId = "web"
|
||||||
|
}
|
||||||
|
|
||||||
const id = item?.source?.id || "";
|
const id = item?.source?.id || "";
|
||||||
|
|
||||||
const result_source = datasourceData[currentService?.id]?.find(
|
const result_source = datasourceData[currentServiceId]?.find(
|
||||||
(data: any) => data.id === id
|
(data: any) => data.id === id
|
||||||
);
|
);
|
||||||
|
|
||||||
const connector_id = result_source?.connector?.id;
|
const connector_id = result_source?.connector?.id;
|
||||||
|
|
||||||
const result_connector = connector_data[currentService?.id]?.find(
|
const result_connector = connector_data[currentServiceId]?.find(
|
||||||
(data: any) => data.id === connector_id
|
(data: any) => data.id === connector_id
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -76,6 +76,7 @@ export function useMessageHandler(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
setCurChatEnd(true);
|
||||||
console.error("parse error:", error);
|
console.error("parse error:", error);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,8 +1,16 @@
|
|||||||
import { useState, useEffect, useCallback, useRef } from "react";
|
import { useState, useEffect, useCallback, useRef } from "react";
|
||||||
import { listen } from "@tauri-apps/api/event";
|
import { useWebSocket as useWebSocketAHook } from "ahooks";
|
||||||
|
|
||||||
import { IServer } from "@/stores/appStore";
|
import { useAppStore, IServer } from "@/stores/appStore";
|
||||||
import { connect_to_server, disconnect } from "@/commands"
|
import { connect_to_server, disconnect as disconnectCommand } from "@/commands"
|
||||||
|
import platformAdapter from "@/utils/platformAdapter";
|
||||||
|
|
||||||
|
enum ReadyState {
|
||||||
|
Connecting = 0,
|
||||||
|
Open = 1,
|
||||||
|
Closing = 2,
|
||||||
|
Closed = 3,
|
||||||
|
}
|
||||||
|
|
||||||
interface WebSocketProps {
|
interface WebSocketProps {
|
||||||
clientId: string;
|
clientId: string;
|
||||||
@@ -21,41 +29,119 @@ export default function useWebSocket({
|
|||||||
dealMsgRef,
|
dealMsgRef,
|
||||||
onWebsocketSessionId,
|
onWebsocketSessionId,
|
||||||
}: WebSocketProps) {
|
}: WebSocketProps) {
|
||||||
|
const isTauri = useAppStore((state) => state.isTauri);
|
||||||
|
const endpoint_websocket = useAppStore((state) => state.endpoint_websocket);
|
||||||
|
|
||||||
|
const websocketIdRef = useRef<string>("");
|
||||||
|
const messageQueue = useRef<string[]>([]);
|
||||||
|
const processingRef = useRef(false);
|
||||||
|
|
||||||
|
const { readyState, connect, disconnect } = useWebSocketAHook(
|
||||||
|
// "wss://coco.infini.cloud/ws",
|
||||||
|
// "ws://localhost:9000/ws",
|
||||||
|
isTauri ? "" : endpoint_websocket,
|
||||||
|
{
|
||||||
|
manual: true,
|
||||||
|
reconnectLimit: 3,
|
||||||
|
reconnectInterval: 3000,
|
||||||
|
onMessage: (event) => {
|
||||||
|
const msg = event.data as string;
|
||||||
|
messageQueue.current.push(msg);
|
||||||
|
processQueue();
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isTauri) {
|
||||||
|
connect();
|
||||||
|
}
|
||||||
|
}, [isTauri, connect]);
|
||||||
|
|
||||||
|
const processMessage = useCallback(
|
||||||
|
(msg: string) => {
|
||||||
|
try {
|
||||||
|
if (msg.includes("websocket-session-id")) {
|
||||||
|
const sessionId = msg.split(":")[1].trim();
|
||||||
|
websocketIdRef.current = sessionId;
|
||||||
|
setConnected(true);
|
||||||
|
onWebsocketSessionId?.(sessionId);
|
||||||
|
} else {
|
||||||
|
dealMsgRef.current?.(msg);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("处理消息出错:", error, msg);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[onWebsocketSessionId]
|
||||||
|
);
|
||||||
|
|
||||||
|
const processQueue = useCallback(() => {
|
||||||
|
if (processingRef.current || messageQueue.current.length === 0) return;
|
||||||
|
|
||||||
|
processingRef.current = true;
|
||||||
|
while (messageQueue.current.length > 0) {
|
||||||
|
const msg = messageQueue.current.shift();
|
||||||
|
if (msg) {
|
||||||
|
console.log("处理消息:", msg.substring(0, 100));
|
||||||
|
processMessage(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
processingRef.current = false;
|
||||||
|
}, [processMessage]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (readyState !== ReadyState.Open) {
|
||||||
|
setConnected(false);
|
||||||
|
}
|
||||||
|
}, [readyState]);
|
||||||
|
|
||||||
const [errorShow, setErrorShow] = useState(false);
|
const [errorShow, setErrorShow] = useState(false);
|
||||||
|
|
||||||
// 1. WebSocket connects when loading or switching services
|
// 1. WebSocket connects when loading or switching services
|
||||||
// src/components/Assistant/ChatHeader.tsx
|
// src/components/Assistant/ChatHeader.tsx
|
||||||
// 2. If not connected or disconnected, input box has a connect button, clicking it will connect to WebSocket
|
// 2. If not connected or disconnected, input box has a connect button, clicking it will connect to WebSocket
|
||||||
// src/components/Search/InputBox.tsx
|
// src/components/Search/InputBox.tsx
|
||||||
const reconnect = useCallback(async (server?: IServer) => {
|
const reconnect = useCallback(
|
||||||
const targetServer = server || currentService;
|
async (server?: IServer) => {
|
||||||
console.log("reconnect_targetServer", targetServer?.id);
|
if (isTauri) {
|
||||||
if (!targetServer?.id) return;
|
const targetServer = server || currentService;
|
||||||
try {
|
if (!targetServer?.id) return;
|
||||||
console.log("reconnect", targetServer.id, clientId);
|
try {
|
||||||
await connect_to_server(targetServer.id, clientId);
|
// console.log("reconnect", targetServer.id);
|
||||||
} catch (error) {
|
await connect_to_server(targetServer.id, clientId);
|
||||||
setConnected(false);
|
} catch (error) {
|
||||||
console.error("Failed to connect:", error);
|
setConnected(false);
|
||||||
}
|
console.error("Failed to connect:", error);
|
||||||
}, [currentService]);
|
}
|
||||||
|
} else {
|
||||||
|
connect();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[currentService]
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
const disconnectWS = async () => {
|
const disconnectWS = async () => {
|
||||||
if (!connected) return;
|
if (!connected) return;
|
||||||
try {
|
if (isTauri) {
|
||||||
console.log("disconnect");
|
try {
|
||||||
await disconnect(clientId);
|
console.log("disconnect");
|
||||||
setConnected(false);
|
await disconnectCommand(clientId);
|
||||||
} catch (error) {
|
setConnected(false);
|
||||||
console.error("Failed to disconnect:", error);
|
} catch (error) {
|
||||||
|
console.error("Failed to disconnect:", error);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
disconnect();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateDealMsg = useCallback((newDealMsg: (msg: string) => void) => {
|
const updateDealMsg = useCallback(
|
||||||
dealMsgRef.current = newDealMsg;
|
(newDealMsg: (msg: string) => void) => {
|
||||||
}, [dealMsgRef]);
|
dealMsgRef.current = newDealMsg;
|
||||||
|
},
|
||||||
const websocketIdRef = useRef<string>('')
|
[dealMsgRef]
|
||||||
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!currentService?.id) return;
|
if (!currentService?.id) return;
|
||||||
@@ -64,7 +150,9 @@ export default function useWebSocket({
|
|||||||
let unlisten_message = null;
|
let unlisten_message = null;
|
||||||
|
|
||||||
setErrorShow(false);
|
setErrorShow(false);
|
||||||
unlisten_error = listen(`ws-error-${clientId}`, (event) => {
|
|
||||||
|
if (!isTauri) return;
|
||||||
|
unlisten_error = platformAdapter.listenEvent(`ws-error-${clientId}`, (event) => {
|
||||||
// {
|
// {
|
||||||
// "error": {
|
// "error": {
|
||||||
// "reason": "invalid login"
|
// "reason": "invalid login"
|
||||||
@@ -76,7 +164,7 @@ export default function useWebSocket({
|
|||||||
setErrorShow(true);
|
setErrorShow(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
unlisten_message = listen(`ws-message-${clientId}`, (event) => {
|
unlisten_message = platformAdapter.listenEvent(`ws-message-${clientId}`, (event) => {
|
||||||
const msg = event.payload as string;
|
const msg = event.payload as string;
|
||||||
console.log(`ws-message-${clientId}`, msg);
|
console.log(`ws-message-${clientId}`, msg);
|
||||||
if (msg.includes("websocket-session-id")) {
|
if (msg.includes("websocket-session-id")) {
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
import { useEffect, useCallback } from "react";
|
import { useState, useEffect, useCallback } from "react";
|
||||||
import { getAllWindows, getCurrentWindow } from "@tauri-apps/api/window";
|
|
||||||
import { WebviewWindow } from "@tauri-apps/api/webviewWindow";
|
import platformAdapter from "@/utils/platformAdapter";
|
||||||
import { listen } from "@tauri-apps/api/event";
|
|
||||||
import { isTauri } from "@tauri-apps/api/core";
|
|
||||||
|
|
||||||
const defaultWindowConfig = {
|
const defaultWindowConfig = {
|
||||||
label: "",
|
label: "",
|
||||||
@@ -23,8 +21,20 @@ const defaultWindowConfig = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const useWindows = () => {
|
export const useWindows = () => {
|
||||||
if (!isTauri()) return {}
|
const [appWindow, setAppWindow] = useState<any>(null);
|
||||||
const appWindow = getCurrentWindow();
|
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchWindow = async () => {
|
||||||
|
try {
|
||||||
|
const window = await platformAdapter.getCurrentWindow();
|
||||||
|
setAppWindow(window);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to get current window:", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fetchWindow();
|
||||||
|
}, []);
|
||||||
|
|
||||||
const createWin = useCallback(async (options: any) => {
|
const createWin = useCallback(async (options: any) => {
|
||||||
const args = { ...defaultWindowConfig, ...options };
|
const args = { ...defaultWindowConfig, ...options };
|
||||||
@@ -39,23 +49,26 @@ export const useWindows = () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const win = new WebviewWindow(args.label, args);
|
const win = await platformAdapter.createWebviewWindow(args.label, args);
|
||||||
|
|
||||||
win.once("tauri://created", async () => {
|
if(win) {
|
||||||
console.log("tauri://created");
|
win.once("tauri://created", async () => {
|
||||||
// if (args.label.includes("main")) {
|
console.log("tauri://created");
|
||||||
//
|
// if (args.label.includes("main")) {
|
||||||
// }
|
//
|
||||||
|
// }
|
||||||
|
|
||||||
if (args.maximized && args.resizable) {
|
if (args.maximized && args.resizable) {
|
||||||
console.log("is-maximized");
|
console.log("is-maximized");
|
||||||
await win.maximize();
|
await win.maximize();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
win.once("tauri://error", (error: any) => {
|
||||||
|
console.error("error:", error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
win.once("tauri://error", (error) => {
|
|
||||||
console.error("error:", error);
|
|
||||||
});
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const closeWin = useCallback(async (label: string) => {
|
const closeWin = useCallback(async (label: string) => {
|
||||||
@@ -75,24 +88,24 @@ export const useWindows = () => {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const getWin = useCallback(async (label: string) => {
|
const getWin = useCallback(async (label: string) => {
|
||||||
return WebviewWindow.getByLabel(label);
|
return platformAdapter.getWindowByLabel(label);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const getAllWin = useCallback(async () => {
|
const getAllWin = useCallback(async () => {
|
||||||
return getAllWindows();
|
return platformAdapter.getAllWindows();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const listenEvents = useCallback(() => {
|
const listenEvents = useCallback(() => {
|
||||||
let unlistenHandlers: { (): void; (): void; (): void; (): void; }[] = [];
|
let unlistenHandlers: { (): void; (): void; (): void; (): void; }[] = [];
|
||||||
|
|
||||||
const setupListeners = async () => {
|
const setupListeners = async () => {
|
||||||
const winCreateHandler = await listen("win-create", (event) => {
|
const winCreateHandler = await platformAdapter.listenWindowEvent("win-create", (event) => {
|
||||||
console.log(event);
|
console.log(event);
|
||||||
createWin(event.payload);
|
createWin(event.payload);
|
||||||
});
|
});
|
||||||
unlistenHandlers.push(winCreateHandler);
|
unlistenHandlers.push(winCreateHandler);
|
||||||
|
|
||||||
const winShowHandler = await listen("win-show", async () => {
|
const winShowHandler = await platformAdapter.listenWindowEvent("win-show", async () => {
|
||||||
if (!appWindow || !appWindow.label.includes("main")) return;
|
if (!appWindow || !appWindow.label.includes("main")) return;
|
||||||
await appWindow.show();
|
await appWindow.show();
|
||||||
await appWindow.unminimize();
|
await appWindow.unminimize();
|
||||||
@@ -100,13 +113,13 @@ export const useWindows = () => {
|
|||||||
});
|
});
|
||||||
unlistenHandlers.push(winShowHandler);
|
unlistenHandlers.push(winShowHandler);
|
||||||
|
|
||||||
const winHideHandler = await listen("win-hide", async () => {
|
const winHideHandler = await platformAdapter.listenWindowEvent("win-hide", async () => {
|
||||||
if (!appWindow || !appWindow.label.includes("main")) return;
|
if (!appWindow || !appWindow.label.includes("main")) return;
|
||||||
await appWindow.hide();
|
await appWindow.hide();
|
||||||
});
|
});
|
||||||
unlistenHandlers.push(winHideHandler);
|
unlistenHandlers.push(winHideHandler);
|
||||||
|
|
||||||
const winCloseHandler = await listen("win-close", async () => {
|
const winCloseHandler = await platformAdapter.listenWindowEvent("win-close", async () => {
|
||||||
await appWindow.close();
|
await appWindow.close();
|
||||||
});
|
});
|
||||||
unlistenHandlers.push(winCloseHandler);
|
unlistenHandlers.push(winCloseHandler);
|
||||||
|
|||||||
@@ -147,12 +147,13 @@
|
|||||||
"version": "{{version}}",
|
"version": "{{version}}",
|
||||||
"updateAvailable": "Update available",
|
"updateAvailable": "Update available",
|
||||||
"select": "Select",
|
"select": "Select",
|
||||||
"open": "Open"
|
"open": "Open",
|
||||||
|
"powered": "Powered by Coco AI"
|
||||||
},
|
},
|
||||||
"input": {
|
"input": {
|
||||||
"searchPlaceholder": "Search whatever you want ...",
|
"searchPlaceholder": "Search whatever you want ...",
|
||||||
"connectionError": "Unable to connect to the server",
|
"connectionError": "Unable to connect to the server",
|
||||||
"reconnect": "Reconnect",
|
"reconnect": "Click here to reconnect.",
|
||||||
"connecting": "Connecting",
|
"connecting": "Connecting",
|
||||||
"deepThink": "Deep Think",
|
"deepThink": "Deep Think",
|
||||||
"search": "Search",
|
"search": "Search",
|
||||||
|
|||||||
@@ -147,12 +147,13 @@
|
|||||||
"version": "{{version}}",
|
"version": "{{version}}",
|
||||||
"updateAvailable": "有可用更新",
|
"updateAvailable": "有可用更新",
|
||||||
"select": "选择",
|
"select": "选择",
|
||||||
"open": "打开"
|
"open": "打开",
|
||||||
|
"powered": "由 Coco AI 提供支持"
|
||||||
},
|
},
|
||||||
"input": {
|
"input": {
|
||||||
"searchPlaceholder": "搜索任何内容...",
|
"searchPlaceholder": "搜索任何内容...",
|
||||||
"connectionError": "无法连接到服务器",
|
"connectionError": "无法连接到服务器",
|
||||||
"reconnect": "重新连接",
|
"reconnect": "点此重连",
|
||||||
"connecting": "连接中",
|
"connecting": "连接中",
|
||||||
"deepThink": "深度思考",
|
"deepThink": "深度思考",
|
||||||
"search": "联网搜索",
|
"search": "联网搜索",
|
||||||
|
|||||||
@@ -1,13 +1,17 @@
|
|||||||
import { useCallback, useEffect } from "react";
|
import { useCallback, useEffect } from "react";
|
||||||
|
import { useKeyPress } from "ahooks";
|
||||||
|
|
||||||
import SearchChat from "@/pages/web/SearchChat";
|
import SearchChat from "@/components/SearchChat";
|
||||||
import platformAdapter from "@/utils/platformAdapter";
|
import platformAdapter from "@/utils/platformAdapter";
|
||||||
import { useShortcutsStore } from "@/stores/shortcutsStore";
|
import { useShortcutsStore } from "@/stores/shortcutsStore";
|
||||||
import { useStartupStore } from "@/stores/startupStore";
|
import { useStartupStore } from "@/stores/startupStore";
|
||||||
import { useKeyPress } from "ahooks";
|
|
||||||
import { modifierKeys } from "@/components/Settings/Advanced/components/Shortcuts";
|
import { modifierKeys } from "@/components/Settings/Advanced/components/Shortcuts";
|
||||||
|
import { useAppStore } from "@/stores/appStore";
|
||||||
|
|
||||||
function MainApp() {
|
function MainApp() {
|
||||||
|
const setIsTauri = useAppStore((state) => state.setIsTauri);
|
||||||
|
setIsTauri(true);
|
||||||
|
|
||||||
const querySearch = useCallback(async (input: string) => {
|
const querySearch = useCallback(async (input: string) => {
|
||||||
try {
|
try {
|
||||||
const response: any = await platformAdapter.invokeBackend(
|
const response: any = await platformAdapter.invokeBackend(
|
||||||
@@ -44,6 +48,11 @@ function MainApp() {
|
|||||||
},
|
},
|
||||||
[]
|
[]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const hideCoco = useCallback(() => {
|
||||||
|
return platformAdapter.hideWindow();
|
||||||
|
}, []);
|
||||||
|
|
||||||
const modifierKey = useShortcutsStore((state) => {
|
const modifierKey = useShortcutsStore((state) => {
|
||||||
return state.modifierKey;
|
return state.modifierKey;
|
||||||
});
|
});
|
||||||
@@ -129,7 +138,13 @@ function MainApp() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SearchChat querySearch={querySearch} queryDocuments={queryDocuments} />
|
<SearchChat
|
||||||
|
isTauri={true}
|
||||||
|
querySearch={querySearch}
|
||||||
|
queryDocuments={queryDocuments}
|
||||||
|
hideCoco={hideCoco}
|
||||||
|
hasModules={["search", "chat"]}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import SettingsPanel from "@/components/Settings/SettingsPanel";
|
|||||||
import GeneralSettings from "@/components/Settings/GeneralSettings";
|
import GeneralSettings from "@/components/Settings/GeneralSettings";
|
||||||
import AboutView from "@/components/Settings/AboutView";
|
import AboutView from "@/components/Settings/AboutView";
|
||||||
import Cloud from "@/components/Cloud/Cloud.tsx";
|
import Cloud from "@/components/Cloud/Cloud.tsx";
|
||||||
import Footer from "@/components/Footer";
|
import Footer from "@/components/Common/UI/SettingsFooter";
|
||||||
import { useTray } from "@/hooks/useTray";
|
import { useTray } from "@/hooks/useTray";
|
||||||
import Advanced from "@/components/Settings/Advanced";
|
import Advanced from "@/components/Settings/Advanced";
|
||||||
|
|
||||||
|
|||||||
100
src/pages/web/README.md
Normal file
100
src/pages/web/README.md
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
# SearchChat Web Component API
|
||||||
|
|
||||||
|
## Props
|
||||||
|
|
||||||
|
### `serverUrl`
|
||||||
|
- **类型**: `string`
|
||||||
|
- **可选**: 是
|
||||||
|
- **默认值**: `""`
|
||||||
|
- **描述**: 设置服务器地址
|
||||||
|
|
||||||
|
### `headers`
|
||||||
|
- **类型**: `Record<string, unknown>`
|
||||||
|
- **可选**: 是
|
||||||
|
- **默认值**: `{}`
|
||||||
|
- **描述**: 请求头配置
|
||||||
|
|
||||||
|
### `width`
|
||||||
|
- **类型**: `number`
|
||||||
|
- **可选**: 是
|
||||||
|
- **默认值**: `680`
|
||||||
|
- **描述**: 组件容器的宽度,单位为像素
|
||||||
|
|
||||||
|
### `height`
|
||||||
|
- **类型**: `number`
|
||||||
|
- **可选**: 是
|
||||||
|
- **默认值**: `590`
|
||||||
|
- **描述**: 组件容器的高度,单位为像素
|
||||||
|
|
||||||
|
### `hasModules`
|
||||||
|
- **类型**: `string[]`
|
||||||
|
- **可选**: 是
|
||||||
|
- **默认值**: `['search', 'chat']`
|
||||||
|
- **描述**: 启用的功能模块列表,目前支持 'search' 和 'chat' 模块
|
||||||
|
|
||||||
|
### `hasFeature`
|
||||||
|
- **类型**: `string[]`
|
||||||
|
- **可选**: 是
|
||||||
|
- **默认值**: `['think', 'search', 'think_active', 'search_active']`
|
||||||
|
- **描述**: 启用的特性列表,支持 'think'、'search'、'think_active'、'search_active' 特性。其中 'think_active' 表示默认开启深度思考,'search_active' 表示默认开启搜索
|
||||||
|
|
||||||
|
### `hideCoco`
|
||||||
|
- **类型**: `() => void`
|
||||||
|
- **可选**: 是
|
||||||
|
- **默认值**: `() => {}`
|
||||||
|
- **描述**: 隐藏搜索窗口的回调函数
|
||||||
|
|
||||||
|
### `theme`
|
||||||
|
- **类型**: `"auto" | "light" | "dark"`
|
||||||
|
- **可选**: 是
|
||||||
|
- **默认值**: `"dark"`
|
||||||
|
- **描述**: 主题设置,支持自动(跟随系统)、亮色和暗色三种模式
|
||||||
|
|
||||||
|
### `searchPlaceholder`
|
||||||
|
- **类型**: `string`
|
||||||
|
- **可选**: 是
|
||||||
|
- **默认值**: `""`
|
||||||
|
- **描述**: 搜索框的占位文本
|
||||||
|
|
||||||
|
### `chatPlaceholder`
|
||||||
|
- **类型**: `string`
|
||||||
|
- **可选**: 是
|
||||||
|
- **默认值**: `""`
|
||||||
|
- **描述**: 聊天输入框的占位文本
|
||||||
|
|
||||||
|
### `showChatHistory`
|
||||||
|
- **类型**: `boolean`
|
||||||
|
- **可选**: 是
|
||||||
|
- **默认值**: `true`
|
||||||
|
- **描述**: 是否显示聊天历史记录
|
||||||
|
|
||||||
|
### `setIsPinned`
|
||||||
|
- **类型**: `(value: boolean) => void`
|
||||||
|
- **可选**: 是
|
||||||
|
- **默认值**: `undefined`
|
||||||
|
- **描述**: 设置窗口置顶状态的回调函数
|
||||||
|
|
||||||
|
## 使用示例
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import SearchChat from 'search-chat';
|
||||||
|
|
||||||
|
function App() {
|
||||||
|
return (
|
||||||
|
<SearchChat
|
||||||
|
serverUrl=""
|
||||||
|
headers={{}}
|
||||||
|
width={680}
|
||||||
|
height={590}
|
||||||
|
hasModules={['search', 'chat']}
|
||||||
|
hasFeature={['think', 'search', 'think_active', 'search_active']}
|
||||||
|
hideCoco={() => console.log('hide')}
|
||||||
|
theme="dark"
|
||||||
|
searchPlaceholder=""
|
||||||
|
chatPlaceholder=""
|
||||||
|
showChatHistory={true}
|
||||||
|
setIsPinned={(isPinned) => console.log('isPinned:', isPinned)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
@@ -1,311 +0,0 @@
|
|||||||
import {
|
|
||||||
useEffect,
|
|
||||||
useRef,
|
|
||||||
useCallback,
|
|
||||||
useReducer,
|
|
||||||
Suspense,
|
|
||||||
memo,
|
|
||||||
} from "react";
|
|
||||||
import clsx from "clsx";
|
|
||||||
|
|
||||||
import ErrorBoundary from "@/components/Common/ErrorBoundary";
|
|
||||||
import Search from "@/components/Search/Search";
|
|
||||||
import InputBox from "@/components/Search/InputBox";
|
|
||||||
import ChatAI, { ChatAIRef } from "@/components/Assistant/Chat";
|
|
||||||
import UpdateApp from "@/components/UpdateApp";
|
|
||||||
import { isLinux, isWin } from "@/utils/platform";
|
|
||||||
import { appReducer, initialAppState } from "@/reducers/appReducer";
|
|
||||||
import { useWindowEvents } from "@/hooks/useWindowEvents";
|
|
||||||
import { useAppStore } from "@/stores/appStore";
|
|
||||||
import { useAuthStore } from "@/stores/authStore";
|
|
||||||
import platformAdapter from "@/utils/platformAdapter";
|
|
||||||
import { useStartupStore } from "@/stores/startupStore";
|
|
||||||
import { DataSource } from "@/types/commands";
|
|
||||||
|
|
||||||
interface SearchChatProps {
|
|
||||||
querySearch: (input: string) => Promise<any>;
|
|
||||||
queryDocuments: (
|
|
||||||
from: number,
|
|
||||||
size: number,
|
|
||||||
queryStrings: any
|
|
||||||
) => Promise<any>;
|
|
||||||
}
|
|
||||||
|
|
||||||
function SearchChat({ querySearch, queryDocuments }: SearchChatProps) {
|
|
||||||
const [state, dispatch] = useReducer(appReducer, initialAppState);
|
|
||||||
const {
|
|
||||||
isChatMode,
|
|
||||||
input,
|
|
||||||
isTransitioned,
|
|
||||||
isSearchActive,
|
|
||||||
isDeepThinkActive,
|
|
||||||
isTyping,
|
|
||||||
} = state;
|
|
||||||
|
|
||||||
useWindowEvents();
|
|
||||||
|
|
||||||
const initializeListeners = useAppStore((state) => state.initializeListeners);
|
|
||||||
const initializeListeners_auth = useAuthStore(
|
|
||||||
(state) => state.initializeListeners
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
initializeListeners();
|
|
||||||
initializeListeners_auth();
|
|
||||||
platformAdapter.invokeBackend("get_app_search_source");
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const chatAIRef = useRef<ChatAIRef>(null);
|
|
||||||
|
|
||||||
const changeMode = useCallback(async (value: boolean) => {
|
|
||||||
dispatch({ type: "SET_CHAT_MODE", payload: value });
|
|
||||||
localStorage.setItem("coco-chat-mode", String(value));
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const handleSendMessage = useCallback(
|
|
||||||
async (value: string) => {
|
|
||||||
dispatch({ type: "SET_INPUT", payload: value });
|
|
||||||
if (isChatMode) {
|
|
||||||
chatAIRef.current?.init(value);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[isChatMode]
|
|
||||||
);
|
|
||||||
|
|
||||||
const cancelChat = useCallback(() => {
|
|
||||||
chatAIRef.current?.cancelChat();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const reconnect = useCallback(() => {
|
|
||||||
chatAIRef.current?.reconnect();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const setInput = useCallback((value: string) => {
|
|
||||||
dispatch({ type: "SET_INPUT", payload: value });
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const toggleSearchActive = useCallback(() => {
|
|
||||||
dispatch({ type: "TOGGLE_SEARCH_ACTIVE" });
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const toggleDeepThinkActive = useCallback(() => {
|
|
||||||
dispatch({ type: "TOGGLE_DEEP_THINK_ACTIVE" });
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const LoadingFallback = () => (
|
|
||||||
<div className="flex items-center justify-center h-full">loading...</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
const hideCoco = useCallback(() => {
|
|
||||||
return platformAdapter.hideWindow();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const getFileUrl = useCallback((path: string) => {
|
|
||||||
return platformAdapter.convertFileSrc(path);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const openSetting = useCallback(() => {
|
|
||||||
return platformAdapter.emitEvent("open_settings", "");
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const setWindowAlwaysOnTop = useCallback(async (isPinned: boolean) => {
|
|
||||||
return platformAdapter.setAlwaysOnTop(isPinned);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const getDataSourcesByServer = useCallback(
|
|
||||||
async (serverId: string): Promise<DataSource[]> => {
|
|
||||||
return platformAdapter.invokeBackend("get_datasources_by_server", {
|
|
||||||
id: serverId,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
[]
|
|
||||||
);
|
|
||||||
|
|
||||||
const setupWindowFocusListener = useCallback(async (callback: () => void) => {
|
|
||||||
return platformAdapter.listenEvent("tauri://focus", callback);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const checkScreenPermission = useCallback(async () => {
|
|
||||||
return platformAdapter.checkScreenRecordingPermission();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const requestScreenPermission = useCallback(() => {
|
|
||||||
return platformAdapter.requestScreenRecordingPermission();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const getScreenMonitors = useCallback(async () => {
|
|
||||||
return platformAdapter.getScreenshotableMonitors();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const getScreenWindows = useCallback(async () => {
|
|
||||||
return platformAdapter.getScreenshotableWindows();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const captureMonitorScreenshot = useCallback(async (id: number) => {
|
|
||||||
return platformAdapter.captureMonitorScreenshot(id);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const captureWindowScreenshot = useCallback(async (id: number) => {
|
|
||||||
return platformAdapter.captureWindowScreenshot(id);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const openFileDialog = useCallback(async (options: { multiple: boolean }) => {
|
|
||||||
return platformAdapter.openFileDialog(options);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const getFileMetadata = useCallback(async (path: string) => {
|
|
||||||
return platformAdapter.getFileMetadata(path);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const getFileIcon = useCallback(async (path: string, size: number) => {
|
|
||||||
return platformAdapter.getFileIcon(path, size);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const checkUpdate = useCallback(async () => {
|
|
||||||
return platformAdapter.checkUpdate();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const relaunchApp = useCallback(async () => {
|
|
||||||
return platformAdapter.relaunchApp();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const defaultStartupWindow = useStartupStore((state) => {
|
|
||||||
return state.defaultStartupWindow;
|
|
||||||
});
|
|
||||||
|
|
||||||
const showCocoListenRef = useRef<(() => void) | undefined>();
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const setupShowCocoListener = async () => {
|
|
||||||
if (showCocoListenRef.current) {
|
|
||||||
showCocoListenRef.current();
|
|
||||||
showCocoListenRef.current = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const unlisten = await platformAdapter.listenEvent("show-coco", () => {
|
|
||||||
const chatMode = localStorage.getItem("coco-chat-mode");
|
|
||||||
changeMode(
|
|
||||||
chatMode ? chatMode === "true" : defaultStartupWindow === "chatMode"
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
showCocoListenRef.current = unlisten;
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Error setting up show-coco listener:", error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
setupShowCocoListener();
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
if (showCocoListenRef.current) {
|
|
||||||
showCocoListenRef.current();
|
|
||||||
showCocoListenRef.current = undefined;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}, [defaultStartupWindow, changeMode]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ErrorBoundary>
|
|
||||||
<div
|
|
||||||
data-tauri-drag-region
|
|
||||||
className={clsx(
|
|
||||||
"size-full m-auto overflow-hidden relative bg-no-repeat bg-cover bg-center",
|
|
||||||
[
|
|
||||||
isTransitioned
|
|
||||||
? "bg-chat_bg_light dark:bg-chat_bg_dark"
|
|
||||||
: "bg-search_bg_light dark:bg-search_bg_dark",
|
|
||||||
],
|
|
||||||
{
|
|
||||||
"rounded-xl": !isWin,
|
|
||||||
"border border-[#E6E6E6] dark:border-[#272626]": isLinux,
|
|
||||||
}
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
data-tauri-drag-region
|
|
||||||
className={`p-2 pb-0 absolute w-full flex items-center justify-center transition-all duration-500 ${
|
|
||||||
isTransitioned
|
|
||||||
? "top-[calc(100vh-90px)] h-[90px] border-t"
|
|
||||||
: "top-0 h-[90px] border-b"
|
|
||||||
} border-[#E6E6E6] dark:border-[#272626]`}
|
|
||||||
>
|
|
||||||
<InputBox
|
|
||||||
isChatMode={isChatMode}
|
|
||||||
inputValue={input}
|
|
||||||
onSend={handleSendMessage}
|
|
||||||
disabled={isTyping}
|
|
||||||
disabledChange={cancelChat}
|
|
||||||
changeMode={changeMode}
|
|
||||||
changeInput={setInput}
|
|
||||||
reconnect={reconnect}
|
|
||||||
isSearchActive={isSearchActive}
|
|
||||||
setIsSearchActive={toggleSearchActive}
|
|
||||||
isDeepThinkActive={isDeepThinkActive}
|
|
||||||
setIsDeepThinkActive={toggleDeepThinkActive}
|
|
||||||
getDataSourcesByServer={getDataSourcesByServer}
|
|
||||||
setupWindowFocusListener={setupWindowFocusListener}
|
|
||||||
checkScreenPermission={checkScreenPermission}
|
|
||||||
requestScreenPermission={requestScreenPermission}
|
|
||||||
getScreenMonitors={getScreenMonitors}
|
|
||||||
getScreenWindows={getScreenWindows}
|
|
||||||
captureMonitorScreenshot={captureMonitorScreenshot}
|
|
||||||
captureWindowScreenshot={captureWindowScreenshot}
|
|
||||||
openFileDialog={openFileDialog}
|
|
||||||
getFileMetadata={getFileMetadata}
|
|
||||||
getFileIcon={getFileIcon}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div
|
|
||||||
data-tauri-drag-region
|
|
||||||
className={`absolute w-full transition-opacity duration-500 ${
|
|
||||||
isTransitioned ? "opacity-0 pointer-events-none" : "opacity-100"
|
|
||||||
} bottom-0 h-[calc(100vh-90px)] `}
|
|
||||||
>
|
|
||||||
<Suspense fallback={<LoadingFallback />}>
|
|
||||||
<Search
|
|
||||||
key="Search"
|
|
||||||
input={input}
|
|
||||||
isChatMode={isChatMode}
|
|
||||||
changeInput={setInput}
|
|
||||||
querySearch={querySearch}
|
|
||||||
queryDocuments={queryDocuments}
|
|
||||||
hideCoco={hideCoco}
|
|
||||||
openSetting={openSetting}
|
|
||||||
setWindowAlwaysOnTop={setWindowAlwaysOnTop}
|
|
||||||
/>
|
|
||||||
</Suspense>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div
|
|
||||||
data-tauri-drag-region
|
|
||||||
className={`absolute w-full transition-all duration-500 select-auto ${
|
|
||||||
isTransitioned
|
|
||||||
? "top-0 opacity-100 pointer-events-auto"
|
|
||||||
: "-top-[506px] opacity-0 pointer-events-none"
|
|
||||||
} h-[calc(100vh-90px)]`}
|
|
||||||
>
|
|
||||||
{isTransitioned && isChatMode ? (
|
|
||||||
<Suspense fallback={<LoadingFallback />}>
|
|
||||||
<ChatAI
|
|
||||||
ref={chatAIRef}
|
|
||||||
key="ChatAI"
|
|
||||||
isTransitioned={isTransitioned}
|
|
||||||
changeInput={setInput}
|
|
||||||
isSearchActive={isSearchActive}
|
|
||||||
isDeepThinkActive={isDeepThinkActive}
|
|
||||||
getFileUrl={getFileUrl}
|
|
||||||
/>
|
|
||||||
</Suspense>
|
|
||||||
) : null}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<UpdateApp checkUpdate={checkUpdate} relaunchApp={relaunchApp} />
|
|
||||||
</div>
|
|
||||||
</ErrorBoundary>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default memo(SearchChat);
|
|
||||||
@@ -1,22 +1,136 @@
|
|||||||
import { useCallback } from "react";
|
import { useEffect, useCallback } from "react";
|
||||||
|
|
||||||
import SearchChat from "./SearchChat";
|
import SearchChat from "@/components/SearchChat";
|
||||||
|
import { useAppStore } from "@/stores/appStore";
|
||||||
|
import { Get } from "@/api/axiosRequest";
|
||||||
|
import { useShortcutsStore } from "@/stores/shortcutsStore";
|
||||||
|
|
||||||
|
import "@/i18n";
|
||||||
|
import "@/web.css";
|
||||||
|
|
||||||
|
interface WebAppProps {
|
||||||
|
headers?: Record<string, unknown>;
|
||||||
|
serverUrl?: string;
|
||||||
|
width?: number;
|
||||||
|
height?: number;
|
||||||
|
hasModules?: string[];
|
||||||
|
defaultModule?: "search" | "chat";
|
||||||
|
hasFeature?: string[];
|
||||||
|
hideCoco?: () => void;
|
||||||
|
theme?: "auto" | "light" | "dark";
|
||||||
|
searchPlaceholder?: string;
|
||||||
|
chatPlaceholder?: string;
|
||||||
|
showChatHistory?: boolean;
|
||||||
|
setIsPinned?: (value: boolean) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
function WebApp({
|
||||||
|
width = 680,
|
||||||
|
height = 590,
|
||||||
|
headers = {
|
||||||
|
"X-API-TOKEN": "cvkm9hmhpcemufsg3vtgxbns7jqioo6uq5btira638fzclrkstbvc0hoe2kd86vnhrnf2e3izpoy4phrmv79",
|
||||||
|
"APP-INTEGRATION-ID": "cvkm9hmhpcemufsg3vug",
|
||||||
|
},
|
||||||
|
// token = "cva1j5ehpcenic3ir7k0h8fb8qtv35iwtywze248oscrej8yoivhb5b1hyovp24xejjk27jy9ddt69ewfi3n", // https://coco.infini.cloud
|
||||||
|
// token = "cv97ieo2sdbbru4vtha094eyxuzxdj6pvp9fbdzxb66dff0djy4rsjyju6yymypxe42lg2h7jl6ohdksecth", // http://localhost:9000
|
||||||
|
// token = "cv5djeb9om602jdvtnmg6kc1muyn2vcadr6te48j9t9pvt59ewrnwj7fwvxrw3va84j2a0lb5y8194fbr3jd", // http://43.153.113.88:9000
|
||||||
|
serverUrl = "http://localhost:9000",
|
||||||
|
hideCoco = () => {},
|
||||||
|
hasModules = ["search", "chat"],
|
||||||
|
defaultModule = "search",
|
||||||
|
hasFeature = ['think_active', 'search_active'],
|
||||||
|
theme="light",
|
||||||
|
searchPlaceholder = "",
|
||||||
|
chatPlaceholder = "",
|
||||||
|
showChatHistory = true,
|
||||||
|
setIsPinned,
|
||||||
|
}: WebAppProps) {
|
||||||
|
const setIsTauri = useAppStore((state) => state.setIsTauri);
|
||||||
|
const setEndpoint = useAppStore((state) => state.setEndpoint);
|
||||||
|
const setModeSwitch = useShortcutsStore((state) => state.setModeSwitch);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setIsTauri(false);
|
||||||
|
setEndpoint(serverUrl);
|
||||||
|
setModeSwitch("S");
|
||||||
|
localStorage.setItem("headers", JSON.stringify(headers||{}));
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const query_coco_fusion = useCallback(async (url: string) => {
|
||||||
|
try {
|
||||||
|
const [error, response]: any = await Get(url);
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
console.error("_search", error);
|
||||||
|
return { hits: [], total: 0 };
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("_suggest", url, response);
|
||||||
|
const hits =
|
||||||
|
response?.hits?.hits?.map((hit: any) => ({
|
||||||
|
document: {
|
||||||
|
...hit._source,
|
||||||
|
},
|
||||||
|
score: hit._score || 0,
|
||||||
|
source: hit._source.source || null,
|
||||||
|
})) || [];
|
||||||
|
const total = response?.hits?.total?.value || 0;
|
||||||
|
|
||||||
|
console.log("_suggest2", url, total, hits);
|
||||||
|
|
||||||
|
return {
|
||||||
|
hits: hits,
|
||||||
|
total_hits: total,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.error("query_coco_fusion error:", error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
function WebApp() {
|
|
||||||
const querySearch = useCallback(async (input: string) => {
|
const querySearch = useCallback(async (input: string) => {
|
||||||
console.log(input);
|
console.log(input);
|
||||||
|
return await query_coco_fusion(`/query/_search?query=${input}`);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const queryDocuments = useCallback(
|
const queryDocuments = useCallback(
|
||||||
async (from: number, size: number, queryStrings: any) => {
|
async (from: number, size: number, queryStrings: any) => {
|
||||||
console.log(from, size, queryStrings);
|
console.log(from, size, queryStrings);
|
||||||
|
try {
|
||||||
|
let url = `/query/_search?query=${queryStrings.query}&datasource=${queryStrings.datasource}&from=${from}&size=${size}`;
|
||||||
|
if (queryStrings?.rich_categories) {
|
||||||
|
url = `/query/_search?query=${queryStrings.query}&rich_category=${queryStrings.rich_category}&from=${from}&size=${size}`;
|
||||||
|
}
|
||||||
|
return await query_coco_fusion(url);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("query_coco_fusion error:", error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
[]
|
[]
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-[680px] h-[590px]">
|
<div
|
||||||
<SearchChat querySearch={querySearch} queryDocuments={queryDocuments} />
|
id="searchChat-container"
|
||||||
|
className={`coco-container ${theme}`}
|
||||||
|
data-theme={theme}
|
||||||
|
style={{ width: `${width}px`, height: `${height}px` }}
|
||||||
|
>
|
||||||
|
<SearchChat
|
||||||
|
isTauri={false}
|
||||||
|
hideCoco={hideCoco}
|
||||||
|
hasModules={hasModules}
|
||||||
|
defaultModule={defaultModule}
|
||||||
|
hasFeature={hasFeature}
|
||||||
|
theme={theme}
|
||||||
|
searchPlaceholder={searchPlaceholder}
|
||||||
|
chatPlaceholder={chatPlaceholder}
|
||||||
|
querySearch={querySearch}
|
||||||
|
queryDocuments={queryDocuments}
|
||||||
|
showChatHistory={showChatHistory}
|
||||||
|
setIsPinned={setIsPinned}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,6 +52,9 @@ export type IAppStore = {
|
|||||||
showCocoShortcuts: string[];
|
showCocoShortcuts: string[];
|
||||||
setShowCocoShortcuts: (showCocoShortcuts: string[]) => void;
|
setShowCocoShortcuts: (showCocoShortcuts: string[]) => void;
|
||||||
|
|
||||||
|
isTauri: boolean;
|
||||||
|
setIsTauri: (isTauri: boolean) => void;
|
||||||
|
|
||||||
visible: boolean;
|
visible: boolean;
|
||||||
withVisibility: <T>(fn: () => Promise<T>) => Promise<T>;
|
withVisibility: <T>(fn: () => Promise<T>) => Promise<T>;
|
||||||
};
|
};
|
||||||
@@ -107,6 +110,8 @@ export const useAppStore = create<IAppStore>()(
|
|||||||
|
|
||||||
return set({ showCocoShortcuts });
|
return set({ showCocoShortcuts });
|
||||||
},
|
},
|
||||||
|
isTauri: true,
|
||||||
|
setIsTauri: (isTauri: boolean) => set({ isTauri }),
|
||||||
visible: false,
|
visible: false,
|
||||||
withVisibility: async <T>(fn: () => Promise<T>) => {
|
withVisibility: async <T>(fn: () => Promise<T>) => {
|
||||||
set({ visible: true });
|
set({ visible: true });
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import { create } from "zustand";
|
import { create } from "zustand";
|
||||||
import { persist } from "zustand/middleware";
|
import { persist } from "zustand/middleware";
|
||||||
import { produce } from "immer";
|
import { produce } from "immer";
|
||||||
import { listen, emit } from "@tauri-apps/api/event";
|
|
||||||
|
import platformAdapter from "@/utils/platformAdapter";
|
||||||
|
|
||||||
const CONNECTOR_CHANGE_EVENT = "connector_data_change";
|
const CONNECTOR_CHANGE_EVENT = "connector_data_change";
|
||||||
const DATASOURCE_CHANGE_EVENT = "datasourceData_change";
|
const DATASOURCE_CHANGE_EVENT = "datasourceData_change";
|
||||||
@@ -53,7 +54,7 @@ export const useConnectStore = create<IConnectStore>()(
|
|||||||
draft.connector_data[key] = connector_data;
|
draft.connector_data[key] = connector_data;
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
await emit(CONNECTOR_CHANGE_EVENT, {
|
await platformAdapter.emitEvent(CONNECTOR_CHANGE_EVENT, {
|
||||||
connector_data,
|
connector_data,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -64,16 +65,16 @@ export const useConnectStore = create<IConnectStore>()(
|
|||||||
draft.datasourceData[key] = datasourceData;
|
draft.datasourceData[key] = datasourceData;
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
await emit(DATASOURCE_CHANGE_EVENT, {
|
await platformAdapter.emitEvent(DATASOURCE_CHANGE_EVENT, {
|
||||||
datasourceData,
|
datasourceData,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
initializeListeners: () => {
|
initializeListeners: () => {
|
||||||
listen(CONNECTOR_CHANGE_EVENT, (event: any) => {
|
platformAdapter.listenEvent(CONNECTOR_CHANGE_EVENT, (event: any) => {
|
||||||
const { connector_data } = event.payload;
|
const { connector_data } = event.payload;
|
||||||
set({ connector_data });
|
set({ connector_data });
|
||||||
});
|
});
|
||||||
listen(DATASOURCE_CHANGE_EVENT, (event: any) => {
|
platformAdapter.listenEvent(DATASOURCE_CHANGE_EVENT, (event: any) => {
|
||||||
const { datasourceData } = event.payload;
|
const { datasourceData } = event.payload;
|
||||||
set({ datasourceData });
|
set({ datasourceData });
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { Update } from "@tauri-apps/plugin-updater";
|
|
||||||
import { create } from "zustand";
|
import { create } from "zustand";
|
||||||
import { persist } from "zustand/middleware";
|
import { persist } from "zustand/middleware";
|
||||||
|
|
||||||
@@ -9,8 +8,8 @@ export type IUpdateStore = {
|
|||||||
setSkipVersion: (skipVersion?: string) => void;
|
setSkipVersion: (skipVersion?: string) => void;
|
||||||
isOptional: boolean;
|
isOptional: boolean;
|
||||||
setIsOptional: (isOptional: boolean) => void;
|
setIsOptional: (isOptional: boolean) => void;
|
||||||
updateInfo?: Update;
|
updateInfo?: any;
|
||||||
setUpdateInfo: (updateInfo?: Update) => void;
|
setUpdateInfo: (updateInfo?: any) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useUpdateStore = create<IUpdateStore>()(
|
export const useUpdateStore = create<IUpdateStore>()(
|
||||||
@@ -27,7 +26,7 @@ export const useUpdateStore = create<IUpdateStore>()(
|
|||||||
setIsOptional: (isOptional: boolean) => {
|
setIsOptional: (isOptional: boolean) => {
|
||||||
return set({ isOptional });
|
return set({ isOptional });
|
||||||
},
|
},
|
||||||
setUpdateInfo: (updateInfo?: Update) => {
|
setUpdateInfo: (updateInfo?: any) => {
|
||||||
return set({ updateInfo });
|
return set({ updateInfo });
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { isTauri } from "@tauri-apps/api/core";
|
|
||||||
import { open } from "@tauri-apps/plugin-shell";
|
|
||||||
|
|
||||||
import { hide_coco } from "@/commands"
|
import { hide_coco } from "@/commands"
|
||||||
|
import { useAppStore } from "@/stores/appStore";
|
||||||
|
import platformAdapter from "./platformAdapter";
|
||||||
|
|
||||||
// 1
|
// 1
|
||||||
export async function copyToClipboard(text: string) {
|
export async function copyToClipboard(text: string) {
|
||||||
@@ -65,10 +65,11 @@ export const IsTauri = () => {
|
|||||||
|
|
||||||
export const OpenURLWithBrowser = async (url: string) => {
|
export const OpenURLWithBrowser = async (url: string) => {
|
||||||
if (!url) return;
|
if (!url) return;
|
||||||
if (isTauri()) {
|
if (IsTauri()) {
|
||||||
try {
|
try {
|
||||||
await open(url);
|
await open(url);
|
||||||
await hide_coco();
|
await hide_coco();
|
||||||
|
await platformAdapter.invokeBackend("hide_coco");
|
||||||
console.log("URL opened in default browser");
|
console.log("URL opened in default browser");
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to open URL:", error);
|
console.error("Failed to open URL:", error);
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { useState } from "react";
|
import { createWebAdapter } from './webAdapter';
|
||||||
import { isTauri } from "@tauri-apps/api/core";
|
// import { createTauriAdapter } from './tauriAdapter';
|
||||||
import { convertFileSrc as tauriConvertFileSrc } from "@tauri-apps/api/core";
|
|
||||||
import type { OpenDialogOptions } from "@tauri-apps/plugin-dialog";
|
|
||||||
import { IShortcutsStore } from "@/stores/shortcutsStore";
|
import { IShortcutsStore } from "@/stores/shortcutsStore";
|
||||||
import { IStartupStore } from "@/stores/startupStore";
|
import { IStartupStore } from "@/stores/startupStore";
|
||||||
|
|
||||||
@@ -29,8 +28,19 @@ export interface EventPayloads {
|
|||||||
open_settings: string | "";
|
open_settings: string | "";
|
||||||
tab_index: string | "";
|
tab_index: string | "";
|
||||||
login_or_logout: any;
|
login_or_logout: any;
|
||||||
|
'show-coco': void;
|
||||||
|
'connector_data_change': void;
|
||||||
|
'datasourceData_change': void;
|
||||||
|
'ws-error': void;
|
||||||
|
'ws-message': void;
|
||||||
|
[key: `ws-error-${string}`]: {
|
||||||
|
error: {
|
||||||
|
reason: string;
|
||||||
|
};
|
||||||
|
status: number;
|
||||||
|
};
|
||||||
|
[key: `ws-message-${string}`]: string;
|
||||||
"change-startup-store": IStartupStore
|
"change-startup-store": IStartupStore
|
||||||
"show-coco": void;
|
|
||||||
"change-shortcuts-store": IShortcutsStore;
|
"change-shortcuts-store": IShortcutsStore;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -40,7 +50,6 @@ export interface PlatformAdapter {
|
|||||||
setWindowSize: (width: number, height: number) => Promise<void>;
|
setWindowSize: (width: number, height: number) => Promise<void>;
|
||||||
hideWindow: () => Promise<void>;
|
hideWindow: () => Promise<void>;
|
||||||
showWindow: () => Promise<void>;
|
showWindow: () => Promise<void>;
|
||||||
isPlatformTauri: () => boolean;
|
|
||||||
convertFileSrc: (path: string) => string;
|
convertFileSrc: (path: string) => string;
|
||||||
emitEvent: (event: string, payload?: any) => Promise<void>;
|
emitEvent: (event: string, payload?: any) => Promise<void>;
|
||||||
listenEvent: <K extends keyof EventPayloads>(
|
listenEvent: <K extends keyof EventPayloads>(
|
||||||
@@ -54,9 +63,9 @@ export interface PlatformAdapter {
|
|||||||
getScreenshotableWindows: () => Promise<any[]>;
|
getScreenshotableWindows: () => Promise<any[]>;
|
||||||
captureMonitorScreenshot: (id: number) => Promise<string>;
|
captureMonitorScreenshot: (id: number) => Promise<string>;
|
||||||
captureWindowScreenshot: (id: number) => Promise<string>;
|
captureWindowScreenshot: (id: number) => Promise<string>;
|
||||||
openFileDialog: (
|
openFileDialog: (options: {
|
||||||
options: OpenDialogOptions
|
multiple: boolean;
|
||||||
) => Promise<string | string[] | null>;
|
}) => Promise<string | string[] | null>;
|
||||||
getFileMetadata: (path: string) => Promise<any>;
|
getFileMetadata: (path: string) => Promise<any>;
|
||||||
getFileIcon: (path: string, size: number) => Promise<string>;
|
getFileIcon: (path: string, size: number) => Promise<string>;
|
||||||
checkUpdate: () => Promise<any>;
|
checkUpdate: () => Promise<any>;
|
||||||
@@ -72,406 +81,18 @@ export interface PlatformAdapter {
|
|||||||
show: () => Promise<void>;
|
show: () => Promise<void>;
|
||||||
setFocus: () => Promise<void>;
|
setFocus: () => Promise<void>;
|
||||||
center: () => Promise<void>;
|
center: () => Promise<void>;
|
||||||
|
close: () => Promise<void>;
|
||||||
} | null>;
|
} | null>;
|
||||||
createWindow: (label: string, options: any) => Promise<void>;
|
createWindow: (label: string, options: any) => Promise<void>;
|
||||||
|
getAllWindows: () => Promise<any[]>;
|
||||||
|
getCurrentWindow: () => Promise<any>;
|
||||||
|
createWebviewWindow: (label: string, options: any) => Promise<any>;
|
||||||
|
listenWindowEvent: (event: string, callback: (event: any) => void) => Promise<() => void>;
|
||||||
|
isTauri: () => boolean;
|
||||||
|
openExternal: (url: string) => Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create Tauri adapter functions
|
|
||||||
export const createTauriAdapter = (): PlatformAdapter => {
|
|
||||||
return {
|
|
||||||
async invokeBackend(command: string, args?: any): Promise<any> {
|
|
||||||
if (isTauri()) {
|
|
||||||
const { invoke } = await import("@tauri-apps/api/core");
|
|
||||||
return invoke(command, args);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
|
|
||||||
async setWindowSize(width: number, height: number): Promise<void> {
|
|
||||||
if (isTauri()) {
|
|
||||||
const { getCurrentWebviewWindow } = await import(
|
|
||||||
"@tauri-apps/api/webviewWindow"
|
|
||||||
);
|
|
||||||
const { LogicalSize } = await import("@tauri-apps/api/dpi");
|
|
||||||
const window = await getCurrentWebviewWindow();
|
|
||||||
if (window) {
|
|
||||||
await window.setSize(new LogicalSize(width, height));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
async hideWindow(): Promise<void> {
|
|
||||||
if (isTauri()) {
|
|
||||||
const { invoke } = await import("@tauri-apps/api/core");
|
|
||||||
await invoke("hide_coco");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
async showWindow(): Promise<void> {
|
|
||||||
if (isTauri()) {
|
|
||||||
const { getCurrentWebviewWindow } = await import(
|
|
||||||
"@tauri-apps/api/webviewWindow"
|
|
||||||
);
|
|
||||||
const window = await getCurrentWebviewWindow();
|
|
||||||
if (window) {
|
|
||||||
await window.show();
|
|
||||||
await window.unminimize();
|
|
||||||
await window.setFocus();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
isPlatformTauri(): boolean {
|
|
||||||
return isTauri();
|
|
||||||
},
|
|
||||||
|
|
||||||
convertFileSrc(path: string): string {
|
|
||||||
if (isTauri()) {
|
|
||||||
return tauriConvertFileSrc(path);
|
|
||||||
}
|
|
||||||
return path;
|
|
||||||
},
|
|
||||||
|
|
||||||
async emitEvent(event: string, payload?: any) {
|
|
||||||
if (isTauri()) {
|
|
||||||
const { emit } = await import("@tauri-apps/api/event");
|
|
||||||
return emit(event, payload);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
async listenEvent<K extends keyof EventPayloads>(
|
|
||||||
event: K,
|
|
||||||
callback: (event: { payload: EventPayloads[K] }) => void
|
|
||||||
) {
|
|
||||||
if (isTauri()) {
|
|
||||||
const { listen } = await import("@tauri-apps/api/event");
|
|
||||||
return listen(event, callback);
|
|
||||||
}
|
|
||||||
return () => {};
|
|
||||||
},
|
|
||||||
|
|
||||||
async setAlwaysOnTop(isPinned: boolean) {
|
|
||||||
if (isTauri()) {
|
|
||||||
const { getCurrentWindow } = await import("@tauri-apps/api/window");
|
|
||||||
const window = getCurrentWindow();
|
|
||||||
return window.setAlwaysOnTop(isPinned);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
async checkScreenRecordingPermission() {
|
|
||||||
if (isTauri()) {
|
|
||||||
const { checkScreenRecordingPermission } = await import(
|
|
||||||
"tauri-plugin-macos-permissions-api"
|
|
||||||
);
|
|
||||||
return checkScreenRecordingPermission();
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
},
|
|
||||||
|
|
||||||
async requestScreenRecordingPermission() {
|
|
||||||
if (isTauri()) {
|
|
||||||
const { requestScreenRecordingPermission } = await import(
|
|
||||||
"tauri-plugin-macos-permissions-api"
|
|
||||||
);
|
|
||||||
return requestScreenRecordingPermission();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
async getScreenshotableMonitors() {
|
|
||||||
if (isTauri()) {
|
|
||||||
const { getScreenshotableMonitors } = await import(
|
|
||||||
"tauri-plugin-screenshots-api"
|
|
||||||
);
|
|
||||||
return getScreenshotableMonitors();
|
|
||||||
}
|
|
||||||
return [];
|
|
||||||
},
|
|
||||||
|
|
||||||
async getScreenshotableWindows() {
|
|
||||||
if (isTauri()) {
|
|
||||||
const { getScreenshotableWindows } = await import(
|
|
||||||
"tauri-plugin-screenshots-api"
|
|
||||||
);
|
|
||||||
return getScreenshotableWindows();
|
|
||||||
}
|
|
||||||
return [];
|
|
||||||
},
|
|
||||||
|
|
||||||
async captureMonitorScreenshot(id: number) {
|
|
||||||
if (isTauri()) {
|
|
||||||
const { getMonitorScreenshot } = await import(
|
|
||||||
"tauri-plugin-screenshots-api"
|
|
||||||
);
|
|
||||||
return getMonitorScreenshot(id);
|
|
||||||
}
|
|
||||||
return "";
|
|
||||||
},
|
|
||||||
|
|
||||||
async captureWindowScreenshot(id: number) {
|
|
||||||
if (isTauri()) {
|
|
||||||
const { getWindowScreenshot } = await import(
|
|
||||||
"tauri-plugin-screenshots-api"
|
|
||||||
);
|
|
||||||
return getWindowScreenshot(id);
|
|
||||||
}
|
|
||||||
return "";
|
|
||||||
},
|
|
||||||
|
|
||||||
async openFileDialog(options: OpenDialogOptions) {
|
|
||||||
if (isTauri()) {
|
|
||||||
const { open } = await import("@tauri-apps/plugin-dialog");
|
|
||||||
return open(options);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
|
|
||||||
async getFileMetadata(path: string) {
|
|
||||||
if (isTauri()) {
|
|
||||||
const { metadata } = await import("tauri-plugin-fs-pro-api");
|
|
||||||
return metadata(path);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
|
|
||||||
async getFileIcon(path: string, size: number) {
|
|
||||||
if (isTauri()) {
|
|
||||||
const { icon } = await import("tauri-plugin-fs-pro-api");
|
|
||||||
return icon(path, size);
|
|
||||||
}
|
|
||||||
return "";
|
|
||||||
},
|
|
||||||
|
|
||||||
async checkUpdate() {
|
|
||||||
if (isTauri()) {
|
|
||||||
const { check } = await import("@tauri-apps/plugin-updater");
|
|
||||||
return check();
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
|
|
||||||
async relaunchApp() {
|
|
||||||
if (isTauri()) {
|
|
||||||
const { relaunch } = await import("@tauri-apps/plugin-process");
|
|
||||||
return relaunch();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
async listenThemeChanged(callback) {
|
|
||||||
if (isTauri()) {
|
|
||||||
const { listen } = await import("@tauri-apps/api/event");
|
|
||||||
return listen("theme-changed", ({ payload }) => {
|
|
||||||
callback(payload);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return () => {};
|
|
||||||
},
|
|
||||||
|
|
||||||
async getWebviewWindow() {
|
|
||||||
if (isTauri()) {
|
|
||||||
const { getCurrentWebviewWindow } = await import(
|
|
||||||
"@tauri-apps/api/webviewWindow"
|
|
||||||
);
|
|
||||||
return getCurrentWebviewWindow();
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
|
|
||||||
async setWindowTheme(theme) {
|
|
||||||
const window = await this.getWebviewWindow();
|
|
||||||
if (window) {
|
|
||||||
return window.setTheme(theme);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
async getWindowTheme() {
|
|
||||||
const window = await this.getWebviewWindow();
|
|
||||||
if (window) {
|
|
||||||
return window.theme();
|
|
||||||
}
|
|
||||||
return "light";
|
|
||||||
},
|
|
||||||
|
|
||||||
async onThemeChanged(callback) {
|
|
||||||
const window = await this.getWebviewWindow();
|
|
||||||
if (window) {
|
|
||||||
window.onThemeChanged(callback);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
async getWindowByLabel(label: string) {
|
|
||||||
if (isTauri()) {
|
|
||||||
const { WebviewWindow } = await import("@tauri-apps/api/webviewWindow");
|
|
||||||
const window = await WebviewWindow.getByLabel(label);
|
|
||||||
return window;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
|
|
||||||
async createWindow(label: string, options: any) {
|
|
||||||
if (isTauri()) {
|
|
||||||
const { WebviewWindow } = await import("@tauri-apps/api/webviewWindow");
|
|
||||||
new WebviewWindow(label, options);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
// Create Web adapter functions
|
|
||||||
export const createWebAdapter = (): PlatformAdapter => {
|
|
||||||
return {
|
|
||||||
async invokeBackend(command: string, args?: any): Promise<any> {
|
|
||||||
console.log(`Web mode simulated backend call: ${command}`, args);
|
|
||||||
// Implement web environment simulation logic or API calls here
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
|
|
||||||
async setWindowSize(width: number, height: number): Promise<void> {
|
|
||||||
console.log(`Web mode simulated window resize: ${width}x${height}`);
|
|
||||||
// No actual operation needed in web environment
|
|
||||||
},
|
|
||||||
|
|
||||||
async hideWindow(): Promise<void> {
|
|
||||||
console.log("Web mode simulated window hide");
|
|
||||||
// No actual operation needed in web environment
|
|
||||||
},
|
|
||||||
|
|
||||||
async showWindow(): Promise<void> {
|
|
||||||
console.log("Web mode simulated window show");
|
|
||||||
// No actual operation needed in web environment
|
|
||||||
},
|
|
||||||
|
|
||||||
isPlatformTauri(): boolean {
|
|
||||||
return false;
|
|
||||||
},
|
|
||||||
|
|
||||||
convertFileSrc(path: string): string {
|
|
||||||
return path;
|
|
||||||
},
|
|
||||||
|
|
||||||
async emitEvent(event: string, payload?: any): Promise<void> {
|
|
||||||
console.log("Web mode simulated event emit", event, payload);
|
|
||||||
},
|
|
||||||
|
|
||||||
async listenEvent<K extends keyof EventPayloads>(
|
|
||||||
event: K,
|
|
||||||
_callback: (event: { payload: EventPayloads[K] }) => void
|
|
||||||
): Promise<() => void> {
|
|
||||||
console.log("Web mode simulated event listen", event);
|
|
||||||
return () => {};
|
|
||||||
},
|
|
||||||
|
|
||||||
async setAlwaysOnTop(isPinned: boolean): Promise<void> {
|
|
||||||
console.log("Web mode simulated set always on top", isPinned);
|
|
||||||
},
|
|
||||||
|
|
||||||
async checkScreenRecordingPermission(): Promise<boolean> {
|
|
||||||
console.log("Web mode simulated check screen recording permission");
|
|
||||||
return false;
|
|
||||||
},
|
|
||||||
|
|
||||||
requestScreenRecordingPermission(): void {
|
|
||||||
console.log("Web mode simulated request screen recording permission");
|
|
||||||
},
|
|
||||||
|
|
||||||
async getScreenshotableMonitors(): Promise<any[]> {
|
|
||||||
console.log("Web mode simulated get screenshotable monitors");
|
|
||||||
return [];
|
|
||||||
},
|
|
||||||
|
|
||||||
async getScreenshotableWindows(): Promise<any[]> {
|
|
||||||
console.log("Web mode simulated get screenshotable windows");
|
|
||||||
return [];
|
|
||||||
},
|
|
||||||
|
|
||||||
async captureMonitorScreenshot(id: number): Promise<string> {
|
|
||||||
console.log("Web mode simulated capture monitor screenshot", id);
|
|
||||||
return "";
|
|
||||||
},
|
|
||||||
|
|
||||||
async captureWindowScreenshot(id: number): Promise<string> {
|
|
||||||
console.log("Web mode simulated capture window screenshot", id);
|
|
||||||
return "";
|
|
||||||
},
|
|
||||||
|
|
||||||
async openFileDialog(options: OpenDialogOptions): Promise<null> {
|
|
||||||
console.log("Web mode simulated open file dialog", options);
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
|
|
||||||
async getFileMetadata(path: string): Promise<null> {
|
|
||||||
console.log("Web mode simulated get file metadata", path);
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
|
|
||||||
async getFileIcon(path: string, size: number): Promise<string> {
|
|
||||||
console.log("Web mode simulated get file icon", path, size);
|
|
||||||
return "";
|
|
||||||
},
|
|
||||||
|
|
||||||
async checkUpdate(): Promise<any> {
|
|
||||||
console.log("Web mode simulated check update");
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
|
|
||||||
async relaunchApp(): Promise<void> {
|
|
||||||
console.log("Web mode simulated relaunch app");
|
|
||||||
},
|
|
||||||
|
|
||||||
async listenThemeChanged() {
|
|
||||||
console.log("Web mode simulated theme change listener");
|
|
||||||
return () => {};
|
|
||||||
},
|
|
||||||
|
|
||||||
async getWebviewWindow() {
|
|
||||||
console.log("Web mode simulated get webview window");
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
|
|
||||||
async setWindowTheme(theme) {
|
|
||||||
console.log("Web mode simulated set window theme:", theme);
|
|
||||||
},
|
|
||||||
|
|
||||||
async getWindowTheme() {
|
|
||||||
console.log("Web mode simulated get window theme");
|
|
||||||
return "light";
|
|
||||||
},
|
|
||||||
|
|
||||||
async onThemeChanged(callback) {
|
|
||||||
console.log("Web mode simulated on theme changed", callback);
|
|
||||||
},
|
|
||||||
|
|
||||||
async getWindowByLabel(label: string) {
|
|
||||||
console.log("Web mode simulated get window by label:", label);
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
|
|
||||||
async createWindow(label: string, options: any) {
|
|
||||||
console.log("Web mode simulated create window:", label, options);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
// Create platform adapter based on environment
|
|
||||||
export const createPlatformAdapter = (): PlatformAdapter => {
|
|
||||||
try {
|
|
||||||
if (isTauri()) {
|
|
||||||
return createTauriAdapter();
|
|
||||||
} else {
|
|
||||||
return createWebAdapter();
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
return createWebAdapter();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Default adapter instance
|
// Default adapter instance
|
||||||
const platformAdapter = createPlatformAdapter();
|
const platformAdapter: PlatformAdapter = typeof window !== 'undefined' ? createWebAdapter() : {} as PlatformAdapter;
|
||||||
|
|
||||||
export default platformAdapter;
|
export default platformAdapter;
|
||||||
|
|
||||||
// Custom hook for using platform adapter
|
|
||||||
export const usePlatformAdapter = () => {
|
|
||||||
const [adapter] = useState<PlatformAdapter>(platformAdapter);
|
|
||||||
|
|
||||||
return adapter;
|
|
||||||
};
|
|
||||||
|
|||||||
188
src/utils/tauriAdapter.ts
Normal file
188
src/utils/tauriAdapter.ts
Normal file
@@ -0,0 +1,188 @@
|
|||||||
|
import type { PlatformAdapter, EventPayloads } from './platformAdapter';
|
||||||
|
|
||||||
|
import type { OpenDialogOptions } from '@tauri-apps/plugin-dialog';
|
||||||
|
|
||||||
|
// Create Tauri adapter functions
|
||||||
|
export const createTauriAdapter = (): PlatformAdapter => {
|
||||||
|
return {
|
||||||
|
async invokeBackend(command: string, args?: any): Promise<any> {
|
||||||
|
const { invoke } = await import("@tauri-apps/api/core");
|
||||||
|
return invoke(command, args);
|
||||||
|
},
|
||||||
|
|
||||||
|
async setWindowSize(width: number, height: number): Promise<void> {
|
||||||
|
const { getCurrentWebviewWindow } = await import("@tauri-apps/api/webviewWindow");
|
||||||
|
const { LogicalSize } = await import("@tauri-apps/api/dpi");
|
||||||
|
const window = await getCurrentWebviewWindow();
|
||||||
|
if (window) {
|
||||||
|
await window.setSize(new LogicalSize(width, height));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async hideWindow(): Promise<void> {
|
||||||
|
const { invoke } = await import("@tauri-apps/api/core");
|
||||||
|
await invoke("hide_coco");
|
||||||
|
},
|
||||||
|
|
||||||
|
async showWindow(): Promise<void> {
|
||||||
|
const { getCurrentWebviewWindow } = await import("@tauri-apps/api/webviewWindow");
|
||||||
|
const window = await getCurrentWebviewWindow();
|
||||||
|
if (window) {
|
||||||
|
await window.show();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
convertFileSrc(path: string): string {
|
||||||
|
const { convertFileSrc } = require("@tauri-apps/api/core");
|
||||||
|
return convertFileSrc(path);
|
||||||
|
},
|
||||||
|
|
||||||
|
async emitEvent(event: string, payload?: any) {
|
||||||
|
const { emit } = await import("@tauri-apps/api/event");
|
||||||
|
return emit(event, payload);
|
||||||
|
},
|
||||||
|
|
||||||
|
async listenEvent<K extends keyof EventPayloads>(
|
||||||
|
event: K,
|
||||||
|
callback: (event: { payload: EventPayloads[K] }) => void
|
||||||
|
) {
|
||||||
|
const { listen } = await import("@tauri-apps/api/event");
|
||||||
|
return listen(event, callback);
|
||||||
|
},
|
||||||
|
|
||||||
|
async setAlwaysOnTop(isPinned: boolean) {
|
||||||
|
const { getCurrentWindow } = await import("@tauri-apps/api/window");
|
||||||
|
const window = getCurrentWindow();
|
||||||
|
return window.setAlwaysOnTop(isPinned);
|
||||||
|
},
|
||||||
|
|
||||||
|
async checkScreenRecordingPermission() {
|
||||||
|
const { checkScreenRecordingPermission } = await import("tauri-plugin-macos-permissions-api");
|
||||||
|
return checkScreenRecordingPermission();
|
||||||
|
},
|
||||||
|
|
||||||
|
async requestScreenRecordingPermission() {
|
||||||
|
const { requestScreenRecordingPermission } = await import("tauri-plugin-macos-permissions-api");
|
||||||
|
return requestScreenRecordingPermission();
|
||||||
|
},
|
||||||
|
|
||||||
|
async getScreenshotableMonitors() {
|
||||||
|
const { getScreenshotableMonitors } = await import("tauri-plugin-screenshots-api");
|
||||||
|
return getScreenshotableMonitors();
|
||||||
|
},
|
||||||
|
|
||||||
|
async getScreenshotableWindows() {
|
||||||
|
const { getScreenshotableWindows } = await import("tauri-plugin-screenshots-api");
|
||||||
|
return getScreenshotableWindows();
|
||||||
|
},
|
||||||
|
|
||||||
|
async captureMonitorScreenshot(id: number) {
|
||||||
|
const { getMonitorScreenshot } = await import("tauri-plugin-screenshots-api");
|
||||||
|
return getMonitorScreenshot(id);
|
||||||
|
},
|
||||||
|
|
||||||
|
async captureWindowScreenshot(id: number) {
|
||||||
|
const { getWindowScreenshot } = await import("tauri-plugin-screenshots-api");
|
||||||
|
return getWindowScreenshot(id);
|
||||||
|
},
|
||||||
|
|
||||||
|
async openFileDialog(options: OpenDialogOptions) {
|
||||||
|
const { open } = await import("@tauri-apps/plugin-dialog");
|
||||||
|
return open(options);
|
||||||
|
},
|
||||||
|
|
||||||
|
async getFileMetadata(path: string) {
|
||||||
|
const { metadata } = await import("tauri-plugin-fs-pro-api");
|
||||||
|
return metadata(path);
|
||||||
|
},
|
||||||
|
|
||||||
|
async getFileIcon(path: string, size: number) {
|
||||||
|
const { icon } = await import("tauri-plugin-fs-pro-api");
|
||||||
|
return icon(path, size);
|
||||||
|
},
|
||||||
|
|
||||||
|
async checkUpdate() {
|
||||||
|
const { check } = await import("@tauri-apps/plugin-updater");
|
||||||
|
return check();
|
||||||
|
},
|
||||||
|
|
||||||
|
async relaunchApp() {
|
||||||
|
const { relaunch } = await import("@tauri-apps/plugin-process");
|
||||||
|
return relaunch();
|
||||||
|
},
|
||||||
|
|
||||||
|
async listenThemeChanged(callback) {
|
||||||
|
const { listen } = await import("@tauri-apps/api/event");
|
||||||
|
return listen("theme-changed", ({ payload }) => {
|
||||||
|
callback(payload);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
async getWebviewWindow() {
|
||||||
|
const { getCurrentWebviewWindow } = await import("@tauri-apps/api/webviewWindow");
|
||||||
|
return getCurrentWebviewWindow();
|
||||||
|
},
|
||||||
|
|
||||||
|
async setWindowTheme(theme) {
|
||||||
|
const window = await this.getWebviewWindow();
|
||||||
|
if (window) {
|
||||||
|
return window.setTheme(theme);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async getWindowTheme() {
|
||||||
|
const window = await this.getWebviewWindow();
|
||||||
|
if (window) {
|
||||||
|
return window.theme();
|
||||||
|
}
|
||||||
|
return 'light';
|
||||||
|
},
|
||||||
|
|
||||||
|
async onThemeChanged(callback) {
|
||||||
|
const window = await this.getWebviewWindow();
|
||||||
|
if (window) {
|
||||||
|
window.onThemeChanged(callback);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async getWindowByLabel(label: string) {
|
||||||
|
const { WebviewWindow } = await import("@tauri-apps/api/webviewWindow");
|
||||||
|
const window = await WebviewWindow.getByLabel(label);
|
||||||
|
return window;
|
||||||
|
},
|
||||||
|
|
||||||
|
async createWindow(label: string, options: any) {
|
||||||
|
const { WebviewWindow } = await import("@tauri-apps/api/webviewWindow");
|
||||||
|
new WebviewWindow(label, options);
|
||||||
|
},
|
||||||
|
|
||||||
|
async getAllWindows(): Promise<any[]> {
|
||||||
|
const { getAllWindows } = await import("@tauri-apps/api/window");
|
||||||
|
return getAllWindows();
|
||||||
|
},
|
||||||
|
|
||||||
|
async getCurrentWindow(): Promise<any> {
|
||||||
|
const { getCurrentWindow } = await import("@tauri-apps/api/window");
|
||||||
|
return getCurrentWindow();
|
||||||
|
},
|
||||||
|
|
||||||
|
async createWebviewWindow(label: string, options: any): Promise<any> {
|
||||||
|
const { WebviewWindow } = await import("@tauri-apps/api/webviewWindow");
|
||||||
|
return new WebviewWindow(label, options);
|
||||||
|
},
|
||||||
|
|
||||||
|
async listenWindowEvent(event: string, callback: (event: any) => void): Promise<() => void> {
|
||||||
|
const { listen } = await import("@tauri-apps/api/event");
|
||||||
|
return listen(event, callback);
|
||||||
|
},
|
||||||
|
|
||||||
|
isTauri(): boolean {
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
|
||||||
|
async openExternal(url: string): Promise<void> {
|
||||||
|
const { open } = await import("@tauri-apps/plugin-shell");
|
||||||
|
return open(url);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
161
src/utils/webAdapter.ts
Normal file
161
src/utils/webAdapter.ts
Normal file
@@ -0,0 +1,161 @@
|
|||||||
|
import type { PlatformAdapter, EventPayloads } from './platformAdapter';
|
||||||
|
|
||||||
|
// Create Web adapter functions
|
||||||
|
export const createWebAdapter = (): PlatformAdapter => {
|
||||||
|
return {
|
||||||
|
async invokeBackend(command: string, args?: any): Promise<any> {
|
||||||
|
console.log(`Web mode simulated backend call: ${command}`, args);
|
||||||
|
// Implement web environment simulation logic or API calls here
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
|
||||||
|
async setWindowSize(width: number, height: number): Promise<void> {
|
||||||
|
console.log(`Web mode simulated window resize: ${width}x${height}`);
|
||||||
|
// No actual operation needed in web environment
|
||||||
|
},
|
||||||
|
|
||||||
|
async hideWindow(): Promise<void> {
|
||||||
|
console.log("Web mode simulated window hide");
|
||||||
|
// No actual operation needed in web environment
|
||||||
|
},
|
||||||
|
|
||||||
|
async showWindow(): Promise<void> {
|
||||||
|
console.log("Web mode simulated window show");
|
||||||
|
// No actual operation needed in web environment
|
||||||
|
},
|
||||||
|
|
||||||
|
convertFileSrc(path: string): string {
|
||||||
|
return path;
|
||||||
|
},
|
||||||
|
|
||||||
|
async emitEvent(event: string, payload?: any): Promise<void> {
|
||||||
|
console.log("Web mode simulated event emit", event, payload);
|
||||||
|
},
|
||||||
|
|
||||||
|
async listenEvent<K extends keyof EventPayloads>(
|
||||||
|
event: K,
|
||||||
|
_callback: (event: { payload: EventPayloads[K] }) => void
|
||||||
|
): Promise<() => void> {
|
||||||
|
console.log("Web mode simulated event listen", event);
|
||||||
|
return () => { };
|
||||||
|
},
|
||||||
|
|
||||||
|
async setAlwaysOnTop(isPinned: boolean): Promise<void> {
|
||||||
|
console.log("Web mode simulated set always on top", isPinned);
|
||||||
|
},
|
||||||
|
|
||||||
|
async checkScreenRecordingPermission(): Promise<boolean> {
|
||||||
|
console.log("Web mode simulated check screen recording permission");
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
|
||||||
|
requestScreenRecordingPermission(): void {
|
||||||
|
console.log("Web mode simulated request screen recording permission");
|
||||||
|
},
|
||||||
|
|
||||||
|
async getScreenshotableMonitors(): Promise<any[]> {
|
||||||
|
console.log("Web mode simulated get screenshotable monitors");
|
||||||
|
return [];
|
||||||
|
},
|
||||||
|
|
||||||
|
async getScreenshotableWindows(): Promise<any[]> {
|
||||||
|
console.log("Web mode simulated get screenshotable windows");
|
||||||
|
return [];
|
||||||
|
},
|
||||||
|
|
||||||
|
async captureMonitorScreenshot(id: number): Promise<string> {
|
||||||
|
console.log("Web mode simulated capture monitor screenshot", id);
|
||||||
|
return "";
|
||||||
|
},
|
||||||
|
|
||||||
|
async captureWindowScreenshot(id: number): Promise<string> {
|
||||||
|
console.log("Web mode simulated capture window screenshot", id);
|
||||||
|
return "";
|
||||||
|
},
|
||||||
|
|
||||||
|
async openFileDialog(options: { multiple: boolean }): Promise<null> {
|
||||||
|
console.log("Web mode simulated open file dialog", options);
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
|
||||||
|
async getFileMetadata(path: string): Promise<null> {
|
||||||
|
console.log("Web mode simulated get file metadata", path);
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
|
||||||
|
async getFileIcon(path: string, size: number): Promise<string> {
|
||||||
|
console.log("Web mode simulated get file icon", path, size);
|
||||||
|
return "";
|
||||||
|
},
|
||||||
|
|
||||||
|
async checkUpdate(): Promise<any> {
|
||||||
|
console.log("Web mode simulated check update");
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
|
||||||
|
async relaunchApp(): Promise<void> {
|
||||||
|
console.log("Web mode simulated relaunch app");
|
||||||
|
},
|
||||||
|
|
||||||
|
async listenThemeChanged() {
|
||||||
|
console.log("Web mode simulated theme change listener");
|
||||||
|
return () => { };
|
||||||
|
},
|
||||||
|
|
||||||
|
async getWebviewWindow() {
|
||||||
|
console.log("Web mode simulated get webview window");
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
|
||||||
|
async setWindowTheme(theme) {
|
||||||
|
console.log("Web mode simulated set window theme:", theme);
|
||||||
|
},
|
||||||
|
|
||||||
|
async getWindowTheme() {
|
||||||
|
console.log("Web mode simulated get window theme");
|
||||||
|
return 'light';
|
||||||
|
},
|
||||||
|
|
||||||
|
async onThemeChanged(callback) {
|
||||||
|
console.log("Web mode simulated on theme changed", callback);
|
||||||
|
},
|
||||||
|
|
||||||
|
async getWindowByLabel(label: string) {
|
||||||
|
console.log("Web mode simulated get window by label:", label);
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
|
||||||
|
async createWindow(label: string, options: any) {
|
||||||
|
console.log("Web mode simulated create window:", label, options);
|
||||||
|
},
|
||||||
|
|
||||||
|
async getAllWindows(): Promise<any[]> {
|
||||||
|
console.log("Web mode simulated get all windows");
|
||||||
|
return [];
|
||||||
|
},
|
||||||
|
|
||||||
|
async getCurrentWindow(): Promise<any> {
|
||||||
|
console.log("Web mode simulated get current window");
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
|
||||||
|
async createWebviewWindow(label: string, options: any): Promise<any> {
|
||||||
|
console.log("Web mode simulated create webview window:", label, options);
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
|
||||||
|
async listenWindowEvent(event: string, _callback: (event: any) => void): Promise<() => void> {
|
||||||
|
console.log("Web mode simulated listen window event:", event);
|
||||||
|
return () => {};
|
||||||
|
},
|
||||||
|
|
||||||
|
isTauri(): boolean {
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
|
||||||
|
async openExternal(url: string): Promise<void> {
|
||||||
|
console.log(`Web mode opening URL: ${url}`);
|
||||||
|
window.open(url, '_blank');
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
729
src/web.css
Normal file
729
src/web.css
Normal file
@@ -0,0 +1,729 @@
|
|||||||
|
/* @tailwind base; */
|
||||||
|
@tailwind components;
|
||||||
|
@tailwind utilities;
|
||||||
|
|
||||||
|
/* Base variables */
|
||||||
|
:host,
|
||||||
|
:root,
|
||||||
|
.searchbox-container,
|
||||||
|
.coco-container {
|
||||||
|
--spacing-base: 12px;
|
||||||
|
--modal-width: 560px;
|
||||||
|
--modal-height: 600px;
|
||||||
|
--searchbox-height: 56px;
|
||||||
|
--hit-height: 56px;
|
||||||
|
--footer-height: 44px;
|
||||||
|
--icon-stroke-width: 1.4;
|
||||||
|
--background: #ffffff;
|
||||||
|
--foreground: #09090b;
|
||||||
|
--border: #e3e3e7;
|
||||||
|
--coco-primary-color: rgb(149, 5, 153);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Light theme */
|
||||||
|
.light.coco-container{
|
||||||
|
--coco-primary-color: rgb(149, 5, 153);
|
||||||
|
--coco-text-color: rgb(28, 30, 33);
|
||||||
|
--coco-muted-color: rgb(150, 159, 175);
|
||||||
|
--coco-modal-container-background: rgba(101, 108, 133, 0.8);
|
||||||
|
--coco-modal-background: rgb(245, 246, 247);
|
||||||
|
--coco-modal-shadow: inset 1px 1px 0 0 rgba(255, 255, 255, 0.5),
|
||||||
|
0 3px 8px 0 rgba(85, 90, 100, 1);
|
||||||
|
--coco-searchbox-background: rgb(235, 237, 240);
|
||||||
|
--coco-searchbox-focus-background: #fff;
|
||||||
|
--coco-hit-color: rgb(68, 73, 80);
|
||||||
|
--coco-hit-active-color: #fff;
|
||||||
|
--coco-hit-background: #fff;
|
||||||
|
--coco-hit-shadow: 0 1px 3px 0 rgb(212, 217, 225);
|
||||||
|
--coco-key-gradient: linear-gradient(
|
||||||
|
-225deg,
|
||||||
|
rgb(213, 219, 228) 0%,
|
||||||
|
rgb(248, 248, 248) 100%
|
||||||
|
);
|
||||||
|
--coco-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, 0.4);
|
||||||
|
--coco-footer-background: #fff;
|
||||||
|
--coco-footer-shadow: 0 -1px 0 0 rgb(224, 227, 232),
|
||||||
|
0 -3px 6px 0 rgba(69, 98, 155, 0.12);
|
||||||
|
--coco-icon-color: rgb(21, 21, 21);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Dark theme */
|
||||||
|
.dark.coco-container {
|
||||||
|
--coco-primary-color: rgb(149, 5, 153);
|
||||||
|
--background: #09090b;
|
||||||
|
--foreground: #f9f9f9;
|
||||||
|
--border: #27272a;
|
||||||
|
--coco-text-color: rgb(245, 246, 247);
|
||||||
|
--coco-modal-container-background: rgba(9, 10, 17, 0.8);
|
||||||
|
--coco-modal-background: rgb(21, 23, 42);
|
||||||
|
--coco-modal-shadow: inset 1px 1px 0 0 rgb(44, 46, 64),
|
||||||
|
0 3px 8px 0 rgb(0, 3, 9);
|
||||||
|
--coco-searchbox-background: rgb(9, 10, 17);
|
||||||
|
--coco-searchbox-focus-background: #000;
|
||||||
|
--coco-hit-color: rgb(190, 195, 201);
|
||||||
|
--coco-hit-shadow: none;
|
||||||
|
--coco-hit-background: rgb(9, 10, 17);
|
||||||
|
--coco-key-gradient: linear-gradient(
|
||||||
|
-26.5deg,
|
||||||
|
rgb(86, 88, 114) 0%,
|
||||||
|
rgb(49, 53, 91) 100%
|
||||||
|
);
|
||||||
|
--coco-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, 0.3);
|
||||||
|
--coco-footer-background: rgb(30, 33, 54);
|
||||||
|
--coco-footer-shadow: inset 0 1px 0 0 rgba(73, 76, 106, 0.5),
|
||||||
|
0 -4px 8px 0 rgba(0, 0, 0, 0.2);
|
||||||
|
--coco-muted-color: rgb(127, 132, 151);
|
||||||
|
--coco-icon-color: rgb(255, 255, 255);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
.dark.coco-container {
|
||||||
|
--coco-primary-color: rgb(149, 5, 153);
|
||||||
|
--background: #09090b;
|
||||||
|
--foreground: #f9f9f9;
|
||||||
|
--border: #27272a;
|
||||||
|
--coco-text-color: rgb(245, 246, 247);
|
||||||
|
--coco-modal-container-background: rgba(9, 10, 17, 0.8);
|
||||||
|
--coco-modal-background: rgb(21, 23, 42);
|
||||||
|
--coco-modal-shadow: inset 1px 1px 0 0 rgb(44, 46, 64),
|
||||||
|
0 3px 8px 0 rgb(0, 3, 9);
|
||||||
|
--coco-searchbox-background: rgb(9, 10, 17);
|
||||||
|
--coco-searchbox-focus-background: #000;
|
||||||
|
--coco-hit-color: rgb(190, 195, 201);
|
||||||
|
--coco-hit-shadow: none;
|
||||||
|
--coco-hit-background: rgb(9, 10, 17);
|
||||||
|
--coco-key-gradient: linear-gradient(
|
||||||
|
-26.5deg,
|
||||||
|
rgb(86, 88, 114) 0%,
|
||||||
|
rgb(49, 53, 91) 100%
|
||||||
|
);
|
||||||
|
--coco-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, 0.3);
|
||||||
|
--coco-footer-background: rgb(30, 33, 54);
|
||||||
|
--coco-footer-shadow: inset 0 1px 0 0 rgba(73, 76, 106, 0.5),
|
||||||
|
0 -4px 8px 0 rgba(0, 0, 0, 0.2);
|
||||||
|
--coco-muted-color: rgb(127, 132, 151);
|
||||||
|
--coco-icon-color: rgb(255, 255, 255);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* html,
|
||||||
|
:host {
|
||||||
|
line-height: 1.5;
|
||||||
|
-webkit-text-size-adjust: 100%;
|
||||||
|
-moz-tab-size: 4;
|
||||||
|
-o-tab-size: 4;
|
||||||
|
tab-size: 4;
|
||||||
|
font-family: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji",
|
||||||
|
"Segoe UI Emoji", Segoe UI Symbol, "Noto Color Emoji";
|
||||||
|
font-feature-settings: normal;
|
||||||
|
font-variation-settings: normal;
|
||||||
|
-webkit-tap-highlight-color: transparent;
|
||||||
|
} */
|
||||||
|
|
||||||
|
/* html {
|
||||||
|
height: 100%;
|
||||||
|
-webkit-user-select: none;
|
||||||
|
-moz-user-select: none;
|
||||||
|
user-select: none;
|
||||||
|
overscroll-behavior: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
body,
|
||||||
|
#root {
|
||||||
|
height: 100%;
|
||||||
|
--tw-text-opacity: 1;
|
||||||
|
color: rgb(17 24 39 / var(--tw-text-opacity, 1));
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
} */
|
||||||
|
|
||||||
|
/* body {
|
||||||
|
margin: 0;
|
||||||
|
line-height: inherit;
|
||||||
|
} */
|
||||||
|
|
||||||
|
.dark body,
|
||||||
|
.dark #root,
|
||||||
|
.dark.coco-container,
|
||||||
|
.dark.coco-container {
|
||||||
|
--tw-text-opacity: 1;
|
||||||
|
color: rgb(243 244 246 / var(--tw-text-opacity, 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-body {
|
||||||
|
overflow: hidden;
|
||||||
|
border-radius: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.\!container {
|
||||||
|
width: 100% !important;
|
||||||
|
}
|
||||||
|
.container {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
@media (min-width: 640px) {
|
||||||
|
.\!container {
|
||||||
|
max-width: 640px !important;
|
||||||
|
}
|
||||||
|
.container {
|
||||||
|
max-width: 640px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
.\!container {
|
||||||
|
max-width: 768px !important;
|
||||||
|
}
|
||||||
|
.container {
|
||||||
|
max-width: 768px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@media (min-width: 1024px) {
|
||||||
|
.\!container {
|
||||||
|
max-width: 1024px !important;
|
||||||
|
}
|
||||||
|
.container {
|
||||||
|
max-width: 1024px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@media (min-width: 1280px) {
|
||||||
|
.\!container {
|
||||||
|
max-width: 1280px !important;
|
||||||
|
}
|
||||||
|
.container {
|
||||||
|
max-width: 1280px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@media (min-width: 1536px) {
|
||||||
|
.\!container {
|
||||||
|
max-width: 1536px !important;
|
||||||
|
}
|
||||||
|
.container {
|
||||||
|
max-width: 1536px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.coco-container {
|
||||||
|
line-height: 1.5;
|
||||||
|
-webkit-text-size-adjust: 100%;
|
||||||
|
-moz-tab-size: 4;
|
||||||
|
-o-tab-size: 4;
|
||||||
|
tab-size: 4;
|
||||||
|
font-family: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji",
|
||||||
|
"Segoe UI Emoji", Segoe UI Symbol, "Noto Color Emoji";
|
||||||
|
font-feature-settings: normal;
|
||||||
|
font-variation-settings: normal;
|
||||||
|
-webkit-tap-highlight-color: transparent;
|
||||||
|
|
||||||
|
*,
|
||||||
|
:before,
|
||||||
|
:after {
|
||||||
|
--tw-border-spacing-x: 0;
|
||||||
|
--tw-border-spacing-y: 0;
|
||||||
|
--tw-translate-x: 0;
|
||||||
|
--tw-translate-y: 0;
|
||||||
|
--tw-rotate: 0;
|
||||||
|
--tw-skew-x: 0;
|
||||||
|
--tw-skew-y: 0;
|
||||||
|
--tw-scale-x: 1;
|
||||||
|
--tw-scale-y: 1;
|
||||||
|
--tw-pan-x: ;
|
||||||
|
--tw-pan-y: ;
|
||||||
|
--tw-pinch-zoom: ;
|
||||||
|
--tw-scroll-snap-strictness: proximity;
|
||||||
|
--tw-gradient-from-position: ;
|
||||||
|
--tw-gradient-via-position: ;
|
||||||
|
--tw-gradient-to-position: ;
|
||||||
|
--tw-ordinal: ;
|
||||||
|
--tw-slashed-zero: ;
|
||||||
|
--tw-numeric-figure: ;
|
||||||
|
--tw-numeric-spacing: ;
|
||||||
|
--tw-numeric-fraction: ;
|
||||||
|
--tw-ring-inset: ;
|
||||||
|
--tw-ring-offset-width: 0px;
|
||||||
|
--tw-ring-offset-color: #fff;
|
||||||
|
--tw-ring-color: rgb(59 130 246 / 0.5);
|
||||||
|
--tw-ring-offset-shadow: 0 0 #0000;
|
||||||
|
--tw-ring-shadow: 0 0 #0000;
|
||||||
|
--tw-shadow: 0 0 #0000;
|
||||||
|
--tw-shadow-colored: 0 0 #0000;
|
||||||
|
--tw-blur: ;
|
||||||
|
--tw-brightness: ;
|
||||||
|
--tw-contrast: ;
|
||||||
|
--tw-grayscale: ;
|
||||||
|
--tw-hue-rotate: ;
|
||||||
|
--tw-invert: ;
|
||||||
|
--tw-saturate: ;
|
||||||
|
--tw-sepia: ;
|
||||||
|
--tw-drop-shadow: ;
|
||||||
|
--tw-backdrop-blur: ;
|
||||||
|
--tw-backdrop-brightness: ;
|
||||||
|
--tw-backdrop-contrast: ;
|
||||||
|
--tw-backdrop-grayscale: ;
|
||||||
|
--tw-backdrop-hue-rotate: ;
|
||||||
|
--tw-backdrop-invert: ;
|
||||||
|
--tw-backdrop-opacity: ;
|
||||||
|
--tw-backdrop-saturate: ;
|
||||||
|
--tw-backdrop-sepia: ;
|
||||||
|
--tw-contain-size: ;
|
||||||
|
--tw-contain-layout: ;
|
||||||
|
--tw-contain-paint: ;
|
||||||
|
--tw-contain-style: ;
|
||||||
|
}
|
||||||
|
::backdrop {
|
||||||
|
--tw-border-spacing-x: 0;
|
||||||
|
--tw-border-spacing-y: 0;
|
||||||
|
--tw-translate-x: 0;
|
||||||
|
--tw-translate-y: 0;
|
||||||
|
--tw-rotate: 0;
|
||||||
|
--tw-skew-x: 0;
|
||||||
|
--tw-skew-y: 0;
|
||||||
|
--tw-scale-x: 1;
|
||||||
|
--tw-scale-y: 1;
|
||||||
|
--tw-pan-x: ;
|
||||||
|
--tw-pan-y: ;
|
||||||
|
--tw-pinch-zoom: ;
|
||||||
|
--tw-scroll-snap-strictness: proximity;
|
||||||
|
--tw-gradient-from-position: ;
|
||||||
|
--tw-gradient-via-position: ;
|
||||||
|
--tw-gradient-to-position: ;
|
||||||
|
--tw-ordinal: ;
|
||||||
|
--tw-slashed-zero: ;
|
||||||
|
--tw-numeric-figure: ;
|
||||||
|
--tw-numeric-spacing: ;
|
||||||
|
--tw-numeric-fraction: ;
|
||||||
|
--tw-ring-inset: ;
|
||||||
|
--tw-ring-offset-width: 0px;
|
||||||
|
--tw-ring-offset-color: #fff;
|
||||||
|
--tw-ring-color: rgb(59 130 246 / 0.5);
|
||||||
|
--tw-ring-offset-shadow: 0 0 #0000;
|
||||||
|
--tw-ring-shadow: 0 0 #0000;
|
||||||
|
--tw-shadow: 0 0 #0000;
|
||||||
|
--tw-shadow-colored: 0 0 #0000;
|
||||||
|
--tw-blur: ;
|
||||||
|
--tw-brightness: ;
|
||||||
|
--tw-contrast: ;
|
||||||
|
--tw-grayscale: ;
|
||||||
|
--tw-hue-rotate: ;
|
||||||
|
--tw-invert: ;
|
||||||
|
--tw-saturate: ;
|
||||||
|
--tw-sepia: ;
|
||||||
|
--tw-drop-shadow: ;
|
||||||
|
--tw-backdrop-blur: ;
|
||||||
|
--tw-backdrop-brightness: ;
|
||||||
|
--tw-backdrop-contrast: ;
|
||||||
|
--tw-backdrop-grayscale: ;
|
||||||
|
--tw-backdrop-hue-rotate: ;
|
||||||
|
--tw-backdrop-invert: ;
|
||||||
|
--tw-backdrop-opacity: ;
|
||||||
|
--tw-backdrop-saturate: ;
|
||||||
|
--tw-backdrop-sepia: ;
|
||||||
|
--tw-contain-size: ;
|
||||||
|
--tw-contain-layout: ;
|
||||||
|
--tw-contain-paint: ;
|
||||||
|
--tw-contain-style: ;
|
||||||
|
}
|
||||||
|
*,
|
||||||
|
:before,
|
||||||
|
:after {
|
||||||
|
box-sizing: border-box;
|
||||||
|
border-width: 0;
|
||||||
|
border-style: solid;
|
||||||
|
border-color: #e5e7eb;
|
||||||
|
}
|
||||||
|
:before,
|
||||||
|
:after {
|
||||||
|
--tw-content: "";
|
||||||
|
}
|
||||||
|
|
||||||
|
hr {
|
||||||
|
height: 0;
|
||||||
|
color: inherit;
|
||||||
|
border-top-width: 1px;
|
||||||
|
}
|
||||||
|
abbr:where([title]) {
|
||||||
|
-webkit-text-decoration: underline dotted;
|
||||||
|
text-decoration: underline dotted;
|
||||||
|
}
|
||||||
|
h1,
|
||||||
|
h2,
|
||||||
|
h3,
|
||||||
|
h4,
|
||||||
|
h5,
|
||||||
|
h6 {
|
||||||
|
font-size: inherit;
|
||||||
|
font-weight: inherit;
|
||||||
|
}
|
||||||
|
a {
|
||||||
|
color: inherit;
|
||||||
|
text-decoration: inherit;
|
||||||
|
}
|
||||||
|
b,
|
||||||
|
strong {
|
||||||
|
font-weight: bolder;
|
||||||
|
}
|
||||||
|
code,
|
||||||
|
kbd,
|
||||||
|
samp,
|
||||||
|
pre {
|
||||||
|
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas,
|
||||||
|
Liberation Mono, Courier New, monospace;
|
||||||
|
font-feature-settings: normal;
|
||||||
|
font-variation-settings: normal;
|
||||||
|
font-size: 1em;
|
||||||
|
}
|
||||||
|
small {
|
||||||
|
font-size: 80%;
|
||||||
|
}
|
||||||
|
sub,
|
||||||
|
sup {
|
||||||
|
font-size: 75%;
|
||||||
|
line-height: 0;
|
||||||
|
position: relative;
|
||||||
|
vertical-align: baseline;
|
||||||
|
}
|
||||||
|
sub {
|
||||||
|
bottom: -0.25em;
|
||||||
|
}
|
||||||
|
sup {
|
||||||
|
top: -0.5em;
|
||||||
|
}
|
||||||
|
table {
|
||||||
|
text-indent: 0;
|
||||||
|
border-color: inherit;
|
||||||
|
border-collapse: collapse;
|
||||||
|
}
|
||||||
|
button,
|
||||||
|
input,
|
||||||
|
optgroup,
|
||||||
|
select,
|
||||||
|
textarea {
|
||||||
|
font-family: inherit;
|
||||||
|
font-feature-settings: inherit;
|
||||||
|
font-variation-settings: inherit;
|
||||||
|
font-size: 100%;
|
||||||
|
font-weight: inherit;
|
||||||
|
line-height: inherit;
|
||||||
|
letter-spacing: inherit;
|
||||||
|
color: inherit;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
button,
|
||||||
|
select {
|
||||||
|
text-transform: none;
|
||||||
|
}
|
||||||
|
button,
|
||||||
|
input:where([type="button"]),
|
||||||
|
input:where([type="reset"]),
|
||||||
|
input:where([type="submit"]) {
|
||||||
|
-webkit-appearance: button;
|
||||||
|
background-color: transparent;
|
||||||
|
background-image: none;
|
||||||
|
}
|
||||||
|
:-moz-focusring {
|
||||||
|
outline: auto;
|
||||||
|
}
|
||||||
|
:-moz-ui-invalid {
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
progress {
|
||||||
|
vertical-align: baseline;
|
||||||
|
}
|
||||||
|
::-webkit-inner-spin-button,
|
||||||
|
::-webkit-outer-spin-button {
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
[type="search"] {
|
||||||
|
-webkit-appearance: textfield;
|
||||||
|
outline-offset: -2px;
|
||||||
|
}
|
||||||
|
::-webkit-search-decoration {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
}
|
||||||
|
::-webkit-file-upload-button {
|
||||||
|
-webkit-appearance: button;
|
||||||
|
font: inherit;
|
||||||
|
}
|
||||||
|
summary {
|
||||||
|
display: list-item;
|
||||||
|
}
|
||||||
|
blockquote,
|
||||||
|
dl,
|
||||||
|
dd,
|
||||||
|
h1,
|
||||||
|
h2,
|
||||||
|
h3,
|
||||||
|
h4,
|
||||||
|
h5,
|
||||||
|
h6,
|
||||||
|
hr,
|
||||||
|
figure,
|
||||||
|
p,
|
||||||
|
pre {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
fieldset {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
legend {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
ol,
|
||||||
|
ul,
|
||||||
|
menu {
|
||||||
|
list-style: none;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
dialog {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
textarea {
|
||||||
|
resize: vertical;
|
||||||
|
}
|
||||||
|
input::-moz-placeholder,
|
||||||
|
textarea::-moz-placeholder {
|
||||||
|
opacity: 1;
|
||||||
|
color: #9ca3af;
|
||||||
|
}
|
||||||
|
input::placeholder,
|
||||||
|
textarea::placeholder {
|
||||||
|
opacity: 1;
|
||||||
|
color: #9ca3af;
|
||||||
|
}
|
||||||
|
button,
|
||||||
|
[role="button"] {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
:disabled {
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
img,
|
||||||
|
svg,
|
||||||
|
video,
|
||||||
|
canvas,
|
||||||
|
audio,
|
||||||
|
iframe,
|
||||||
|
embed,
|
||||||
|
object {
|
||||||
|
display: block;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
img,
|
||||||
|
video {
|
||||||
|
max-width: 100%;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
[hidden]:where(:not([hidden="until-found"])) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
border-color: var(--border);
|
||||||
|
}
|
||||||
|
.settings-input {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
border-radius: 0.375rem;
|
||||||
|
--tw-border-opacity: 1;
|
||||||
|
border-color: rgb(209 213 219 / var(--tw-border-opacity, 1));
|
||||||
|
--tw-bg-opacity: 1;
|
||||||
|
background-color: rgb(255 255 255 / var(--tw-bg-opacity, 1));
|
||||||
|
--tw-text-opacity: 1;
|
||||||
|
color: rgb(17 24 39 / var(--tw-text-opacity, 1));
|
||||||
|
--tw-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05);
|
||||||
|
--tw-shadow-colored: 0 1px 2px 0 var(--tw-shadow-color);
|
||||||
|
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000),
|
||||||
|
var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
|
||||||
|
transition-property: color, background-color, border-color,
|
||||||
|
text-decoration-color, fill, stroke;
|
||||||
|
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
transition-duration: 0.2s;
|
||||||
|
}
|
||||||
|
.settings-input:focus {
|
||||||
|
--tw-border-opacity: 1;
|
||||||
|
border-color: rgb(59 130 246 / var(--tw-border-opacity, 1));
|
||||||
|
--tw-ring-opacity: 1;
|
||||||
|
--tw-ring-color: rgb(59 130 246 / var(--tw-ring-opacity, 1));
|
||||||
|
}
|
||||||
|
.settings-input:is([data-theme="dark"] *) {
|
||||||
|
--tw-border-opacity: 1;
|
||||||
|
border-color: rgb(75 85 99 / var(--tw-border-opacity, 1));
|
||||||
|
--tw-bg-opacity: 1;
|
||||||
|
background-color: rgb(55 65 81 / var(--tw-bg-opacity, 1));
|
||||||
|
--tw-text-opacity: 1;
|
||||||
|
color: rgb(243 244 246 / var(--tw-text-opacity, 1));
|
||||||
|
}
|
||||||
|
.settings-select {
|
||||||
|
border-radius: 0.375rem;
|
||||||
|
--tw-border-opacity: 1;
|
||||||
|
border-color: rgb(209 213 219 / var(--tw-border-opacity, 1));
|
||||||
|
--tw-bg-opacity: 1;
|
||||||
|
background-color: rgb(255 255 255 / var(--tw-bg-opacity, 1));
|
||||||
|
font-size: 0.875rem;
|
||||||
|
line-height: 1.25rem;
|
||||||
|
--tw-text-opacity: 1;
|
||||||
|
color: rgb(17 24 39 / var(--tw-text-opacity, 1));
|
||||||
|
--tw-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05);
|
||||||
|
--tw-shadow-colored: 0 1px 2px 0 var(--tw-shadow-color);
|
||||||
|
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000),
|
||||||
|
var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
|
||||||
|
transition-property: color, background-color, border-color,
|
||||||
|
text-decoration-color, fill, stroke;
|
||||||
|
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
transition-duration: 0.2s;
|
||||||
|
}
|
||||||
|
.settings-select:focus {
|
||||||
|
--tw-border-opacity: 1;
|
||||||
|
border-color: rgb(59 130 246 / var(--tw-border-opacity, 1));
|
||||||
|
--tw-ring-opacity: 1;
|
||||||
|
--tw-ring-color: rgb(59 130 246 / var(--tw-ring-opacity, 1));
|
||||||
|
}
|
||||||
|
.settings-select:is([data-theme="dark"] *) {
|
||||||
|
--tw-border-opacity: 1;
|
||||||
|
border-color: rgb(75 85 99 / var(--tw-border-opacity, 1));
|
||||||
|
--tw-bg-opacity: 1;
|
||||||
|
background-color: rgb(55 65 81 / var(--tw-bg-opacity, 1));
|
||||||
|
--tw-text-opacity: 1;
|
||||||
|
color: rgb(243 244 246 / var(--tw-text-opacity, 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Component styles */
|
||||||
|
@layer components {
|
||||||
|
.settings-input {
|
||||||
|
@apply block w-full rounded-md border-gray-300 dark:border-gray-600
|
||||||
|
bg-white dark:bg-gray-700
|
||||||
|
text-gray-900 dark:text-gray-100
|
||||||
|
shadow-sm focus:border-blue-500 focus:ring-blue-500
|
||||||
|
transition-colors duration-200;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-select {
|
||||||
|
@apply text-sm rounded-md border-gray-300 dark:border-gray-600
|
||||||
|
bg-white dark:bg-gray-700
|
||||||
|
text-gray-900 dark:text-gray-100
|
||||||
|
shadow-sm focus:border-blue-500 focus:ring-blue-500
|
||||||
|
transition-colors duration-200;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Utility styles */
|
||||||
|
@layer utilities {
|
||||||
|
/* Scrollbar styles */
|
||||||
|
.custom-scrollbar {
|
||||||
|
scrollbar-width: thin;
|
||||||
|
scrollbar-color: #cbd5e1 transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-scrollbar::-webkit-scrollbar {
|
||||||
|
width: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-scrollbar::-webkit-scrollbar-track {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-scrollbar::-webkit-scrollbar-thumb {
|
||||||
|
background-color: #cbd5e1;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .custom-scrollbar {
|
||||||
|
scrollbar-color: #475569 transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .custom-scrollbar::-webkit-scrollbar-thumb {
|
||||||
|
background-color: #475569;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Background styles */
|
||||||
|
.bg-100 {
|
||||||
|
background-size: 100% 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Error page styles */
|
||||||
|
#error-page {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
height: 100%;
|
||||||
|
background: linear-gradient(to right, #f79c42, #f2d600);
|
||||||
|
font-family: "Arial", sans-serif;
|
||||||
|
color: #fff;
|
||||||
|
text-align: center;
|
||||||
|
padding: 0 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-content {
|
||||||
|
background-color: rgba(0, 0, 0, 0.6);
|
||||||
|
padding: 40px;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0px 10px 20px rgba(0, 0, 0, 0.2);
|
||||||
|
max-width: 500px;
|
||||||
|
width: 100%;
|
||||||
|
margin: 0 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-title {
|
||||||
|
font-size: 60px;
|
||||||
|
font-weight: bold;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
color: #f2d600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-message {
|
||||||
|
font-size: 18px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
font-weight: 300;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-details {
|
||||||
|
font-size: 16px;
|
||||||
|
color: #ffcc00;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-content button {
|
||||||
|
background-color: #f2d600;
|
||||||
|
border: none;
|
||||||
|
padding: 10px 20px;
|
||||||
|
font-size: 16px;
|
||||||
|
color: #333;
|
||||||
|
border-radius: 5px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-content button:hover {
|
||||||
|
background-color: #f79c42;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* coco styles */
|
||||||
|
.coco-modal-footer-commands-key {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border-radius: 4px;
|
||||||
|
border: 0;
|
||||||
|
padding: 2px;
|
||||||
|
background: var(--coco-key-gradient);
|
||||||
|
box-shadow: var(--coco-key-shadow);
|
||||||
|
color: var(--coco-muted-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* User selection styles */
|
||||||
|
.user-select {
|
||||||
|
-webkit-touch-callout: none;
|
||||||
|
-webkit-user-select: none;
|
||||||
|
-khtml-user-select: none;
|
||||||
|
-moz-user-select: none;
|
||||||
|
-ms-user-select: none;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
/** @type {import('tailwindcss').Config} */
|
/** @type {import('tailwindcss').Config} */
|
||||||
export default {
|
export default {
|
||||||
content: ["./index.html", "./src/**/*.{html,js,jsx,ts,tsx}"],
|
content: ["./index.html", "./src/**/*.{html,js,jsx,ts,tsx}", "./src/**/*.css"],
|
||||||
|
important: '.coco-container',
|
||||||
theme: {
|
theme: {
|
||||||
extend: {
|
extend: {
|
||||||
backgroundColor: {
|
backgroundColor: {
|
||||||
|
|||||||
@@ -3,18 +3,22 @@ import { writeFileSync, readFileSync } from 'fs';
|
|||||||
import { join, resolve } from 'path';
|
import { join, resolve } from 'path';
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
entry: ['src/pages/web/SearchChat.tsx'],
|
entry: ['src/pages/web/index.tsx'],
|
||||||
format: ['esm', 'cjs'],
|
format: ['esm'],
|
||||||
dts: true,
|
dts: true,
|
||||||
splitting: false,
|
splitting: true,
|
||||||
sourcemap: true,
|
sourcemap: false,
|
||||||
clean: true,
|
clean: true,
|
||||||
|
treeshake: true,
|
||||||
|
minify: true,
|
||||||
|
env: {
|
||||||
|
BUILD_TARGET: 'web',
|
||||||
|
NODE_ENV: 'production',
|
||||||
|
},
|
||||||
external: [
|
external: [
|
||||||
'react',
|
'react',
|
||||||
'react-dom',
|
'react-dom',
|
||||||
],
|
],
|
||||||
treeshake: true,
|
|
||||||
minify: true,
|
|
||||||
esbuildOptions(options) {
|
esbuildOptions(options) {
|
||||||
options.bundle = true;
|
options.bundle = true;
|
||||||
options.platform = 'browser';
|
options.platform = 'browser';
|
||||||
@@ -28,6 +32,22 @@ export default defineConfig({
|
|||||||
options.alias = {
|
options.alias = {
|
||||||
'@': resolve(__dirname, './src')
|
'@': resolve(__dirname, './src')
|
||||||
}
|
}
|
||||||
|
options.external = [
|
||||||
|
'@tauri-apps/api',
|
||||||
|
'@tauri-apps/plugin-*',
|
||||||
|
'tauri-plugin-*',
|
||||||
|
];
|
||||||
|
options.treeShaking = true;
|
||||||
|
options.define = {
|
||||||
|
'process.env.BUILD_TARGET': '"web"',
|
||||||
|
'process.env.NODE_ENV': '"production"',
|
||||||
|
'process.env.DEBUG': 'false',
|
||||||
|
'process.env.IS_DEV': 'false',
|
||||||
|
};
|
||||||
|
options.pure = ['console.log'];
|
||||||
|
options.target = 'es2020';
|
||||||
|
options.legalComments = 'none';
|
||||||
|
options.ignoreAnnotations = false;
|
||||||
},
|
},
|
||||||
esbuildPlugins: [
|
esbuildPlugins: [
|
||||||
{
|
{
|
||||||
@@ -38,36 +58,65 @@ export default defineConfig({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
outDir: 'dist/search-chat',
|
outDir: 'out/search-chat',
|
||||||
|
|
||||||
async onSuccess() {
|
async onSuccess() {
|
||||||
const projectPackageJson = JSON.parse(
|
const projectPackageJson = JSON.parse(
|
||||||
readFileSync(join(__dirname, 'package.json'), 'utf-8')
|
readFileSync(join(__dirname, 'package.json'), 'utf-8')
|
||||||
);
|
);
|
||||||
|
|
||||||
const packageJson = {
|
const packageJson = {
|
||||||
name: "search-chat",
|
name: "@infinilabs/search-chat",
|
||||||
version: "1.0.0",
|
version: "1.0.10",
|
||||||
main: "SearchChat.cjs",
|
main: "index.js",
|
||||||
module: "SearchChat.js",
|
module: "index.js",
|
||||||
types: "SearchChat.d.ts",
|
type: "module",
|
||||||
|
types: "index.d.ts",
|
||||||
dependencies: projectPackageJson.dependencies,
|
dependencies: projectPackageJson.dependencies,
|
||||||
peerDependencies: {
|
peerDependencies: {
|
||||||
"react": "^18.0.0",
|
"react": "^18.0.0",
|
||||||
"react-dom": "^18.0.0"
|
"react-dom": "^18.0.0"
|
||||||
|
},
|
||||||
|
"sideEffects": [
|
||||||
|
"*.css",
|
||||||
|
"*.scss"
|
||||||
|
],
|
||||||
|
"publishConfig": {
|
||||||
|
"access": "public",
|
||||||
|
"registry": "https://registry.npmjs.org/"
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const noNeedDeps = [
|
||||||
|
"@wavesurfer/react",
|
||||||
|
"dotenv",
|
||||||
|
"uuid",
|
||||||
|
"wavesurfer.js",
|
||||||
|
]
|
||||||
|
|
||||||
const tauriDeps = Object.keys(packageJson.dependencies).filter(dep =>
|
const tauriDeps = Object.keys(packageJson.dependencies).filter(dep =>
|
||||||
dep.includes('@tauri-apps') ||
|
dep.includes('@tauri-apps') ||
|
||||||
dep.includes('tauri-plugin')
|
dep.includes('tauri-plugin') ||
|
||||||
|
noNeedDeps.includes(dep)
|
||||||
);
|
);
|
||||||
tauriDeps.forEach(dep => {
|
tauriDeps.forEach(dep => {
|
||||||
delete packageJson.dependencies[dep];
|
delete packageJson.dependencies[dep];
|
||||||
});
|
});
|
||||||
|
|
||||||
writeFileSync(
|
writeFileSync(
|
||||||
join(__dirname, 'dist/search-chat/package.json'),
|
join(__dirname, 'out/search-chat/package.json'),
|
||||||
JSON.stringify(packageJson, null, 2)
|
JSON.stringify(packageJson, null, 2)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const readmePath = join(__dirname, 'src/pages/web/README.md');
|
||||||
|
const readmeContent = readFileSync(readmePath, 'utf-8');
|
||||||
|
writeFileSync(
|
||||||
|
join(__dirname, 'out/search-chat/README.md'),
|
||||||
|
readmeContent
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to copy README.md:', error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -56,6 +56,11 @@ export default defineConfig(async () => ({
|
|||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
secure: false,
|
secure: false,
|
||||||
},
|
},
|
||||||
|
"/integration": {
|
||||||
|
target: process.env.COCO_SERVER_URL,
|
||||||
|
changeOrigin: true,
|
||||||
|
secure: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
build: {
|
build: {
|
||||||
|
|||||||
Reference in New Issue
Block a user