mirror of
https://github.com/infinilabs/coco-app.git
synced 2025-12-16 11:37:47 +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",
|
||||
"VITE",
|
||||
"walkdir",
|
||||
"wavesurfer",
|
||||
"webviews",
|
||||
"xzvf",
|
||||
"yuque",
|
||||
|
||||
@@ -19,6 +19,8 @@ Information about release notes of Coco Server is provided here.
|
||||
|
||||
### Improvements
|
||||
|
||||
- refactor: web components #331
|
||||
|
||||
## 0.3.0 (2025-03-31)
|
||||
|
||||
### Breaking changes
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "tsc && vite build",
|
||||
"build:web": "tsc && tsup",
|
||||
"publish:web": "cd dist/search-chat && npm publish",
|
||||
"build:web": "cross-env BUILD_TARGET=web tsc && cross-env BUILD_TARGET=web tsup --format esm",
|
||||
"publish:web": "cd out/search-chat && npm publish",
|
||||
"publish:web:beta": "cd dist/search-chat && npm publish --tag beta",
|
||||
"publish:web:alpha": "cd dist/search-chat && npm publish --tag alpha",
|
||||
"publish:web:rc": "cd dist/search-chat && npm publish --tag rc",
|
||||
@@ -34,6 +34,7 @@
|
||||
"@tauri-apps/plugin-window": "2.0.0-alpha.1",
|
||||
"@wavesurfer/react": "^1.0.9",
|
||||
"ahooks": "^3.8.4",
|
||||
"axios": "^1.8.4",
|
||||
"clsx": "^2.1.1",
|
||||
"dayjs": "^1.11.13",
|
||||
"dotenv": "^16.4.7",
|
||||
@@ -76,6 +77,7 @@
|
||||
"@types/react-window": "^1.8.8",
|
||||
"@vitejs/plugin-react": "^4.3.4",
|
||||
"autoprefixer": "^10.4.21",
|
||||
"cross-env": "^7.0.3",
|
||||
"immer": "^10.1.1",
|
||||
"postcss": "^8.5.3",
|
||||
"release-it": "^18.1.2",
|
||||
|
||||
169
pnpm-lock.yaml
generated
169
pnpm-lock.yaml
generated
@@ -56,6 +56,9 @@ importers:
|
||||
ahooks:
|
||||
specifier: ^3.8.4
|
||||
version: 3.8.4(react@18.3.1)
|
||||
axios:
|
||||
specifier: ^1.8.4
|
||||
version: 1.8.4
|
||||
clsx:
|
||||
specifier: ^2.1.1
|
||||
version: 2.1.1
|
||||
@@ -177,6 +180,9 @@ importers:
|
||||
autoprefixer:
|
||||
specifier: ^10.4.21
|
||||
version: 10.4.21(postcss@8.5.3)
|
||||
cross-env:
|
||||
specifier: ^7.0.3
|
||||
version: 7.0.3
|
||||
immer:
|
||||
specifier: ^10.1.1
|
||||
version: 10.1.1
|
||||
@@ -1418,6 +1424,9 @@ packages:
|
||||
async-retry@1.3.3:
|
||||
resolution: {integrity: sha512-wfr/jstw9xNi/0teMHrRW7dsz3Lt5ARhYNZ2ewpadnhaIp5mbALhOAP+EAdsC7t4Z6wqsDVv9+W6gm1Dk9mEyw==}
|
||||
|
||||
asynckit@0.4.0:
|
||||
resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
|
||||
|
||||
atomically@2.0.3:
|
||||
resolution: {integrity: sha512-kU6FmrwZ3Lx7/7y3hPS5QnbJfaohcIul5fGqf7ok+4KklIEk9tJ0C2IQPdacSbVUWv6zVHXEBWoWd6NrVMT7Cw==}
|
||||
|
||||
@@ -1428,6 +1437,9 @@ packages:
|
||||
peerDependencies:
|
||||
postcss: ^8.1.0
|
||||
|
||||
axios@1.8.4:
|
||||
resolution: {integrity: sha512-eBSYY4Y68NNlHbHBMdeDmKNtDgXWhQsJcGqzO3iLUM0GraQFSS9cVgPX5I9b3lbdFKyYoAEGAZF1DwhTaljNAw==}
|
||||
|
||||
bail@2.0.2:
|
||||
resolution: {integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==}
|
||||
|
||||
@@ -1478,6 +1490,10 @@ packages:
|
||||
resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==}
|
||||
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:
|
||||
resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==}
|
||||
engines: {node: '>=6'}
|
||||
@@ -1565,6 +1581,10 @@ packages:
|
||||
color-name@1.1.4:
|
||||
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:
|
||||
resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==}
|
||||
|
||||
@@ -1618,6 +1638,11 @@ packages:
|
||||
typescript:
|
||||
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:
|
||||
resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
|
||||
engines: {node: '>= 8'}
|
||||
@@ -1828,6 +1853,10 @@ packages:
|
||||
delaunator@5.0.1:
|
||||
resolution: {integrity: sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw==}
|
||||
|
||||
delayed-stream@1.0.0:
|
||||
resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
|
||||
engines: {node: '>=0.4.0'}
|
||||
|
||||
dequal@2.0.3:
|
||||
resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==}
|
||||
engines: {node: '>=6'}
|
||||
@@ -1852,6 +1881,10 @@ packages:
|
||||
resolution: {integrity: sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
dunder-proto@1.0.1:
|
||||
resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
eastasianwidth@0.2.0:
|
||||
resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==}
|
||||
|
||||
@@ -1878,6 +1911,22 @@ packages:
|
||||
error-ex@1.3.2:
|
||||
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:
|
||||
resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==}
|
||||
engines: {node: '>=12'}
|
||||
@@ -1969,10 +2018,23 @@ packages:
|
||||
resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==}
|
||||
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:
|
||||
resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==}
|
||||
engines: {node: '>=14'}
|
||||
|
||||
form-data@4.0.2:
|
||||
resolution: {integrity: sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==}
|
||||
engines: {node: '>= 6'}
|
||||
|
||||
fraction.js@4.3.7:
|
||||
resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==}
|
||||
|
||||
@@ -1995,6 +2057,14 @@ packages:
|
||||
resolution: {integrity: sha512-vpeMIQKxczTD/0s2CdEWHcb0eeJe6TFjxb+J5xgX7hScxqrGuyjmv4c1D4A/gelKfyox0gJJwIHF+fLjeaM8kQ==}
|
||||
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:
|
||||
resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==}
|
||||
engines: {node: '>=16'}
|
||||
@@ -2048,6 +2118,10 @@ packages:
|
||||
resolution: {integrity: sha512-s3Fq41ZVh7vbbe2PN3nrW7yC7U7MFVc5c98/iTl9c2GawNMKx/J648KQRW6WKkuU8GIbbh2IXfIRQjOZnXcTnw==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
gopd@1.2.0:
|
||||
resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
graceful-fs@4.2.10:
|
||||
resolution: {integrity: sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==}
|
||||
|
||||
@@ -2057,6 +2131,14 @@ packages:
|
||||
hachure-fill@0.5.2:
|
||||
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:
|
||||
resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
|
||||
engines: {node: '>= 0.4'}
|
||||
@@ -2433,6 +2515,10 @@ packages:
|
||||
engines: {node: '>= 18'}
|
||||
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:
|
||||
resolution: {integrity: sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==}
|
||||
|
||||
@@ -4653,6 +4739,8 @@ snapshots:
|
||||
dependencies:
|
||||
retry: 0.13.1
|
||||
|
||||
asynckit@0.4.0: {}
|
||||
|
||||
atomically@2.0.3:
|
||||
dependencies:
|
||||
stubborn-fs: 1.2.5
|
||||
@@ -4668,6 +4756,14 @@ snapshots:
|
||||
postcss: 8.5.3
|
||||
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: {}
|
||||
|
||||
balanced-match@1.0.2: {}
|
||||
@@ -4720,6 +4816,11 @@ snapshots:
|
||||
|
||||
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: {}
|
||||
|
||||
camelcase-css@2.0.1: {}
|
||||
@@ -4794,6 +4895,10 @@ snapshots:
|
||||
|
||||
color-name@1.1.4: {}
|
||||
|
||||
combined-stream@1.0.8:
|
||||
dependencies:
|
||||
delayed-stream: 1.0.0
|
||||
|
||||
comma-separated-tokens@2.0.3: {}
|
||||
|
||||
commander@4.1.1: {}
|
||||
@@ -4841,6 +4946,10 @@ snapshots:
|
||||
optionalDependencies:
|
||||
typescript: 5.8.2
|
||||
|
||||
cross-env@7.0.3:
|
||||
dependencies:
|
||||
cross-spawn: 7.0.6
|
||||
|
||||
cross-spawn@7.0.6:
|
||||
dependencies:
|
||||
path-key: 3.1.1
|
||||
@@ -5068,6 +5177,8 @@ snapshots:
|
||||
dependencies:
|
||||
robust-predicates: 3.0.2
|
||||
|
||||
delayed-stream@1.0.0: {}
|
||||
|
||||
dequal@2.0.3: {}
|
||||
|
||||
devlop@1.1.0:
|
||||
@@ -5088,6 +5199,12 @@ snapshots:
|
||||
|
||||
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: {}
|
||||
|
||||
electron-to-chromium@1.5.123: {}
|
||||
@@ -5106,6 +5223,21 @@ snapshots:
|
||||
dependencies:
|
||||
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:
|
||||
optionalDependencies:
|
||||
'@esbuild/aix-ppc64': 0.21.5
|
||||
@@ -5247,11 +5379,20 @@ snapshots:
|
||||
dependencies:
|
||||
to-regex-range: 5.0.1
|
||||
|
||||
follow-redirects@1.15.9: {}
|
||||
|
||||
foreground-child@3.3.1:
|
||||
dependencies:
|
||||
cross-spawn: 7.0.6
|
||||
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: {}
|
||||
|
||||
fs.realpath@1.0.0: {}
|
||||
@@ -5265,6 +5406,24 @@ snapshots:
|
||||
|
||||
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@9.0.1:
|
||||
@@ -5336,12 +5495,20 @@ snapshots:
|
||||
slash: 5.1.0
|
||||
unicorn-magic: 0.1.0
|
||||
|
||||
gopd@1.2.0: {}
|
||||
|
||||
graceful-fs@4.2.10: {}
|
||||
|
||||
graceful-fs@4.2.11: {}
|
||||
|
||||
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:
|
||||
dependencies:
|
||||
function-bind: 1.1.2
|
||||
@@ -5707,6 +5874,8 @@ snapshots:
|
||||
|
||||
marked@15.0.7: {}
|
||||
|
||||
math-intrinsics@1.1.0: {}
|
||||
|
||||
mdast-util-find-and-replace@3.0.2:
|
||||
dependencies:
|
||||
'@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 ConnectPrompt from "./ConnectPrompt";
|
||||
import type { Chat } from "./types";
|
||||
import PrevSuggestion from "@/components/ChatMessage/PrevSuggestion";
|
||||
|
||||
interface ChatAIProps {
|
||||
isTransitioned: boolean;
|
||||
@@ -33,6 +34,7 @@ interface ChatAIProps {
|
||||
clearChatPage?: () => void;
|
||||
isChatPage?: boolean;
|
||||
getFileUrl: (path: string) => string;
|
||||
showChatHistory?: boolean;
|
||||
}
|
||||
|
||||
export interface ChatAIRef {
|
||||
@@ -56,6 +58,7 @@ const ChatAI = memo(
|
||||
clearChatPage,
|
||||
isChatPage = false,
|
||||
getFileUrl,
|
||||
showChatHistory = true,
|
||||
},
|
||||
ref
|
||||
) => {
|
||||
@@ -89,7 +92,9 @@ const ChatAI = memo(
|
||||
|
||||
const [Question, setQuestion] = useState<string>("");
|
||||
|
||||
const [websocketSessionId, setWebsocketSessionId] = useState('');
|
||||
const [showPrevSuggestion, setShowPrevSuggestion] = useState(true);
|
||||
|
||||
const [websocketSessionId, setWebsocketSessionId] = useState("");
|
||||
|
||||
const onWebsocketSessionId = useCallback((sessionId: string) => {
|
||||
setWebsocketSessionId(sessionId);
|
||||
@@ -119,16 +124,21 @@ const ChatAI = memo(
|
||||
|
||||
const dealMsgRef = useRef<((msg: string) => void) | null>(null);
|
||||
|
||||
const clientId = isChatPage ? "standalone" : "popup"
|
||||
const { errorShow, setErrorShow, reconnect, disconnectWS, updateDealMsg } =
|
||||
useWebSocket({
|
||||
clientId,
|
||||
connected,
|
||||
setConnected,
|
||||
currentService,
|
||||
dealMsgRef,
|
||||
onWebsocketSessionId,
|
||||
});
|
||||
const clientId = isChatPage ? "standalone" : "popup";
|
||||
const {
|
||||
errorShow,
|
||||
setErrorShow,
|
||||
reconnect,
|
||||
disconnectWS,
|
||||
updateDealMsg,
|
||||
} = useWebSocket({
|
||||
clientId,
|
||||
connected,
|
||||
setConnected,
|
||||
currentService,
|
||||
dealMsgRef,
|
||||
onWebsocketSessionId,
|
||||
});
|
||||
|
||||
const {
|
||||
chatClose,
|
||||
@@ -161,7 +171,7 @@ const ChatAI = memo(
|
||||
setTimedoutShow,
|
||||
(chat) => cancelChat(chat || activeChat),
|
||||
setLoadingStep,
|
||||
handlers,
|
||||
handlers
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -189,16 +199,28 @@ const ChatAI = memo(
|
||||
]);
|
||||
|
||||
const init = useCallback(
|
||||
(value: string) => {
|
||||
if (!isLogin) return;
|
||||
if (!curChatEnd) return;
|
||||
if (!activeChat?._id) {
|
||||
createNewChat(value, activeChat, websocketSessionId);
|
||||
} else {
|
||||
handleSendMessage(value, activeChat, websocketSessionId);
|
||||
async (value: string) => {
|
||||
try {
|
||||
console.log("init", isLogin, curChatEnd, activeChat?._id);
|
||||
if (!isLogin || !curChatEnd) return;
|
||||
setShowPrevSuggestion(false);
|
||||
if (!activeChat?._id) {
|
||||
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();
|
||||
@@ -207,14 +229,17 @@ const ChatAI = memo(
|
||||
}, [createChatWindow, createWin]);
|
||||
|
||||
useEffect(() => {
|
||||
setCurChatEnd(true);
|
||||
return () => {
|
||||
if (messageTimeoutRef.current) {
|
||||
clearTimeout(messageTimeoutRef.current);
|
||||
}
|
||||
chatClose(activeChat);
|
||||
setActiveChat(undefined);
|
||||
setCurChatEnd(true);
|
||||
disconnectWS();
|
||||
Promise.resolve().then(() => {
|
||||
chatClose(activeChat);
|
||||
setActiveChat(undefined);
|
||||
setCurChatEnd(true);
|
||||
disconnectWS();
|
||||
});
|
||||
};
|
||||
}, [chatClose, setCurChatEnd]);
|
||||
|
||||
@@ -240,17 +265,20 @@ const ChatAI = memo(
|
||||
]
|
||||
);
|
||||
|
||||
const deleteChat = useCallback((chatId: string) => {
|
||||
setChats((prev) => prev.filter((chat) => chat._id !== chatId));
|
||||
if (activeChat?._id === chatId) {
|
||||
const remainingChats = chats.filter((chat) => chat._id !== chatId);
|
||||
if (remainingChats.length > 0) {
|
||||
setActiveChat(remainingChats[0]);
|
||||
} else {
|
||||
init("");
|
||||
const deleteChat = useCallback(
|
||||
(chatId: string) => {
|
||||
setChats((prev) => prev.filter((chat) => chat._id !== chatId));
|
||||
if (activeChat?._id === chatId) {
|
||||
const remainingChats = chats.filter((chat) => chat._id !== chatId);
|
||||
if (remainingChats.length > 0) {
|
||||
setActiveChat(remainingChats[0]);
|
||||
} else {
|
||||
init("");
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [activeChat, chats, init, setActiveChat]);
|
||||
},
|
||||
[activeChat, chats, init, setActiveChat]
|
||||
);
|
||||
|
||||
const handleOutsideClick = useCallback((e: MouseEvent) => {
|
||||
const sidebar = document.querySelector("[data-sidebar]");
|
||||
@@ -297,9 +325,9 @@ const ChatAI = memo(
|
||||
return (
|
||||
<div
|
||||
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
|
||||
isSidebarOpen={isSidebarOpenChat}
|
||||
chats={chats}
|
||||
@@ -320,6 +348,7 @@ const ChatAI = memo(
|
||||
reconnect={reconnect}
|
||||
isChatPage={isChatPage}
|
||||
setIsLogin={setIsLoginChat}
|
||||
showChatHistory={showChatHistory}
|
||||
/>
|
||||
{isLogin ? (
|
||||
<ChatContent
|
||||
@@ -335,12 +364,16 @@ const ChatAI = memo(
|
||||
timedoutShow={timedoutShow}
|
||||
errorShow={errorShow}
|
||||
Question={Question}
|
||||
handleSendMessage={(value) => handleSendMessage(value, activeChat)}
|
||||
handleSendMessage={(value) =>
|
||||
handleSendMessage(value, activeChat)
|
||||
}
|
||||
getFileUrl={getFileUrl}
|
||||
/>
|
||||
) : (
|
||||
<ConnectPrompt />
|
||||
)}
|
||||
|
||||
{showPrevSuggestion ? <PrevSuggestion sendMessage={init} /> : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -29,6 +29,8 @@ import { useChatStore } from "@/stores/chatStore";
|
||||
import type { Chat } from "./types";
|
||||
import { useConnectStore } from "@/stores/connectStore";
|
||||
import platformAdapter from "@/utils/platformAdapter";
|
||||
import { list_coco_servers } from "@/commands";
|
||||
|
||||
interface ChatHeaderProps {
|
||||
onCreateNewChat: () => void;
|
||||
onOpenChatAI: () => void;
|
||||
@@ -38,6 +40,7 @@ interface ChatHeaderProps {
|
||||
reconnect: (server?: IServer) => void;
|
||||
setIsLogin: (isLogin: boolean) => void;
|
||||
isChatPage?: boolean;
|
||||
showChatHistory?: boolean;
|
||||
}
|
||||
|
||||
export function ChatHeader({
|
||||
@@ -48,6 +51,7 @@ export function ChatHeader({
|
||||
reconnect,
|
||||
setIsLogin,
|
||||
isChatPage = false,
|
||||
showChatHistory,
|
||||
}: ChatHeaderProps) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
@@ -63,34 +67,39 @@ export function ChatHeader({
|
||||
const currentService = useConnectStore((state) => state.currentService);
|
||||
const setCurrentService = useConnectStore((state) => state.setCurrentService);
|
||||
|
||||
const fetchServers = useCallback(async (resetSelection: boolean) => {
|
||||
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);
|
||||
const isTauri = useAppStore((state) => state.isTauri);
|
||||
|
||||
if (resetSelection && enabledServers.length > 0) {
|
||||
const currentServiceExists = enabledServers.find(
|
||||
(server) => server.id === currentService?.id
|
||||
const fetchServers = useCallback(
|
||||
async (resetSelection: boolean) => {
|
||||
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) {
|
||||
switchServer(currentServiceExists);
|
||||
} else {
|
||||
switchServer(enabledServers[enabledServers.length - 1]);
|
||||
if (resetSelection && enabledServers.length > 0) {
|
||||
const currentServiceExists = enabledServers.find(
|
||||
(server) => server.id === currentService?.id
|
||||
);
|
||||
|
||||
if (currentServiceExists) {
|
||||
switchServer(currentServiceExists);
|
||||
} else {
|
||||
switchServer(enabledServers[enabledServers.length - 1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch((err: any) => {
|
||||
console.error(err);
|
||||
});
|
||||
}, [currentService?.id]);
|
||||
})
|
||||
.catch((err: any) => {
|
||||
console.error(err);
|
||||
});
|
||||
},
|
||||
[currentService?.id]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
fetchServers(true);
|
||||
isTauri && fetchServers(true);
|
||||
|
||||
const unlisten = platformAdapter.listenEvent("login_or_logout", (event) => {
|
||||
console.log("Login or Logout:", currentService, event);
|
||||
@@ -103,8 +112,6 @@ export function ChatHeader({
|
||||
};
|
||||
}, []);
|
||||
|
||||
|
||||
|
||||
const switchServer = async (server: IServer) => {
|
||||
if (!server) return;
|
||||
try {
|
||||
@@ -119,7 +126,7 @@ export function ChatHeader({
|
||||
return;
|
||||
}
|
||||
setIsLogin(true);
|
||||
// The Rust backend will automatically disconnect,
|
||||
// The Rust backend will automatically disconnect,
|
||||
// so we don't need to handle disconnection on the frontend
|
||||
// src-tauri/src/server/websocket.rs
|
||||
reconnect && reconnect(server);
|
||||
@@ -149,52 +156,60 @@ export function ChatHeader({
|
||||
data-tauri-drag-region
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
data-sidebar-button
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
setIsSidebarOpen();
|
||||
}}
|
||||
className="p-2 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-800"
|
||||
>
|
||||
<HistoryIcon />
|
||||
</button>
|
||||
{isTauri && (
|
||||
<button
|
||||
data-sidebar-button
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
setIsSidebarOpen();
|
||||
}}
|
||||
className="p-2 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-800"
|
||||
>
|
||||
<HistoryIcon />
|
||||
</button>
|
||||
)}
|
||||
|
||||
<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
|
||||
src={logoImg}
|
||||
className="w-4 h-4"
|
||||
alt={t("assistant.message.logo")}
|
||||
/>
|
||||
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>
|
||||
|
||||
<MenuItems
|
||||
transition
|
||||
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">
|
||||
<img
|
||||
src={logoImg}
|
||||
className="w-4 h-4"
|
||||
alt={t("assistant.message.logo")}
|
||||
/>
|
||||
Coco AI
|
||||
</button>
|
||||
</MenuItem>
|
||||
</MenuItems>
|
||||
{showChatHistory && isTauri ? (
|
||||
<MenuItems
|
||||
transition
|
||||
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">
|
||||
<img
|
||||
src={logoImg}
|
||||
className="w-4 h-4"
|
||||
alt={t("assistant.message.logo")}
|
||||
/>
|
||||
Coco AI
|
||||
</button>
|
||||
</MenuItem>
|
||||
</MenuItems>
|
||||
) : null}
|
||||
</Menu>
|
||||
|
||||
<button
|
||||
onClick={onCreateNewChat}
|
||||
className="p-2 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-800"
|
||||
>
|
||||
<MessageSquarePlus className="h-4 w-4" />
|
||||
</button>
|
||||
{showChatHistory && isTauri ? (
|
||||
<button
|
||||
onClick={onCreateNewChat}
|
||||
className="p-2 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-800"
|
||||
>
|
||||
<MessageSquarePlus className="h-4 w-4" />
|
||||
</button>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
@@ -205,7 +220,7 @@ export function ChatHeader({
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
{isTauri ? <div className="flex items-center gap-2">
|
||||
<button
|
||||
onClick={togglePin}
|
||||
className={`${isPinned ? "text-blue-500" : ""}`}
|
||||
@@ -315,7 +330,7 @@ export function ChatHeader({
|
||||
<WindowsFullIcon className="rotate-30 scale-x-[-1]" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>: <div/>}
|
||||
</header>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -25,11 +25,14 @@ export const ChatSidebar: React.FC<ChatSidebarProps> = ({
|
||||
return (
|
||||
<div
|
||||
data-sidebar
|
||||
className={`fixed inset-y-0 left-0 z-50 w-64 transform transition-all duration-300 ease-in-out
|
||||
${isSidebarOpen ? "translate-x-0" : "-translate-x-[calc(100%)]"}
|
||||
md:relative md:translate-x-0 bg-gray-100 dark:bg-gray-800
|
||||
border-r border-gray-200 dark:border-gray-700 rounded-tl-xl rounded-bl-xl
|
||||
overflow-hidden`}
|
||||
className={`
|
||||
h-[calc(100%+90px)] absolute top-0 left-0 z-10 w-64
|
||||
transform transition-all duration-300 ease-in-out
|
||||
${isSidebarOpen ? "translate-x-0" : "-translate-x-full"}
|
||||
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
|
||||
chats={chats}
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
import { useConnectStore } from "@/stores/connectStore";
|
||||
import clsx from "clsx";
|
||||
import { filesize } from "filesize";
|
||||
import { Files, Trash2, X } from "lucide-react";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
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 { AttachmentHit } from "@/types/commands";
|
||||
import { useAppStore } from "@/stores/appStore";
|
||||
|
||||
interface SessionFileProps {
|
||||
sessionId: string;
|
||||
@@ -16,6 +18,8 @@ interface SessionFileProps {
|
||||
const SessionFile = (props: SessionFileProps) => {
|
||||
const { sessionId } = props;
|
||||
const { t } = useTranslation();
|
||||
|
||||
const isTauri = useAppStore((state) => state.isTauri);
|
||||
const currentService = useConnectStore((state) => state.currentService);
|
||||
const [uploadedFiles, setUploadedFiles] = useState<AttachmentHit[]>([]);
|
||||
const [visible, setVisible] = useState(false);
|
||||
@@ -32,14 +36,20 @@ const SessionFile = (props: SessionFileProps) => {
|
||||
}, [sessionId]);
|
||||
|
||||
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 result = await delete_attachment({ serverId, id });
|
||||
|
||||
let result;
|
||||
if (isTauri) {
|
||||
result = await delete_attachment({ serverId, id });
|
||||
} else {
|
||||
}
|
||||
if (!result) return;
|
||||
|
||||
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 React from 'react';
|
||||
|
||||
import IconWrapper from "./IconWrapper";
|
||||
import ThemedIcon from "./ThemedIcon";
|
||||
@@ -11,7 +12,7 @@ interface ItemIconProps {
|
||||
onClick?: React.MouseEventHandler<HTMLDivElement>;
|
||||
}
|
||||
|
||||
function ItemIcon({
|
||||
const ItemIcon = React.memo(function ItemIcon({
|
||||
item,
|
||||
className = "w-5 h-5 flex-shrink-0",
|
||||
onClick = () => {},
|
||||
@@ -34,7 +35,7 @@ function ItemIcon({
|
||||
|
||||
let selectedIcon = icons[item?.icon];
|
||||
if (!selectedIcon) {
|
||||
selectedIcon=item?.icon
|
||||
selectedIcon = item?.icon;
|
||||
}
|
||||
|
||||
if (!selectedIcon) {
|
||||
@@ -65,6 +66,6 @@ function ItemIcon({
|
||||
</IconWrapper>
|
||||
);
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
export default ItemIcon;
|
||||
|
||||
@@ -20,6 +20,19 @@ function TypeIcon({
|
||||
const endpoint_http = useAppStore((state) => state.endpoint_http);
|
||||
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
|
||||
const isBase64 = connectorSource?.icon?.startsWith("data:image/");
|
||||
if (isBase64) {
|
||||
|
||||
@@ -2,21 +2,24 @@ import { ArrowDown01, Command, CornerDownLeft } from "lucide-react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import clsx from "clsx";
|
||||
|
||||
import logoImg from "@/assets/icon.svg";
|
||||
import { useSearchStore } from "@/stores/searchStore";
|
||||
import TypeIcon from "@/components/Common/Icons/TypeIcon";
|
||||
import { useAppStore } from "@/stores/appStore";
|
||||
import Copyright from "@/components/Common/Copyright";
|
||||
import { isMac } from "@/utils/platform";
|
||||
import PinOffIcon from "@/icons/PinOff";
|
||||
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";
|
||||
|
||||
interface FooterProps {
|
||||
isTauri: boolean;
|
||||
openSetting: () => void;
|
||||
setWindowAlwaysOnTop: (isPinned: boolean) => Promise<void>;
|
||||
}
|
||||
|
||||
export default function Footer({
|
||||
isTauri,
|
||||
openSetting,
|
||||
setWindowAlwaysOnTop,
|
||||
}: FooterProps) {
|
||||
@@ -41,46 +44,53 @@ export default function Footer({
|
||||
|
||||
return (
|
||||
<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"
|
||||
>
|
||||
<div className="flex items-center">
|
||||
<div className="flex items-center space-x-2">
|
||||
{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>
|
||||
{isTauri ? (
|
||||
<div className="flex items-center">
|
||||
<div className="flex items-center space-x-2">
|
||||
{sourceData?.source?.name ? (
|
||||
<TypeIcon item={sourceData} className="w-4 h-4" />
|
||||
) : (
|
||||
sourceData?.source?.name ||
|
||||
t("search.footer.version", {
|
||||
version: process.env.VERSION || "v1.0.0",
|
||||
})
|
||||
<img
|
||||
src={logoImg}
|
||||
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
|
||||
onClick={togglePin}
|
||||
className={clsx({
|
||||
"text-blue-500": isPinned,
|
||||
"pl-2": updateInfo?.available,
|
||||
})}
|
||||
>
|
||||
{isPinned ? <PinIcon /> : <PinOffIcon />}
|
||||
</button>
|
||||
<button
|
||||
onClick={togglePin}
|
||||
className={clsx({
|
||||
"text-blue-500": isPinned,
|
||||
"pl-2": updateInfo?.available,
|
||||
})}
|
||||
>
|
||||
{isPinned ? <PinIcon /> : <PinOffIcon />}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<Copyright />
|
||||
)}
|
||||
|
||||
<div className="flex items-center gap-3">
|
||||
<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 { isMac } from "@/utils/platform";
|
||||
import { useShortcutsStore } from "@/stores/shortcutsStore";
|
||||
import noDataImg from "@/assets/coconut-tree.png";
|
||||
|
||||
export const NoResults = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const modeSwitch = useShortcutsStore((state) => state.modeSwitch);
|
||||
|
||||
return (
|
||||
<div
|
||||
data-tauri-drag-region
|
||||
@@ -30,7 +33,7 @@ export const NoResults = () => {
|
||||
</span>
|
||||
)}
|
||||
<span className="ml-1 w-5 h-5 rounded-[6px] border border-[#D8D8D8] flex justify-center items-center">
|
||||
T
|
||||
{modeSwitch}
|
||||
</span>
|
||||
</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 { Link } from "react-router-dom";
|
||||
import logoImg from "../assets/icon.svg";
|
||||
import {useAppStore} from "@/stores/appStore";
|
||||
import {OctagonAlert, X} from 'lucide-react';
|
||||
|
||||
import logoImg from "@/assets/icon.svg";
|
||||
import { useAppStore } from "@/stores/appStore";
|
||||
|
||||
const Footer = () => {
|
||||
const error = useAppStore((state) => state.error);
|
||||
const setError = useAppStore((state) => state.setError);
|
||||
|
||||
const error = useAppStore((state) => state.error);
|
||||
const setError = useAppStore((state) => state.setError);
|
||||
return (
|
||||
<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="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>
|
||||
)}
|
||||
|
||||
<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">
|
||||
<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
|
||||
</span>
|
||||
{/* <ChevronUp className="w-4 h-4 text-gray-500 dark:text-gray-400" /> */}
|
||||
</MenuButton>
|
||||
{/* <ChevronUp className="w-4 h-4 text-gray-500 dark:text-gray-400" /> */}
|
||||
</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">
|
||||
<MenuItem>
|
||||
{({ active }) => (
|
||||
@@ -104,21 +104,20 @@ const Footer = () => {
|
||||
</MenuItem>
|
||||
</div>
|
||||
</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">
|
||||
Version {process.env.VERSION || "v1.0.0"}
|
||||
</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">
|
||||
Check for Updates
|
||||
</button> */}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Footer;
|
||||
@@ -6,13 +6,14 @@ interface AutoResizeTextareaProps {
|
||||
setInput: (value: string) => void;
|
||||
handleKeyDown?: (e: React.KeyboardEvent<HTMLTextAreaElement>) => void;
|
||||
connected: boolean;
|
||||
chatPlaceholder?: string;
|
||||
}
|
||||
|
||||
// Forward ref to allow parent to interact with this component
|
||||
const AutoResizeTextarea = forwardRef<
|
||||
{ reset: () => void; focus: () => void },
|
||||
AutoResizeTextareaProps
|
||||
>(({ input, setInput, handleKeyDown, connected }, ref) => {
|
||||
>(({ input, setInput, handleKeyDown, connected, chatPlaceholder }, ref) => {
|
||||
const { t } = useTranslation();
|
||||
const textareaRef = useRef<HTMLTextAreaElement>(null);
|
||||
|
||||
@@ -34,8 +35,10 @@ const AutoResizeTextarea = forwardRef<
|
||||
autoCapitalize="none"
|
||||
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"
|
||||
placeholder={connected ? t('search.textarea.placeholder') : ""}
|
||||
aria-label={t('search.textarea.ariaLabel')}
|
||||
placeholder={
|
||||
connected ? chatPlaceholder || t("search.textarea.placeholder") : ""
|
||||
}
|
||||
aria-label={t("search.textarea.ariaLabel")}
|
||||
value={input}
|
||||
onChange={(e) => setInput(e.target.value)}
|
||||
onKeyDown={(e) => handleKeyDown?.(e)}
|
||||
|
||||
@@ -20,7 +20,7 @@ interface State {
|
||||
}
|
||||
|
||||
interface ContextMenuProps {
|
||||
hideCoco: () => Promise<void>;
|
||||
hideCoco?: () => void;
|
||||
}
|
||||
|
||||
const ContextMenu = ({ hideCoco }: ContextMenuProps) => {
|
||||
@@ -54,7 +54,7 @@ const ContextMenu = ({ hideCoco }: ContextMenuProps) => {
|
||||
|
||||
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 { useTranslation } from "react-i18next";
|
||||
import clsx from "clsx";
|
||||
import { useKeyPress } from "ahooks";
|
||||
|
||||
import ChatSwitch from "@/components/Common/ChatSwitch";
|
||||
import AutoResizeTextarea from "./AutoResizeTextarea";
|
||||
@@ -12,12 +13,11 @@ import { useSearchStore } from "@/stores/searchStore";
|
||||
import { metaOrCtrlKey } from "@/utils/keyboardUtils";
|
||||
import SearchPopover from "./SearchPopover";
|
||||
// import AudioRecording from "../AudioRecording";
|
||||
import { hide_coco } from "@/commands";
|
||||
import { DataSource } from "@/types/commands";
|
||||
// import InputExtra from "./InputExtra";
|
||||
// import { useConnectStore } from "@/stores/connectStore";
|
||||
import { useShortcutsStore } from "@/stores/shortcutsStore";
|
||||
import { useKeyPress } from "ahooks";
|
||||
import Copyright from "@/components/Common/Copyright";
|
||||
|
||||
interface ChatInputProps {
|
||||
onSend: (message: string) => void;
|
||||
@@ -46,6 +46,11 @@ interface ChatInputProps {
|
||||
}) => Promise<string | string[] | null>;
|
||||
getFileMetadata: (path: string) => Promise<any>;
|
||||
getFileIcon: (path: string, size: number) => Promise<string>;
|
||||
hideCoco?: () => void;
|
||||
hasFeature?: string[];
|
||||
hasModules?: string[];
|
||||
searchPlaceholder?: string;
|
||||
chatPlaceholder?: string;
|
||||
}
|
||||
|
||||
export default function ChatInput({
|
||||
@@ -64,16 +69,12 @@ export default function ChatInput({
|
||||
isChatPage = false,
|
||||
getDataSourcesByServer,
|
||||
setupWindowFocusListener,
|
||||
}: // checkScreenPermission,
|
||||
// requestScreenPermission,
|
||||
// getScreenMonitors,
|
||||
// getScreenWindows,
|
||||
// captureMonitorScreenshot,
|
||||
// captureWindowScreenshot,
|
||||
// openFileDialog,
|
||||
// getFileMetadata,
|
||||
// getFileIcon,
|
||||
ChatInputProps) {
|
||||
hasFeature = ["think", "search", "think_icon", "search_icon"],
|
||||
hideCoco,
|
||||
hasModules = [],
|
||||
searchPlaceholder,
|
||||
chatPlaceholder,
|
||||
}: ChatInputProps) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const showTooltip = useAppStore(
|
||||
@@ -156,6 +157,7 @@ ChatInputProps) {
|
||||
|
||||
const handleSubmit = useCallback(() => {
|
||||
const trimmedValue = inputValue.trim();
|
||||
console.log("handleSubmit", trimmedValue, disabled);
|
||||
if (trimmedValue && !disabled) {
|
||||
changeInput("");
|
||||
onSend(trimmedValue);
|
||||
@@ -168,7 +170,7 @@ ChatInputProps) {
|
||||
if (inputValue) {
|
||||
changeInput("");
|
||||
} else if (!isPinned) {
|
||||
hide_coco();
|
||||
hideCoco && hideCoco();
|
||||
}
|
||||
}, [inputValue, isPinned]);
|
||||
|
||||
@@ -295,12 +297,17 @@ ChatInputProps) {
|
||||
changeInput(value);
|
||||
}}
|
||||
connected={connected}
|
||||
handleKeyDown={(e) => {
|
||||
handleKeyDown={(e: React.KeyboardEvent<HTMLTextAreaElement>) => {
|
||||
if (e.key === "Enter") {
|
||||
if (e.nativeEvent.isComposing) {
|
||||
return;
|
||||
}
|
||||
console.log("handleKeyDown", e.nativeEvent.isComposing);
|
||||
e.preventDefault();
|
||||
handleSubmit();
|
||||
}
|
||||
}}
|
||||
chatPlaceholder={chatPlaceholder}
|
||||
/>
|
||||
) : (
|
||||
<input
|
||||
@@ -311,7 +318,9 @@ ChatInputProps) {
|
||||
autoCapitalize="none"
|
||||
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"
|
||||
placeholder={t("search.input.searchPlaceholder")}
|
||||
placeholder={
|
||||
searchPlaceholder || t("search.input.searchPlaceholder")
|
||||
}
|
||||
value={inputValue}
|
||||
onChange={(e) => {
|
||||
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">
|
||||
{t("search.input.connectionError")}
|
||||
<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={() => {
|
||||
reconnect();
|
||||
setReconnectCountdown(10);
|
||||
@@ -424,38 +433,47 @@ ChatInputProps) {
|
||||
/>
|
||||
)} */}
|
||||
|
||||
<button
|
||||
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,
|
||||
}
|
||||
)}
|
||||
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"
|
||||
{hasFeature.includes("think") && (
|
||||
<button
|
||||
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,
|
||||
}
|
||||
>
|
||||
{t("search.input.deepThink")}
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
)}
|
||||
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")}
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
)}
|
||||
|
||||
<SearchPopover
|
||||
isSearchActive={isSearchActive}
|
||||
setIsSearchActive={setIsSearchActive}
|
||||
getDataSourcesByServer={getDataSourcesByServer}
|
||||
/>
|
||||
{hasFeature.includes("search") && (
|
||||
<SearchPopover
|
||||
isSearchActive={isSearchActive}
|
||||
setIsSearchActive={setIsSearchActive}
|
||||
getDataSourcesByServer={getDataSourcesByServer}
|
||||
/>
|
||||
)}
|
||||
{!hasFeature.includes("search") && !hasFeature.includes("think") ? (
|
||||
<div className="px-2">
|
||||
<Copyright />
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
) : (
|
||||
<div
|
||||
@@ -464,7 +482,7 @@ ChatInputProps) {
|
||||
></div>
|
||||
)}
|
||||
|
||||
{isChatPage ? null : (
|
||||
{isChatPage || hasModules?.length !== 2 ? null : (
|
||||
<div className="relative w-16 flex justify-end items-center">
|
||||
{showTooltip && modifierKeyPressed ? (
|
||||
<div
|
||||
|
||||
@@ -2,13 +2,13 @@ import { useEffect, useState, useCallback, useRef } from "react";
|
||||
import { debounce } from "lodash-es";
|
||||
|
||||
import DropdownList from "./DropdownList";
|
||||
import Footer from "./Footer";
|
||||
import { SearchResults } from "@/components/Search/SearchResults";
|
||||
import { useSearchStore } from "@/stores/searchStore";
|
||||
import ContextMenu from "./ContextMenu";
|
||||
import { NoResults } from "./NoResults";
|
||||
|
||||
import { NoResults } from "@/components/Common/UI/NoResults";
|
||||
import Footer from "@/components/Common/UI/Footer";
|
||||
interface SearchProps {
|
||||
isTauri: boolean;
|
||||
changeInput: (val: string) => void;
|
||||
isChatMode: boolean;
|
||||
input: string;
|
||||
@@ -18,12 +18,13 @@ interface SearchProps {
|
||||
size: number,
|
||||
queryStrings: any
|
||||
) => Promise<any>;
|
||||
hideCoco: () => Promise<any>;
|
||||
hideCoco?: () => void;
|
||||
openSetting: () => void;
|
||||
setWindowAlwaysOnTop: (isPinned: boolean) => Promise<void>;
|
||||
}
|
||||
|
||||
function Search({
|
||||
isTauri,
|
||||
isChatMode,
|
||||
input,
|
||||
querySearch,
|
||||
@@ -81,7 +82,7 @@ function Search({
|
||||
return (
|
||||
<div
|
||||
ref={mainWindowRef}
|
||||
className={`h-[calc(100vh-90px)] pb-10 w-full relative`}
|
||||
className={`h-full pb-10 w-full relative`}
|
||||
>
|
||||
{/* Search Results Panel */}
|
||||
{suggests.length > 0 ? (
|
||||
@@ -105,6 +106,7 @@ function Search({
|
||||
)}
|
||||
|
||||
<Footer
|
||||
isTauri={isTauri}
|
||||
openSetting={openSetting}
|
||||
setWindowAlwaysOnTop={setWindowAlwaysOnTop}
|
||||
/>
|
||||
|
||||
@@ -16,7 +16,7 @@ interface SearchListItemProps {
|
||||
showListRight?: boolean;
|
||||
}
|
||||
|
||||
const SearchListItem: React.FC<SearchListItemProps> = ({
|
||||
const SearchListItem: React.FC<SearchListItemProps> = React.memo(({
|
||||
item,
|
||||
isSelected,
|
||||
currentIndex,
|
||||
@@ -68,6 +68,6 @@ const SearchListItem: React.FC<SearchListItemProps> = ({
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
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 { ChevronDownIcon, RefreshCw, Layers, Globe } from "lucide-react";
|
||||
import clsx from "clsx";
|
||||
@@ -8,7 +8,7 @@ import TypeIcon from "@/components/Common/Icons/TypeIcon";
|
||||
import { useConnectStore } from "@/stores/connectStore";
|
||||
import { useSearchStore } from "@/stores/searchStore";
|
||||
import { DataSource } from "@/types/commands";
|
||||
import Checkbox from "../Common/Checkbox";
|
||||
import Checkbox from "@/components/Common/Checkbox";
|
||||
|
||||
interface SearchPopoverProps {
|
||||
isSearchActive: boolean;
|
||||
@@ -30,18 +30,26 @@ export default function SearchPopover({
|
||||
|
||||
const currentService = useConnectStore((state) => state.currentService);
|
||||
|
||||
const [showDataSource, setShowDataSource] = useState(false);
|
||||
|
||||
const getDataSourceList = useCallback(async () => {
|
||||
try {
|
||||
const res: DataSource[] = await getDataSourcesByServer(
|
||||
currentService?.id
|
||||
);
|
||||
const data = [
|
||||
{
|
||||
id: "all",
|
||||
name: "search.input.searchPopover.allScope",
|
||||
},
|
||||
...(res || []),
|
||||
];
|
||||
if (res?.length === 0) {
|
||||
setDataSourceList([]);
|
||||
return;
|
||||
}
|
||||
const data = res?.length
|
||||
? [
|
||||
{
|
||||
id: "all",
|
||||
name: "search.input.searchPopover.allScope",
|
||||
},
|
||||
...res,
|
||||
]
|
||||
: [];
|
||||
setDataSourceList(data);
|
||||
} catch (err) {
|
||||
setDataSourceList([]);
|
||||
@@ -49,6 +57,29 @@ export default function SearchPopover({
|
||||
}
|
||||
}, [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(() => {
|
||||
if (dataSourceList.length > 0) {
|
||||
onSelectDataSource("all", true, true);
|
||||
@@ -111,8 +142,16 @@ export default function SearchPopover({
|
||||
</span>
|
||||
|
||||
{dataSourceList?.length > 0 && (
|
||||
<Popover>
|
||||
<PopoverButton as="span" className={clsx("flex items-center")}>
|
||||
<Popover className="relative">
|
||||
<PopoverButton
|
||||
as="span"
|
||||
ref={buttonRef}
|
||||
className={clsx("flex items-center")}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
setShowDataSource((prev) => !prev);
|
||||
}}
|
||||
>
|
||||
<ChevronDownIcon
|
||||
className={clsx("size-5", [
|
||||
isSearchActive
|
||||
@@ -122,79 +161,82 @@ export default function SearchPopover({
|
||||
/>
|
||||
</PopoverButton>
|
||||
|
||||
<PopoverPanel
|
||||
anchor="top start"
|
||||
className="min-w-[220px] bg-white dark:bg-[#202126] rounded-lg shadow-lg border border-gray-200 dark:border-gray-700"
|
||||
>
|
||||
<div
|
||||
className="text-sm px-[12px] py-[18px]"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
{showDataSource ? (
|
||||
<PopoverPanel
|
||||
static
|
||||
ref={popoverRef}
|
||||
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"
|
||||
>
|
||||
<div className="flex justify-between mb-[18px]">
|
||||
<span>{t("search.input.searchPopover.title")}</span>
|
||||
<div
|
||||
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
|
||||
onClick={async () => {
|
||||
setIsRefreshDataSource(true);
|
||||
<div
|
||||
onClick={async () => {
|
||||
setIsRefreshDataSource(true);
|
||||
|
||||
getDataSourceList();
|
||||
getDataSourceList();
|
||||
|
||||
setTimeout(() => {
|
||||
setIsRefreshDataSource(false);
|
||||
}, 1000);
|
||||
}}
|
||||
className="size-[24px] flex justify-center items-center rounded-lg border border-black/10 dark:border-white/10 cursor-pointer"
|
||||
>
|
||||
<RefreshCw
|
||||
className={`size-3 text-[#0287FF] transition-transform duration-1000 ${
|
||||
isRefreshDataSource ? "animate-spin" : ""
|
||||
}`}
|
||||
/>
|
||||
setTimeout(() => {
|
||||
setIsRefreshDataSource(false);
|
||||
}, 1000);
|
||||
}}
|
||||
className="size-[24px] flex justify-center items-center rounded-lg border border-black/10 dark:border-white/10 cursor-pointer"
|
||||
>
|
||||
<RefreshCw
|
||||
className={`size-3 text-[#0287FF] transition-transform duration-1000 ${
|
||||
isRefreshDataSource ? "animate-spin" : ""
|
||||
}`}
|
||||
/>
|
||||
</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>
|
||||
<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>
|
||||
</PopoverPanel>
|
||||
</PopoverPanel>
|
||||
) : null}
|
||||
</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 { formatKey } from "@/utils/keyboardUtils";
|
||||
import SettingsItem from "@/components/Settings/SettingsItem";
|
||||
import { Command } from "lucide-react";
|
||||
import { ChangeEvent, useEffect } from "react";
|
||||
import { emit } from "@tauri-apps/api/event";
|
||||
|
||||
import { formatKey } from "@/utils/keyboardUtils";
|
||||
import SettingsItem from "@/components/Settings/SettingsItem";
|
||||
import { isMac } from "@/utils/platform";
|
||||
import { ModifierKey, useShortcutsStore } from "@/stores/shortcutsStore";
|
||||
|
||||
export const modifierKeys: ModifierKey[] = isMac
|
||||
? ["meta", "ctrl"]
|
||||
|
||||
@@ -69,7 +69,7 @@ const UpdateApp = ({ checkUpdate, relaunchApp }: UpdateAppProps) => {
|
||||
|
||||
state.loading = true;
|
||||
|
||||
await updateInfo?.downloadAndInstall((progress) => {
|
||||
await updateInfo?.downloadAndInstall((progress: any) => {
|
||||
switch (progress.event) {
|
||||
case "Started":
|
||||
state.total = progress.data.contentLength;
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { useCallback } from "react";
|
||||
import { isTauri } from "@tauri-apps/api/core";
|
||||
|
||||
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 { useAppStore } from "@/stores/appStore";
|
||||
import { Get, Post } from "@/api/axiosRequest";
|
||||
|
||||
export function useChatActions(
|
||||
currentServiceId: string | undefined,
|
||||
@@ -19,14 +20,27 @@ export function useChatActions(
|
||||
changeInput?: (val: string) => void,
|
||||
websocketSessionId?: string,
|
||||
) {
|
||||
const isTauri = useAppStore((state) => state.isTauri);
|
||||
|
||||
const chatClose = useCallback(async (activeChat?: Chat) => {
|
||||
if (!activeChat?._id || !currentServiceId) return;
|
||||
if (!activeChat?._id) return;
|
||||
try {
|
||||
let response: any = await close_session_chat({
|
||||
serverId: currentServiceId,
|
||||
sessionId: activeChat?._id,
|
||||
});
|
||||
response = JSON.parse(response || "");
|
||||
let response: any
|
||||
if (isTauri) {
|
||||
if (!currentServiceId) return;
|
||||
response = await close_session_chat({
|
||||
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);
|
||||
} catch (error) {
|
||||
console.error("chatClose:", error);
|
||||
@@ -35,13 +49,24 @@ export function useChatActions(
|
||||
|
||||
const cancelChat = useCallback(async (activeChat?: Chat) => {
|
||||
setCurChatEnd(true);
|
||||
if (!activeChat?._id || !currentServiceId) return;
|
||||
if (!activeChat?._id) return;
|
||||
try {
|
||||
let response: any = await cancel_session_chat({
|
||||
serverId: currentServiceId,
|
||||
sessionId: activeChat?._id,
|
||||
});
|
||||
response = JSON.parse(response || "");
|
||||
let response: any
|
||||
if (isTauri) {
|
||||
if (!currentServiceId) return;
|
||||
response = await cancel_session_chat({
|
||||
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);
|
||||
} catch (error) {
|
||||
console.error("cancelChat:", error);
|
||||
@@ -52,15 +77,29 @@ export function useChatActions(
|
||||
chat: Chat,
|
||||
callback?: (chat: Chat) => void
|
||||
) => {
|
||||
if (!chat?._id || !currentServiceId) return;
|
||||
if (!chat?._id) return;
|
||||
try {
|
||||
let response: any = await session_chat_history({
|
||||
serverId: currentServiceId,
|
||||
sessionId: chat?._id,
|
||||
from: 0,
|
||||
size: 20,
|
||||
});
|
||||
response = JSON.parse(response || "");
|
||||
let response: any
|
||||
if (isTauri) {
|
||||
if (!currentServiceId) return;
|
||||
response = await session_chat_history({
|
||||
serverId: currentServiceId,
|
||||
sessionId: chat?._id,
|
||||
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 updatedChat: Chat = {
|
||||
...chat,
|
||||
@@ -76,29 +115,50 @@ export function useChatActions(
|
||||
|
||||
const createNewChat = useCallback(
|
||||
async (value: string = "", activeChat?: Chat, id?: string) => {
|
||||
setTimedoutShow(false);
|
||||
setErrorShow(false);
|
||||
chatClose(activeChat);
|
||||
clearAllChunkData();
|
||||
setQuestion(value);
|
||||
if (!currentServiceId) return;
|
||||
try {
|
||||
if (!(websocketSessionId || id)){
|
||||
setTimedoutShow(false);
|
||||
setErrorShow(false);
|
||||
await chatClose(activeChat);
|
||||
clearAllChunkData();
|
||||
setQuestion(value);
|
||||
if (!(websocketSessionId || id)) {
|
||||
setErrorShow(true);
|
||||
console.error("websocketSessionId", websocketSessionId, id);
|
||||
return;
|
||||
}
|
||||
console.log("sourceDataIds", sourceDataIds, websocketSessionId, id);
|
||||
let response: any = await new_chat({
|
||||
serverId: currentServiceId,
|
||||
websocketId: websocketSessionId || id,
|
||||
message: value,
|
||||
queryParams: {
|
||||
let response: any
|
||||
if (isTauri) {
|
||||
if (!currentServiceId) return;
|
||||
response = await new_chat({
|
||||
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,
|
||||
deep_thinking: isDeepThinkActive,
|
||||
datasource: sourceDataIds?.join(",") || "",
|
||||
},
|
||||
});
|
||||
}, {
|
||||
"WEBSOCKET-SESSION-ID": websocketSessionId || id,
|
||||
})
|
||||
if (error) {
|
||||
setErrorShow(true);
|
||||
console.error('_new', error);
|
||||
return
|
||||
}
|
||||
response = res
|
||||
}
|
||||
console.log("_new", response);
|
||||
const newChat: Chat = response;
|
||||
curIdRef.current = response?.payload?.id;
|
||||
@@ -124,27 +184,49 @@ export function useChatActions(
|
||||
|
||||
const sendMessage = useCallback(
|
||||
async (content: string, newChat: Chat, id?: string) => {
|
||||
if (!newChat?._id || !currentServiceId || !content) return;
|
||||
|
||||
if (!newChat?._id || !content) return;
|
||||
|
||||
clearAllChunkData();
|
||||
try {
|
||||
if (!(websocketSessionId || id)){
|
||||
if (!(websocketSessionId || id)) {
|
||||
setErrorShow(true);
|
||||
console.error("websocketSessionId", websocketSessionId, id);
|
||||
return;
|
||||
}
|
||||
let response: any = await send_message({
|
||||
serverId: currentServiceId,
|
||||
websocketId: websocketSessionId || id,
|
||||
sessionId: newChat?._id,
|
||||
queryParams: {
|
||||
let response: any
|
||||
if (isTauri) {
|
||||
if (!currentServiceId) return;
|
||||
response = await send_message({
|
||||
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,
|
||||
deep_thinking: isDeepThinkActive,
|
||||
datasource: sourceDataIds?.join(",") || "",
|
||||
},
|
||||
message: content,
|
||||
});
|
||||
response = JSON.parse(response || "");
|
||||
}, {
|
||||
"WEBSOCKET-SESSION-ID": websocketSessionId || id,
|
||||
})
|
||||
|
||||
if (error) {
|
||||
setErrorShow(true);
|
||||
console.error('_cancel', error);
|
||||
return
|
||||
}
|
||||
response = res
|
||||
}
|
||||
console.log("_send", response);
|
||||
curIdRef.current = response[0]?._id;
|
||||
|
||||
@@ -178,13 +260,25 @@ export function useChatActions(
|
||||
);
|
||||
|
||||
const openSessionChat = useCallback(async (chat: Chat) => {
|
||||
if (!chat?._id || !currentServiceId) return;
|
||||
if (!chat?._id) return;
|
||||
try {
|
||||
let response: any = await open_session_chat({
|
||||
serverId: currentServiceId,
|
||||
sessionId: chat?._id,
|
||||
});
|
||||
response = JSON.parse(response || "");
|
||||
let response: any
|
||||
if (isTauri) {
|
||||
if (!currentServiceId) return;
|
||||
response = await open_session_chat({
|
||||
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);
|
||||
return response;
|
||||
} catch (error) {
|
||||
@@ -194,14 +288,27 @@ export function useChatActions(
|
||||
}, [currentServiceId]);
|
||||
|
||||
const getChatHistory = useCallback(async () => {
|
||||
if (!currentServiceId) return [];
|
||||
try {
|
||||
let response: any = await chat_history({
|
||||
serverId: currentServiceId,
|
||||
from: 0,
|
||||
size: 20,
|
||||
});
|
||||
response = JSON.parse(response || "");
|
||||
let response: any
|
||||
if (isTauri) {
|
||||
if (!currentServiceId) return [];
|
||||
response = await chat_history({
|
||||
serverId: currentServiceId,
|
||||
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);
|
||||
const hits = response?.hits?.hits || [];
|
||||
return hits;
|
||||
@@ -212,7 +319,7 @@ export function useChatActions(
|
||||
}, [currentServiceId]);
|
||||
|
||||
const createChatWindow = useCallback(async (createWin: any) => {
|
||||
if (isTauri()) {
|
||||
if (isTauri) {
|
||||
createWin && createWin({
|
||||
label: "chat",
|
||||
title: "Coco Chat",
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
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 handleEscape = async (event: KeyboardEvent) => {
|
||||
@@ -12,16 +10,14 @@ const useEscape = () => {
|
||||
event.preventDefault();
|
||||
|
||||
// Hide the Tauri app window when 'Esc' is pressed
|
||||
await hide_coco()
|
||||
await platformAdapter.invokeBackend("hide_coco");
|
||||
|
||||
console.log("App window hidden successfully.");
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!isTauri()) return;
|
||||
|
||||
const unlisten = listen("tauri://focus", () => {
|
||||
const unlisten = platformAdapter.listenEvent("tauri://focus", () => {
|
||||
// Add event listener for keydown
|
||||
window.addEventListener("keydown", handleEscape);
|
||||
});
|
||||
|
||||
@@ -1,19 +1,26 @@
|
||||
import { useConnectStore } from "@/stores/connectStore";
|
||||
import { useAppStore } from "@/stores/appStore";
|
||||
|
||||
export function useFindConnectorIcon(item: any) {
|
||||
const connector_data = useConnectStore((state) => state.connector_data);
|
||||
const datasourceData = useConnectStore((state) => state.datasourceData);
|
||||
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 result_source = datasourceData[currentService?.id]?.find(
|
||||
const result_source = datasourceData[currentServiceId]?.find(
|
||||
(data: any) => data.id === 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
|
||||
);
|
||||
|
||||
|
||||
@@ -76,6 +76,7 @@ export function useMessageHandler(
|
||||
return;
|
||||
}
|
||||
} catch (error) {
|
||||
setCurChatEnd(true);
|
||||
console.error("parse error:", error);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1,8 +1,16 @@
|
||||
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 { connect_to_server, disconnect } from "@/commands"
|
||||
import { useAppStore, IServer } from "@/stores/appStore";
|
||||
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 {
|
||||
clientId: string;
|
||||
@@ -21,41 +29,119 @@ export default function useWebSocket({
|
||||
dealMsgRef,
|
||||
onWebsocketSessionId,
|
||||
}: 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);
|
||||
|
||||
// 1. WebSocket connects when loading or switching services
|
||||
// src/components/Assistant/ChatHeader.tsx
|
||||
// 2. If not connected or disconnected, input box has a connect button, clicking it will connect to WebSocket
|
||||
// src/components/Search/InputBox.tsx
|
||||
const reconnect = useCallback(async (server?: IServer) => {
|
||||
const targetServer = server || currentService;
|
||||
console.log("reconnect_targetServer", targetServer?.id);
|
||||
if (!targetServer?.id) return;
|
||||
try {
|
||||
console.log("reconnect", targetServer.id, clientId);
|
||||
await connect_to_server(targetServer.id, clientId);
|
||||
} catch (error) {
|
||||
setConnected(false);
|
||||
console.error("Failed to connect:", error);
|
||||
}
|
||||
}, [currentService]);
|
||||
const reconnect = useCallback(
|
||||
async (server?: IServer) => {
|
||||
if (isTauri) {
|
||||
const targetServer = server || currentService;
|
||||
if (!targetServer?.id) return;
|
||||
try {
|
||||
// console.log("reconnect", targetServer.id);
|
||||
await connect_to_server(targetServer.id, clientId);
|
||||
} catch (error) {
|
||||
setConnected(false);
|
||||
console.error("Failed to connect:", error);
|
||||
}
|
||||
} else {
|
||||
connect();
|
||||
}
|
||||
},
|
||||
[currentService]
|
||||
);
|
||||
|
||||
|
||||
const disconnectWS = async () => {
|
||||
if (!connected) return;
|
||||
try {
|
||||
console.log("disconnect");
|
||||
await disconnect(clientId);
|
||||
setConnected(false);
|
||||
} catch (error) {
|
||||
console.error("Failed to disconnect:", error);
|
||||
if (isTauri) {
|
||||
try {
|
||||
console.log("disconnect");
|
||||
await disconnectCommand(clientId);
|
||||
setConnected(false);
|
||||
} catch (error) {
|
||||
console.error("Failed to disconnect:", error);
|
||||
}
|
||||
} else {
|
||||
disconnect();
|
||||
}
|
||||
};
|
||||
|
||||
const updateDealMsg = useCallback((newDealMsg: (msg: string) => void) => {
|
||||
dealMsgRef.current = newDealMsg;
|
||||
}, [dealMsgRef]);
|
||||
|
||||
const websocketIdRef = useRef<string>('')
|
||||
const updateDealMsg = useCallback(
|
||||
(newDealMsg: (msg: string) => void) => {
|
||||
dealMsgRef.current = newDealMsg;
|
||||
},
|
||||
[dealMsgRef]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!currentService?.id) return;
|
||||
@@ -64,7 +150,9 @@ export default function useWebSocket({
|
||||
let unlisten_message = null;
|
||||
|
||||
setErrorShow(false);
|
||||
unlisten_error = listen(`ws-error-${clientId}`, (event) => {
|
||||
|
||||
if (!isTauri) return;
|
||||
unlisten_error = platformAdapter.listenEvent(`ws-error-${clientId}`, (event) => {
|
||||
// {
|
||||
// "error": {
|
||||
// "reason": "invalid login"
|
||||
@@ -76,7 +164,7 @@ export default function useWebSocket({
|
||||
setErrorShow(true);
|
||||
});
|
||||
|
||||
unlisten_message = listen(`ws-message-${clientId}`, (event) => {
|
||||
unlisten_message = platformAdapter.listenEvent(`ws-message-${clientId}`, (event) => {
|
||||
const msg = event.payload as string;
|
||||
console.log(`ws-message-${clientId}`, msg);
|
||||
if (msg.includes("websocket-session-id")) {
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import { useEffect, useCallback } from "react";
|
||||
import { getAllWindows, getCurrentWindow } from "@tauri-apps/api/window";
|
||||
import { WebviewWindow } from "@tauri-apps/api/webviewWindow";
|
||||
import { listen } from "@tauri-apps/api/event";
|
||||
import { isTauri } from "@tauri-apps/api/core";
|
||||
import { useState, useEffect, useCallback } from "react";
|
||||
|
||||
import platformAdapter from "@/utils/platformAdapter";
|
||||
|
||||
const defaultWindowConfig = {
|
||||
label: "",
|
||||
@@ -23,8 +21,20 @@ const defaultWindowConfig = {
|
||||
};
|
||||
|
||||
export const useWindows = () => {
|
||||
if (!isTauri()) return {}
|
||||
const appWindow = getCurrentWindow();
|
||||
const [appWindow, setAppWindow] = useState<any>(null);
|
||||
|
||||
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 args = { ...defaultWindowConfig, ...options };
|
||||
@@ -39,23 +49,26 @@ export const useWindows = () => {
|
||||
return;
|
||||
}
|
||||
|
||||
const win = new WebviewWindow(args.label, args);
|
||||
const win = await platformAdapter.createWebviewWindow(args.label, args);
|
||||
|
||||
win.once("tauri://created", async () => {
|
||||
console.log("tauri://created");
|
||||
// if (args.label.includes("main")) {
|
||||
//
|
||||
// }
|
||||
if(win) {
|
||||
win.once("tauri://created", async () => {
|
||||
console.log("tauri://created");
|
||||
// if (args.label.includes("main")) {
|
||||
//
|
||||
// }
|
||||
|
||||
if (args.maximized && args.resizable) {
|
||||
console.log("is-maximized");
|
||||
await win.maximize();
|
||||
}
|
||||
});
|
||||
if (args.maximized && args.resizable) {
|
||||
console.log("is-maximized");
|
||||
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) => {
|
||||
@@ -75,24 +88,24 @@ export const useWindows = () => {
|
||||
}, []);
|
||||
|
||||
const getWin = useCallback(async (label: string) => {
|
||||
return WebviewWindow.getByLabel(label);
|
||||
return platformAdapter.getWindowByLabel(label);
|
||||
}, []);
|
||||
|
||||
const getAllWin = useCallback(async () => {
|
||||
return getAllWindows();
|
||||
return platformAdapter.getAllWindows();
|
||||
}, []);
|
||||
|
||||
const listenEvents = useCallback(() => {
|
||||
let unlistenHandlers: { (): void; (): void; (): void; (): void; }[] = [];
|
||||
|
||||
const setupListeners = async () => {
|
||||
const winCreateHandler = await listen("win-create", (event) => {
|
||||
const winCreateHandler = await platformAdapter.listenWindowEvent("win-create", (event) => {
|
||||
console.log(event);
|
||||
createWin(event.payload);
|
||||
});
|
||||
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;
|
||||
await appWindow.show();
|
||||
await appWindow.unminimize();
|
||||
@@ -100,13 +113,13 @@ export const useWindows = () => {
|
||||
});
|
||||
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;
|
||||
await appWindow.hide();
|
||||
});
|
||||
unlistenHandlers.push(winHideHandler);
|
||||
|
||||
const winCloseHandler = await listen("win-close", async () => {
|
||||
const winCloseHandler = await platformAdapter.listenWindowEvent("win-close", async () => {
|
||||
await appWindow.close();
|
||||
});
|
||||
unlistenHandlers.push(winCloseHandler);
|
||||
|
||||
@@ -147,12 +147,13 @@
|
||||
"version": "{{version}}",
|
||||
"updateAvailable": "Update available",
|
||||
"select": "Select",
|
||||
"open": "Open"
|
||||
"open": "Open",
|
||||
"powered": "Powered by Coco AI"
|
||||
},
|
||||
"input": {
|
||||
"searchPlaceholder": "Search whatever you want ...",
|
||||
"connectionError": "Unable to connect to the server",
|
||||
"reconnect": "Reconnect",
|
||||
"reconnect": "Click here to reconnect.",
|
||||
"connecting": "Connecting",
|
||||
"deepThink": "Deep Think",
|
||||
"search": "Search",
|
||||
|
||||
@@ -147,12 +147,13 @@
|
||||
"version": "{{version}}",
|
||||
"updateAvailable": "有可用更新",
|
||||
"select": "选择",
|
||||
"open": "打开"
|
||||
"open": "打开",
|
||||
"powered": "由 Coco AI 提供支持"
|
||||
},
|
||||
"input": {
|
||||
"searchPlaceholder": "搜索任何内容...",
|
||||
"connectionError": "无法连接到服务器",
|
||||
"reconnect": "重新连接",
|
||||
"reconnect": "点此重连",
|
||||
"connecting": "连接中",
|
||||
"deepThink": "深度思考",
|
||||
"search": "联网搜索",
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
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 { useShortcutsStore } from "@/stores/shortcutsStore";
|
||||
import { useStartupStore } from "@/stores/startupStore";
|
||||
import { useKeyPress } from "ahooks";
|
||||
import { modifierKeys } from "@/components/Settings/Advanced/components/Shortcuts";
|
||||
import { useAppStore } from "@/stores/appStore";
|
||||
|
||||
function MainApp() {
|
||||
const setIsTauri = useAppStore((state) => state.setIsTauri);
|
||||
setIsTauri(true);
|
||||
|
||||
const querySearch = useCallback(async (input: string) => {
|
||||
try {
|
||||
const response: any = await platformAdapter.invokeBackend(
|
||||
@@ -44,6 +48,11 @@ function MainApp() {
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
const hideCoco = useCallback(() => {
|
||||
return platformAdapter.hideWindow();
|
||||
}, []);
|
||||
|
||||
const modifierKey = useShortcutsStore((state) => {
|
||||
return state.modifierKey;
|
||||
});
|
||||
@@ -129,7 +138,13 @@ function MainApp() {
|
||||
);
|
||||
|
||||
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 AboutView from "@/components/Settings/AboutView";
|
||||
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 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) => {
|
||||
console.log(input);
|
||||
return await query_coco_fusion(`/query/_search?query=${input}`);
|
||||
}, []);
|
||||
|
||||
const queryDocuments = useCallback(
|
||||
async (from: number, size: number, queryStrings: any) => {
|
||||
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 (
|
||||
<div className="w-[680px] h-[590px]">
|
||||
<SearchChat querySearch={querySearch} queryDocuments={queryDocuments} />
|
||||
<div
|
||||
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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -52,6 +52,9 @@ export type IAppStore = {
|
||||
showCocoShortcuts: string[];
|
||||
setShowCocoShortcuts: (showCocoShortcuts: string[]) => void;
|
||||
|
||||
isTauri: boolean;
|
||||
setIsTauri: (isTauri: boolean) => void;
|
||||
|
||||
visible: boolean;
|
||||
withVisibility: <T>(fn: () => Promise<T>) => Promise<T>;
|
||||
};
|
||||
@@ -107,6 +110,8 @@ export const useAppStore = create<IAppStore>()(
|
||||
|
||||
return set({ showCocoShortcuts });
|
||||
},
|
||||
isTauri: true,
|
||||
setIsTauri: (isTauri: boolean) => set({ isTauri }),
|
||||
visible: false,
|
||||
withVisibility: async <T>(fn: () => Promise<T>) => {
|
||||
set({ visible: true });
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { create } from "zustand";
|
||||
import { persist } from "zustand/middleware";
|
||||
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 DATASOURCE_CHANGE_EVENT = "datasourceData_change";
|
||||
@@ -53,7 +54,7 @@ export const useConnectStore = create<IConnectStore>()(
|
||||
draft.connector_data[key] = connector_data;
|
||||
})
|
||||
);
|
||||
await emit(CONNECTOR_CHANGE_EVENT, {
|
||||
await platformAdapter.emitEvent(CONNECTOR_CHANGE_EVENT, {
|
||||
connector_data,
|
||||
});
|
||||
},
|
||||
@@ -64,16 +65,16 @@ export const useConnectStore = create<IConnectStore>()(
|
||||
draft.datasourceData[key] = datasourceData;
|
||||
})
|
||||
);
|
||||
await emit(DATASOURCE_CHANGE_EVENT, {
|
||||
await platformAdapter.emitEvent(DATASOURCE_CHANGE_EVENT, {
|
||||
datasourceData,
|
||||
});
|
||||
},
|
||||
initializeListeners: () => {
|
||||
listen(CONNECTOR_CHANGE_EVENT, (event: any) => {
|
||||
platformAdapter.listenEvent(CONNECTOR_CHANGE_EVENT, (event: any) => {
|
||||
const { connector_data } = event.payload;
|
||||
set({ connector_data });
|
||||
});
|
||||
listen(DATASOURCE_CHANGE_EVENT, (event: any) => {
|
||||
platformAdapter.listenEvent(DATASOURCE_CHANGE_EVENT, (event: any) => {
|
||||
const { datasourceData } = event.payload;
|
||||
set({ datasourceData });
|
||||
});
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { Update } from "@tauri-apps/plugin-updater";
|
||||
import { create } from "zustand";
|
||||
import { persist } from "zustand/middleware";
|
||||
|
||||
@@ -9,8 +8,8 @@ export type IUpdateStore = {
|
||||
setSkipVersion: (skipVersion?: string) => void;
|
||||
isOptional: boolean;
|
||||
setIsOptional: (isOptional: boolean) => void;
|
||||
updateInfo?: Update;
|
||||
setUpdateInfo: (updateInfo?: Update) => void;
|
||||
updateInfo?: any;
|
||||
setUpdateInfo: (updateInfo?: any) => void;
|
||||
};
|
||||
|
||||
export const useUpdateStore = create<IUpdateStore>()(
|
||||
@@ -27,7 +26,7 @@ export const useUpdateStore = create<IUpdateStore>()(
|
||||
setIsOptional: (isOptional: boolean) => {
|
||||
return set({ isOptional });
|
||||
},
|
||||
setUpdateInfo: (updateInfo?: Update) => {
|
||||
setUpdateInfo: (updateInfo?: any) => {
|
||||
return set({ updateInfo });
|
||||
},
|
||||
}),
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
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 { useAppStore } from "@/stores/appStore";
|
||||
import platformAdapter from "./platformAdapter";
|
||||
|
||||
// 1
|
||||
export async function copyToClipboard(text: string) {
|
||||
@@ -65,10 +65,11 @@ export const IsTauri = () => {
|
||||
|
||||
export const OpenURLWithBrowser = async (url: string) => {
|
||||
if (!url) return;
|
||||
if (isTauri()) {
|
||||
if (IsTauri()) {
|
||||
try {
|
||||
await open(url);
|
||||
await hide_coco();
|
||||
await platformAdapter.invokeBackend("hide_coco");
|
||||
console.log("URL opened in default browser");
|
||||
} catch (error) {
|
||||
console.error("Failed to open URL:", error);
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { useState } from "react";
|
||||
import { isTauri } from "@tauri-apps/api/core";
|
||||
import { convertFileSrc as tauriConvertFileSrc } from "@tauri-apps/api/core";
|
||||
import type { OpenDialogOptions } from "@tauri-apps/plugin-dialog";
|
||||
import { createWebAdapter } from './webAdapter';
|
||||
// import { createTauriAdapter } from './tauriAdapter';
|
||||
|
||||
import { IShortcutsStore } from "@/stores/shortcutsStore";
|
||||
import { IStartupStore } from "@/stores/startupStore";
|
||||
|
||||
@@ -29,8 +28,19 @@ export interface EventPayloads {
|
||||
open_settings: string | "";
|
||||
tab_index: string | "";
|
||||
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
|
||||
"show-coco": void;
|
||||
"change-shortcuts-store": IShortcutsStore;
|
||||
}
|
||||
|
||||
@@ -40,7 +50,6 @@ export interface PlatformAdapter {
|
||||
setWindowSize: (width: number, height: number) => Promise<void>;
|
||||
hideWindow: () => Promise<void>;
|
||||
showWindow: () => Promise<void>;
|
||||
isPlatformTauri: () => boolean;
|
||||
convertFileSrc: (path: string) => string;
|
||||
emitEvent: (event: string, payload?: any) => Promise<void>;
|
||||
listenEvent: <K extends keyof EventPayloads>(
|
||||
@@ -54,9 +63,9 @@ export interface PlatformAdapter {
|
||||
getScreenshotableWindows: () => Promise<any[]>;
|
||||
captureMonitorScreenshot: (id: number) => Promise<string>;
|
||||
captureWindowScreenshot: (id: number) => Promise<string>;
|
||||
openFileDialog: (
|
||||
options: OpenDialogOptions
|
||||
) => Promise<string | string[] | null>;
|
||||
openFileDialog: (options: {
|
||||
multiple: boolean;
|
||||
}) => Promise<string | string[] | null>;
|
||||
getFileMetadata: (path: string) => Promise<any>;
|
||||
getFileIcon: (path: string, size: number) => Promise<string>;
|
||||
checkUpdate: () => Promise<any>;
|
||||
@@ -72,406 +81,18 @@ export interface PlatformAdapter {
|
||||
show: () => Promise<void>;
|
||||
setFocus: () => Promise<void>;
|
||||
center: () => Promise<void>;
|
||||
close: () => Promise<void>;
|
||||
} | null>;
|
||||
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
|
||||
const platformAdapter = createPlatformAdapter();
|
||||
const platformAdapter: PlatformAdapter = typeof window !== 'undefined' ? createWebAdapter() : {} as PlatformAdapter;
|
||||
|
||||
export default platformAdapter;
|
||||
|
||||
// Custom hook for using platform adapter
|
||||
export const usePlatformAdapter = () => {
|
||||
const [adapter] = useState<PlatformAdapter>(platformAdapter);
|
||||
|
||||
return adapter;
|
||||
};
|
||||
export default platformAdapter;
|
||||
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} */
|
||||
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: {
|
||||
extend: {
|
||||
backgroundColor: {
|
||||
|
||||
@@ -3,18 +3,22 @@ import { writeFileSync, readFileSync } from 'fs';
|
||||
import { join, resolve } from 'path';
|
||||
|
||||
export default defineConfig({
|
||||
entry: ['src/pages/web/SearchChat.tsx'],
|
||||
format: ['esm', 'cjs'],
|
||||
entry: ['src/pages/web/index.tsx'],
|
||||
format: ['esm'],
|
||||
dts: true,
|
||||
splitting: false,
|
||||
sourcemap: true,
|
||||
splitting: true,
|
||||
sourcemap: false,
|
||||
clean: true,
|
||||
treeshake: true,
|
||||
minify: true,
|
||||
env: {
|
||||
BUILD_TARGET: 'web',
|
||||
NODE_ENV: 'production',
|
||||
},
|
||||
external: [
|
||||
'react',
|
||||
'react-dom',
|
||||
],
|
||||
treeshake: true,
|
||||
minify: true,
|
||||
esbuildOptions(options) {
|
||||
options.bundle = true;
|
||||
options.platform = 'browser';
|
||||
@@ -28,6 +32,22 @@ export default defineConfig({
|
||||
options.alias = {
|
||||
'@': 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: [
|
||||
{
|
||||
@@ -38,36 +58,65 @@ export default defineConfig({
|
||||
},
|
||||
},
|
||||
],
|
||||
outDir: 'dist/search-chat',
|
||||
outDir: 'out/search-chat',
|
||||
|
||||
async onSuccess() {
|
||||
const projectPackageJson = JSON.parse(
|
||||
readFileSync(join(__dirname, 'package.json'), 'utf-8')
|
||||
);
|
||||
|
||||
const packageJson = {
|
||||
name: "search-chat",
|
||||
version: "1.0.0",
|
||||
main: "SearchChat.cjs",
|
||||
module: "SearchChat.js",
|
||||
types: "SearchChat.d.ts",
|
||||
name: "@infinilabs/search-chat",
|
||||
version: "1.0.10",
|
||||
main: "index.js",
|
||||
module: "index.js",
|
||||
type: "module",
|
||||
types: "index.d.ts",
|
||||
dependencies: projectPackageJson.dependencies,
|
||||
peerDependencies: {
|
||||
"react": "^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 =>
|
||||
dep.includes('@tauri-apps') ||
|
||||
dep.includes('tauri-plugin')
|
||||
const tauriDeps = Object.keys(packageJson.dependencies).filter(dep =>
|
||||
dep.includes('@tauri-apps') ||
|
||||
dep.includes('tauri-plugin') ||
|
||||
noNeedDeps.includes(dep)
|
||||
);
|
||||
tauriDeps.forEach(dep => {
|
||||
delete packageJson.dependencies[dep];
|
||||
});
|
||||
|
||||
writeFileSync(
|
||||
join(__dirname, 'dist/search-chat/package.json'),
|
||||
join(__dirname, 'out/search-chat/package.json'),
|
||||
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,
|
||||
secure: false,
|
||||
},
|
||||
"/integration": {
|
||||
target: process.env.COCO_SERVER_URL,
|
||||
changeOrigin: true,
|
||||
secure: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
build: {
|
||||
|
||||
Reference in New Issue
Block a user