mirror of
https://github.com/infinilabs/coco-app.git
synced 2025-12-17 20:17:43 +01:00
Compare commits
23 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bfc7b488ad | ||
|
|
249cc2eae4 | ||
|
|
388dac6452 | ||
|
|
dc8d1b5054 | ||
|
|
046c3dda82 | ||
|
|
60ce678e3e | ||
|
|
8d79b9ba1a | ||
|
|
969126ed89 | ||
|
|
e2df2b583a | ||
|
|
9d948d4fc6 | ||
|
|
81c770ba7e | ||
|
|
c9e9a72a0e | ||
|
|
96e6aae30b | ||
|
|
d319f5ebc7 | ||
|
|
04ff358dc7 | ||
|
|
22872ab02f | ||
|
|
fcfd21be45 | ||
|
|
0044e9a536 | ||
|
|
44a3ea3868 | ||
|
|
b444dc35ae | ||
|
|
8c9ccef218 | ||
|
|
a3bc997efe | ||
|
|
910841013f |
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@@ -73,7 +73,7 @@ jobs:
|
||||
sudo apt-get install -y libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf
|
||||
|
||||
- name: Install Rust stable
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
run: rustup toolchain install stable
|
||||
|
||||
- name: Rust cache
|
||||
uses: swatinem/rust-cache@v2
|
||||
|
||||
12
.vscode/settings.json
vendored
12
.vscode/settings.json
vendored
@@ -4,34 +4,45 @@
|
||||
"autolaunch",
|
||||
"Avenir",
|
||||
"callout",
|
||||
"changelogithub",
|
||||
"clsx",
|
||||
"codegen",
|
||||
"dtolnay",
|
||||
"dyld",
|
||||
"elif",
|
||||
"fullscreen",
|
||||
"headlessui",
|
||||
"Icdbb",
|
||||
"icns",
|
||||
"INFINI",
|
||||
"infinilabs",
|
||||
"inputbox",
|
||||
"katex",
|
||||
"khtml",
|
||||
"languagedetector",
|
||||
"libappindicator",
|
||||
"librsvg",
|
||||
"libwebkit",
|
||||
"localstorage",
|
||||
"lucide",
|
||||
"maximizable",
|
||||
"Minimizable",
|
||||
"msvc",
|
||||
"nord",
|
||||
"nowrap",
|
||||
"nspanel",
|
||||
"nsstring",
|
||||
"overscan",
|
||||
"partialize",
|
||||
"patchelf",
|
||||
"Raycast",
|
||||
"rehype",
|
||||
"reqwest",
|
||||
"rgba",
|
||||
"rustup",
|
||||
"screenshotable",
|
||||
"serde",
|
||||
"swatinem",
|
||||
"tailwindcss",
|
||||
"tauri",
|
||||
"thiserror",
|
||||
@@ -46,6 +57,7 @@
|
||||
"VITE",
|
||||
"walkdir",
|
||||
"webviews",
|
||||
"xzvf",
|
||||
"yuque",
|
||||
"zustand"
|
||||
],
|
||||
|
||||
@@ -7,6 +7,12 @@ theme: book
|
||||
disablePathToLower: true
|
||||
enableGitInfo: false
|
||||
|
||||
outputs:
|
||||
home:
|
||||
- HTML
|
||||
- RSS
|
||||
- JSON
|
||||
|
||||
# Needed for mermaid/katex shortcodes
|
||||
markup:
|
||||
goldmark:
|
||||
|
||||
@@ -9,14 +9,39 @@ Information about release notes of Coco Server is provided here.
|
||||
|
||||
## Latest (In development)
|
||||
|
||||
### Breaking changes
|
||||
|
||||
### Features
|
||||
|
||||
### Bug fix
|
||||
|
||||
### Improvements
|
||||
|
||||
## 0.2.1 (2025-03-14)
|
||||
|
||||
### Features
|
||||
|
||||
- support for automatic in-app updates #274
|
||||
|
||||
### Breaking changes
|
||||
|
||||
### Bug fix
|
||||
|
||||
- Fix the issue that the fusion search include disabled servers
|
||||
- Fix incorrect version type: should be string instead of u32
|
||||
- Fix the chat end judgment type #280
|
||||
- Fix the chat scrolling and chat rendering #282
|
||||
- Fix: store data is not shared among multiple windows #298
|
||||
|
||||
### Improvements
|
||||
|
||||
- Refactor: chat components #273
|
||||
- Feat:add endpoint display #282
|
||||
- Chore: chat window min width & remove input bg #284
|
||||
- Chore: remove selected function & add hide_coco #286
|
||||
- Chore:websocket timeout increased to 2 minutes #289
|
||||
- Chore: remove chat input border & clear input #295
|
||||
|
||||
## 0.2.0 (2025-03-07)
|
||||
|
||||
### Features
|
||||
@@ -25,7 +50,7 @@ Information about release notes of Coco Server is provided here.
|
||||
- Add api to disable or enable server #185
|
||||
- Networked search supports selection of data sources #209
|
||||
- Add deepthink and knowledge search options to RAG based chat
|
||||
- Support i18n, add Chinese language support
|
||||
- Support i18n, add Chinese language support
|
||||
- Support Windows platform
|
||||
- etc.
|
||||
|
||||
@@ -54,7 +79,6 @@ Information about release notes of Coco Server is provided here.
|
||||
- Allow to switch servers in the settings page
|
||||
- etc.
|
||||
|
||||
|
||||
## 0.1.0 (2025-02-16)
|
||||
|
||||
### Features
|
||||
|
||||
13
package.json
13
package.json
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "coco",
|
||||
"private": true,
|
||||
"version": "0.2.0",
|
||||
"version": "0.2.1",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
@@ -20,10 +20,10 @@
|
||||
"@tauri-apps/plugin-dialog": "^2.2.0",
|
||||
"@tauri-apps/plugin-global-shortcut": "~2.0.0",
|
||||
"@tauri-apps/plugin-http": "~2.0.2",
|
||||
"@tauri-apps/plugin-os": "^2.2.0",
|
||||
"@tauri-apps/plugin-os": "^2.2.1",
|
||||
"@tauri-apps/plugin-process": "^2.2.0",
|
||||
"@tauri-apps/plugin-shell": "^2.2.0",
|
||||
"@tauri-apps/plugin-updater": "^2.5.1",
|
||||
"@tauri-apps/plugin-updater": "^2.6.0",
|
||||
"@tauri-apps/plugin-websocket": "~2.3.0",
|
||||
"@tauri-apps/plugin-window": "2.0.0-alpha.1",
|
||||
"ahooks": "^3.8.4",
|
||||
@@ -35,7 +35,7 @@
|
||||
"lodash-es": "^4.17.21",
|
||||
"lucide-react": "^0.461.0",
|
||||
"mermaid": "^11.4.1",
|
||||
"nanoid": "^5.1.2",
|
||||
"nanoid": "^5.1.3",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-hotkeys-hook": "^4.6.1",
|
||||
@@ -57,16 +57,17 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tauri-apps/cli": "^2.3.1",
|
||||
"@types/dom-speech-recognition": "^0.0.4",
|
||||
"@types/lodash-es": "^4.17.12",
|
||||
"@types/markdown-it": "^14.1.2",
|
||||
"@types/node": "^22.13.9",
|
||||
"@types/node": "^22.13.10",
|
||||
"@types/react": "^18.3.18",
|
||||
"@types/react-dom": "^18.3.5",
|
||||
"@types/react-i18next": "^8.1.0",
|
||||
"@types/react-katex": "^3.0.4",
|
||||
"@types/react-window": "^1.8.8",
|
||||
"@vitejs/plugin-react": "^4.3.4",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"autoprefixer": "^10.4.21",
|
||||
"immer": "^10.1.1",
|
||||
"postcss": "^8.5.3",
|
||||
"release-it": "^18.1.2",
|
||||
|
||||
766
pnpm-lock.yaml
generated
766
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
329
src-tauri/Cargo.lock
generated
329
src-tauri/Cargo.lock
generated
@@ -181,7 +181,7 @@ checksum = "0ae92a5119aa49cdbcf6b9f893fe4e1d98b04ccbf82ee0584ad948a44a734dea"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.99",
|
||||
"syn 2.0.100",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -272,7 +272,7 @@ dependencies = [
|
||||
"futures-lite",
|
||||
"parking",
|
||||
"polling",
|
||||
"rustix",
|
||||
"rustix 0.38.44",
|
||||
"slab",
|
||||
"tracing",
|
||||
"windows-sys 0.59.0",
|
||||
@@ -304,7 +304,7 @@ dependencies = [
|
||||
"cfg-if",
|
||||
"event-listener",
|
||||
"futures-lite",
|
||||
"rustix",
|
||||
"rustix 0.38.44",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
@@ -316,7 +316,7 @@ checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.99",
|
||||
"syn 2.0.100",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -331,7 +331,7 @@ dependencies = [
|
||||
"cfg-if",
|
||||
"futures-core",
|
||||
"futures-io",
|
||||
"rustix",
|
||||
"rustix 0.38.44",
|
||||
"signal-hook-registry",
|
||||
"slab",
|
||||
"windows-sys 0.59.0",
|
||||
@@ -351,7 +351,7 @@ checksum = "d556ec1359574147ec0c4fc5eb525f3f23263a592b1a9c07e0a75b427de55c97"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.99",
|
||||
"syn 2.0.100",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -582,9 +582,9 @@ checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495"
|
||||
|
||||
[[package]]
|
||||
name = "bytes"
|
||||
version = "1.10.0"
|
||||
version = "1.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f61dac84819c6588b558454b194026eb1f09c293b9036ae9b159e74e73ab6cf9"
|
||||
checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
@@ -733,7 +733,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "coco"
|
||||
version = "0.2.0"
|
||||
version = "0.2.1"
|
||||
dependencies = [
|
||||
"applications",
|
||||
"async-trait",
|
||||
@@ -1103,7 +1103,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"syn 2.0.99",
|
||||
"syn 2.0.100",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1113,7 +1113,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32a2785755761f3ddc1492979ce1e48d2c00d09311c39e4466429188f3dd6501"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"syn 2.0.99",
|
||||
"syn 2.0.100",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1137,7 +1137,7 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"strsim",
|
||||
"syn 2.0.99",
|
||||
"syn 2.0.100",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1148,7 +1148,7 @@ checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806"
|
||||
dependencies = [
|
||||
"darling_core",
|
||||
"quote",
|
||||
"syn 2.0.99",
|
||||
"syn 2.0.100",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1192,7 +1192,7 @@ checksum = "30542c1ad912e0e3d22a1935c290e12e8a29d704a420177a31faad4a601a0800"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.99",
|
||||
"syn 2.0.100",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1205,7 +1205,7 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"rustc_version",
|
||||
"syn 2.0.99",
|
||||
"syn 2.0.100",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1294,7 +1294,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.99",
|
||||
"syn 2.0.100",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1326,7 +1326,7 @@ checksum = "f2b99bf03862d7f545ebc28ddd33a665b50865f4dfd84031a393823879bd4c54"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.99",
|
||||
"syn 2.0.100",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1412,9 +1412,9 @@ checksum = "1c7a8fb8a9fbf66c1f703fe16184d10ca0ee9d23be5b4436400408ba54a95005"
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.14.0"
|
||||
version = "1.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b7914353092ddf589ad78f25c5c1c21b7f80b0ff8621e7c814c3485b5306da9d"
|
||||
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
|
||||
|
||||
[[package]]
|
||||
name = "embed-resource"
|
||||
@@ -1469,7 +1469,7 @@ checksum = "fc4caf64a58d7a6d65ab00639b046ff54399a39f5f2554728895ace4b297cd79"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.99",
|
||||
"syn 2.0.100",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1484,14 +1484,14 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "env_logger"
|
||||
version = "0.11.6"
|
||||
version = "0.11.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dcaee3d8e3cfc3fd92428d477bc97fc29ec8716d180c0d74c643bb26166660e0"
|
||||
checksum = "c3716d7a920fb4fac5d84e9d4bce8ceb321e9414b4409da61b07b75c1e3d0697"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
"env_filter",
|
||||
"humantime",
|
||||
"jiff",
|
||||
"log",
|
||||
]
|
||||
|
||||
@@ -1652,7 +1652,7 @@ checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.99",
|
||||
"syn 2.0.100",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1770,7 +1770,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.99",
|
||||
"syn 2.0.100",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1929,11 +1929,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "gethostname"
|
||||
version = "0.5.0"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc3655aa6818d65bc620d6911f05aa7b6aeb596291e1e9f79e52df85583d1e30"
|
||||
checksum = "4fd4b8790c0792e3b11895efdf5f289ebe8b59107a6624f1cce68f24ff8c7035"
|
||||
dependencies = [
|
||||
"rustix",
|
||||
"rustix 0.38.44",
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
@@ -2106,7 +2106,7 @@ dependencies = [
|
||||
"proc-macro-error",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.99",
|
||||
"syn 2.0.100",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2116,10 +2116,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "715601f8f02e71baef9c1f94a657a9a77c192aea6097cf9ae7e5e177cd8cde68"
|
||||
dependencies = [
|
||||
"heck 0.5.0",
|
||||
"proc-macro-crate 3.2.0",
|
||||
"proc-macro-crate 3.3.0",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.99",
|
||||
"syn 2.0.100",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2236,7 +2236,7 @@ dependencies = [
|
||||
"proc-macro-error",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.99",
|
||||
"syn 2.0.100",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2251,7 +2251,7 @@ dependencies = [
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
"http 1.2.0",
|
||||
"indexmap 2.7.1",
|
||||
"indexmap 2.8.0",
|
||||
"slab",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
@@ -2413,12 +2413,6 @@ version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
|
||||
|
||||
[[package]]
|
||||
name = "humantime"
|
||||
version = "2.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
|
||||
|
||||
[[package]]
|
||||
name = "hyper"
|
||||
version = "0.14.32"
|
||||
@@ -2662,7 +2656,7 @@ checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.99",
|
||||
"syn 2.0.100",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2744,9 +2738,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.7.1"
|
||||
version = "2.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652"
|
||||
checksum = "3954d50fe15b02142bf25d3b8bdadb634ec3948f103d04ffe3031bc8fe9d7058"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown 0.15.2",
|
||||
@@ -2799,7 +2793,7 @@ checksum = "c34819042dc3d3971c46c2190835914dfbe0c3c13f61449b2997f4e9722dfa60"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.99",
|
||||
"syn 2.0.100",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2877,6 +2871,30 @@ dependencies = [
|
||||
"system-deps 6.2.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jiff"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d699bc6dfc879fb1bf9bdff0d4c56f0884fc6f0d0eb0fba397a6d00cd9a6b85e"
|
||||
dependencies = [
|
||||
"jiff-static",
|
||||
"log",
|
||||
"portable-atomic",
|
||||
"portable-atomic-util",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jiff-static"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8d16e75759ee0aa64c57a56acbf43916987b20c77373cb7e808979e02b93c9f9"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.100",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jni"
|
||||
version = "0.21.1"
|
||||
@@ -3088,6 +3106,12 @@ version = "0.4.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab"
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
version = "0.9.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6db9c683daf087dc577b7506e9695b3d556a9f3849903fa28186283afd6809e9"
|
||||
|
||||
[[package]]
|
||||
name = "litemap"
|
||||
version = "0.7.5"
|
||||
@@ -3450,7 +3474,7 @@ checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.99",
|
||||
"syn 2.0.100",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3497,10 +3521,10 @@ version = "0.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56"
|
||||
dependencies = [
|
||||
"proc-macro-crate 3.2.0",
|
||||
"proc-macro-crate 3.3.0",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.99",
|
||||
"syn 2.0.100",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3824,9 +3848,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.20.3"
|
||||
version = "1.21.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e"
|
||||
checksum = "cde51589ab56b20a6f686b2c68f7a0bd6add753d697abf720d63f8db3ab7b1ad"
|
||||
|
||||
[[package]]
|
||||
name = "open"
|
||||
@@ -3863,7 +3887,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.99",
|
||||
"syn 2.0.100",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4143,7 +4167,7 @@ dependencies = [
|
||||
"phf_shared 0.11.3",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.99",
|
||||
"syn 2.0.100",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4227,7 +4251,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42cf17e9a1800f5f396bc67d193dc9411b59012a5876445ef450d449881e1016"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"indexmap 2.7.1",
|
||||
"indexmap 2.8.0",
|
||||
"quick-xml 0.32.0",
|
||||
"serde",
|
||||
"time",
|
||||
@@ -4256,11 +4280,26 @@ dependencies = [
|
||||
"concurrent-queue",
|
||||
"hermit-abi",
|
||||
"pin-project-lite",
|
||||
"rustix",
|
||||
"rustix 0.38.44",
|
||||
"tracing",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "portable-atomic"
|
||||
version = "1.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "350e9b48cbc6b0e028b0473b114454c6316e57336ee184ceab6e53f72c178b3e"
|
||||
|
||||
[[package]]
|
||||
name = "portable-atomic-util"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507"
|
||||
dependencies = [
|
||||
"portable-atomic",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "powerfmt"
|
||||
version = "0.2.0"
|
||||
@@ -4269,11 +4308,11 @@ checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
|
||||
|
||||
[[package]]
|
||||
name = "ppv-lite86"
|
||||
version = "0.2.20"
|
||||
version = "0.2.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04"
|
||||
checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
|
||||
dependencies = [
|
||||
"zerocopy 0.7.35",
|
||||
"zerocopy 0.8.23",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4303,9 +4342,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-crate"
|
||||
version = "3.2.0"
|
||||
version = "3.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ecf48c7ca261d60b74ab1a7b20da18bede46776b2e55535cb958eb595c5fa7b"
|
||||
checksum = "edce586971a4dfaa28950c6f18ed55e0406c1ab88bbce2c6f6293a7aaba73d35"
|
||||
dependencies = [
|
||||
"toml_edit 0.22.24",
|
||||
]
|
||||
@@ -4365,7 +4404,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a65f2e60fbf1063868558d69c6beacf412dc755f9fc020f514b7955fc914fe30"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"syn 2.0.99",
|
||||
"syn 2.0.100",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4520,7 +4559,7 @@ checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94"
|
||||
dependencies = [
|
||||
"rand_chacha 0.9.0",
|
||||
"rand_core 0.9.3",
|
||||
"zerocopy 0.8.21",
|
||||
"zerocopy 0.8.23",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4820,9 +4859,9 @@ checksum = "57397d16646700483b67d2dd6511d79318f9d057fdbd21a4066aeac8b41d310a"
|
||||
|
||||
[[package]]
|
||||
name = "ring"
|
||||
version = "0.17.11"
|
||||
version = "0.17.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "da5349ae27d3887ca812fb375b45a4fbb36d8d12d2df394968cd86e35683fe73"
|
||||
checksum = "70ac5d832aa16abd7d1def883a8545280c20a60f523a370aa3a9617c2b8550ee"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"cfg-if",
|
||||
@@ -4895,7 +4934,20 @@ dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
"linux-raw-sys 0.4.15",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f7178faa4b75a30e269c71e61c353ce2748cf3d76f0c44c393f4e60abf49b825"
|
||||
dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys 0.9.2",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
@@ -5018,7 +5070,7 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"serde_derive_internals",
|
||||
"syn 2.0.99",
|
||||
"syn 2.0.100",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5097,9 +5149,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.218"
|
||||
version = "1.0.219"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e8dfc9d19bdbf6d17e22319da49161d5d0108e4188e8b680aef6299eed22df60"
|
||||
checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
@@ -5117,13 +5169,13 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.218"
|
||||
version = "1.0.219"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f09503e191f4e797cb8aac08e9a4a4695c5edf6a2e70e376d961ddd5c969f82b"
|
||||
checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.99",
|
||||
"syn 2.0.100",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5134,7 +5186,7 @@ checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.99",
|
||||
"syn 2.0.100",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5157,7 +5209,7 @@ checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.99",
|
||||
"syn 2.0.100",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5191,7 +5243,7 @@ dependencies = [
|
||||
"chrono",
|
||||
"hex",
|
||||
"indexmap 1.9.3",
|
||||
"indexmap 2.7.1",
|
||||
"indexmap 2.8.0",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
@@ -5208,7 +5260,7 @@ dependencies = [
|
||||
"darling",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.99",
|
||||
"syn 2.0.100",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5475,9 +5527,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.99"
|
||||
version = "2.0.100"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e02e925281e18ffd9d640e234264753c43edc62d64b2d4cf898f1bc5e75f3fc2"
|
||||
checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -5501,7 +5553,7 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.99",
|
||||
"syn 2.0.100",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5562,9 +5614,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tao"
|
||||
version = "0.32.7"
|
||||
version = "0.32.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7e7f38988a68dfb559899ea307b97577f008d3254f6cfdd219a67e27ce34c95b"
|
||||
checksum = "63c8b1020610b9138dd7b1e06cf259ae91aa05c30f3bd0d6b42a03997b92dec1"
|
||||
dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
"core-foundation 0.10.0",
|
||||
@@ -5607,7 +5659,7 @@ checksum = "f4e16beb8b2ac17db28eab8bca40e62dbfbb34c0fcdc6d9826b11b7b5d047dfd"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.99",
|
||||
"syn 2.0.100",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5719,7 +5771,7 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2",
|
||||
"syn 2.0.99",
|
||||
"syn 2.0.100",
|
||||
"tauri-utils",
|
||||
"thiserror 2.0.12",
|
||||
"time",
|
||||
@@ -5747,7 +5799,7 @@ dependencies = [
|
||||
"heck 0.5.0",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.99",
|
||||
"syn 2.0.100",
|
||||
"tauri-codegen",
|
||||
"tauri-utils",
|
||||
]
|
||||
@@ -5755,7 +5807,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "tauri-nspanel"
|
||||
version = "2.0.1"
|
||||
source = "git+https://github.com/ahkohd/tauri-nspanel?branch=v2#d20d53af786f1e2bdd48caac0145cd8ec3990c9b"
|
||||
source = "git+https://github.com/ahkohd/tauri-nspanel?branch=v2#16111b14441716350b6dc8157d926a5fda481687"
|
||||
dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
"block",
|
||||
@@ -5911,9 +5963,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin-http"
|
||||
version = "2.3.0"
|
||||
version = "2.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3a8137a106e0741fdd357366178fc6e0597abe7d20796f53f44171a1bcec1683"
|
||||
checksum = "028093def653e1f9da23a80beedfd33b88899427693b2c8357ce0c1cc26284b2"
|
||||
dependencies = [
|
||||
"data-url",
|
||||
"http 1.2.0",
|
||||
@@ -5947,9 +5999,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin-os"
|
||||
version = "2.2.0"
|
||||
version = "2.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dda2d571a9baf0664c1f2088db227e3072f9028602fafa885deade7547c3b738"
|
||||
checksum = "424f19432397850c2ddd42aa58078630c15287bbce3866eb1d90e7dbee680637"
|
||||
dependencies = [
|
||||
"gethostname",
|
||||
"log",
|
||||
@@ -6041,9 +6093,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin-updater"
|
||||
version = "2.5.1"
|
||||
version = "2.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "69b7db616844d73d55e4d00190be101b29de463d5cb70321c2840fa4e9c414c4"
|
||||
checksum = "67cd78a6cbd1255e989e96eedec004e9e8949e6c6359b41f861279aba64ea306"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"dirs 6.0.0",
|
||||
@@ -6051,6 +6103,7 @@ dependencies = [
|
||||
"futures-util",
|
||||
"http 1.2.0",
|
||||
"infer",
|
||||
"log",
|
||||
"minisign-verify",
|
||||
"osakit",
|
||||
"percent-encoding",
|
||||
@@ -6184,15 +6237,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tempfile"
|
||||
version = "3.17.1"
|
||||
version = "3.18.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "22e5a0acb1f3f55f65cc4a866c361b2fb2a0ff6366785ae6fbb5f85df07ba230"
|
||||
checksum = "2c317e0a526ee6120d8dabad239c8dadca62b24b6f168914bbbc8e2fb1f0e567"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"fastrand",
|
||||
"getrandom 0.3.1",
|
||||
"once_cell",
|
||||
"rustix",
|
||||
"rustix 1.0.2",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
@@ -6239,7 +6292,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.99",
|
||||
"syn 2.0.100",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -6250,7 +6303,7 @@ checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.99",
|
||||
"syn 2.0.100",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -6266,9 +6319,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.3.37"
|
||||
version = "0.3.39"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21"
|
||||
checksum = "dad298b01a40a23aac4580b67e3dbedb7cc8402f3592d7f49469de2ea4aecdd8"
|
||||
dependencies = [
|
||||
"deranged",
|
||||
"itoa 1.0.15",
|
||||
@@ -6281,15 +6334,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "time-core"
|
||||
version = "0.1.2"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3"
|
||||
checksum = "765c97a5b985b7c11d7bc27fa927dc4fe6af3a6dfb021d28deb60d3bf51e76ef"
|
||||
|
||||
[[package]]
|
||||
name = "time-macros"
|
||||
version = "0.2.19"
|
||||
version = "0.2.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2834e6017e3e5e4b9834939793b282bc03b37a3336245fa820e35e233e2a85de"
|
||||
checksum = "e8093bc3e81c3bc5f7879de09619d06c9a5a5e45ca44dfeeb7225bae38005c5c"
|
||||
dependencies = [
|
||||
"num-conv",
|
||||
"time-core",
|
||||
@@ -6331,9 +6384,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
||||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "1.43.0"
|
||||
version = "1.44.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3d61fa4ffa3de412bfea335c6ecff681de2b609ba3c77ef3e00e521813a9ed9e"
|
||||
checksum = "9975ea0f48b5aa3972bf2d888c238182458437cc2a19374b81b25cdf1023fb3a"
|
||||
dependencies = [
|
||||
"backtrace",
|
||||
"bytes",
|
||||
@@ -6356,7 +6409,7 @@ checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.99",
|
||||
"syn 2.0.100",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -6460,7 +6513,7 @@ version = "0.19.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421"
|
||||
dependencies = [
|
||||
"indexmap 2.7.1",
|
||||
"indexmap 2.8.0",
|
||||
"toml_datetime",
|
||||
"winnow 0.5.40",
|
||||
]
|
||||
@@ -6471,7 +6524,7 @@ version = "0.20.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "70f427fce4d84c72b5b732388bf4a9f4531b53f74e2887e3ecb2481f68f66d81"
|
||||
dependencies = [
|
||||
"indexmap 2.7.1",
|
||||
"indexmap 2.8.0",
|
||||
"toml_datetime",
|
||||
"winnow 0.5.40",
|
||||
]
|
||||
@@ -6482,7 +6535,7 @@ version = "0.22.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "17b4795ff5edd201c7cd6dca065ae59972ce77d1b80fa0a84d94950ece7d1474"
|
||||
dependencies = [
|
||||
"indexmap 2.7.1",
|
||||
"indexmap 2.8.0",
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
@@ -6535,7 +6588,7 @@ checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.99",
|
||||
"syn 2.0.100",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -6895,7 +6948,7 @@ dependencies = [
|
||||
"log",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.99",
|
||||
"syn 2.0.100",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
@@ -6930,7 +6983,7 @@ checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.99",
|
||||
"syn 2.0.100",
|
||||
"wasm-bindgen-backend",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
@@ -6965,7 +7018,7 @@ checksum = "b7208998eaa3870dad37ec8836979581506e0c5c64c20c9e79e9d2a10d6f47bf"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"downcast-rs",
|
||||
"rustix",
|
||||
"rustix 0.38.44",
|
||||
"scoped-tls",
|
||||
"smallvec",
|
||||
"wayland-sys",
|
||||
@@ -6978,7 +7031,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c2120de3d33638aaef5b9f4472bff75f07c56379cf76ea320bd3a3d65ecaf73f"
|
||||
dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
"rustix",
|
||||
"rustix 0.38.44",
|
||||
"wayland-backend",
|
||||
"wayland-scanner",
|
||||
]
|
||||
@@ -7118,7 +7171,7 @@ checksum = "1d228f15bba3b9d56dde8bddbee66fa24545bd17b48d5128ccf4a8742b18e431"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.99",
|
||||
"syn 2.0.100",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -7329,7 +7382,7 @@ checksum = "12168c33176773b86799be25e2a2ba07c7aab9968b37541f1094dbd7a60c8946"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.99",
|
||||
"syn 2.0.100",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -7340,7 +7393,7 @@ checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.99",
|
||||
"syn 2.0.100",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -7351,7 +7404,7 @@ checksum = "83577b051e2f49a058c308f17f273b570a6a758386fc291b5f6a934dd84e48c1"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.99",
|
||||
"syn 2.0.100",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -7362,7 +7415,7 @@ checksum = "9d8dc32e0095a7eeccebd0e3f09e9509365ecb3fc6ac4d6f5f14a3f6392942d1"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.99",
|
||||
"syn 2.0.100",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -7373,7 +7426,7 @@ checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.99",
|
||||
"syn 2.0.100",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -7384,7 +7437,7 @@ checksum = "cb26fd936d991781ea39e87c3a27285081e3c0da5ca0fcbc02d368cc6f52ff01"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.99",
|
||||
"syn 2.0.100",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -7818,9 +7871,9 @@ checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51"
|
||||
|
||||
[[package]]
|
||||
name = "wry"
|
||||
version = "0.50.3"
|
||||
version = "0.50.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d2ec139df5102db821f92a42033c3fa0467c5ab434511e79c65881d6bdf2b369"
|
||||
checksum = "804a7d1613bd699beccaa60f3b3c679acee21cebba1945a693f5eab95c08d1fa"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"block2 0.6.0",
|
||||
@@ -7883,13 +7936,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "xattr"
|
||||
version = "1.4.0"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e105d177a3871454f754b33bb0ee637ecaaac997446375fd3e5d43a2ed00c909"
|
||||
checksum = "0d65cbf2f12c15564212d48f4e3dfb87923d25d611f2aed18f4cb23f0413d89e"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
"rustix",
|
||||
"rustix 1.0.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -7955,7 +8007,7 @@ checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.99",
|
||||
"syn 2.0.100",
|
||||
"synstructure",
|
||||
]
|
||||
|
||||
@@ -8002,10 +8054,10 @@ version = "5.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f325ad10eb0d0a3eb060203494c3b7ec3162a01a59db75d2deee100339709fc0"
|
||||
dependencies = [
|
||||
"proc-macro-crate 3.2.0",
|
||||
"proc-macro-crate 3.3.0",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.99",
|
||||
"syn 2.0.100",
|
||||
"zbus_names",
|
||||
"zvariant",
|
||||
"zvariant_utils",
|
||||
@@ -8029,17 +8081,16 @@ version = "0.7.35"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"zerocopy-derive 0.7.35",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.8.21"
|
||||
version = "0.8.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dcf01143b2dd5d134f11f545cf9f1431b13b749695cb33bcce051e7568f99478"
|
||||
checksum = "fd97444d05a4328b90e75e503a34bad781f14e28a823ad3557f0750df1ebcbc6"
|
||||
dependencies = [
|
||||
"zerocopy-derive 0.8.21",
|
||||
"zerocopy-derive 0.8.23",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -8050,18 +8101,18 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.99",
|
||||
"syn 2.0.100",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy-derive"
|
||||
version = "0.8.21"
|
||||
version = "0.8.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712c8386f4f4299382c9abee219bee7084f78fb939d88b6840fcc1320d5f6da2"
|
||||
checksum = "6352c01d0edd5db859a63e2605f4ea3183ddbd15e2c4a9e7d32184df75e4f154"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.99",
|
||||
"syn 2.0.100",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -8081,7 +8132,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.99",
|
||||
"syn 2.0.100",
|
||||
"synstructure",
|
||||
]
|
||||
|
||||
@@ -8110,7 +8161,7 @@ checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.99",
|
||||
"syn 2.0.100",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -8123,7 +8174,7 @@ dependencies = [
|
||||
"crc32fast",
|
||||
"crossbeam-utils",
|
||||
"displaydoc",
|
||||
"indexmap 2.7.1",
|
||||
"indexmap 2.8.0",
|
||||
"memchr",
|
||||
"thiserror 2.0.12",
|
||||
]
|
||||
@@ -8174,10 +8225,10 @@ version = "5.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "74170caa85b8b84cc4935f2d56a57c7a15ea6185ccdd7eadb57e6edd90f94b2f"
|
||||
dependencies = [
|
||||
"proc-macro-crate 3.2.0",
|
||||
"proc-macro-crate 3.3.0",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.99",
|
||||
"syn 2.0.100",
|
||||
"zvariant_utils",
|
||||
]
|
||||
|
||||
@@ -8191,6 +8242,6 @@ dependencies = [
|
||||
"quote",
|
||||
"serde",
|
||||
"static_assertions",
|
||||
"syn 2.0.99",
|
||||
"syn 2.0.100",
|
||||
"winnow 0.7.3",
|
||||
]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "coco"
|
||||
version = "0.2.0"
|
||||
version = "0.2.1"
|
||||
description = "Search, connect, collaborate – all in one place."
|
||||
authors = ["INFINI Labs"]
|
||||
edition = "2021"
|
||||
@@ -89,3 +89,4 @@ strip = true # Ensures debug symbols are removed.
|
||||
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
|
||||
tauri-plugin-autostart = "^2.2"
|
||||
tauri-plugin-global-shortcut = "2"
|
||||
tauri-plugin-updater = "2"
|
||||
|
||||
@@ -31,5 +31,12 @@
|
||||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
|
||||
<key>NSMicrophoneUsageDescription</key>
|
||||
<string>Coco AI needs access to your microphone for voice input and audio recording features.</string>
|
||||
<key>NSCameraUsageDescription</key>
|
||||
<string>Coco AI requires camera access for scanning documents and capturing images.</string>
|
||||
<key>NSSpeechRecognitionUsageDescription</key>
|
||||
<string>Coco AI uses speech recognition to convert your voice into text for a hands-free experience.</string>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -67,6 +67,7 @@
|
||||
"macos-permissions:default",
|
||||
"screenshots:default",
|
||||
"core:window:allow-set-theme",
|
||||
"process:default"
|
||||
"process:default",
|
||||
"updater:default"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
"autostart:allow-enable",
|
||||
"autostart:allow-disable",
|
||||
"autostart:allow-is-enabled",
|
||||
"global-shortcut:default"
|
||||
"global-shortcut:default",
|
||||
"updater:default"
|
||||
]
|
||||
}
|
||||
@@ -29,6 +29,11 @@ pub struct AuthProvider {
|
||||
pub sso: Sso,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct MinimalClientVersion {
|
||||
number: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Server {
|
||||
#[serde(default = "default_empty_string")] // Custom default function for empty string
|
||||
@@ -39,12 +44,13 @@ pub struct Server {
|
||||
pub endpoint: String,
|
||||
pub provider: Provider,
|
||||
pub version: Version,
|
||||
pub minimal_client_version: Option<MinimalClientVersion>,
|
||||
pub updated: String,
|
||||
#[serde(default = "default_enabled_type")]
|
||||
pub enabled: bool,
|
||||
#[serde(default = "default_bool_type")]
|
||||
pub public: bool,
|
||||
|
||||
|
||||
#[serde(default = "default_available_type")]
|
||||
pub available: bool,
|
||||
|
||||
@@ -70,7 +76,6 @@ impl Hash for Server {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ServerAccessToken {
|
||||
#[serde(default = "default_empty_string")] // Custom default function for empty string
|
||||
@@ -104,11 +109,11 @@ impl Hash for ServerAccessToken {
|
||||
}
|
||||
|
||||
fn default_empty_string() -> String {
|
||||
"".to_string() // Default to empty string if not provided
|
||||
"".to_string() // Default to empty string if not provided
|
||||
}
|
||||
|
||||
fn default_bool_type() -> bool {
|
||||
false // Default to false if not provided
|
||||
false // Default to false if not provided
|
||||
}
|
||||
|
||||
fn default_enabled_type() -> bool {
|
||||
@@ -123,4 +128,4 @@ fn default_priority_type() -> u32 {
|
||||
}
|
||||
fn default_user_profile_type() -> Option<UserProfile> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,12 +11,9 @@ mod util;
|
||||
use crate::common::register::SearchSourceRegistry;
|
||||
// use crate::common::traits::SearchSource;
|
||||
use crate::common::{MAIN_WINDOW_LABEL, SETTINGS_WINDOW_LABEL};
|
||||
use crate::server::search::CocoSearchSource;
|
||||
use crate::server::servers::{load_or_insert_default_server, load_servers_token};
|
||||
use autostart::{change_autostart, enable_autostart};
|
||||
use lazy_static::lazy_static;
|
||||
use reqwest::Client;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Mutex;
|
||||
#[cfg(target_os = "macos")]
|
||||
use tauri::ActivationPolicy;
|
||||
@@ -83,7 +80,8 @@ pub fn run() {
|
||||
.plugin(tauri_plugin_fs_pro::init())
|
||||
.plugin(tauri_plugin_macos_permissions::init())
|
||||
.plugin(tauri_plugin_screenshots::init())
|
||||
.plugin(tauri_plugin_process::init());
|
||||
.plugin(tauri_plugin_process::init())
|
||||
.plugin(tauri_plugin_updater::Builder::new().build());
|
||||
|
||||
// Conditional compilation for macOS
|
||||
#[cfg(target_os = "macos")]
|
||||
@@ -226,8 +224,8 @@ pub async fn init<R: Runtime>(app_handle: &AppHandle<R>) {
|
||||
let registry: State<SearchSourceRegistry> = app_handle.state::<SearchSourceRegistry>();
|
||||
|
||||
for server in coco_servers {
|
||||
let source = CocoSearchSource::new(server.clone(), Client::new());
|
||||
registry.register_source(source).await;
|
||||
crate::server::servers::try_register_server_to_search_source(app_handle.clone(), &server)
|
||||
.await;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
use crate::common::auth::RequestAccessTokenResponse;
|
||||
use crate::common::register::SearchSourceRegistry;
|
||||
use crate::common::server::ServerAccessToken;
|
||||
use crate::server::http_client::HttpClient;
|
||||
use crate::server::profile::get_user_profiles;
|
||||
use crate::server::search::CocoSearchSource;
|
||||
use crate::server::servers::{get_server_by_id, persist_servers, persist_servers_token, save_access_token, save_server};
|
||||
use reqwest::{Client, StatusCode};
|
||||
use crate::server::servers::{get_server_by_id, persist_servers, persist_servers_token, save_access_token, save_server, try_register_server_to_search_source};
|
||||
use reqwest::StatusCode;
|
||||
use std::collections::HashMap;
|
||||
use tauri::{AppHandle, Manager, Runtime};
|
||||
use tauri::{AppHandle, Runtime};
|
||||
fn request_access_token_url(request_id: &str) -> String {
|
||||
// Remove the endpoint part and keep just the path for the request
|
||||
format!("/auth/request_access_token?request_id={}", request_id)
|
||||
@@ -54,9 +52,8 @@ pub async fn handle_sso_callback<R: Runtime>(
|
||||
save_access_token(server_id.clone(), access_token);
|
||||
persist_servers_token(&app_handle)?;
|
||||
|
||||
let registry = app_handle.state::<SearchSourceRegistry>();
|
||||
let source = CocoSearchSource::new(server.clone(), Client::new());
|
||||
registry.register_source(source).await;
|
||||
// Register the server to the search source
|
||||
try_register_server_to_search_source(app_handle.clone(), &server).await;
|
||||
|
||||
// Update the server's profile using the util::http::HttpClient::get method
|
||||
let profile = get_user_profiles(app_handle.clone(), server_id.clone()).await;
|
||||
|
||||
@@ -132,6 +132,7 @@ fn get_default_server() -> Server {
|
||||
version: Version {
|
||||
number: "1.0.0_SNAPSHOT".to_string(),
|
||||
},
|
||||
minimal_client_version: None,
|
||||
updated: "2025-01-24T12:12:17.326286927+08:00".to_string(),
|
||||
public: false,
|
||||
available: true,
|
||||
@@ -259,7 +260,6 @@ pub async fn load_or_insert_default_server<R: Runtime>(
|
||||
pub async fn list_coco_servers<R: Runtime>(
|
||||
_app_handle: AppHandle<R>,
|
||||
) -> Result<Vec<Server>, String> {
|
||||
|
||||
//hard fresh all server's info, in order to get the actual health
|
||||
refresh_all_coco_server_info(_app_handle.clone()).await;
|
||||
|
||||
@@ -282,9 +282,7 @@ pub const COCO_SERVERS: &str = "coco_servers";
|
||||
|
||||
const COCO_SERVER_TOKENS: &str = "coco_server_tokens";
|
||||
|
||||
pub async fn refresh_all_coco_server_info<R: Runtime>(
|
||||
app_handle: AppHandle<R>,
|
||||
) {
|
||||
pub async fn refresh_all_coco_server_info<R: Runtime>(app_handle: AppHandle<R>) {
|
||||
let servers = get_all_servers();
|
||||
for server in servers {
|
||||
let _ = refresh_coco_server_info(app_handle.clone(), server.id.clone()).await;
|
||||
@@ -334,7 +332,6 @@ pub async fn refresh_coco_server_info<R: Runtime>(
|
||||
|
||||
let _ = get_datasources_by_server(&id).await;
|
||||
|
||||
|
||||
Ok(server)
|
||||
}
|
||||
Err(e) => Err(format!("Failed to deserialize the response: {:?}", e)),
|
||||
@@ -407,9 +404,8 @@ pub async fn add_coco_server<R: Runtime>(
|
||||
// Save the new server to the cache
|
||||
save_server(&server);
|
||||
|
||||
let registry = app_handle.state::<SearchSourceRegistry>();
|
||||
let source = CocoSearchSource::new(server.clone(), Client::new());
|
||||
registry.register_source(source).await;
|
||||
// Register the server to the search source
|
||||
try_register_server_to_search_source(app_handle.clone(), &server).await;
|
||||
|
||||
// Persist the servers to the store
|
||||
persist_servers(&app_handle)
|
||||
@@ -459,9 +455,8 @@ pub async fn enable_server<R: Runtime>(app_handle: AppHandle<R>, id: String) ->
|
||||
server.enabled = true;
|
||||
save_server(&server);
|
||||
|
||||
let registry = app_handle.state::<SearchSourceRegistry>();
|
||||
let source = CocoSearchSource::new(server.clone(), Client::new());
|
||||
registry.register_source(source).await;
|
||||
// Register the server to the search source
|
||||
try_register_server_to_search_source(app_handle.clone(), &server).await;
|
||||
|
||||
persist_servers(&app_handle)
|
||||
.await
|
||||
@@ -470,6 +465,16 @@ pub async fn enable_server<R: Runtime>(app_handle: AppHandle<R>, id: String) ->
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn try_register_server_to_search_source(
|
||||
app_handle: AppHandle<impl Runtime>,
|
||||
server: &Server,
|
||||
) {
|
||||
if server.enabled {
|
||||
let registry = app_handle.state::<SearchSourceRegistry>();
|
||||
let source = CocoSearchSource::new(server.clone(), Client::new());
|
||||
registry.register_source(source).await;
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn mark_server_as_offline(id: &str) {
|
||||
// println!("server_is_offline: {}", id);
|
||||
@@ -584,6 +589,7 @@ fn test_trim_endpoint_last_forward_slash() {
|
||||
version: Version {
|
||||
number: "".to_string(),
|
||||
},
|
||||
minimal_client_version: None,
|
||||
updated: "".to_string(),
|
||||
public: false,
|
||||
available: false,
|
||||
|
||||
@@ -113,7 +113,7 @@
|
||||
"updater": {
|
||||
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IDlDRjNDRUU0NTdBMzdCRTMKUldUamU2Tlg1TTd6bkUwZWM0d2Zjdk0wdXJmendWVlpMMmhKN25EcmprYmIydnJ3dmFUME9QYXkK",
|
||||
"endpoints": [
|
||||
"https://api.coco.rs/update/{{target}}/{{arch}}/{{current_version}}"
|
||||
"https://release.infinilabs.com/coco/app/.latest.json?target={{target}}&arch={{arch}}¤t_version={{current_version}}"
|
||||
]
|
||||
},
|
||||
"websocket": {},
|
||||
|
||||
@@ -4,27 +4,23 @@ import {
|
||||
useCallback,
|
||||
useEffect,
|
||||
useImperativeHandle,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
} from "react";
|
||||
import { invoke, isTauri } from "@tauri-apps/api/core";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { debounce } from "lodash-es";
|
||||
|
||||
import { ChatMessage } from "@/components/ChatMessage";
|
||||
import type { Chat } from "./types";
|
||||
import { useChatStore } from "@/stores/chatStore";
|
||||
import { useWindows } from "@/hooks/useWindows";
|
||||
import { ChatHeader } from "./ChatHeader";
|
||||
import { Sidebar } from "@/components/Assistant/Sidebar";
|
||||
import { useConnectStore } from "@/stores/connectStore";
|
||||
import { useSearchStore } from "@/stores/searchStore";
|
||||
import FileList from "@/components/Search/FileList";
|
||||
import { Greetings } from "./Greetings";
|
||||
import ConnectPrompt from "./ConnectPrompt";
|
||||
import { useWindows } from "@/hooks/useWindows";
|
||||
import useMessageChunkData from "@/hooks/useMessageChunkData";
|
||||
import useWebSocket from "@/hooks/useWebSocket";
|
||||
import { useChatActions } from "@/hooks/useChatActions";
|
||||
import { useMessageHandler } from "@/hooks/useMessageHandler";
|
||||
import { ChatSidebar } from "./ChatSidebar";
|
||||
import { ChatHeader } from "./ChatHeader";
|
||||
import { ChatContent } from "./ChatContent";
|
||||
import ConnectPrompt from "./ConnectPrompt";
|
||||
import type { Chat } from "./types";
|
||||
|
||||
interface ChatAIProps {
|
||||
isTransitioned: boolean;
|
||||
@@ -63,17 +59,13 @@ const ChatAI = memo(
|
||||
) => {
|
||||
if (!isTransitioned) return null;
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
init: init,
|
||||
cancelChat: cancelChat,
|
||||
cancelChat: () => cancelChat(activeChat),
|
||||
reconnect: reconnect,
|
||||
clearChat: clearChat,
|
||||
}));
|
||||
|
||||
const { createWin } = useWindows();
|
||||
|
||||
const { curChatEnd, setCurChatEnd, connected, setConnected } =
|
||||
useChatStore();
|
||||
|
||||
@@ -81,23 +73,18 @@ const ChatAI = memo(
|
||||
|
||||
const [activeChat, setActiveChat] = useState<Chat>();
|
||||
const [timedoutShow, setTimedoutShow] = useState(false);
|
||||
const [IsLogin, setIsLogin] = useState(true);
|
||||
|
||||
const messagesEndRef = useRef<HTMLDivElement>(null);
|
||||
const [isLogin, setIsLogin] = useState(true);
|
||||
|
||||
const curIdRef = useRef("");
|
||||
|
||||
const [isSidebarOpenChat, setIsSidebarOpenChat] = useState(isSidebarOpen);
|
||||
const [chats, setChats] = useState<Chat[]>([]);
|
||||
const sourceDataIds = useSearchStore((state) => state.sourceDataIds);
|
||||
const uploadFiles = useChatStore((state) => state.uploadFiles);
|
||||
|
||||
useEffect(() => {
|
||||
activeChatProp && setActiveChat(activeChatProp);
|
||||
}, [activeChatProp]);
|
||||
|
||||
const messageTimeoutRef = useRef<NodeJS.Timeout>();
|
||||
|
||||
const [Question, setQuestion] = useState<string>("");
|
||||
|
||||
const {
|
||||
@@ -122,385 +109,125 @@ const ChatAI = memo(
|
||||
response: false,
|
||||
});
|
||||
|
||||
const dealMsg = useCallback(
|
||||
(msg: string) => {
|
||||
if (messageTimeoutRef.current) {
|
||||
clearTimeout(messageTimeoutRef.current);
|
||||
}
|
||||
const dealMsgRef = useRef<((msg: string) => void) | null>(null);
|
||||
|
||||
if (!msg.includes("PRIVATE")) return;
|
||||
const { errorShow, setErrorShow, reconnect, updateDealMsg } =
|
||||
useWebSocket({
|
||||
connected,
|
||||
setConnected,
|
||||
currentService,
|
||||
dealMsgRef,
|
||||
});
|
||||
|
||||
messageTimeoutRef.current = setTimeout(() => {
|
||||
if (!curChatEnd) {
|
||||
console.log("AI response timeout");
|
||||
setTimedoutShow(true);
|
||||
cancelChat();
|
||||
}
|
||||
}, 60000);
|
||||
|
||||
if (msg.includes("assistant finished output")) {
|
||||
clearTimeout(messageTimeoutRef.current);
|
||||
console.log("AI finished output");
|
||||
setCurChatEnd(true);
|
||||
return;
|
||||
}
|
||||
|
||||
const cleanedData = msg.replace(/^PRIVATE /, "");
|
||||
try {
|
||||
const chunkData = JSON.parse(cleanedData);
|
||||
|
||||
if (chunkData.reply_to_message !== curIdRef.current) return;
|
||||
|
||||
setLoadingStep(() => ({
|
||||
query_intent: false,
|
||||
fetch_source: false,
|
||||
pick_source: false,
|
||||
deep_read: false,
|
||||
think: false,
|
||||
response: false,
|
||||
[chunkData.chunk_type]: true,
|
||||
}));
|
||||
|
||||
// ['query_intent', 'fetch_source', 'pick_source', 'deep_read', 'think', 'response'];
|
||||
if (chunkData.chunk_type === "query_intent") {
|
||||
handlers.deal_query_intent(chunkData);
|
||||
} else if (chunkData.chunk_type === "fetch_source") {
|
||||
handlers.deal_fetch_source(chunkData);
|
||||
} else if (chunkData.chunk_type === "pick_source") {
|
||||
handlers.deal_pick_source(chunkData);
|
||||
} else if (chunkData.chunk_type === "deep_read") {
|
||||
handlers.deal_deep_read(chunkData);
|
||||
} else if (chunkData.chunk_type === "think") {
|
||||
handlers.deal_think(chunkData);
|
||||
} else if (chunkData.chunk_type === "response") {
|
||||
handlers.deal_response(chunkData);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("parse error:", error);
|
||||
}
|
||||
},
|
||||
[curChatEnd]
|
||||
const {
|
||||
chatClose,
|
||||
cancelChat,
|
||||
chatHistory,
|
||||
createNewChat,
|
||||
handleSendMessage,
|
||||
openSessionChat,
|
||||
getChatHistory,
|
||||
createChatWindow,
|
||||
} = useChatActions(
|
||||
currentService?.id,
|
||||
setActiveChat,
|
||||
setCurChatEnd,
|
||||
setErrorShow,
|
||||
setTimedoutShow,
|
||||
clearAllChunkData,
|
||||
setQuestion,
|
||||
curIdRef,
|
||||
isSearchActive,
|
||||
isDeepThinkActive,
|
||||
sourceDataIds,
|
||||
changeInput
|
||||
);
|
||||
|
||||
const { errorShow, setErrorShow, reconnect } = useWebSocket({
|
||||
connected,
|
||||
setConnected,
|
||||
currentService,
|
||||
dealMsg,
|
||||
});
|
||||
|
||||
const updatedChat = useMemo(() => {
|
||||
if (!activeChat?._id) return null;
|
||||
return {
|
||||
...activeChat,
|
||||
messages: [...(activeChat.messages || [])],
|
||||
};
|
||||
}, [activeChat]);
|
||||
|
||||
const simulateAssistantResponse = useCallback(() => {
|
||||
if (!updatedChat) return;
|
||||
|
||||
// console.log("updatedChat:", updatedChat);
|
||||
setActiveChat(updatedChat);
|
||||
}, [updatedChat]);
|
||||
const { dealMsg, messageTimeoutRef } = useMessageHandler(
|
||||
curIdRef,
|
||||
setCurChatEnd,
|
||||
setTimedoutShow,
|
||||
(chat) => cancelChat(chat || activeChat),
|
||||
setLoadingStep,
|
||||
handlers
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (curChatEnd) {
|
||||
simulateAssistantResponse();
|
||||
if (dealMsg) {
|
||||
dealMsgRef.current = dealMsg;
|
||||
updateDealMsg && updateDealMsg(dealMsg);
|
||||
}
|
||||
}, [curChatEnd]);
|
||||
}, [dealMsg, updateDealMsg]);
|
||||
|
||||
const [userScrolling, setUserScrolling] = useState(false);
|
||||
const scrollTimeoutRef = useRef<NodeJS.Timeout>();
|
||||
|
||||
const scrollToBottom = useCallback(
|
||||
debounce(() => {
|
||||
if (!userScrolling) {
|
||||
const container = messagesEndRef.current?.parentElement;
|
||||
if (container) {
|
||||
container.scrollTo({
|
||||
top: container.scrollHeight,
|
||||
behavior: "smooth",
|
||||
});
|
||||
}
|
||||
}
|
||||
}, 100),
|
||||
[userScrolling]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const container = messagesEndRef.current?.parentElement;
|
||||
if (!container) return;
|
||||
|
||||
const handleScroll = () => {
|
||||
if (scrollTimeoutRef.current) {
|
||||
clearTimeout(scrollTimeoutRef.current);
|
||||
}
|
||||
|
||||
const { scrollTop, scrollHeight, clientHeight } = container;
|
||||
const isAtBottom =
|
||||
Math.abs(scrollHeight - scrollTop - clientHeight) < 10;
|
||||
|
||||
setUserScrolling(!isAtBottom);
|
||||
|
||||
if (isAtBottom) {
|
||||
setUserScrolling(false);
|
||||
}
|
||||
|
||||
scrollTimeoutRef.current = setTimeout(() => {
|
||||
const {
|
||||
scrollTop: newScrollTop,
|
||||
scrollHeight: newScrollHeight,
|
||||
clientHeight: newClientHeight,
|
||||
} = container;
|
||||
const nowAtBottom =
|
||||
Math.abs(newScrollHeight - newScrollTop - newClientHeight) < 10;
|
||||
if (nowAtBottom) {
|
||||
setUserScrolling(false);
|
||||
}
|
||||
}, 500);
|
||||
};
|
||||
|
||||
container.addEventListener("scroll", handleScroll);
|
||||
return () => {
|
||||
container.removeEventListener("scroll", handleScroll);
|
||||
if (scrollTimeoutRef.current) {
|
||||
clearTimeout(scrollTimeoutRef.current);
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
scrollToBottom();
|
||||
}, [
|
||||
activeChat?.messages,
|
||||
query_intent?.message_chunk,
|
||||
fetch_source?.message_chunk,
|
||||
pick_source?.message_chunk,
|
||||
deep_read?.message_chunk,
|
||||
think?.message_chunk,
|
||||
response?.message_chunk,
|
||||
]);
|
||||
|
||||
const clearChat = () => {
|
||||
const clearChat = useCallback(() => {
|
||||
console.log("clearChat");
|
||||
chatClose();
|
||||
setTimedoutShow(false);
|
||||
setErrorShow(false);
|
||||
chatClose(activeChat);
|
||||
setActiveChat(undefined);
|
||||
setCurChatEnd(true);
|
||||
clearChatPage && clearChatPage();
|
||||
};
|
||||
}, [
|
||||
activeChat,
|
||||
chatClose,
|
||||
clearChatPage,
|
||||
setCurChatEnd,
|
||||
setErrorShow,
|
||||
setTimedoutShow,
|
||||
]);
|
||||
|
||||
const createNewChat = useCallback(
|
||||
async (value: string = "") => {
|
||||
setTimedoutShow(false);
|
||||
setErrorShow(false);
|
||||
chatClose();
|
||||
clearAllChunkData();
|
||||
setQuestion(value);
|
||||
try {
|
||||
console.log("sourceDataIds", sourceDataIds);
|
||||
let response: any = await invoke("new_chat", {
|
||||
serverId: currentService?.id,
|
||||
message: value,
|
||||
queryParams: {
|
||||
search: isSearchActive,
|
||||
deep_thinking: isDeepThinkActive,
|
||||
datasource: sourceDataIds.join(","),
|
||||
},
|
||||
});
|
||||
console.log("_new", response);
|
||||
const newChat: Chat = response;
|
||||
curIdRef.current = response?.payload?.id;
|
||||
|
||||
newChat._source = {
|
||||
message: value,
|
||||
};
|
||||
const updatedChat: Chat = {
|
||||
...newChat,
|
||||
messages: [newChat],
|
||||
};
|
||||
|
||||
changeInput && changeInput("");
|
||||
//console.log("updatedChat2", updatedChat);
|
||||
setActiveChat(updatedChat);
|
||||
setCurChatEnd(false);
|
||||
} catch (error) {
|
||||
setErrorShow(true);
|
||||
console.error("createNewChat:", error);
|
||||
const init = useCallback(
|
||||
(value: string) => {
|
||||
if (!isLogin) return;
|
||||
if (!curChatEnd) return;
|
||||
if (!activeChat?._id) {
|
||||
createNewChat(value, activeChat);
|
||||
} else {
|
||||
handleSendMessage(value, activeChat);
|
||||
}
|
||||
},
|
||||
[currentService?.id, sourceDataIds, isSearchActive, isDeepThinkActive]
|
||||
[isLogin, curChatEnd, activeChat, createNewChat, handleSendMessage]
|
||||
);
|
||||
|
||||
const init = (value: string) => {
|
||||
if (!IsLogin) return;
|
||||
if (!curChatEnd) return;
|
||||
if (!activeChat?._id) {
|
||||
createNewChat(value);
|
||||
} else {
|
||||
handleSendMessage(value);
|
||||
}
|
||||
};
|
||||
|
||||
const sendMessage = useCallback(
|
||||
async (content: string, newChat: Chat) => {
|
||||
if (!newChat?._id || !content) return;
|
||||
|
||||
try {
|
||||
//console.log("sourceDataIds", sourceDataIds);
|
||||
let response: any = await invoke("send_message", {
|
||||
serverId: currentService?.id,
|
||||
sessionId: newChat?._id,
|
||||
queryParams: {
|
||||
search: isSearchActive,
|
||||
deep_thinking: isDeepThinkActive,
|
||||
datasource: sourceDataIds.join(","),
|
||||
},
|
||||
message: content,
|
||||
});
|
||||
response = JSON.parse(response || "");
|
||||
console.log("_send", response);
|
||||
curIdRef.current = response[0]?._id;
|
||||
|
||||
const updatedChat: Chat = {
|
||||
...newChat,
|
||||
messages: [...(newChat?.messages || []), ...(response || [])],
|
||||
};
|
||||
|
||||
changeInput && changeInput("");
|
||||
//console.log("updatedChat2", updatedChat);
|
||||
setActiveChat(updatedChat);
|
||||
setCurChatEnd(false);
|
||||
} catch (error) {
|
||||
setErrorShow(true);
|
||||
console.error("sendMessage:", error);
|
||||
}
|
||||
},
|
||||
[
|
||||
JSON.stringify(activeChat?.messages),
|
||||
currentService?.id,
|
||||
sourceDataIds,
|
||||
isSearchActive,
|
||||
isDeepThinkActive,
|
||||
]
|
||||
);
|
||||
|
||||
const handleSendMessage = useCallback(
|
||||
async (content: string, newChat?: Chat) => {
|
||||
newChat = newChat || activeChat;
|
||||
if (!newChat?._id || !content) return;
|
||||
setQuestion(content);
|
||||
await chatHistory(newChat, (chat) => sendMessage(content, chat));
|
||||
|
||||
setTimedoutShow(false);
|
||||
setErrorShow(false);
|
||||
clearAllChunkData();
|
||||
},
|
||||
[activeChat, sendMessage]
|
||||
);
|
||||
|
||||
const chatClose = async () => {
|
||||
if (!activeChat?._id) return;
|
||||
try {
|
||||
let response: any = await invoke("close_session_chat", {
|
||||
serverId: currentService?.id,
|
||||
sessionId: activeChat?._id,
|
||||
});
|
||||
response = JSON.parse(response || "");
|
||||
console.log("_close", response);
|
||||
} catch (error) {
|
||||
console.error("chatClose:", error);
|
||||
}
|
||||
};
|
||||
|
||||
const cancelChat = async () => {
|
||||
setCurChatEnd(true);
|
||||
if (!activeChat?._id) return;
|
||||
try {
|
||||
let response: any = await invoke("cancel_session_chat", {
|
||||
serverId: currentService?.id,
|
||||
sessionId: activeChat?._id,
|
||||
});
|
||||
response = JSON.parse(response || "");
|
||||
console.log("_cancel", response);
|
||||
} catch (error) {
|
||||
console.error("cancelChat:", error);
|
||||
}
|
||||
};
|
||||
|
||||
async function openChatAI() {
|
||||
if (isTauri()) {
|
||||
createWin &&
|
||||
createWin({
|
||||
label: "chat",
|
||||
title: "Coco Chat",
|
||||
dragDropEnabled: true,
|
||||
center: true,
|
||||
width: 1000,
|
||||
height: 800,
|
||||
alwaysOnTop: false,
|
||||
skipTaskbar: false,
|
||||
decorations: true,
|
||||
closable: true,
|
||||
url: "/ui/chat",
|
||||
});
|
||||
}
|
||||
}
|
||||
const { createWin } = useWindows();
|
||||
const openChatAI = useCallback(() => {
|
||||
createChatWindow(createWin);
|
||||
}, [createChatWindow, createWin]);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (messageTimeoutRef.current) {
|
||||
clearTimeout(messageTimeoutRef.current);
|
||||
}
|
||||
chatClose();
|
||||
chatClose(activeChat);
|
||||
setActiveChat(undefined);
|
||||
setCurChatEnd(true);
|
||||
scrollToBottom.cancel();
|
||||
};
|
||||
}, []);
|
||||
}, [chatClose, setCurChatEnd]);
|
||||
|
||||
const chatHistory = async (
|
||||
chat: Chat,
|
||||
callback?: (chat: Chat) => void
|
||||
) => {
|
||||
try {
|
||||
let response: any = await invoke("session_chat_history", {
|
||||
serverId: currentService?.id,
|
||||
sessionId: chat?._id,
|
||||
from: 0,
|
||||
size: 20,
|
||||
});
|
||||
response = JSON.parse(response || "");
|
||||
const hits = response?.hits?.hits || [];
|
||||
const updatedChat: Chat = {
|
||||
...chat,
|
||||
messages: hits,
|
||||
};
|
||||
console.log("id_history", response, updatedChat);
|
||||
setActiveChat(updatedChat);
|
||||
callback && callback(updatedChat);
|
||||
} catch (error) {
|
||||
console.error("chatHistory:", error);
|
||||
}
|
||||
};
|
||||
const onSelectChat = useCallback(
|
||||
async (chat: Chat) => {
|
||||
setTimedoutShow(false);
|
||||
setErrorShow(false);
|
||||
clearAllChunkData();
|
||||
await cancelChat(activeChat);
|
||||
await chatClose(activeChat);
|
||||
const response = await openSessionChat(chat);
|
||||
if (response) {
|
||||
chatHistory(response);
|
||||
}
|
||||
},
|
||||
[
|
||||
clearAllChunkData,
|
||||
cancelChat,
|
||||
activeChat,
|
||||
chatClose,
|
||||
openSessionChat,
|
||||
chatHistory,
|
||||
]
|
||||
);
|
||||
|
||||
const onSelectChat = async (chat: any) => {
|
||||
chatClose();
|
||||
clearAllChunkData();
|
||||
try {
|
||||
let response: any = await invoke("open_session_chat", {
|
||||
serverId: currentService?.id,
|
||||
sessionId: chat?._id,
|
||||
});
|
||||
response = JSON.parse(response || "");
|
||||
console.log("_open", response);
|
||||
chatHistory(response);
|
||||
} catch (error) {
|
||||
console.error("open_session_chat:", error);
|
||||
}
|
||||
};
|
||||
|
||||
const deleteChat = (chatId: string) => {
|
||||
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);
|
||||
@@ -510,7 +237,7 @@ const ChatAI = memo(
|
||||
init("");
|
||||
}
|
||||
}
|
||||
};
|
||||
}, [activeChat, chats, init, setActiveChat]);
|
||||
|
||||
const handleOutsideClick = useCallback((e: MouseEvent) => {
|
||||
const sidebar = document.querySelector("[data-sidebar]");
|
||||
@@ -534,152 +261,69 @@ const ChatAI = memo(
|
||||
};
|
||||
}, [isSidebarOpenChat, handleOutsideClick]);
|
||||
|
||||
const getChatHistory = useCallback(async () => {
|
||||
if (!currentService?.id) return;
|
||||
try {
|
||||
let response: any = await invoke("chat_history", {
|
||||
serverId: currentService?.id,
|
||||
from: 0,
|
||||
size: 20,
|
||||
});
|
||||
response = JSON.parse(response || "");
|
||||
console.log("_history", response);
|
||||
const hits = response?.hits?.hits || [];
|
||||
setChats(hits);
|
||||
} catch (error) {
|
||||
console.error("chat_history:", error);
|
||||
}
|
||||
}, [currentService?.id]);
|
||||
const fetchChatHistory = useCallback(async () => {
|
||||
const hits = await getChatHistory();
|
||||
setChats(hits);
|
||||
}, [getChatHistory]);
|
||||
|
||||
const setIsLoginChat = useCallback(
|
||||
(value: boolean) => {
|
||||
setIsLogin(value);
|
||||
value && currentService && !setIsSidebarOpen && getChatHistory();
|
||||
value && currentService && !setIsSidebarOpen && fetchChatHistory();
|
||||
!value && setChats([]);
|
||||
},
|
||||
[currentService]
|
||||
[currentService, setIsSidebarOpen, fetchChatHistory]
|
||||
);
|
||||
|
||||
const toggleSidebar = useCallback(() => {
|
||||
setIsSidebarOpenChat(!isSidebarOpenChat);
|
||||
setIsSidebarOpen && setIsSidebarOpen(!isSidebarOpenChat);
|
||||
!isSidebarOpenChat && fetchChatHistory();
|
||||
}, [isSidebarOpenChat, setIsSidebarOpen, fetchChatHistory]);
|
||||
|
||||
return (
|
||||
<div
|
||||
data-tauri-drag-region
|
||||
className={`h-full flex flex-col rounded-xl overflow-hidden`}
|
||||
>
|
||||
{setIsSidebarOpen ? null : (
|
||||
<div
|
||||
data-sidebar
|
||||
className={`fixed inset-y-0 left-0 z-50 w-64 transform transition-all duration-300 ease-in-out
|
||||
${
|
||||
isSidebarOpenChat
|
||||
? "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`}
|
||||
>
|
||||
<Sidebar
|
||||
chats={chats}
|
||||
activeChat={activeChat}
|
||||
onNewChat={clearChat}
|
||||
onSelectChat={onSelectChat}
|
||||
onDeleteChat={deleteChat}
|
||||
/>
|
||||
</div>
|
||||
{!setIsSidebarOpen && (
|
||||
<ChatSidebar
|
||||
isSidebarOpen={isSidebarOpenChat}
|
||||
chats={chats}
|
||||
activeChat={activeChat}
|
||||
onNewChat={clearChat}
|
||||
onSelectChat={onSelectChat}
|
||||
onDeleteChat={deleteChat}
|
||||
fetchChatHistory={fetchChatHistory}
|
||||
/>
|
||||
)}
|
||||
|
||||
<ChatHeader
|
||||
onCreateNewChat={clearChat}
|
||||
onOpenChatAI={openChatAI}
|
||||
setIsSidebarOpen={() => {
|
||||
setIsSidebarOpenChat(!isSidebarOpenChat);
|
||||
setIsSidebarOpen && setIsSidebarOpen(!isSidebarOpenChat);
|
||||
!isSidebarOpenChat && getChatHistory();
|
||||
}}
|
||||
setIsSidebarOpen={toggleSidebar}
|
||||
isSidebarOpen={isSidebarOpenChat}
|
||||
activeChat={activeChat}
|
||||
reconnect={reconnect}
|
||||
isChatPage={isChatPage}
|
||||
setIsLogin={setIsLoginChat}
|
||||
/>
|
||||
{IsLogin ? (
|
||||
<div className="flex flex-col h-full justify-between overflow-hidden">
|
||||
<div className="flex-1 w-full overflow-x-hidden overflow-y-auto border-t border-[rgba(0,0,0,0.1)] dark:border-[rgba(255,255,255,0.15)] custom-scrollbar relative">
|
||||
<Greetings />
|
||||
{activeChat?.messages?.map((message, index) => (
|
||||
<ChatMessage
|
||||
key={message._id + index}
|
||||
message={message}
|
||||
isTyping={false}
|
||||
onResend={handleSendMessage}
|
||||
/>
|
||||
))}
|
||||
{(query_intent ||
|
||||
fetch_source ||
|
||||
pick_source ||
|
||||
deep_read ||
|
||||
think ||
|
||||
response) &&
|
||||
activeChat?._id ? (
|
||||
<ChatMessage
|
||||
key={"current"}
|
||||
message={{
|
||||
_id: "current",
|
||||
_source: {
|
||||
type: "assistant",
|
||||
message: "",
|
||||
question: Question,
|
||||
},
|
||||
}}
|
||||
onResend={handleSendMessage}
|
||||
isTyping={!curChatEnd}
|
||||
query_intent={query_intent}
|
||||
fetch_source={fetch_source}
|
||||
pick_source={pick_source}
|
||||
deep_read={deep_read}
|
||||
think={think}
|
||||
response={response}
|
||||
loadingStep={loadingStep}
|
||||
/>
|
||||
) : null}
|
||||
{timedoutShow ? (
|
||||
<ChatMessage
|
||||
key={"timedout"}
|
||||
message={{
|
||||
_id: "timedout",
|
||||
_source: {
|
||||
type: "assistant",
|
||||
message: t("assistant.chat.timedout"),
|
||||
question: Question,
|
||||
},
|
||||
}}
|
||||
onResend={handleSendMessage}
|
||||
isTyping={false}
|
||||
/>
|
||||
) : null}
|
||||
{errorShow ? (
|
||||
<ChatMessage
|
||||
key={"error"}
|
||||
message={{
|
||||
_id: "error",
|
||||
_source: {
|
||||
type: "assistant",
|
||||
message: t("assistant.chat.error"),
|
||||
question: Question,
|
||||
},
|
||||
}}
|
||||
onResend={handleSendMessage}
|
||||
isTyping={false}
|
||||
/>
|
||||
) : null}
|
||||
<div ref={messagesEndRef} />
|
||||
</div>
|
||||
|
||||
{uploadFiles.length > 0 && (
|
||||
<div className="max-h-[120px] overflow-auto p-2">
|
||||
<FileList />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{isLogin ? (
|
||||
<ChatContent
|
||||
activeChat={activeChat}
|
||||
curChatEnd={curChatEnd}
|
||||
query_intent={query_intent}
|
||||
fetch_source={fetch_source}
|
||||
pick_source={pick_source}
|
||||
deep_read={deep_read}
|
||||
think={think}
|
||||
response={response}
|
||||
loadingStep={loadingStep}
|
||||
timedoutShow={timedoutShow}
|
||||
errorShow={errorShow}
|
||||
Question={Question}
|
||||
handleSendMessage={(value) => handleSendMessage(value, activeChat)}
|
||||
/>
|
||||
) : (
|
||||
<ConnectPrompt />
|
||||
)}
|
||||
|
||||
151
src/components/Assistant/ChatContent.tsx
Normal file
151
src/components/Assistant/ChatContent.tsx
Normal file
@@ -0,0 +1,151 @@
|
||||
import { useRef, useEffect } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import { ChatMessage } from "@/components/ChatMessage";
|
||||
import { Greetings } from "./Greetings";
|
||||
import FileList from "@/components/Search/FileList";
|
||||
import { useChatScroll } from "@/hooks/useChatScroll";
|
||||
import { useChatStore } from "@/stores/chatStore";
|
||||
import type { Chat, IChunkData } from "./types";
|
||||
|
||||
interface ChatContentProps {
|
||||
activeChat?: Chat;
|
||||
curChatEnd: boolean;
|
||||
query_intent?: IChunkData;
|
||||
fetch_source?: IChunkData;
|
||||
pick_source?: IChunkData;
|
||||
deep_read?: IChunkData;
|
||||
think?: IChunkData;
|
||||
response?: IChunkData;
|
||||
loadingStep?: Record<string, boolean>;
|
||||
timedoutShow: boolean;
|
||||
errorShow: boolean;
|
||||
Question: string;
|
||||
handleSendMessage: (content: string, newChat?: Chat) => void;
|
||||
}
|
||||
|
||||
export const ChatContent = ({
|
||||
activeChat,
|
||||
curChatEnd,
|
||||
query_intent,
|
||||
fetch_source,
|
||||
pick_source,
|
||||
deep_read,
|
||||
think,
|
||||
response,
|
||||
loadingStep,
|
||||
timedoutShow,
|
||||
errorShow,
|
||||
Question,
|
||||
handleSendMessage,
|
||||
}: ChatContentProps) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const uploadFiles = useChatStore((state) => state.uploadFiles);
|
||||
|
||||
const messagesEndRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const { scrollToBottom } = useChatScroll(messagesEndRef);
|
||||
|
||||
useEffect(() => {
|
||||
scrollToBottom();
|
||||
}, [
|
||||
activeChat?.messages,
|
||||
query_intent?.message_chunk,
|
||||
fetch_source?.message_chunk,
|
||||
pick_source?.message_chunk,
|
||||
deep_read?.message_chunk,
|
||||
think?.message_chunk,
|
||||
response?.message_chunk,
|
||||
curChatEnd,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
scrollToBottom.cancel();
|
||||
};
|
||||
}, [scrollToBottom]);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col h-full justify-between overflow-hidden">
|
||||
<div className="flex-1 w-full overflow-x-hidden overflow-y-auto border-t border-[rgba(0,0,0,0.1)] dark:border-[rgba(255,255,255,0.15)] custom-scrollbar relative">
|
||||
<Greetings />
|
||||
|
||||
{activeChat?.messages?.map((message, index) => (
|
||||
<ChatMessage
|
||||
key={message._id + index}
|
||||
message={message}
|
||||
isTyping={false}
|
||||
onResend={handleSendMessage}
|
||||
/>
|
||||
))}
|
||||
{(!curChatEnd ||
|
||||
query_intent ||
|
||||
fetch_source ||
|
||||
pick_source ||
|
||||
deep_read ||
|
||||
think ||
|
||||
response) &&
|
||||
activeChat?._id ? (
|
||||
<ChatMessage
|
||||
key={"current"}
|
||||
message={{
|
||||
_id: "current",
|
||||
_source: {
|
||||
type: "assistant",
|
||||
message: "",
|
||||
question: Question,
|
||||
},
|
||||
}}
|
||||
onResend={handleSendMessage}
|
||||
isTyping={!curChatEnd}
|
||||
query_intent={query_intent}
|
||||
fetch_source={fetch_source}
|
||||
pick_source={pick_source}
|
||||
deep_read={deep_read}
|
||||
think={think}
|
||||
response={response}
|
||||
loadingStep={loadingStep}
|
||||
/>
|
||||
) : null}
|
||||
{timedoutShow ? (
|
||||
<ChatMessage
|
||||
key={"timedout"}
|
||||
message={{
|
||||
_id: "timedout",
|
||||
_source: {
|
||||
type: "assistant",
|
||||
message: t("assistant.chat.timedout"),
|
||||
question: Question,
|
||||
},
|
||||
}}
|
||||
onResend={handleSendMessage}
|
||||
isTyping={false}
|
||||
/>
|
||||
) : null}
|
||||
{errorShow ? (
|
||||
<ChatMessage
|
||||
key={"error"}
|
||||
message={{
|
||||
_id: "error",
|
||||
_source: {
|
||||
type: "assistant",
|
||||
message: t("assistant.chat.error"),
|
||||
question: Question,
|
||||
},
|
||||
}}
|
||||
onResend={handleSendMessage}
|
||||
isTyping={false}
|
||||
/>
|
||||
) : null}
|
||||
<div ref={messagesEndRef} />
|
||||
</div>
|
||||
|
||||
{uploadFiles.length > 0 && (
|
||||
<div className="max-h-[120px] overflow-auto p-2">
|
||||
<FileList />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
44
src/components/Assistant/ChatSidebar.tsx
Normal file
44
src/components/Assistant/ChatSidebar.tsx
Normal file
@@ -0,0 +1,44 @@
|
||||
import React from "react";
|
||||
|
||||
import { Sidebar } from "@/components/Assistant/Sidebar";
|
||||
import type { Chat } from "./types";
|
||||
|
||||
interface ChatSidebarProps {
|
||||
isSidebarOpen: boolean;
|
||||
chats: Chat[];
|
||||
activeChat?: Chat;
|
||||
onNewChat: () => void;
|
||||
onSelectChat: (chat: any) => void;
|
||||
onDeleteChat: (chatId: string) => void;
|
||||
fetchChatHistory: () => void;
|
||||
}
|
||||
|
||||
export const ChatSidebar: React.FC<ChatSidebarProps> = ({
|
||||
isSidebarOpen,
|
||||
chats,
|
||||
activeChat,
|
||||
onNewChat,
|
||||
onSelectChat,
|
||||
onDeleteChat,
|
||||
fetchChatHistory,
|
||||
}) => {
|
||||
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`}
|
||||
>
|
||||
<Sidebar
|
||||
chats={chats}
|
||||
activeChat={activeChat}
|
||||
onNewChat={onNewChat}
|
||||
onSelectChat={onSelectChat}
|
||||
onDeleteChat={onDeleteChat}
|
||||
fetchChatHistory={fetchChatHistory}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { MessageSquare, Plus } from "lucide-react";
|
||||
import { MessageSquare, Plus, RefreshCw } from "lucide-react";
|
||||
|
||||
import type { Chat } from "./types";
|
||||
|
||||
@@ -10,6 +11,7 @@ interface SidebarProps {
|
||||
onSelectChat: (chat: Chat) => void;
|
||||
onDeleteChat: (chatId: string) => void;
|
||||
className?: string;
|
||||
fetchChatHistory: () => void;
|
||||
}
|
||||
|
||||
export function Sidebar({
|
||||
@@ -18,19 +20,37 @@ export function Sidebar({
|
||||
onNewChat,
|
||||
onSelectChat,
|
||||
className = "",
|
||||
fetchChatHistory,
|
||||
}: SidebarProps) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [isRefreshing, setIsRefreshing] = useState(false);
|
||||
|
||||
return (
|
||||
<div className={`h-full flex flex-col ${className}`}>
|
||||
<div className="p-4">
|
||||
<div className="flex justify-between gap-1 p-4">
|
||||
<button
|
||||
onClick={onNewChat}
|
||||
className={`w-full flex items-center gap-3 px-4 py-3 text-sm font-medium rounded-xl transition-all border border-[#E6E6E6] dark:border-[#272626] text-gray-700 hover:bg-gray-50/80 active:bg-gray-100/80 dark:text-white dark:hover:bg-gray-600/50 dark:active:bg-gray-500/50`}
|
||||
className={`flex items-center gap-3 px-4 py-3 text-sm font-medium rounded-xl transition-all border border-[#E6E6E6] dark:border-[#272626] text-gray-700 hover:bg-gray-50/80 active:bg-gray-100/80 dark:text-white dark:hover:bg-gray-600/50 dark:active:bg-gray-500/50`}
|
||||
>
|
||||
<Plus className={`h-4 w-4 text-[#0072FF] dark:text-[#0072FF]`} />
|
||||
{t("assistant.sidebar.newChat")}
|
||||
</button>
|
||||
<button
|
||||
onClick={async () => {
|
||||
setIsRefreshing(true);
|
||||
fetchChatHistory();
|
||||
setTimeout(() => setIsRefreshing(false), 1000);
|
||||
}}
|
||||
className="p-1 rounded-md hover:bg-gray-100 dark:hover:bg-gray-800 text-gray-500 dark:text-gray-400"
|
||||
disabled={isRefreshing}
|
||||
>
|
||||
<RefreshCw
|
||||
className={`h-4 w-4 text-[#0287FF] transition-transform duration-1000 ${
|
||||
isRefreshing ? "animate-spin" : ""
|
||||
}`}
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
<div className="flex-1 overflow-y-auto px-3 pb-3 space-y-2 custom-scrollbar">
|
||||
{chats.map((chat) => (
|
||||
|
||||
@@ -9,7 +9,9 @@ import RehypeHighlight from "rehype-highlight";
|
||||
import mermaid from "mermaid";
|
||||
import { useDebouncedCallback } from "use-debounce";
|
||||
|
||||
import { copyToClipboard, useWindowSize } from "@/utils";
|
||||
import { copyToClipboard,
|
||||
// useWindowSize
|
||||
} from "@/utils";
|
||||
|
||||
import "./markdown.css";
|
||||
import "./highlight.css";
|
||||
@@ -67,9 +69,9 @@ function PreCode(props: { children?: any }) {
|
||||
const ref = useRef<HTMLPreElement>(null);
|
||||
// const previewRef = useRef<HTMLPreviewHander>(null);
|
||||
const [mermaidCode, setMermaidCode] = useState("");
|
||||
const [htmlCode, setHtmlCode] = useState("");
|
||||
const { height } = useWindowSize();
|
||||
console.log(htmlCode, height);
|
||||
// const [htmlCode, setHtmlCode] = useState("");
|
||||
// const { height } = useWindowSize();
|
||||
// console.log(htmlCode, height);
|
||||
|
||||
const renderArtifacts = useDebouncedCallback(() => {
|
||||
if (!ref.current) return;
|
||||
@@ -77,17 +79,17 @@ function PreCode(props: { children?: any }) {
|
||||
if (mermaidDom) {
|
||||
setMermaidCode((mermaidDom as HTMLElement).innerText);
|
||||
}
|
||||
const htmlDom = ref.current.querySelector("code.language-html");
|
||||
const refText = ref.current.querySelector("code")?.innerText;
|
||||
if (htmlDom) {
|
||||
setHtmlCode((htmlDom as HTMLElement).innerText);
|
||||
} else if (refText?.startsWith("<!DOCTYPE")) {
|
||||
setHtmlCode(refText);
|
||||
}
|
||||
// const htmlDom = ref.current.querySelector("code.language-html");
|
||||
// const refText = ref.current.querySelector("code")?.innerText;
|
||||
// if (htmlDom) {
|
||||
// setHtmlCode((htmlDom as HTMLElement).innerText);
|
||||
// } else if (refText?.startsWith("<!DOCTYPE")) {
|
||||
// setHtmlCode(refText);
|
||||
// }
|
||||
}, 600);
|
||||
|
||||
const enableArtifacts = true;
|
||||
console.log(enableArtifacts);
|
||||
// const enableArtifacts = true;
|
||||
// console.log(enableArtifacts);
|
||||
|
||||
//Wrap the paragraph for plain-text
|
||||
useEffect(() => {
|
||||
|
||||
@@ -36,7 +36,7 @@ export const PickSource = ({
|
||||
useEffect(() => {
|
||||
if (!ChunkData?.message_chunk) return;
|
||||
|
||||
if (loading) {
|
||||
if (!loading) {
|
||||
try {
|
||||
const cleanContent = ChunkData.message_chunk.replace(/^"|"$/g, "");
|
||||
const allMatches = cleanContent.match(/<JSON>([\s\S]*?)<\/JSON>/g);
|
||||
@@ -44,7 +44,7 @@ export const PickSource = ({
|
||||
if (allMatches) {
|
||||
for (let i = allMatches.length - 1; i >= 0; i--) {
|
||||
try {
|
||||
const jsonString = allMatches[i].replace(/<JSON>|<\/JSON>/g, "");
|
||||
const jsonString = allMatches[i].replace(/<JSON>|<\/JSON>|<think>|<\/think>/g, "");
|
||||
const data = JSON.parse(jsonString.trim());
|
||||
|
||||
if (
|
||||
|
||||
@@ -42,7 +42,7 @@ export const QueryIntent = ({
|
||||
|
||||
useEffect(() => {
|
||||
if (!ChunkData?.message_chunk) return;
|
||||
if (loading) {
|
||||
if (!loading) {
|
||||
const cleanContent = ChunkData.message_chunk.replace(/^"|"$/g, "");
|
||||
const allMatches = cleanContent.match(/<JSON>([\s\S]*?)<\/JSON>/g);
|
||||
if (allMatches) {
|
||||
@@ -108,7 +108,7 @@ export const QueryIntent = ({
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{Data?.keyword?.map((keyword, index) => (
|
||||
<span
|
||||
key={index}
|
||||
key={keyword + index}
|
||||
className="text-[#333333] dark:text-[#D8D8D8]"
|
||||
>
|
||||
{keyword}
|
||||
@@ -144,8 +144,8 @@ export const QueryIntent = ({
|
||||
- {t("assistant.message.steps.relatedQuestions")}:
|
||||
</span>
|
||||
<div className="flex-1 flex flex-col text-[#333333] dark:text-[#D8D8D8]">
|
||||
{Data?.query?.map((question) => (
|
||||
<span key={question}>- {question}</span>
|
||||
{Data?.query?.map((question, qIndex) => (
|
||||
<span key={question + qIndex}>- {question}</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
33
src/components/ChatMessage/UserMessage.tsx
Normal file
33
src/components/ChatMessage/UserMessage.tsx
Normal file
@@ -0,0 +1,33 @@
|
||||
import { useState } from "react";
|
||||
|
||||
import { CopyButton } from "@/components/Common/CopyButton";
|
||||
|
||||
interface UserMessageProps {
|
||||
messageContent: string;
|
||||
}
|
||||
|
||||
export const UserMessage = ({ messageContent }: UserMessageProps) => {
|
||||
const [showCopyButton, setShowCopyButton] = useState(false);
|
||||
|
||||
return (
|
||||
<div
|
||||
className="flex gap-1 items-center"
|
||||
onMouseEnter={() => setShowCopyButton(true)}
|
||||
onMouseLeave={() => setShowCopyButton(false)}
|
||||
>
|
||||
{showCopyButton && <CopyButton textToCopy={messageContent} />}
|
||||
<div
|
||||
className="px-3 py-2 bg-white dark:bg-[#202126] rounded-xl border border-black/12 dark:border-black/15 font-normal text-sm text-[#333333] dark:text-[#D8D8D8] cursor-pointer select-none"
|
||||
onDoubleClick={(e) => {
|
||||
const selection = window.getSelection();
|
||||
const range = document.createRange();
|
||||
range.selectNodeContents(e.currentTarget);
|
||||
selection?.removeAllRanges();
|
||||
selection?.addRange(range);
|
||||
}}
|
||||
>
|
||||
{messageContent}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -11,6 +11,7 @@ import { Think } from "./Think";
|
||||
import { MessageActions } from "./MessageActions";
|
||||
import Markdown from "./Markdown";
|
||||
import { SuggestionList } from "./SuggestionList";
|
||||
import { UserMessage } from "./UserMessage";
|
||||
|
||||
interface ChatMessageProps {
|
||||
message: Message;
|
||||
@@ -55,11 +56,7 @@ export const ChatMessage = memo(function ChatMessage({
|
||||
|
||||
const renderContent = () => {
|
||||
if (!isAssistant) {
|
||||
return (
|
||||
<div className="px-3 py-2 bg-white dark:bg-[#202126] rounded-xl border border-black/12 dark:border-black/15 font-normal text-sm text-[#333333] dark:text-[#D8D8D8]">
|
||||
{messageContent}
|
||||
</div>
|
||||
);
|
||||
return <UserMessage messageContent={messageContent} />;
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -235,7 +235,6 @@
|
||||
padding: 0;
|
||||
margin: 24px 0;
|
||||
background-color: var(--color-border-default);
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.markdown-body input {
|
||||
|
||||
@@ -28,6 +28,7 @@ import { useAppStore } from "@/stores/appStore";
|
||||
import { useConnectStore } from "@/stores/connectStore";
|
||||
import bannerImg from "@/assets/images/coco-cloud-banner.jpeg";
|
||||
import SettingsToggle from "@/components/Settings/SettingsToggle";
|
||||
import Tooltip from "@/components/Common/Tooltip";
|
||||
|
||||
export default function Cloud() {
|
||||
const { t } = useTranslation();
|
||||
@@ -312,19 +313,22 @@ export default function Cloud() {
|
||||
});
|
||||
};
|
||||
|
||||
const enable_coco_server = useCallback(async (enabled: boolean) => {
|
||||
try {
|
||||
const command = enabled ? "enable_server" : "disable_server";
|
||||
const enable_coco_server = useCallback(
|
||||
async (enabled: boolean) => {
|
||||
try {
|
||||
const command = enabled ? "enable_server" : "disable_server";
|
||||
|
||||
await invoke(command, { id: currentService?.id });
|
||||
await invoke(command, { id: currentService?.id });
|
||||
|
||||
setCurrentService({ ...currentService, enabled });
|
||||
setCurrentService({ ...currentService, enabled });
|
||||
|
||||
await fetchServers(false);
|
||||
} catch (error) {
|
||||
setError(error);
|
||||
}
|
||||
}, [currentService?.id]);
|
||||
await fetchServers(false);
|
||||
} catch (error) {
|
||||
setError(error);
|
||||
}
|
||||
},
|
||||
[currentService?.id]
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="flex bg-gray-50 dark:bg-gray-900">
|
||||
@@ -346,9 +350,11 @@ export default function Cloud() {
|
||||
</div>
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<div className="flex items-center space-x-3">
|
||||
<div className="flex items-center text-gray-900 dark:text-white font-medium">
|
||||
{currentService?.name}
|
||||
</div>
|
||||
<Tooltip content={currentService?.endpoint}>
|
||||
<div className="flex items-center text-gray-900 dark:text-white font-medium cursor-pointer">
|
||||
{currentService?.name}
|
||||
</div>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<SettingsToggle
|
||||
|
||||
36
src/components/Common/CopyButton/index.tsx
Normal file
36
src/components/Common/CopyButton/index.tsx
Normal file
@@ -0,0 +1,36 @@
|
||||
import { useState } from "react";
|
||||
import { Copy, Check } from "lucide-react";
|
||||
|
||||
interface CopyButtonProps {
|
||||
textToCopy: string;
|
||||
}
|
||||
|
||||
export const CopyButton = ({ textToCopy }: CopyButtonProps) => {
|
||||
const [copied, setCopied] = useState(false);
|
||||
|
||||
const handleCopy = async () => {
|
||||
try {
|
||||
await navigator.clipboard.writeText(textToCopy);
|
||||
setCopied(true);
|
||||
const timerID = setTimeout(() => {
|
||||
setCopied(false);
|
||||
clearTimeout(timerID);
|
||||
}, 2000);
|
||||
} catch (err) {
|
||||
console.error("copy error:", err);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<button
|
||||
className={`p-1 bg-gray-200 dark:bg-gray-700 rounded`}
|
||||
onClick={handleCopy}
|
||||
>
|
||||
{copied ? (
|
||||
<Check className="w-4 h-4 text-[#38C200] dark:text-[#38C200]" />
|
||||
) : (
|
||||
<Copy className="w-4 h-4 text-gray-600 dark:text-gray-300" />
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
};
|
||||
@@ -47,6 +47,10 @@ const ContextMenu = () => {
|
||||
shortcut: "enter",
|
||||
clickEvent: () => {
|
||||
OpenURLWithBrowser(selectedSearchContent?.url);
|
||||
|
||||
setVisibleContextMenu(false);
|
||||
|
||||
invoke("hide_coco");
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -56,6 +60,8 @@ const ContextMenu = () => {
|
||||
shortcut: isMac ? "meta.l" : "ctrl.l",
|
||||
clickEvent: () => {
|
||||
copyToClipboard(selectedSearchContent?.url);
|
||||
|
||||
setVisibleContextMenu(false);
|
||||
},
|
||||
},
|
||||
];
|
||||
@@ -108,7 +114,7 @@ const ContextMenu = () => {
|
||||
|
||||
const item = menus.find((item) => item.shortcut === key);
|
||||
|
||||
handleClick(item?.clickEvent);
|
||||
item?.clickEvent();
|
||||
}
|
||||
);
|
||||
|
||||
@@ -118,14 +124,6 @@ const ContextMenu = () => {
|
||||
event.stopImmediatePropagation();
|
||||
});
|
||||
|
||||
const handleClick = (clickEvent?: () => void) => {
|
||||
clickEvent?.();
|
||||
|
||||
setVisibleContextMenu(false);
|
||||
|
||||
invoke("hide_coco");
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{visibleContextMenu && (
|
||||
@@ -165,7 +163,7 @@ const ContextMenu = () => {
|
||||
onMouseEnter={() => {
|
||||
state.activeMenuIndex = index;
|
||||
}}
|
||||
onClick={() => handleClick(clickEvent)}
|
||||
onClick={clickEvent}
|
||||
>
|
||||
<div className="flex items-center gap-2 text-black/80 dark:text-white/80">
|
||||
{cloneElement(icon, { className: "size-4" })}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import React, { useState, useRef, useEffect, useCallback } from "react";
|
||||
import { useInfiniteScroll } from "ahooks";
|
||||
import { isTauri, invoke } from "@tauri-apps/api/core";
|
||||
import { open } from "@tauri-apps/plugin-shell";
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { FixedSizeList } from "react-window";
|
||||
|
||||
@@ -10,6 +9,7 @@ import { SearchHeader } from "./SearchHeader";
|
||||
import noDataImg from "@/assets/coconut-tree.png";
|
||||
import { metaOrCtrlKey } from "@/utils/keyboardUtils";
|
||||
import SearchListItem from "./SearchListItem";
|
||||
import { OpenURLWithBrowser } from "@/utils/index";
|
||||
|
||||
interface DocumentListProps {
|
||||
onSelectDocument: (id: string) => void;
|
||||
@@ -111,18 +111,6 @@ export const DocumentList: React.FC<DocumentListProps> = ({
|
||||
setIsKeyboardMode(false);
|
||||
}, [isChatMode, input]);
|
||||
|
||||
const handleOpenURL = async (url: string) => {
|
||||
if (!url) return;
|
||||
try {
|
||||
if (isTauri()) {
|
||||
await open(url);
|
||||
// console.log("URL opened in default browser");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to open URL:", error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleKeyDown = useCallback(
|
||||
(e: KeyboardEvent) => {
|
||||
if (!data?.list?.length) return;
|
||||
@@ -158,7 +146,7 @@ export const DocumentList: React.FC<DocumentListProps> = ({
|
||||
if (e.key === "Enter" && selectedItem !== null) {
|
||||
const item = data?.list?.[selectedItem];
|
||||
if (item?.url) {
|
||||
handleOpenURL(item?.url);
|
||||
OpenURLWithBrowser(item?.url);
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -211,7 +199,7 @@ export const DocumentList: React.FC<DocumentListProps> = ({
|
||||
onMouseEnter={() => onMouseEnter(index, item)}
|
||||
onItemClick={() => {
|
||||
if (item?.url) {
|
||||
handleOpenURL(item?.url);
|
||||
OpenURLWithBrowser(item?.url);
|
||||
}
|
||||
}}
|
||||
showListRight={viewMode === "list"}
|
||||
@@ -219,7 +207,7 @@ export const DocumentList: React.FC<DocumentListProps> = ({
|
||||
</div>
|
||||
);
|
||||
},
|
||||
[data, selectedItem, viewMode, onMouseEnter, handleOpenURL]
|
||||
[data, selectedItem, viewMode, onMouseEnter, OpenURLWithBrowser]
|
||||
);
|
||||
|
||||
return (
|
||||
|
||||
@@ -14,7 +14,6 @@ import { OpenURLWithBrowser } from "@/utils/index";
|
||||
type ISearchData = Record<string, any[]>;
|
||||
|
||||
interface DropdownListProps {
|
||||
selected: (item: any) => void;
|
||||
suggests: any[];
|
||||
SearchData: ISearchData;
|
||||
IsError: boolean;
|
||||
@@ -23,7 +22,6 @@ interface DropdownListProps {
|
||||
}
|
||||
|
||||
function DropdownList({
|
||||
selected,
|
||||
suggests,
|
||||
SearchData,
|
||||
IsError,
|
||||
@@ -110,8 +108,6 @@ function DropdownList({
|
||||
const item = globalItemIndexMap[selectedItem];
|
||||
if (item?.url) {
|
||||
OpenURLWithBrowser(item?.url);
|
||||
} else {
|
||||
selected(item);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,12 +116,10 @@ function DropdownList({
|
||||
const item = globalItemIndexMap[parseInt(e.key, 10)];
|
||||
if (item?.url) {
|
||||
OpenURLWithBrowser(item?.url);
|
||||
} else {
|
||||
selected(item);
|
||||
}
|
||||
}
|
||||
},
|
||||
[suggests, selectedItem, showIndex, selected, globalItemIndexMap]
|
||||
[suggests, selectedItem, showIndex, globalItemIndexMap]
|
||||
);
|
||||
|
||||
const handleKeyUp = useCallback((e: KeyboardEvent) => {
|
||||
@@ -158,7 +152,6 @@ function DropdownList({
|
||||
|
||||
function goToTwoPage(item: any) {
|
||||
setSourceData(item);
|
||||
selected && selected(item);
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -219,8 +212,6 @@ function DropdownList({
|
||||
onItemClick={() => {
|
||||
if (item?.url) {
|
||||
OpenURLWithBrowser(item?.url);
|
||||
} else {
|
||||
selected(item);
|
||||
}
|
||||
}}
|
||||
goToTwoPage={goToTwoPage}
|
||||
|
||||
@@ -10,10 +10,10 @@ import { useAppStore } from "@/stores/appStore";
|
||||
import { isMac } from "@/utils/platform";
|
||||
import PinOffIcon from "@/icons/PinOff";
|
||||
import PinIcon from "@/icons/Pin";
|
||||
import { useUpdateStore } from "@/stores/updateStore";
|
||||
import clsx from "clsx";
|
||||
|
||||
interface FooterProps {
|
||||
isChat: boolean;
|
||||
name?: string;
|
||||
}
|
||||
|
||||
export default function Footer({}: FooterProps) {
|
||||
@@ -22,6 +22,8 @@ export default function Footer({}: FooterProps) {
|
||||
|
||||
const isPinned = useAppStore((state) => state.isPinned);
|
||||
const setIsPinned = useAppStore((state) => state.setIsPinned);
|
||||
const setVisible = useUpdateStore((state) => state.setVisible);
|
||||
const updateInfo = useUpdateStore((state) => state.updateInfo);
|
||||
|
||||
function openSetting() {
|
||||
emit("open_settings", "");
|
||||
@@ -55,16 +57,26 @@ export default function Footer({}: FooterProps) {
|
||||
alt={t("search.footer.logoAlt")}
|
||||
/>
|
||||
)}
|
||||
<span className="text-xs text-gray-500 dark:text-gray-400">
|
||||
{sourceData?.source?.name ||
|
||||
<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",
|
||||
})}
|
||||
</span>
|
||||
})
|
||||
)}
|
||||
</div>
|
||||
|
||||
<button
|
||||
onClick={togglePin}
|
||||
className={`${isPinned ? "text-blue-500" : ""}`}
|
||||
className={clsx({
|
||||
"text-blue-500": isPinned,
|
||||
"pl-2": updateInfo?.available,
|
||||
})}
|
||||
>
|
||||
{isPinned ? <PinIcon /> : <PinOffIcon />}
|
||||
</button>
|
||||
|
||||
@@ -213,11 +213,7 @@ export default function ChatInput({
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`w-full relative ${
|
||||
isChatPage
|
||||
? "bg-inputbox_bg_light dark:bg-inputbox_bg_dark bg-cover rounded-xl border border-[#E6E6E6] dark:border-[#272626]"
|
||||
: ""
|
||||
}`}
|
||||
className={`w-full relative`}
|
||||
>
|
||||
<div
|
||||
className={`p-2 flex items-center dark:text-[#D8D8D8] bg-[#ededed] dark:bg-[#202126] rounded transition-all relative `}
|
||||
@@ -281,23 +277,13 @@ export default function ChatInput({
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
{/* {isChatMode ? (
|
||||
<button
|
||||
className={`p-1 hover:bg-gray-50 dark:hover:bg-gray-700 rounded-full transition-colors ${
|
||||
isListening ? "bg-blue-100 dark:bg-blue-900" : ""
|
||||
}`}
|
||||
type="button"
|
||||
onClick={() => {}}
|
||||
>
|
||||
<Mic
|
||||
className={`w-4 h-4 ${
|
||||
isListening
|
||||
? "text-blue-500 animate-pulse"
|
||||
: "text-[#999] dark:text-[#999]"
|
||||
}`}
|
||||
/>
|
||||
</button>
|
||||
) : null} */}
|
||||
{/* {isChatMode && (
|
||||
<SpeechToText
|
||||
onChange={(transcript) => {
|
||||
changeInput(inputValue + transcript);
|
||||
}}
|
||||
/>
|
||||
)} */}
|
||||
|
||||
{isChatMode && curChatEnd ? (
|
||||
<button
|
||||
@@ -396,7 +382,14 @@ export default function ChatInput({
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div className="w-28 flex gap-2 relative"></div>
|
||||
<div data-tauri-drag-region className="w-28 flex gap-2 relative">
|
||||
{/* <SpeechToText
|
||||
Icon={AudioLines}
|
||||
onChange={(transcript) => {
|
||||
changeInput(inputValue + transcript);
|
||||
}}
|
||||
/> */}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{isChatPage ? null : (
|
||||
|
||||
@@ -26,7 +26,6 @@ function Search({ isChatMode, input }: SearchProps) {
|
||||
const [suggests, setSuggests] = useState<any[]>([]);
|
||||
const [SearchData, setSearchData] = useState<any>({});
|
||||
const [isSearchComplete, setIsSearchComplete] = useState(false);
|
||||
const [selectedItem, setSelectedItem] = useState<any>();
|
||||
|
||||
const mainWindowRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
@@ -87,7 +86,6 @@ function Search({ isChatMode, input }: SearchProps) {
|
||||
IsError={IsError}
|
||||
isSearchComplete={isSearchComplete}
|
||||
isChatMode={isChatMode}
|
||||
selected={(item) => setSelectedItem(item)}
|
||||
/>
|
||||
)
|
||||
) : (
|
||||
@@ -119,7 +117,7 @@ function Search({ isChatMode, input }: SearchProps) {
|
||||
</div>
|
||||
)}
|
||||
|
||||
<Footer isChat={false} name={selectedItem?.source?.name} />
|
||||
<Footer />
|
||||
|
||||
<ContextMenu />
|
||||
</div>
|
||||
|
||||
@@ -126,7 +126,7 @@ export default function SearchPopover({
|
||||
|
||||
{dataSourceList?.length > 0 && (
|
||||
<Popover>
|
||||
<PopoverButton className={clsx("flex items-center")}>
|
||||
<PopoverButton as="span" className={clsx("flex items-center")}>
|
||||
<ChevronDownIcon
|
||||
className={clsx("size-5", [
|
||||
isSearchActive
|
||||
|
||||
97
src/components/SpeechToText/index.tsx
Normal file
97
src/components/SpeechToText/index.tsx
Normal file
@@ -0,0 +1,97 @@
|
||||
import { useEventListener, useReactive } from "ahooks";
|
||||
import clsx from "clsx";
|
||||
import { LucideIcon, Mic } from "lucide-react";
|
||||
import { FC, useEffect } from "react";
|
||||
|
||||
interface SpeechToTextProps {
|
||||
Icon?: LucideIcon;
|
||||
onChange?: (transcript: string) => void;
|
||||
}
|
||||
|
||||
let recognition: SpeechRecognition | null = null;
|
||||
|
||||
const SpeechToText: FC<SpeechToTextProps> = (props) => {
|
||||
const { Icon = Mic, onChange } = props;
|
||||
|
||||
const state = useReactive({
|
||||
speaking: false,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
return destroyRecognition;
|
||||
}, []);
|
||||
|
||||
useEventListener("focusin", (event) => {
|
||||
const { target } = event;
|
||||
|
||||
const isInputElement =
|
||||
target instanceof HTMLInputElement ||
|
||||
target instanceof HTMLTextAreaElement;
|
||||
|
||||
if (state.speaking && isInputElement) {
|
||||
target.blur();
|
||||
}
|
||||
});
|
||||
|
||||
const handleSpeak = () => {
|
||||
if (state.speaking) {
|
||||
return destroyRecognition();
|
||||
}
|
||||
|
||||
const SpeechRecognition =
|
||||
window.SpeechRecognition || window.webkitSpeechRecognition;
|
||||
|
||||
recognition = new SpeechRecognition();
|
||||
recognition.continuous = true;
|
||||
recognition.interimResults = true;
|
||||
recognition.lang = "zh-CN";
|
||||
|
||||
recognition.onresult = (event) => {
|
||||
const transcript = [...event.results]
|
||||
.map((result) => result[0].transcript)
|
||||
.join("");
|
||||
|
||||
onChange?.(transcript);
|
||||
};
|
||||
|
||||
recognition.onerror = destroyRecognition;
|
||||
|
||||
recognition.onend = destroyRecognition;
|
||||
|
||||
recognition.start();
|
||||
|
||||
state.speaking = true;
|
||||
};
|
||||
|
||||
const destroyRecognition = () => {
|
||||
if (recognition) {
|
||||
recognition.abort();
|
||||
recognition.onresult = null;
|
||||
recognition.onerror = null;
|
||||
recognition.onend = null;
|
||||
recognition = null;
|
||||
}
|
||||
|
||||
state.speaking = false;
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className={clsx(
|
||||
"p-1 hover:bg-gray-50 dark:hover:bg-gray-700 rounded-full transition cursor-pointer",
|
||||
{
|
||||
"bg-blue-100 dark:bg-blue-900": state.speaking,
|
||||
}
|
||||
)}
|
||||
>
|
||||
<Icon
|
||||
className={clsx("size-4 text-[#999] dark:text-[#999]", {
|
||||
"text-blue-500 animate-pulse": state.speaking,
|
||||
})}
|
||||
onClick={handleSpeak}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SpeechToText;
|
||||
BIN
src/components/UpdateApp/imgs/dark-icon.png
Executable file
BIN
src/components/UpdateApp/imgs/dark-icon.png
Executable file
Binary file not shown.
|
After Width: | Height: | Size: 1.8 KiB |
BIN
src/components/UpdateApp/imgs/light-icon.png
Normal file
BIN
src/components/UpdateApp/imgs/light-icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.8 KiB |
181
src/components/UpdateApp/index.tsx
Normal file
181
src/components/UpdateApp/index.tsx
Normal file
@@ -0,0 +1,181 @@
|
||||
import { Button, Dialog, DialogPanel } from "@headlessui/react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import lightIcon from "./imgs/light-icon.png";
|
||||
import darkIcon from "./imgs/dark-icon.png";
|
||||
import { useThemeStore } from "@/stores/themeStore";
|
||||
import { noop } from "lodash-es";
|
||||
import { LoaderCircle, X } from "lucide-react";
|
||||
import { useUpdateStore } from "@/stores/updateStore";
|
||||
import { useInterval, useReactive } from "ahooks";
|
||||
import { check } from "@tauri-apps/plugin-updater";
|
||||
import { useCallback, useMemo } from "react";
|
||||
import { relaunch } from "@tauri-apps/plugin-process";
|
||||
import clsx from "clsx";
|
||||
import { open } from "@tauri-apps/plugin-shell";
|
||||
|
||||
interface State {
|
||||
loading?: boolean;
|
||||
total?: number;
|
||||
download: number;
|
||||
}
|
||||
|
||||
const UpdateApp = () => {
|
||||
const { t } = useTranslation();
|
||||
const isDark = useThemeStore((state) => state.isDark);
|
||||
const visible = useUpdateStore((state) => state.visible);
|
||||
const setVisible = useUpdateStore((state) => state.setVisible);
|
||||
const skipVersion = useUpdateStore((state) => state.skipVersion);
|
||||
const setSkipVersion = useUpdateStore((state) => state.setSkipVersion);
|
||||
const isOptional = useUpdateStore((state) => state.isOptional);
|
||||
const updateInfo = useUpdateStore((state) => state.updateInfo);
|
||||
const setUpdateInfo = useUpdateStore((state) => state.setUpdateInfo);
|
||||
|
||||
const state = useReactive<State>({ download: 0 });
|
||||
|
||||
useInterval(() => checkUpdate(), 1000 * 60 * 60 * 24, {
|
||||
immediate: true,
|
||||
});
|
||||
|
||||
const checkUpdate = useCallback(async () => {
|
||||
const update = await check();
|
||||
|
||||
if (update?.available) {
|
||||
setUpdateInfo(update);
|
||||
|
||||
if (skipVersion === update.version) return;
|
||||
|
||||
setVisible(true);
|
||||
}
|
||||
}, [skipVersion]);
|
||||
|
||||
const cursorClassName = useMemo(() => {
|
||||
return state.loading ? "cursor-not-allowed" : "cursor-pointer";
|
||||
}, [state.loading]);
|
||||
|
||||
const percent = useMemo(() => {
|
||||
const { total, download } = state;
|
||||
|
||||
if (!total) return 0;
|
||||
|
||||
return ((download / total) * 100).toFixed(2);
|
||||
}, [state.total, state.download]);
|
||||
|
||||
const handleDownload = async () => {
|
||||
if (state.loading) return;
|
||||
|
||||
state.loading = true;
|
||||
|
||||
await updateInfo?.downloadAndInstall((progress) => {
|
||||
switch (progress.event) {
|
||||
case "Started":
|
||||
state.total = progress.data.contentLength;
|
||||
break;
|
||||
case "Progress":
|
||||
state.download += progress.data.chunkLength;
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
state.loading = false;
|
||||
|
||||
relaunch();
|
||||
};
|
||||
|
||||
const handleCancel = () => {
|
||||
if (state.loading) return;
|
||||
|
||||
setVisible(false);
|
||||
};
|
||||
|
||||
const handleSkip = () => {
|
||||
if (state.loading) return;
|
||||
|
||||
setSkipVersion(updateInfo?.version);
|
||||
|
||||
setVisible(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
open={visible}
|
||||
as="div"
|
||||
className="relative z-10 focus:outline-none"
|
||||
onClose={noop}
|
||||
>
|
||||
<div className="fixed inset-0 z-10 w-screen overflow-y-auto">
|
||||
<div className="flex min-h-full items-center justify-center p-4">
|
||||
<DialogPanel
|
||||
transition
|
||||
className="relative w-[340px] py-8 flex flex-col items-center bg-white shadow-md border border-[#EDEDED] rounded-lg dark:bg-[#333] dark:border-black/20"
|
||||
>
|
||||
<X
|
||||
className={clsx(
|
||||
"absolute size-5 text-[#999] top-3 right-3 dark:text-[#D8D8D8]",
|
||||
cursorClassName,
|
||||
{
|
||||
hidden: !isOptional,
|
||||
}
|
||||
)}
|
||||
onClick={handleCancel}
|
||||
/>
|
||||
|
||||
<img src={isDark ? darkIcon : lightIcon} className="h-6" />
|
||||
|
||||
<div className="text-[#333] text-sm leading-5 py-2 dark:text-[#D8D8D8]">
|
||||
{isOptional ? (
|
||||
t("update.optional_description")
|
||||
) : (
|
||||
<div className="leading-5 text-center">
|
||||
<p>{t("update.force_description1")}</p>
|
||||
<p>{t("update.force_description2")}</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div
|
||||
className="text-xs text-[#0072FF] cursor-pointer"
|
||||
onClick={() => {
|
||||
open(
|
||||
"https://docs.infinilabs.com/coco-app/main/docs/release-notes"
|
||||
);
|
||||
}}
|
||||
>
|
||||
v{updateInfo?.version} {t("update.releaseNotes")}
|
||||
</div>
|
||||
|
||||
<Button
|
||||
className={clsx(
|
||||
"mb-3 mt-6 bg-[#0072FF] text-white text-sm px-[14px] py-[8px] rounded-lg",
|
||||
cursorClassName,
|
||||
{
|
||||
"opacity-50": state.loading,
|
||||
}
|
||||
)}
|
||||
onClick={handleDownload}
|
||||
>
|
||||
{state.loading ? (
|
||||
<div className="flex justify-center items-center gap-2">
|
||||
<LoaderCircle className="animate-spin size-5" />
|
||||
{percent}%
|
||||
</div>
|
||||
) : (
|
||||
t("update.button.download")
|
||||
)}
|
||||
</Button>
|
||||
|
||||
<div
|
||||
className={clsx("text-xs text-[#999]", cursorClassName, {
|
||||
hidden: !isOptional,
|
||||
})}
|
||||
onClick={handleSkip}
|
||||
>
|
||||
{t("update.skip_version")}
|
||||
</div>
|
||||
</DialogPanel>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
export default UpdateApp;
|
||||
@@ -1,33 +1,227 @@
|
||||
import { useCallback } from "react";
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import { invoke, isTauri } from "@tauri-apps/api/core";
|
||||
|
||||
import { IServer } from "@/stores/appStore";
|
||||
import type { Chat } from "@/components/Assistant/types";
|
||||
|
||||
export default function useChatActions(currentService: IServer, activeChat?: Chat) {
|
||||
const chatClose = useCallback(async () => {
|
||||
export function useChatActions(
|
||||
currentServiceId: string | undefined,
|
||||
setActiveChat: (chat: Chat | undefined) => void,
|
||||
setCurChatEnd: (value: boolean) => void,
|
||||
setErrorShow: (value: boolean) => void,
|
||||
setTimedoutShow: (value: boolean) => void,
|
||||
clearAllChunkData: () => void,
|
||||
setQuestion: (value: string) => void,
|
||||
curIdRef: React.MutableRefObject<string>,
|
||||
isSearchActive?: boolean,
|
||||
isDeepThinkActive?: boolean,
|
||||
sourceDataIds?: string[],
|
||||
changeInput?: (val: string) => void,
|
||||
) {
|
||||
const chatClose = useCallback(async (activeChat?: Chat) => {
|
||||
if (!activeChat?._id) return;
|
||||
try {
|
||||
await invoke("close_session_chat", {
|
||||
serverId: currentService?.id,
|
||||
let response: any = await invoke("close_session_chat", {
|
||||
serverId: currentServiceId,
|
||||
sessionId: activeChat?._id,
|
||||
});
|
||||
response = JSON.parse(response || "");
|
||||
console.log("_close", response);
|
||||
} catch (error) {
|
||||
console.error("Failed to close chat:", error);
|
||||
console.error("chatClose:", error);
|
||||
}
|
||||
}, [currentService?.id, activeChat?._id]);
|
||||
}, [currentServiceId]);
|
||||
|
||||
const cancelChat = useCallback(async () => {
|
||||
const cancelChat = useCallback(async (activeChat?: Chat) => {
|
||||
setCurChatEnd(true);
|
||||
if (!activeChat?._id) return;
|
||||
try {
|
||||
await invoke("cancel_session_chat", {
|
||||
serverId: currentService?.id,
|
||||
let response: any = await invoke("cancel_session_chat", {
|
||||
serverId: currentServiceId,
|
||||
sessionId: activeChat?._id,
|
||||
});
|
||||
response = JSON.parse(response || "");
|
||||
console.log("_cancel", response);
|
||||
} catch (error) {
|
||||
console.error("Failed to cancel chat:", error);
|
||||
console.error("cancelChat:", error);
|
||||
}
|
||||
}, [currentService?.id, activeChat?._id]);
|
||||
}, [currentServiceId, setCurChatEnd]);
|
||||
|
||||
return { chatClose, cancelChat };
|
||||
const chatHistory = useCallback(async (
|
||||
chat: Chat,
|
||||
callback?: (chat: Chat) => void
|
||||
) => {
|
||||
try {
|
||||
let response: any = await invoke("session_chat_history", {
|
||||
serverId: currentServiceId,
|
||||
sessionId: chat?._id,
|
||||
from: 0,
|
||||
size: 20,
|
||||
});
|
||||
response = JSON.parse(response || "");
|
||||
const hits = response?.hits?.hits || [];
|
||||
const updatedChat: Chat = {
|
||||
...chat,
|
||||
messages: hits,
|
||||
};
|
||||
console.log("id_history", response, updatedChat);
|
||||
setActiveChat(updatedChat);
|
||||
callback && callback(updatedChat);
|
||||
} catch (error) {
|
||||
console.error("chatHistory:", error);
|
||||
}
|
||||
}, [currentServiceId, setActiveChat]);
|
||||
|
||||
const createNewChat = useCallback(
|
||||
async (value: string = "", activeChat?: Chat) => {
|
||||
setTimedoutShow(false);
|
||||
setErrorShow(false);
|
||||
chatClose(activeChat);
|
||||
clearAllChunkData();
|
||||
setQuestion(value);
|
||||
try {
|
||||
console.log("sourceDataIds", sourceDataIds);
|
||||
let response: any = await invoke("new_chat", {
|
||||
serverId: currentServiceId,
|
||||
message: value,
|
||||
queryParams: {
|
||||
search: isSearchActive,
|
||||
deep_thinking: isDeepThinkActive,
|
||||
datasource: sourceDataIds?.join(",") || "",
|
||||
},
|
||||
});
|
||||
console.log("_new", response);
|
||||
const newChat: Chat = response;
|
||||
curIdRef.current = response?.payload?.id;
|
||||
|
||||
newChat._source = {
|
||||
message: value,
|
||||
};
|
||||
const updatedChat: Chat = {
|
||||
...newChat,
|
||||
messages: [newChat],
|
||||
};
|
||||
|
||||
changeInput && changeInput("");
|
||||
setActiveChat(updatedChat);
|
||||
setCurChatEnd(false);
|
||||
} catch (error) {
|
||||
setErrorShow(true);
|
||||
console.error("createNewChat:", error);
|
||||
}
|
||||
},
|
||||
[currentServiceId, sourceDataIds, isSearchActive, isDeepThinkActive, curIdRef]
|
||||
);
|
||||
|
||||
const sendMessage = useCallback(
|
||||
async (content: string, newChat: Chat) => {
|
||||
if (!newChat?._id || !content) return;
|
||||
clearAllChunkData();
|
||||
try {
|
||||
let response: any = await invoke("send_message", {
|
||||
serverId: currentServiceId,
|
||||
sessionId: newChat?._id,
|
||||
queryParams: {
|
||||
search: isSearchActive,
|
||||
deep_thinking: isDeepThinkActive,
|
||||
datasource: sourceDataIds?.join(",") || "",
|
||||
},
|
||||
message: content,
|
||||
});
|
||||
response = JSON.parse(response || "");
|
||||
console.log("_send", response);
|
||||
curIdRef.current = response[0]?._id;
|
||||
|
||||
const updatedChat: Chat = {
|
||||
...newChat,
|
||||
messages: [...(newChat?.messages || []), ...(response || [])],
|
||||
};
|
||||
|
||||
changeInput && changeInput("");
|
||||
setActiveChat(updatedChat);
|
||||
setCurChatEnd(false);
|
||||
} catch (error) {
|
||||
setErrorShow(true);
|
||||
console.error("sendMessage:", error);
|
||||
}
|
||||
},
|
||||
[currentServiceId, sourceDataIds, isSearchActive, isDeepThinkActive, curIdRef, setActiveChat, setCurChatEnd, setErrorShow, changeInput]
|
||||
);
|
||||
|
||||
const handleSendMessage = useCallback(
|
||||
async (content: string, activeChat?: Chat) => {
|
||||
if (!activeChat?._id || !content) return;
|
||||
setQuestion(content);
|
||||
|
||||
setTimedoutShow(false);
|
||||
setErrorShow(false);
|
||||
|
||||
await chatHistory(activeChat, (chat) => sendMessage(content, chat));
|
||||
},
|
||||
[chatHistory, sendMessage, setQuestion, setTimedoutShow, setErrorShow, clearAllChunkData]
|
||||
);
|
||||
|
||||
const openSessionChat = useCallback(async (chat: Chat) => {
|
||||
try {
|
||||
let response: any = await invoke("open_session_chat", {
|
||||
serverId: currentServiceId,
|
||||
sessionId: chat?._id,
|
||||
});
|
||||
response = JSON.parse(response || "");
|
||||
console.log("_open", response);
|
||||
return response;
|
||||
} catch (error) {
|
||||
console.error("open_session_chat:", error);
|
||||
return null;
|
||||
}
|
||||
}, [currentServiceId]);
|
||||
|
||||
const getChatHistory = useCallback(async () => {
|
||||
if (!currentServiceId) return [];
|
||||
try {
|
||||
let response: any = await invoke("chat_history", {
|
||||
serverId: currentServiceId,
|
||||
from: 0,
|
||||
size: 20,
|
||||
});
|
||||
response = JSON.parse(response || "");
|
||||
console.log("_history", response);
|
||||
const hits = response?.hits?.hits || [];
|
||||
return hits;
|
||||
} catch (error) {
|
||||
console.error("chat_history:", error);
|
||||
return [];
|
||||
}
|
||||
}, [currentServiceId]);
|
||||
|
||||
const createChatWindow = useCallback(async (createWin: any) => {
|
||||
if (isTauri()) {
|
||||
createWin && createWin({
|
||||
label: "chat",
|
||||
title: "Coco Chat",
|
||||
dragDropEnabled: true,
|
||||
center: true,
|
||||
width: 1000,
|
||||
height: 800,
|
||||
minWidth: 1000,
|
||||
minHeight: 800,
|
||||
alwaysOnTop: false,
|
||||
skipTaskbar: false,
|
||||
decorations: true,
|
||||
closable: true,
|
||||
url: "/ui/chat",
|
||||
});
|
||||
}
|
||||
}, []);
|
||||
|
||||
return {
|
||||
chatClose,
|
||||
cancelChat,
|
||||
chatHistory,
|
||||
createNewChat,
|
||||
sendMessage,
|
||||
handleSendMessage,
|
||||
openSessionChat,
|
||||
getChatHistory,
|
||||
createChatWindow
|
||||
};
|
||||
}
|
||||
69
src/hooks/useChatScroll.ts
Normal file
69
src/hooks/useChatScroll.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { debounce } from "lodash-es";
|
||||
|
||||
export function useChatScroll(messagesEndRef: React.RefObject<HTMLDivElement>) {
|
||||
const [userScrolling, setUserScrolling] = useState(false);
|
||||
const scrollTimeoutRef = useRef<NodeJS.Timeout>();
|
||||
|
||||
const lastScrollHeightRef = useRef<number>(0);
|
||||
|
||||
const isNearBottom = (container: HTMLElement) => {
|
||||
const { scrollTop, scrollHeight, clientHeight } = container;
|
||||
return Math.abs(scrollHeight - scrollTop - clientHeight) < 150;
|
||||
};
|
||||
|
||||
const scrollToBottom = useCallback(
|
||||
debounce(() => {
|
||||
const container = messagesEndRef.current?.parentElement;
|
||||
if (!container) return;
|
||||
|
||||
const contentChanged = lastScrollHeightRef.current !== container.scrollHeight;
|
||||
lastScrollHeightRef.current = container.scrollHeight;
|
||||
|
||||
if (!userScrolling || (contentChanged && isNearBottom(container))) {
|
||||
container.scrollTo({
|
||||
top: container.scrollHeight,
|
||||
behavior: "smooth",
|
||||
});
|
||||
}
|
||||
}, 50),
|
||||
[userScrolling, messagesEndRef]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const container = messagesEndRef.current?.parentElement;
|
||||
if (!container) return;
|
||||
|
||||
lastScrollHeightRef.current = container.scrollHeight;
|
||||
|
||||
const handleScroll = () => {
|
||||
if (scrollTimeoutRef.current) {
|
||||
clearTimeout(scrollTimeoutRef.current);
|
||||
}
|
||||
|
||||
const near = isNearBottom(container);
|
||||
if (!near) {
|
||||
setUserScrolling(true);
|
||||
}
|
||||
|
||||
scrollTimeoutRef.current = setTimeout(() => {
|
||||
if (isNearBottom(container)) {
|
||||
setUserScrolling(false);
|
||||
}
|
||||
}, 300);
|
||||
};
|
||||
|
||||
container.addEventListener("scroll", handleScroll);
|
||||
return () => {
|
||||
container.removeEventListener("scroll", handleScroll);
|
||||
if (scrollTimeoutRef.current) {
|
||||
clearTimeout(scrollTimeoutRef.current);
|
||||
}
|
||||
};
|
||||
}, [messagesEndRef]);
|
||||
|
||||
return {
|
||||
userScrolling,
|
||||
scrollToBottom
|
||||
};
|
||||
}
|
||||
83
src/hooks/useMessageHandler.ts
Normal file
83
src/hooks/useMessageHandler.ts
Normal file
@@ -0,0 +1,83 @@
|
||||
import { useCallback, useRef } from "react";
|
||||
|
||||
import type { IChunkData, Chat } from "@/components/Assistant/types";
|
||||
|
||||
export function useMessageHandler(
|
||||
curIdRef: React.MutableRefObject<string>,
|
||||
setCurChatEnd: (value: boolean) => void,
|
||||
setTimedoutShow: (value: boolean) => void,
|
||||
onCancel: (chat?: Chat) => void,
|
||||
setLoadingStep: (value: Record<string, boolean> | ((prev: Record<string, boolean>) => Record<string, boolean>)) => void,
|
||||
handlers: {
|
||||
deal_query_intent: (data: IChunkData) => void;
|
||||
deal_fetch_source: (data: IChunkData) => void;
|
||||
deal_pick_source: (data: IChunkData) => void;
|
||||
deal_deep_read: (data: IChunkData) => void;
|
||||
deal_think: (data: IChunkData) => void;
|
||||
deal_response: (data: IChunkData) => void;
|
||||
}
|
||||
) {
|
||||
const messageTimeoutRef = useRef<NodeJS.Timeout>();
|
||||
|
||||
const dealMsg = useCallback(
|
||||
(msg: string) => {
|
||||
if (messageTimeoutRef.current) {
|
||||
clearTimeout(messageTimeoutRef.current);
|
||||
}
|
||||
|
||||
if (!msg.includes("PRIVATE")) return;
|
||||
|
||||
messageTimeoutRef.current = setTimeout(() => {
|
||||
console.log("AI response timeout");
|
||||
setTimedoutShow(true);
|
||||
onCancel();
|
||||
}, 120000);
|
||||
|
||||
const cleanedData = msg.replace(/^PRIVATE /, "");
|
||||
try {
|
||||
const chunkData = JSON.parse(cleanedData);
|
||||
|
||||
if (chunkData.reply_to_message !== curIdRef.current) return;
|
||||
|
||||
setLoadingStep(() => ({
|
||||
query_intent: false,
|
||||
fetch_source: false,
|
||||
pick_source: false,
|
||||
deep_read: false,
|
||||
think: false,
|
||||
response: false,
|
||||
[chunkData.chunk_type]: true,
|
||||
}));
|
||||
|
||||
if (chunkData.chunk_type === "query_intent") {
|
||||
handlers.deal_query_intent(chunkData);
|
||||
} else if (chunkData.chunk_type === "fetch_source") {
|
||||
handlers.deal_fetch_source(chunkData);
|
||||
} else if (chunkData.chunk_type === "pick_source") {
|
||||
handlers.deal_pick_source(chunkData);
|
||||
} else if (chunkData.chunk_type === "deep_read") {
|
||||
handlers.deal_deep_read(chunkData);
|
||||
} else if (chunkData.chunk_type === "think") {
|
||||
handlers.deal_think(chunkData);
|
||||
} else if (chunkData.chunk_type === "response") {
|
||||
handlers.deal_response(chunkData);
|
||||
} else if (chunkData.chunk_type === "reply_end") {
|
||||
if (messageTimeoutRef.current) {
|
||||
clearTimeout(messageTimeoutRef.current);
|
||||
}
|
||||
setCurChatEnd(true);
|
||||
console.log("AI finished output");
|
||||
return;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("parse error:", error);
|
||||
}
|
||||
},
|
||||
[onCancel, setCurChatEnd, setTimedoutShow, curIdRef.current]
|
||||
);
|
||||
|
||||
return {
|
||||
dealMsg,
|
||||
messageTimeoutRef
|
||||
};
|
||||
}
|
||||
@@ -8,14 +8,14 @@ interface WebSocketProps {
|
||||
connected: boolean;
|
||||
setConnected: (connected: boolean) => void;
|
||||
currentService: IServer | null;
|
||||
dealMsg: (msg: string) => void;
|
||||
dealMsgRef: React.MutableRefObject<((msg: string) => void) | null>;
|
||||
}
|
||||
|
||||
export default function useWebSocket({
|
||||
connected,
|
||||
setConnected,
|
||||
currentService,
|
||||
dealMsg
|
||||
dealMsgRef,
|
||||
}: WebSocketProps) {
|
||||
const [errorShow, setErrorShow] = useState(false);
|
||||
|
||||
@@ -32,7 +32,13 @@ export default function useWebSocket({
|
||||
}
|
||||
}, [currentService]);
|
||||
|
||||
const updateDealMsg = useCallback((newDealMsg: (msg: string) => void) => {
|
||||
dealMsgRef.current = newDealMsg;
|
||||
}, [dealMsgRef]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!currentService?.id) return;
|
||||
|
||||
let unlisten_error = null;
|
||||
let unlisten_message = null;
|
||||
|
||||
@@ -52,7 +58,8 @@ export default function useWebSocket({
|
||||
});
|
||||
|
||||
unlisten_message = listen("ws-message", (event) => {
|
||||
dealMsg(String(event.payload));
|
||||
const msg = event.payload as string;
|
||||
dealMsgRef.current && dealMsgRef.current(msg);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -60,7 +67,7 @@ export default function useWebSocket({
|
||||
unlisten_error?.then((fn: any) => fn());
|
||||
unlisten_message?.then((fn: any) => fn());
|
||||
};
|
||||
}, [connected, dealMsg]);
|
||||
}, [connected, dealMsgRef]);
|
||||
|
||||
return { errorShow, setErrorShow, reconnect };
|
||||
return { errorShow, setErrorShow, reconnect, updateDealMsg };
|
||||
}
|
||||
@@ -9,7 +9,9 @@ const defaultWindowConfig = {
|
||||
title: "",
|
||||
url: "",
|
||||
width: 1000,
|
||||
height: 640,
|
||||
height: 800,
|
||||
minWidth: 1000,
|
||||
minHeight: 800,
|
||||
center: true,
|
||||
resizable: true,
|
||||
maximized: false,
|
||||
|
||||
@@ -81,6 +81,7 @@
|
||||
"footer": {
|
||||
"logoAlt": "Coco Logo",
|
||||
"version": "{{version}}",
|
||||
"updateAvailable": "Update available",
|
||||
"select": "Select",
|
||||
"open": "Open"
|
||||
},
|
||||
@@ -206,5 +207,16 @@
|
||||
"showCoco": "Show Coco",
|
||||
"settings": "Settings...",
|
||||
"quitCoco": "Quit Coco"
|
||||
},
|
||||
"update": {
|
||||
"title": "New update available for Coco AI.",
|
||||
"optional_description": "New update available for Coco AI.",
|
||||
"force_description1": "Coco AI update required.",
|
||||
"force_description2": "Please install the latest version to continue.",
|
||||
"releaseNotes": "Release Notes",
|
||||
"button": {
|
||||
"download": "Download"
|
||||
},
|
||||
"skip_version": "Skip this version"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,6 +81,7 @@
|
||||
"footer": {
|
||||
"logoAlt": "Coco 图标",
|
||||
"version": "{{version}}",
|
||||
"updateAvailable": "有可用更新",
|
||||
"select": "选择",
|
||||
"open": "打开"
|
||||
},
|
||||
@@ -206,5 +207,15 @@
|
||||
"showCoco": "显示 Coco",
|
||||
"settings": "偏好设置",
|
||||
"quitCoco": "退出 Coco"
|
||||
},
|
||||
"update": {
|
||||
"optional_description": "Coco AI 有新的可用更新。",
|
||||
"force_description1": "Coco Al 需要更新。",
|
||||
"force_description2": "请安装最新版本后继续使用。",
|
||||
"releaseNotes": "更新日志",
|
||||
"button": {
|
||||
"download": "下载"
|
||||
},
|
||||
"skip_version": "跳过此版本"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -149,6 +149,7 @@ export default function Chat({}: ChatProps) {
|
||||
}}
|
||||
onSelectChat={onSelectChat}
|
||||
onDeleteChat={deleteChat}
|
||||
fetchChatHistory={getChatHistory}
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
@@ -167,10 +168,11 @@ export default function Chat({}: ChatProps) {
|
||||
isSidebarOpen={isSidebarOpen}
|
||||
clearChatPage={clearChat}
|
||||
isChatPage={isChatPage}
|
||||
changeInput={setInput}
|
||||
/>
|
||||
|
||||
{/* Input area */}
|
||||
<div className={`border-t p-4 border-gray-200 dark:border-gray-800`}>
|
||||
<div className={`border-t p-4 pb-0 border-gray-200 dark:border-gray-800`}>
|
||||
<InputBox
|
||||
isChatMode={true}
|
||||
inputValue={input}
|
||||
|
||||
@@ -10,6 +10,7 @@ import ChatAI, { ChatAIRef } from "@/components/Assistant/Chat";
|
||||
import { useAppStore } from "@/stores/appStore";
|
||||
import { useAuthStore } from "@/stores/authStore";
|
||||
import { isLinux, isWin } from "@/utils/platform";
|
||||
import UpdateApp from "@/components/UpdateApp";
|
||||
|
||||
export default function DesktopApp() {
|
||||
const initializeListeners = useAppStore((state) => state.initializeListeners);
|
||||
@@ -163,6 +164,8 @@ export default function DesktopApp() {
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
<UpdateApp />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -21,6 +21,9 @@ export interface IServer {
|
||||
status: string;
|
||||
};
|
||||
assistantCount?: number;
|
||||
minimal_client_version?: {
|
||||
number: number;
|
||||
};
|
||||
}
|
||||
|
||||
export type IAppStore = {
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
import { create } from "zustand";
|
||||
import { persist } from "zustand/middleware";
|
||||
import { produce } from 'immer'
|
||||
import { listen, emit } from "@tauri-apps/api/event";
|
||||
|
||||
const CONNECTOR_CHANGE_EVENT = "connector_data_change";
|
||||
const DATASOURCE_CHANGE_EVENT = "datasourceData_change";
|
||||
|
||||
type keyArrayObject = {
|
||||
[key: string]: any[];
|
||||
@@ -20,18 +24,18 @@ export type IConnectStore = {
|
||||
export const useConnectStore = create<IConnectStore>()(
|
||||
persist(
|
||||
(set) => ({
|
||||
serverList: [],
|
||||
setServerList: (serverList: []) => {
|
||||
console.log("set serverList:",serverList)
|
||||
serverList: [],
|
||||
setServerList: (serverList: []) => {
|
||||
console.log("set serverList:", serverList)
|
||||
set(produce((draft) => {
|
||||
draft.serverList = serverList;
|
||||
draft.serverList = serverList;
|
||||
}))
|
||||
},
|
||||
currentService: "default_coco_server",
|
||||
setCurrentService: (server: any) => {
|
||||
console.log("set default server:",server)
|
||||
console.log("set default server:", server)
|
||||
set(produce((draft) => {
|
||||
draft.currentService = server;
|
||||
draft.currentService = server;
|
||||
}))
|
||||
},
|
||||
connector_data: {},
|
||||
@@ -41,6 +45,9 @@ export const useConnectStore = create<IConnectStore>()(
|
||||
draft.connector_data[key] = connector_data
|
||||
})
|
||||
);
|
||||
await emit(CONNECTOR_CHANGE_EVENT, {
|
||||
connector_data,
|
||||
});
|
||||
},
|
||||
datasourceData: {},
|
||||
setDatasourceData: async (datasourceData: any[], key: string) => {
|
||||
@@ -49,6 +56,19 @@ export const useConnectStore = create<IConnectStore>()(
|
||||
draft.datasourceData[key] = datasourceData
|
||||
})
|
||||
);
|
||||
await emit(DATASOURCE_CHANGE_EVENT, {
|
||||
datasourceData,
|
||||
});
|
||||
},
|
||||
initializeListeners: () => {
|
||||
listen(CONNECTOR_CHANGE_EVENT, (event: any) => {
|
||||
const { connector_data } = event.payload;
|
||||
set({ connector_data });
|
||||
});
|
||||
listen(DATASOURCE_CHANGE_EVENT, (event: any) => {
|
||||
const { datasourceData } = event.payload;
|
||||
set({ datasourceData });
|
||||
});
|
||||
},
|
||||
}),
|
||||
{
|
||||
@@ -60,4 +80,4 @@ export const useConnectStore = create<IConnectStore>()(
|
||||
}),
|
||||
}
|
||||
)
|
||||
);
|
||||
);
|
||||
41
src/stores/updateStore.ts
Normal file
41
src/stores/updateStore.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { Update } from "@tauri-apps/plugin-updater";
|
||||
import { create } from "zustand";
|
||||
import { persist } from "zustand/middleware";
|
||||
|
||||
export type IUpdateStore = {
|
||||
visible: boolean;
|
||||
setVisible: (visible: boolean) => void;
|
||||
skipVersion?: string;
|
||||
setSkipVersion: (skipVersion?: string) => void;
|
||||
isOptional: boolean;
|
||||
setIsOptional: (isOptional: boolean) => void;
|
||||
updateInfo?: Update;
|
||||
setUpdateInfo: (updateInfo?: Update) => void;
|
||||
};
|
||||
|
||||
export const useUpdateStore = create<IUpdateStore>()(
|
||||
persist(
|
||||
(set) => ({
|
||||
visible: false,
|
||||
setVisible: (visible: boolean) => {
|
||||
return set({ visible });
|
||||
},
|
||||
setSkipVersion: (skipVersion?: string) => {
|
||||
return set({ skipVersion });
|
||||
},
|
||||
isOptional: true,
|
||||
setIsOptional: (isOptional: boolean) => {
|
||||
return set({ isOptional });
|
||||
},
|
||||
setUpdateInfo: (updateInfo?: Update) => {
|
||||
return set({ updateInfo });
|
||||
},
|
||||
}),
|
||||
{
|
||||
name: "update-store",
|
||||
partialize: (state) => ({
|
||||
skipVersion: state.skipVersion,
|
||||
}),
|
||||
}
|
||||
)
|
||||
);
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { isTauri } from "@tauri-apps/api/core";
|
||||
import { invoke, isTauri } from "@tauri-apps/api/core";
|
||||
import { open } from "@tauri-apps/plugin-shell";
|
||||
|
||||
// 1
|
||||
@@ -56,8 +56,8 @@ export function useWindowSize() {
|
||||
export const IsTauri = () => {
|
||||
return Boolean(
|
||||
typeof window !== "undefined" &&
|
||||
window !== undefined &&
|
||||
(window as any).__TAURI_INTERNALS__ !== undefined
|
||||
window !== undefined &&
|
||||
(window as any).__TAURI_INTERNALS__ !== undefined
|
||||
);
|
||||
};
|
||||
|
||||
@@ -66,6 +66,7 @@ export const OpenURLWithBrowser = async (url: string) => {
|
||||
if (isTauri()) {
|
||||
try {
|
||||
await open(url);
|
||||
await invoke("hide_coco");
|
||||
console.log("URL opened in default browser");
|
||||
} catch (error) {
|
||||
console.error("Failed to open URL:", error);
|
||||
|
||||
Reference in New Issue
Block a user