feat: add tauri sidecar agent integration
This commit is contained in:
parent
606fa585bf
commit
689d65f9ae
3
.gitignore
vendored
3
.gitignore
vendored
@ -5,5 +5,8 @@ dist/
|
||||
src-tauri/target/
|
||||
src-tauri/gen/
|
||||
|
||||
agent/agent
|
||||
agent/agent-*
|
||||
|
||||
.DS_Store
|
||||
*.log
|
||||
|
||||
19
agent/REAMD.md
Normal file
19
agent/REAMD.md
Normal file
@ -0,0 +1,19 @@
|
||||
## golang 智能体
|
||||
|
||||
### 需要读取制定文件的md提示词、知识库、skill技能、员工蒸馏文件、历史回话记录
|
||||
|
||||
### 接入兼容OpenAI协议的模型
|
||||
|
||||
### 接入tts模型,支持声音克隆与用户语音通话
|
||||
|
||||
### 接入asr模型。支持实时语音转文字
|
||||
|
||||
### 接入多模态模型,支持识别用户发来的视频、图片、文档等信息
|
||||
|
||||
### 支持多agent写作,主agent调度
|
||||
- 对话agent
|
||||
- 任务agent
|
||||
- 记忆总结agent
|
||||
- 多模态agent
|
||||
- 语音agent
|
||||
- 视频agent
|
||||
3
agent/go.mod
Normal file
3
agent/go.mod
Normal file
@ -0,0 +1,3 @@
|
||||
module agent
|
||||
|
||||
go 1.26.1
|
||||
58
agent/main.go
Normal file
58
agent/main.go
Normal file
@ -0,0 +1,58 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
var waitGroup sync.WaitGroup
|
||||
|
||||
type logEntry struct {
|
||||
Level string `json:"level"`
|
||||
Message string `json:"message"`
|
||||
Timestamp string `json:"timestamp"`
|
||||
}
|
||||
|
||||
// 创建一个函数,启动一个协程,每隔600ms输出一次日志信息到命令行界面
|
||||
func logToConsole() {
|
||||
defer waitGroup.Done()
|
||||
for {
|
||||
time.Sleep(600 * time.Millisecond)
|
||||
fmt.Println(randomLogMsg())
|
||||
}
|
||||
}
|
||||
|
||||
// 生成随机日志信息,日志信息从以下列表中随机选择 日志格式为json格式,格式{"level": "info", "message": "正在处理用户请求..."},日志级别有info、debug、error,warning, think五种,日志信息有正在处理用户请求...、正在连接服务器...、正在加载数据...、正在分析用户输入...、正在生成回复...、正在优化模型...、正在更新知识库...等
|
||||
func randomLogMsg() string {
|
||||
logMsgs := []logEntry{
|
||||
{Level: "info", Message: "正在处理用户请求..."},
|
||||
{Level: "debug", Message: "正在连接服务器..."},
|
||||
{Level: "error", Message: "正在加载数据..."},
|
||||
{Level: "warning", Message: "正在分析用户输入..."},
|
||||
{Level: "think", Message: "正在生成回复..."},
|
||||
{Level: "info", Message: "正在优化模型..."},
|
||||
{Level: "info", Message: "正在更新知识库..."},
|
||||
}
|
||||
logMsg := logMsgs[time.Now().UnixNano()%int64(len(logMsgs))]
|
||||
logMsg.Timestamp = time.Now().Format("15:04:05")
|
||||
|
||||
data, err := json.Marshal(logMsg)
|
||||
if err != nil {
|
||||
return fmt.Sprintf(`{"level":"error","message":"%s","timestamp":"%s"}`, err.Error(), time.Now().Format("15:04:05"))
|
||||
}
|
||||
|
||||
return string(data)
|
||||
}
|
||||
|
||||
func main() {
|
||||
waitGroup = sync.WaitGroup{}
|
||||
// 启动日志协程
|
||||
waitGroup.Add(1)
|
||||
go logToConsole()
|
||||
// 等待协程main执行完成
|
||||
time.Sleep(50 * time.Second)
|
||||
waitGroup.Wait()
|
||||
|
||||
}
|
||||
11
package-lock.json
generated
11
package-lock.json
generated
@ -8,6 +8,7 @@
|
||||
"name": "wechat-ai-ui",
|
||||
"version": "0.1.0",
|
||||
"dependencies": {
|
||||
"@tauri-apps/api": "^2.11.0",
|
||||
"@vitejs/plugin-react": "^5.0.0",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"lucide-react": "^1.17.0",
|
||||
@ -1123,6 +1124,16 @@
|
||||
"win32"
|
||||
]
|
||||
},
|
||||
"node_modules/@tauri-apps/api": {
|
||||
"version": "2.11.0",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/api/-/api-2.11.0.tgz",
|
||||
"integrity": "sha512-7CinYODhky9lmO23xHnUFv0Xt43fbtWMyxZcLcRBlFkcgXKuEirBvHpmtJ89YMhyeGcq20Wuc47Fa4XjyniywA==",
|
||||
"license": "Apache-2.0 OR MIT",
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/tauri"
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/cli": {
|
||||
"version": "2.11.2",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli/-/cli-2.11.2.tgz",
|
||||
|
||||
@ -10,6 +10,7 @@
|
||||
"preview": "vite preview --host 127.0.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@tauri-apps/api": "^2.11.0",
|
||||
"@vitejs/plugin-react": "^5.0.0",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"lucide-react": "^1.17.0",
|
||||
|
||||
4
src-tauri/.gitignore
vendored
4
src-tauri/.gitignore
vendored
@ -2,3 +2,7 @@
|
||||
# will have compiled files and executables
|
||||
/target/
|
||||
/gen/schemas
|
||||
|
||||
# Go sidecar build outputs copied under src-tauri during local packaging
|
||||
/agent
|
||||
/agent-*
|
||||
|
||||
206
src-tauri/Cargo.lock
generated
206
src-tauri/Cargo.lock
generated
@ -85,6 +85,7 @@ dependencies = [
|
||||
"tauri",
|
||||
"tauri-build",
|
||||
"tauri-plugin-log",
|
||||
"tauri-plugin-shell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -824,6 +825,15 @@ version = "1.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4ef6b89e5b37196644d8796de5268852ff179b44e96276cf4290264843743bb7"
|
||||
|
||||
[[package]]
|
||||
name = "encoding_rs"
|
||||
version = "0.8.35"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "env_filter"
|
||||
version = "0.1.4"
|
||||
@ -851,6 +861,16 @@ dependencies = [
|
||||
"typeid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "errno"
|
||||
version = "0.3.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fastrand"
|
||||
version = "2.4.1"
|
||||
@ -1646,6 +1666,25 @@ version = "2.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2"
|
||||
|
||||
[[package]]
|
||||
name = "is-docker"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "928bae27f42bc99b60d9ac7334e3a21d10ad8f1835a4e12ec3ec0464765ed1b3"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "is-wsl"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "173609498df190136aa7dea1a91db051746d339e18476eed5ca40521f02d7aa5"
|
||||
dependencies = [
|
||||
"is-docker",
|
||||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.18"
|
||||
@ -2202,12 +2241,34 @@ version = "1.21.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
|
||||
|
||||
[[package]]
|
||||
name = "open"
|
||||
version = "5.3.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2fbaa89d2ddc8473c78a3adf69eea8cffa28c483b8e02a971ef31527cd0fc92c"
|
||||
dependencies = [
|
||||
"dunce",
|
||||
"is-wsl",
|
||||
"libc",
|
||||
"pathdiff",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "option-ext"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
|
||||
|
||||
[[package]]
|
||||
name = "os_pipe"
|
||||
version = "1.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7d8fae84b431384b68627d0f9b3b1245fcf9f46f6c0e3dc902e9dce64edd1967"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pango"
|
||||
version = "0.18.3"
|
||||
@ -2256,6 +2317,12 @@ dependencies = [
|
||||
"windows-link 0.2.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pathdiff"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3"
|
||||
|
||||
[[package]]
|
||||
name = "percent-encoding"
|
||||
version = "2.3.2"
|
||||
@ -3009,12 +3076,54 @@ dependencies = [
|
||||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shared_child"
|
||||
version = "1.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e362d9935bc50f019969e2f9ecd66786612daae13e8f277be7bfb66e8bed3f7"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"sigchld",
|
||||
"windows-sys 0.60.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shlex"
|
||||
version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba"
|
||||
|
||||
[[package]]
|
||||
name = "sigchld"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "47106eded3c154e70176fc83df9737335c94ce22f821c32d17ed1db1f83badb1"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"os_pipe",
|
||||
"signal-hook",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook"
|
||||
version = "0.3.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"signal-hook-registry",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook-registry"
|
||||
version = "1.4.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b"
|
||||
dependencies = [
|
||||
"errno",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "simd-adler32"
|
||||
version = "0.3.9"
|
||||
@ -3419,6 +3528,27 @@ dependencies = [
|
||||
"time",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin-shell"
|
||||
version = "2.3.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8457dbf9e2bab1edd8df22bb2c20857a59a9868e79cb3eac5ed639eec4d0c73b"
|
||||
dependencies = [
|
||||
"encoding_rs",
|
||||
"log",
|
||||
"open",
|
||||
"os_pipe",
|
||||
"regex",
|
||||
"schemars 0.8.22",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"shared_child",
|
||||
"tauri",
|
||||
"tauri-plugin",
|
||||
"thiserror 2.0.18",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-runtime"
|
||||
version = "2.11.2"
|
||||
@ -4480,6 +4610,15 @@ dependencies = [
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.60.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb"
|
||||
dependencies = [
|
||||
"windows-targets 0.53.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.61.2"
|
||||
@ -4513,13 +4652,30 @@ dependencies = [
|
||||
"windows_aarch64_gnullvm 0.52.6",
|
||||
"windows_aarch64_msvc 0.52.6",
|
||||
"windows_i686_gnu 0.52.6",
|
||||
"windows_i686_gnullvm",
|
||||
"windows_i686_gnullvm 0.52.6",
|
||||
"windows_i686_msvc 0.52.6",
|
||||
"windows_x86_64_gnu 0.52.6",
|
||||
"windows_x86_64_gnullvm 0.52.6",
|
||||
"windows_x86_64_msvc 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.53.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3"
|
||||
dependencies = [
|
||||
"windows-link 0.2.1",
|
||||
"windows_aarch64_gnullvm 0.53.1",
|
||||
"windows_aarch64_msvc 0.53.1",
|
||||
"windows_i686_gnu 0.53.1",
|
||||
"windows_i686_gnullvm 0.53.1",
|
||||
"windows_i686_msvc 0.53.1",
|
||||
"windows_x86_64_gnu 0.53.1",
|
||||
"windows_x86_64_gnullvm 0.53.1",
|
||||
"windows_x86_64_msvc 0.53.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-threading"
|
||||
version = "0.1.0"
|
||||
@ -4550,6 +4706,12 @@ version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.42.2"
|
||||
@ -4562,6 +4724,12 @@ version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.42.2"
|
||||
@ -4574,12 +4742,24 @@ version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnullvm"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnullvm"
|
||||
version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.42.2"
|
||||
@ -4592,6 +4772,12 @@ version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.42.2"
|
||||
@ -4604,6 +4790,12 @@ version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.42.2"
|
||||
@ -4616,6 +4808,12 @@ version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.42.2"
|
||||
@ -4628,6 +4826,12 @@ version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650"
|
||||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "0.5.40"
|
||||
|
||||
@ -21,5 +21,6 @@ tauri-build = { version = "2.6.2", features = [] }
|
||||
serde_json = "1.0"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
log = "0.4"
|
||||
tauri = { version = "2.11.2", features = [] }
|
||||
tauri = { version = "2.11.2", features = ["macos-private-api"] }
|
||||
tauri-plugin-log = "2"
|
||||
tauri-plugin-shell = "2.3.5"
|
||||
|
||||
@ -3,9 +3,12 @@
|
||||
"identifier": "default",
|
||||
"description": "enables the default permissions",
|
||||
"windows": [
|
||||
"main"
|
||||
"main",
|
||||
"popup-*"
|
||||
],
|
||||
"permissions": [
|
||||
"core:default"
|
||||
"core:default",
|
||||
"core:window:allow-close",
|
||||
"core:window:allow-start-dragging"
|
||||
]
|
||||
}
|
||||
|
||||
@ -1,6 +1,161 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::sync::Mutex;
|
||||
use tauri::{Emitter, Manager, WebviewUrl, WebviewWindowBuilder};
|
||||
use tauri_plugin_shell::{
|
||||
process::{CommandChild, CommandEvent},
|
||||
ShellExt,
|
||||
};
|
||||
|
||||
struct AgentProcess(Mutex<Option<CommandChild>>);
|
||||
|
||||
#[derive(Clone, Deserialize, Serialize)]
|
||||
struct AgentLog {
|
||||
level: String,
|
||||
message: String,
|
||||
timestamp: Option<String>,
|
||||
}
|
||||
|
||||
fn emit_agent_log(app: &tauri::AppHandle, level: &str, message: impl Into<String>) {
|
||||
let _ = app.emit(
|
||||
"agent-log",
|
||||
AgentLog {
|
||||
level: level.to_string(),
|
||||
message: message.into(),
|
||||
timestamp: None,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
fn emit_agent_line(app: &tauri::AppHandle, fallback_level: &str, line: String) {
|
||||
if let Ok(log) = serde_json::from_str::<AgentLog>(&line) {
|
||||
let _ = app.emit("agent-log", log);
|
||||
return;
|
||||
}
|
||||
|
||||
emit_agent_log(app, fallback_level, line);
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn start_agent(app: tauri::AppHandle, process: tauri::State<AgentProcess>) -> Result<(), String> {
|
||||
let mut process_guard = process.0.lock().map_err(|error| error.to_string())?;
|
||||
|
||||
if process_guard.is_some() {
|
||||
emit_agent_log(&app, "warning", "agent already running");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let (mut rx, child) = app
|
||||
.shell()
|
||||
.sidecar("agent")
|
||||
.map_err(|error| error.to_string())?
|
||||
.spawn()
|
||||
.map_err(|error| error.to_string())?;
|
||||
|
||||
*process_guard = Some(child);
|
||||
emit_agent_log(&app, "info", "agent started");
|
||||
|
||||
let app_handle = app.clone();
|
||||
tauri::async_runtime::spawn(async move {
|
||||
while let Some(event) = rx.recv().await {
|
||||
match event {
|
||||
CommandEvent::Stdout(line) => {
|
||||
emit_agent_line(
|
||||
&app_handle,
|
||||
"info",
|
||||
String::from_utf8_lossy(&line).trim().to_string(),
|
||||
);
|
||||
}
|
||||
CommandEvent::Stderr(line) => {
|
||||
emit_agent_line(
|
||||
&app_handle,
|
||||
"error",
|
||||
String::from_utf8_lossy(&line).trim().to_string(),
|
||||
);
|
||||
}
|
||||
CommandEvent::Error(error) => {
|
||||
emit_agent_log(&app_handle, "error", error);
|
||||
}
|
||||
CommandEvent::Terminated(payload) => {
|
||||
emit_agent_log(
|
||||
&app_handle,
|
||||
"warning",
|
||||
format!("agent exited with code {:?}", payload.code),
|
||||
);
|
||||
|
||||
if let Ok(mut process_guard) = app_handle.state::<AgentProcess>().0.lock() {
|
||||
*process_guard = None;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn stop_agent(app: tauri::AppHandle, process: tauri::State<AgentProcess>) -> Result<(), String> {
|
||||
let mut process_guard = process.0.lock().map_err(|error| error.to_string())?;
|
||||
|
||||
if let Some(child) = process_guard.take() {
|
||||
child.kill().map_err(|error| error.to_string())?;
|
||||
emit_agent_log(&app, "warning", "agent stopped");
|
||||
} else {
|
||||
emit_agent_log(&app, "warning", "agent is not running");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn open_popup_window(app: tauri::AppHandle, route: String) -> Result<(), String> {
|
||||
let route = if route.starts_with('/') {
|
||||
route
|
||||
} else {
|
||||
format!("/{route}")
|
||||
};
|
||||
let label = format!("popup-{}", route.trim_start_matches('/').replace('/', "-"));
|
||||
|
||||
if let Some(window) = app.get_webview_window(&label) {
|
||||
window.set_focus().map_err(|error| error.to_string())?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let title = match route.as_str() {
|
||||
"/window/settings" => "设置",
|
||||
"/window/engine-logs" => "分身引擎日志",
|
||||
"/window/knowledge-create" => "新增知识库",
|
||||
"/window/knowledge-detail" => "知识库详情",
|
||||
"/window/skill-create" => "skill技能详情",
|
||||
"/window/employee-detail" => "员工蒸馏详情",
|
||||
"/window/distill-start" => "开始蒸馏",
|
||||
_ => "二级窗口",
|
||||
};
|
||||
|
||||
WebviewWindowBuilder::new(
|
||||
&app,
|
||||
label,
|
||||
WebviewUrl::App(format!("index.html#{route}").into()),
|
||||
)
|
||||
.title(title)
|
||||
.inner_size(1080.0, 800.0)
|
||||
.decorations(false)
|
||||
.transparent(true)
|
||||
.resizable(true)
|
||||
.build()
|
||||
.map_err(|error| error.to_string())?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||
pub fn run() {
|
||||
tauri::Builder::default()
|
||||
.manage(AgentProcess(Mutex::new(None)))
|
||||
.plugin(tauri_plugin_shell::init())
|
||||
.setup(|app| {
|
||||
if cfg!(debug_assertions) {
|
||||
app.handle().plugin(
|
||||
@ -11,6 +166,11 @@ pub fn run() {
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
open_popup_window,
|
||||
start_agent,
|
||||
stop_agent
|
||||
])
|
||||
.run(tauri::generate_context!())
|
||||
.expect("error while running tauri application");
|
||||
}
|
||||
|
||||
@ -10,11 +10,16 @@
|
||||
"beforeBuildCommand": "npm run build"
|
||||
},
|
||||
"app": {
|
||||
"macOSPrivateApi": true,
|
||||
"windows": [
|
||||
{
|
||||
"title": "微信AI助手",
|
||||
"width": 800,
|
||||
"height": 600,
|
||||
"width": 500,
|
||||
"height": 900,
|
||||
"minWidth": 500,
|
||||
"minHeight": 900,
|
||||
"decorations": false,
|
||||
"transparent": true,
|
||||
"resizable": true,
|
||||
"fullscreen": false
|
||||
}
|
||||
@ -26,6 +31,9 @@
|
||||
"bundle": {
|
||||
"active": true,
|
||||
"targets": "all",
|
||||
"externalBin": [
|
||||
"../agent/agent"
|
||||
],
|
||||
"icon": [
|
||||
"icons/32x32.png",
|
||||
"icons/128x128.png",
|
||||
|
||||
@ -24,7 +24,7 @@ function App() {
|
||||
const isWindowRoute = route.startsWith("/window/");
|
||||
|
||||
return (
|
||||
<div className="min-h-dvh px-4 py-4 text-text sm:px-6">
|
||||
<div className="min-h-dvh overflow-hidden rounded-[12px] bg-page text-text">
|
||||
{isWindowRoute ? (
|
||||
<SecondaryWindow
|
||||
route={route}
|
||||
|
||||
@ -16,7 +16,7 @@ export default function MainShell(props) {
|
||||
}[props.activeTab];
|
||||
|
||||
return (
|
||||
<div className="app-window mx-auto flex min-h-[calc(100dvh-32px)] w-full max-w-[750px] flex-col overflow-hidden rounded-[10px] border border-line shadow-panel">
|
||||
<div className="app-window flex h-dvh w-full flex-col overflow-hidden rounded-[12px]">
|
||||
<TitleBar title={activeTitle} theme={props.theme} setTheme={props.setTheme} />
|
||||
<main className="relative flex min-h-0 flex-1 flex-col overflow-y-auto px-5 py-5">{content}</main>
|
||||
<BottomTabs activeTab={props.activeTab} setActiveTab={props.setActiveTab} />
|
||||
|
||||
@ -1,27 +1,58 @@
|
||||
export default function Terminal({ title, lines, tall = false }) {
|
||||
import { useEffect, useRef } from "react";
|
||||
|
||||
export default function Terminal({ title, lines, tall = false, onClear }) {
|
||||
const scrollRef = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!scrollRef.current) return;
|
||||
scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
|
||||
}, [lines.length]);
|
||||
|
||||
const levelClass = {
|
||||
debug: "terminal-line-debug",
|
||||
info: "terminal-line-info",
|
||||
error: "terminal-line-error",
|
||||
think: "terminal-line-think",
|
||||
warning: "terminal-line-warning",
|
||||
waring: "terminal-line-warning",
|
||||
};
|
||||
|
||||
const levelLabel = {
|
||||
debug: "调试",
|
||||
error: "错误",
|
||||
info: "日志",
|
||||
think: "思考",
|
||||
warning: "警告",
|
||||
waring: "警告",
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={`terminal ${tall ? "basis-[650px]" : "basis-[250px]"} flex min-h-0 flex-1 flex-col`}>
|
||||
<div className="mb-3 flex shrink-0 items-center justify-between">
|
||||
<h3 className="text-[13px] font-black text-[#d7ffe4]">{title}</h3>
|
||||
<span className="cursor-pointer text-[11px] font-bold text-[#7ea58b]">清理日志</span>
|
||||
<button className="cursor-pointer border-0 bg-transparent p-0 text-[11px] font-bold text-[#7ea58b]" onClick={onClear}>清理日志</button>
|
||||
</div>
|
||||
<div className="min-h-0 flex-1 space-y-2 overflow-y-auto font-mono text-[12px] leading-5">
|
||||
<div ref={scrollRef} className="scrollbar-hidden relative min-h-0 flex-1 overflow-y-auto font-mono text-[12px] leading-5">
|
||||
{lines.length ? (
|
||||
lines.map((line, index) => {
|
||||
<div className="space-y-2">
|
||||
{lines.map((line, index) => {
|
||||
const level = typeof line === "string" ? "info" : line.level;
|
||||
const message = typeof line === "string" ? line : line.message;
|
||||
const timestamp = typeof line === "string" ? null : line.timestamp;
|
||||
|
||||
return <p key={`${message}-${index}`} className={levelClass[level] ?? levelClass.info}>{message}</p>;
|
||||
})
|
||||
return (
|
||||
<p key={`${timestamp ?? ""}-${message}-${index}`} className="text-[#d7ffe4]">
|
||||
{timestamp ? <span className="text-[#6f8978]">{timestamp} </span> : null}
|
||||
<span className={levelClass[level] ?? levelClass.info}>[{levelLabel[level] ?? level}] </span>
|
||||
{message}
|
||||
</p>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
) : (
|
||||
<p className="terminal-line-info">引擎未启动</p>
|
||||
<div className="absolute inset-0 grid place-items-center">
|
||||
<p className="text-[16px] font-medium tracking-[0.18em] text-[#d7ffe4]">引擎未启动</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -1,13 +1,19 @@
|
||||
import { LogOut, RotateCcw } from "lucide-react";
|
||||
import ThemeSwitch from "./ThemeSwitch";
|
||||
import { go } from "../utils/navigation";
|
||||
import { closeWindow, startWindowDrag } from "../utils/navigation";
|
||||
|
||||
export default function TitleBar({ title, theme, setTheme, compact = false }) {
|
||||
return (
|
||||
<header className="glass-bar relative z-10 flex items-center justify-between gap-4 border-b border-line px-5 py-2">
|
||||
<div className="flex min-w-0 items-center gap-3">
|
||||
<div
|
||||
data-tauri-drag-region
|
||||
className="flex min-w-0 flex-1 cursor-pointer select-none items-center gap-3 self-stretch"
|
||||
onPointerDown={(event) => {
|
||||
if (event.button === 0) startWindowDrag();
|
||||
}}
|
||||
>
|
||||
<div className="min-w-0">
|
||||
<h1 className="truncate text-[18px] font-extrabold leading-tight">{title}</h1>
|
||||
<h1 data-tauri-drag-region className="truncate text-[18px] font-medium leading-tight">{title}</h1>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex shrink-0 items-center gap-5">
|
||||
@ -18,7 +24,7 @@ export default function TitleBar({ title, theme, setTheme, compact = false }) {
|
||||
<button className="icon-btn" aria-label="退出"><LogOut size={16} strokeWidth={2.5} /></button>
|
||||
</>
|
||||
) : (
|
||||
<button className="btn-secondary h-9 px-3" onClick={() => go("/")}>关闭</button>
|
||||
<button className="btn-secondary h-9 px-3" onClick={closeWindow}>关闭</button>
|
||||
)}
|
||||
</div>
|
||||
</header>
|
||||
|
||||
@ -52,14 +52,17 @@ html,
|
||||
body,
|
||||
#root {
|
||||
min-height: 100%;
|
||||
background: transparent;
|
||||
overflow: hidden;
|
||||
overscroll-behavior: none;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
background:
|
||||
radial-gradient(circle at 12% 10%, rgba(7, 193, 96, 0.11), transparent 30%),
|
||||
radial-gradient(circle at 86% 4%, rgba(45, 140, 255, 0.1), transparent 24%),
|
||||
var(--bg);
|
||||
background: transparent;
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif;
|
||||
letter-spacing: 0;
|
||||
}
|
||||
@ -74,23 +77,27 @@ button {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.scrollbar-hidden {
|
||||
scrollbar-width: none;
|
||||
-ms-overflow-style: none;
|
||||
}
|
||||
|
||||
.scrollbar-hidden::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@layer components {
|
||||
.app-window {
|
||||
position: relative;
|
||||
box-shadow:
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.54),
|
||||
inset 0 -1px 0 rgba(128, 128, 128, 0.08),
|
||||
var(--shadow);
|
||||
background:
|
||||
radial-gradient(circle at 16% 4%, rgba(7, 193, 96, 0.12), transparent 28%),
|
||||
radial-gradient(circle at 86% 2%, rgba(45, 140, 255, 0.11), transparent 24%),
|
||||
radial-gradient(circle at 50% 36%, rgba(7, 193, 96, 0.08), transparent 30%),
|
||||
linear-gradient(180deg, color-mix(in srgb, var(--page) 68%, transparent), color-mix(in srgb, var(--page) 82%, transparent) 72%);
|
||||
radial-gradient(circle at 16% 4%, rgba(7, 193, 96, 0.18), transparent 30%),
|
||||
radial-gradient(circle at 86% 2%, rgba(45, 140, 255, 0.16), transparent 26%),
|
||||
radial-gradient(circle at 50% 36%, rgba(7, 193, 96, 0.13), transparent 32%),
|
||||
linear-gradient(180deg, color-mix(in srgb, var(--page) 58%, transparent), color-mix(in srgb, var(--page) 76%, transparent) 72%);
|
||||
}
|
||||
|
||||
html[data-theme="dark"] .app-window {
|
||||
box-shadow:
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.08),
|
||||
inset 0 -1px 0 rgba(255, 255, 255, 0.04),
|
||||
var(--shadow);
|
||||
}
|
||||
@ -101,7 +108,6 @@ button {
|
||||
backdrop-filter: blur(10px) saturate(1.35);
|
||||
-webkit-backdrop-filter: blur(28px) saturate(1.35);
|
||||
box-shadow:
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.36),
|
||||
0 10px 28px rgba(20, 36, 27, 0.06);
|
||||
}
|
||||
|
||||
@ -194,7 +200,7 @@ button {
|
||||
}
|
||||
|
||||
.status {
|
||||
@apply inline-flex h-6 items-center rounded-full px-2.5 text-[11px] font-black;
|
||||
@apply inline-flex h-6 items-center rounded-full px-2.5 text-[11px] font-medium;
|
||||
}
|
||||
|
||||
.status-success {
|
||||
@ -224,6 +230,14 @@ button {
|
||||
color: #8ff0b3;
|
||||
}
|
||||
|
||||
.terminal-line-debug {
|
||||
color: #82c7ff;
|
||||
}
|
||||
|
||||
.terminal-line-think {
|
||||
color: #c9a7ff;
|
||||
}
|
||||
|
||||
.terminal-line-warning {
|
||||
color: #ffd166;
|
||||
}
|
||||
@ -285,7 +299,7 @@ button {
|
||||
}
|
||||
|
||||
.fab {
|
||||
@apply fixed bottom-24 right-[max(24px,calc((100vw-750px)/2+24px))] grid h-14 w-14 place-items-center rounded-full bg-primary text-[30px] font-light leading-none text-white shadow-[0_18px_36px_rgba(7,193,96,.28)] transition hover:bg-[#05964d];
|
||||
@apply fixed bottom-24 right-6 grid h-14 w-14 place-items-center rounded-full bg-primary text-[30px] font-light leading-none text-white shadow-[0_18px_36px_rgba(7,193,96,.28)] transition hover:bg-[#05964d];
|
||||
}
|
||||
|
||||
.input-like {
|
||||
|
||||
@ -1,12 +1,59 @@
|
||||
import { useState } from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import { listen } from "@tauri-apps/api/event";
|
||||
import { Power, PowerOff, Settings } from "lucide-react";
|
||||
import { logs } from "../data/mockData";
|
||||
import StatusPill from "../components/StatusPill";
|
||||
import Terminal from "../components/Terminal";
|
||||
import { go } from "../utils/navigation";
|
||||
import { openWindow } from "../utils/navigation";
|
||||
|
||||
export default function ClonePage() {
|
||||
const [engineEnabled, setEngineEnabled] = useState(false);
|
||||
const [agentLogs, setAgentLogs] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!window.__TAURI_INTERNALS__) return undefined;
|
||||
|
||||
let unlisten;
|
||||
let cancelled = false;
|
||||
|
||||
listen("agent-log", (event) => {
|
||||
setAgentLogs((currentLogs) => [...currentLogs.slice(-300), event.payload]);
|
||||
}).then((cleanup) => {
|
||||
if (cancelled) {
|
||||
cleanup();
|
||||
return;
|
||||
}
|
||||
|
||||
unlisten = cleanup;
|
||||
});
|
||||
|
||||
return () => {
|
||||
cancelled = true;
|
||||
if (unlisten) unlisten();
|
||||
};
|
||||
}, []);
|
||||
|
||||
async function toggleAgent() {
|
||||
if (!window.__TAURI_INTERNALS__) {
|
||||
setEngineEnabled((enabled) => !enabled);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (engineEnabled) {
|
||||
await invoke("stop_agent");
|
||||
setEngineEnabled(false);
|
||||
} else {
|
||||
await invoke("start_agent");
|
||||
setEngineEnabled(true);
|
||||
}
|
||||
} catch (error) {
|
||||
setAgentLogs((currentLogs) => [
|
||||
...currentLogs,
|
||||
{ level: "error", message: String(error) },
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<section className="flex min-h-full flex-1 flex-col gap-3">
|
||||
@ -22,15 +69,15 @@ export default function ClonePage() {
|
||||
<div className="flex w-full items-center justify-center gap-4">
|
||||
<button
|
||||
className={`${engineEnabled ? "btn-danger" : "btn-primary"} w-full gap-2`}
|
||||
onClick={() => setEngineEnabled((enabled) => !enabled)}
|
||||
onClick={toggleAgent}
|
||||
>
|
||||
{engineEnabled ? <PowerOff size={16} strokeWidth={2.6} /> : <Power size={16} strokeWidth={2.6} />}
|
||||
{engineEnabled ? "停用引擎" : "启动引擎"}
|
||||
</button>
|
||||
<button className="icon-action shrink-0" onClick={() => go("/window/settings")} aria-label="设置"><Settings size={18} strokeWidth={2.5} /></button>
|
||||
<button className="icon-action shrink-0" onClick={() => openWindow("/window/settings")} aria-label="设置"><Settings size={18} strokeWidth={2.5} /></button>
|
||||
</div>
|
||||
</div>
|
||||
<Terminal title="运行日志" lines={logs} />
|
||||
<Terminal title="运行日志" lines={agentLogs} onClear={() => setAgentLogs([])} />
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { employeeItems } from "../data/mockData";
|
||||
import { go } from "../utils/navigation";
|
||||
import { openWindow } from "../utils/navigation";
|
||||
|
||||
export default function DistillPage() {
|
||||
return (
|
||||
@ -9,7 +9,7 @@ export default function DistillPage() {
|
||||
<p className="mt-3 text-[13px] text-muted">导入聊天记录、话术和案例,沉淀可授权调用的员工能力。</p>
|
||||
<div className="mt-5 w-full flex flex-wrap gap-8">
|
||||
<button className="btn-secondary flex-1">蒸馏教程</button>
|
||||
<button className="btn-primary flex-1" onClick={() => go("/window/distill-start")}>开始蒸馏</button>
|
||||
<button className="btn-primary flex-1" onClick={() => openWindow("/window/distill-start")}>开始蒸馏</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="panel rounded-md p-3">
|
||||
@ -30,7 +30,7 @@ export default function DistillPage() {
|
||||
</div>
|
||||
<div className="space-y-3">
|
||||
{employeeItems.map((item) => (
|
||||
<article key={item.name} className="panel rounded-md p-3" onClick={() => go("/window/employee-detail")}>
|
||||
<article key={item.name} className="panel rounded-md p-3" onClick={() => openWindow("/window/employee-detail")}>
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<h3 className="min-w-0 truncate text-[15px] font-extrabold">{item.name}</h3>
|
||||
<div className="flex shrink-0 items-center gap-1.5 text-[12px] font-semibold text-muted">
|
||||
|
||||
@ -2,7 +2,7 @@ import { knowledgeItems } from "../data/mockData";
|
||||
import DraggableFab from "../components/DraggableFab";
|
||||
import Segmented from "../components/Segmented";
|
||||
import StatusPill from "../components/StatusPill";
|
||||
import { go } from "../utils/navigation";
|
||||
import { openWindow } from "../utils/navigation";
|
||||
|
||||
export default function KnowledgePage({ active, setActive }) {
|
||||
const types = ["全部", "视频", "图片", "文件", "案例", "其他"];
|
||||
@ -13,7 +13,7 @@ export default function KnowledgePage({ active, setActive }) {
|
||||
<Segmented items={types} active={active} onChange={setActive} />
|
||||
<div className="space-y-3">
|
||||
{filtered.map((item) => (
|
||||
<article key={item.title} className="list-card" onClick={() => go("/window/knowledge-detail")}>
|
||||
<article key={item.title} className="list-card" onClick={() => openWindow("/window/knowledge-detail")}>
|
||||
<div className="min-w-0">
|
||||
<div className="flex items-center gap-2">
|
||||
<h3 className="truncate text-[14px] font-extrabold">{item.title}</h3>
|
||||
@ -27,7 +27,7 @@ export default function KnowledgePage({ active, setActive }) {
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
<DraggableFab onClick={() => go("/window/knowledge-create")} ariaLabel="新增知识库" />
|
||||
<DraggableFab onClick={() => openWindow("/window/knowledge-create")} ariaLabel="新增知识库" />
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { skillItems, tagToneClasses } from "../data/mockData";
|
||||
import DraggableFab from "../components/DraggableFab";
|
||||
import Segmented from "../components/Segmented";
|
||||
import { go } from "../utils/navigation";
|
||||
import { openWindow } from "../utils/navigation";
|
||||
|
||||
export default function SkillsPage({ active, setActive }) {
|
||||
const types = ["全部", "内置", "自定义"];
|
||||
@ -31,7 +31,7 @@ export default function SkillsPage({ active, setActive }) {
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
<DraggableFab onClick={() => go("/window/skill-create")} ariaLabel="新增 skill" />
|
||||
<DraggableFab onClick={() => openWindow("/window/skill-create")} ariaLabel="新增 skill" />
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,3 +1,35 @@
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import { getCurrentWindow } from "@tauri-apps/api/window";
|
||||
|
||||
export function go(path) {
|
||||
window.location.hash = path === "/" ? "" : path;
|
||||
}
|
||||
|
||||
export async function openWindow(path) {
|
||||
if (!window.__TAURI_INTERNALS__) {
|
||||
go(path);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await invoke("open_popup_window", { route: path });
|
||||
} catch (error) {
|
||||
console.error("Failed to open popup window", error);
|
||||
go(path);
|
||||
}
|
||||
}
|
||||
|
||||
export async function closeWindow() {
|
||||
if (!window.__TAURI_INTERNALS__) {
|
||||
go("/");
|
||||
return;
|
||||
}
|
||||
|
||||
await getCurrentWindow().close();
|
||||
}
|
||||
|
||||
export async function startWindowDrag() {
|
||||
if (!window.__TAURI_INTERNALS__) return;
|
||||
|
||||
await getCurrentWindow().startDragging();
|
||||
}
|
||||
|
||||
@ -18,8 +18,8 @@ export default function SecondaryWindow({ route, theme, setTheme, settingSection
|
||||
}, [route]);
|
||||
|
||||
return (
|
||||
<div className="app-window mx-auto flex min-h-[calc(100dvh-32px)] w-full max-w-[750px] flex-col overflow-hidden rounded-[10px] border border-line shadow-panel">
|
||||
<TitleBar title={title} subtitle="750 x 950 Web 窗口预览" theme={theme} setTheme={setTheme} compact />
|
||||
<div className="app-window flex h-dvh w-full flex-col overflow-hidden rounded-[12px]">
|
||||
<TitleBar title={title} theme={theme} setTheme={setTheme} compact />
|
||||
<main className="content-canvas flex min-h-0 flex-1 flex-col overflow-y-auto">
|
||||
{route.includes("settings") ? (
|
||||
<SettingsWindow active={settingSection} setActive={setSettingSection} />
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user