feat: add Tauri desktop app support and terminal management
2
.gitignore
vendored
@@ -49,3 +49,5 @@ CLAUDE.local.md
|
|||||||
|
|
||||||
# Task CLI
|
# Task CLI
|
||||||
.tasks/
|
.tasks/
|
||||||
|
|
||||||
|
.debug.conversations/
|
||||||
|
|||||||
1
.tool-versions
Normal file
@@ -0,0 +1 @@
|
|||||||
|
rust 1.85.1
|
||||||
282
package-lock.json
generated
@@ -13,7 +13,8 @@
|
|||||||
"packages/server",
|
"packages/server",
|
||||||
"packages/app",
|
"packages/app",
|
||||||
"packages/relay",
|
"packages/relay",
|
||||||
"packages/website"
|
"packages/website",
|
||||||
|
"packages/desktop"
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@anthropic-ai/claude-agent-sdk": "^0.2.11"
|
"@anthropic-ai/claude-agent-sdk": "^0.2.11"
|
||||||
@@ -6313,6 +6314,10 @@
|
|||||||
"resolved": "packages/app",
|
"resolved": "packages/app",
|
||||||
"link": true
|
"link": true
|
||||||
},
|
},
|
||||||
|
"node_modules/@paseo/desktop": {
|
||||||
|
"resolved": "packages/desktop",
|
||||||
|
"link": true
|
||||||
|
},
|
||||||
"node_modules/@paseo/relay": {
|
"node_modules/@paseo/relay": {
|
||||||
"resolved": "packages/relay",
|
"resolved": "packages/relay",
|
||||||
"link": true
|
"link": true
|
||||||
@@ -8410,6 +8415,233 @@
|
|||||||
"url": "https://github.com/sponsors/tannerlinsley"
|
"url": "https://github.com/sponsors/tannerlinsley"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@tauri-apps/api": {
|
||||||
|
"version": "2.9.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tauri-apps/api/-/api-2.9.1.tgz",
|
||||||
|
"integrity": "sha512-IGlhP6EivjXHepbBic618GOmiWe4URJiIeZFlB7x3czM0yDHHYviH1Xvoiv4FefdkQtn6v7TuwWCRfOGdnVUGw==",
|
||||||
|
"license": "Apache-2.0 OR MIT",
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/tauri"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@tauri-apps/cli": {
|
||||||
|
"version": "2.9.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tauri-apps/cli/-/cli-2.9.6.tgz",
|
||||||
|
"integrity": "sha512-3xDdXL5omQ3sPfBfdC8fCtDKcnyV7OqyzQgfyT5P3+zY6lcPqIYKQBvUasNvppi21RSdfhy44ttvJmftb0PCDw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0 OR MIT",
|
||||||
|
"bin": {
|
||||||
|
"tauri": "tauri.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/tauri"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"@tauri-apps/cli-darwin-arm64": "2.9.6",
|
||||||
|
"@tauri-apps/cli-darwin-x64": "2.9.6",
|
||||||
|
"@tauri-apps/cli-linux-arm-gnueabihf": "2.9.6",
|
||||||
|
"@tauri-apps/cli-linux-arm64-gnu": "2.9.6",
|
||||||
|
"@tauri-apps/cli-linux-arm64-musl": "2.9.6",
|
||||||
|
"@tauri-apps/cli-linux-riscv64-gnu": "2.9.6",
|
||||||
|
"@tauri-apps/cli-linux-x64-gnu": "2.9.6",
|
||||||
|
"@tauri-apps/cli-linux-x64-musl": "2.9.6",
|
||||||
|
"@tauri-apps/cli-win32-arm64-msvc": "2.9.6",
|
||||||
|
"@tauri-apps/cli-win32-ia32-msvc": "2.9.6",
|
||||||
|
"@tauri-apps/cli-win32-x64-msvc": "2.9.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@tauri-apps/cli-darwin-arm64": {
|
||||||
|
"version": "2.9.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-arm64/-/cli-darwin-arm64-2.9.6.tgz",
|
||||||
|
"integrity": "sha512-gf5no6N9FCk1qMrti4lfwP77JHP5haASZgVbBgpZG7BUepB3fhiLCXGUK8LvuOjP36HivXewjg72LTnPDScnQQ==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0 OR MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@tauri-apps/cli-darwin-x64": {
|
||||||
|
"version": "2.9.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-x64/-/cli-darwin-x64-2.9.6.tgz",
|
||||||
|
"integrity": "sha512-oWh74WmqbERwwrwcueJyY6HYhgCksUc6NT7WKeXyrlY/FPmNgdyQAgcLuTSkhRFuQ6zh4Np1HZpOqCTpeZBDcw==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0 OR MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@tauri-apps/cli-linux-arm-gnueabihf": {
|
||||||
|
"version": "2.9.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm-gnueabihf/-/cli-linux-arm-gnueabihf-2.9.6.tgz",
|
||||||
|
"integrity": "sha512-/zde3bFroFsNXOHN204DC2qUxAcAanUjVXXSdEGmhwMUZeAQalNj5cz2Qli2elsRjKN/hVbZOJj0gQ5zaYUjSg==",
|
||||||
|
"cpu": [
|
||||||
|
"arm"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0 OR MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@tauri-apps/cli-linux-arm64-gnu": {
|
||||||
|
"version": "2.9.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-gnu/-/cli-linux-arm64-gnu-2.9.6.tgz",
|
||||||
|
"integrity": "sha512-pvbljdhp9VOo4RnID5ywSxgBs7qiylTPlK56cTk7InR3kYSTJKYMqv/4Q/4rGo/mG8cVppesKIeBMH42fw6wjg==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0 OR MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@tauri-apps/cli-linux-arm64-musl": {
|
||||||
|
"version": "2.9.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.9.6.tgz",
|
||||||
|
"integrity": "sha512-02TKUndpodXBCR0oP//6dZWGYcc22Upf2eP27NvC6z0DIqvkBBFziQUcvi2n6SrwTRL0yGgQjkm9K5NIn8s6jw==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0 OR MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@tauri-apps/cli-linux-riscv64-gnu": {
|
||||||
|
"version": "2.9.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-riscv64-gnu/-/cli-linux-riscv64-gnu-2.9.6.tgz",
|
||||||
|
"integrity": "sha512-fmp1hnulbqzl1GkXl4aTX9fV+ubHw2LqlLH1PE3BxZ11EQk+l/TmiEongjnxF0ie4kV8DQfDNJ1KGiIdWe1GvQ==",
|
||||||
|
"cpu": [
|
||||||
|
"riscv64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0 OR MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@tauri-apps/cli-linux-x64-gnu": {
|
||||||
|
"version": "2.9.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-gnu/-/cli-linux-x64-gnu-2.9.6.tgz",
|
||||||
|
"integrity": "sha512-vY0le8ad2KaV1PJr+jCd8fUF9VOjwwQP/uBuTJvhvKTloEwxYA/kAjKK9OpIslGA9m/zcnSo74czI6bBrm2sYA==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0 OR MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@tauri-apps/cli-linux-x64-musl": {
|
||||||
|
"version": "2.9.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-musl/-/cli-linux-x64-musl-2.9.6.tgz",
|
||||||
|
"integrity": "sha512-TOEuB8YCFZTWVDzsO2yW0+zGcoMiPPwcUgdnW1ODnmgfwccpnihDRoks+ABT1e3fHb1ol8QQWsHSCovb3o2ENQ==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0 OR MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@tauri-apps/cli-win32-arm64-msvc": {
|
||||||
|
"version": "2.9.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-arm64-msvc/-/cli-win32-arm64-msvc-2.9.6.tgz",
|
||||||
|
"integrity": "sha512-ujmDGMRc4qRLAnj8nNG26Rlz9klJ0I0jmZs2BPpmNNf0gM/rcVHhqbEkAaHPTBVIrtUdf7bGvQAD2pyIiUrBHQ==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0 OR MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@tauri-apps/cli-win32-ia32-msvc": {
|
||||||
|
"version": "2.9.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-ia32-msvc/-/cli-win32-ia32-msvc-2.9.6.tgz",
|
||||||
|
"integrity": "sha512-S4pT0yAJgFX8QRCyKA1iKjZ9Q/oPjCZf66A/VlG5Yw54Nnr88J1uBpmenINbXxzyhduWrIXBaUbEY1K80ZbpMg==",
|
||||||
|
"cpu": [
|
||||||
|
"ia32"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0 OR MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@tauri-apps/cli-win32-x64-msvc": {
|
||||||
|
"version": "2.9.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-x64-msvc/-/cli-win32-x64-msvc-2.9.6.tgz",
|
||||||
|
"integrity": "sha512-ldWuWSSkWbKOPjQMJoYVj9wLHcOniv7diyI5UAJ4XsBdtaFB0pKHQsqw/ItUma0VXGC7vB4E9fZjivmxur60aw==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0 OR MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@tsconfig/node10": {
|
"node_modules/@tsconfig/node10": {
|
||||||
"version": "1.0.11",
|
"version": "1.0.11",
|
||||||
"resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz",
|
"resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz",
|
||||||
@@ -9507,6 +9739,15 @@
|
|||||||
"node": ">=10.0.0"
|
"node": ">=10.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@xterm/headless": {
|
||||||
|
"version": "6.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@xterm/headless/-/headless-6.0.0.tgz",
|
||||||
|
"integrity": "sha512-5Yj1QINYCyzrZtf8OFIHi47iQtI+0qYFPHmouEfG8dHNxbZ9Tb9YGSuLcsEwj9Z+OL75GJqPyJbyoFer80a2Hw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"workspaces": [
|
||||||
|
"addons/*"
|
||||||
|
]
|
||||||
|
},
|
||||||
"node_modules/@yarnpkg/lockfile": {
|
"node_modules/@yarnpkg/lockfile": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz",
|
||||||
@@ -17994,9 +18235,7 @@
|
|||||||
"version": "2.23.0",
|
"version": "2.23.0",
|
||||||
"resolved": "https://registry.npmjs.org/nan/-/nan-2.23.0.tgz",
|
"resolved": "https://registry.npmjs.org/nan/-/nan-2.23.0.tgz",
|
||||||
"integrity": "sha512-1UxuyYGdoQHcGg87Lkqm3FzefucTa0NAiOcuRsDmysep3c1LVCRK2krrUDafMWtjSG04htvAmvg96+SDknOmgQ==",
|
"integrity": "sha512-1UxuyYGdoQHcGg87Lkqm3FzefucTa0NAiOcuRsDmysep3c1LVCRK2krrUDafMWtjSG04htvAmvg96+SDknOmgQ==",
|
||||||
"dev": true,
|
"license": "MIT"
|
||||||
"license": "MIT",
|
|
||||||
"optional": true
|
|
||||||
},
|
},
|
||||||
"node_modules/nanoid": {
|
"node_modules/nanoid": {
|
||||||
"version": "3.3.11",
|
"version": "3.3.11",
|
||||||
@@ -19886,6 +20125,20 @@
|
|||||||
"integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==",
|
"integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/react-native-webview": {
|
||||||
|
"version": "13.16.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-native-webview/-/react-native-webview-13.16.0.tgz",
|
||||||
|
"integrity": "sha512-Nh13xKZWW35C0dbOskD7OX01nQQavOzHbCw9XoZmar4eXCo7AvrYJ0jlUfRVVIJzqINxHlpECYLdmAdFsl9xDA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"escape-string-regexp": "^4.0.0",
|
||||||
|
"invariant": "2.2.4"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "*",
|
||||||
|
"react-native": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/react-native-worklets": {
|
"node_modules/react-native-worklets": {
|
||||||
"version": "0.5.1",
|
"version": "0.5.1",
|
||||||
"resolved": "https://registry.npmjs.org/react-native-worklets/-/react-native-worklets-0.5.1.tgz",
|
"resolved": "https://registry.npmjs.org/react-native-worklets/-/react-native-worklets-0.5.1.tgz",
|
||||||
@@ -24597,6 +24850,7 @@
|
|||||||
"@react-navigation/elements": "^2.6.3",
|
"@react-navigation/elements": "^2.6.3",
|
||||||
"@react-navigation/native": "^7.1.8",
|
"@react-navigation/native": "^7.1.8",
|
||||||
"@tanstack/react-query": "^5.90.11",
|
"@tanstack/react-query": "^5.90.11",
|
||||||
|
"@tauri-apps/api": "^2.9.1",
|
||||||
"buffer": "^6.0.3",
|
"buffer": "^6.0.3",
|
||||||
"expo": "^54.0.18",
|
"expo": "^54.0.18",
|
||||||
"expo-audio": "~1.0.13",
|
"expo-audio": "~1.0.13",
|
||||||
@@ -24639,6 +24893,7 @@
|
|||||||
"react-native-svg": "^15.14.0",
|
"react-native-svg": "^15.14.0",
|
||||||
"react-native-unistyles": "^3.0.15",
|
"react-native-unistyles": "^3.0.15",
|
||||||
"react-native-web": "~0.21.0",
|
"react-native-web": "~0.21.0",
|
||||||
|
"react-native-webview": "^13.16.0",
|
||||||
"react-native-worklets": "0.5.1",
|
"react-native-worklets": "0.5.1",
|
||||||
"zod": "^3.23.8",
|
"zod": "^3.23.8",
|
||||||
"zustand": "^5.0.9"
|
"zustand": "^5.0.9"
|
||||||
@@ -24675,6 +24930,13 @@
|
|||||||
"url": "https://github.com/sponsors/colinhacks"
|
"url": "https://github.com/sponsors/colinhacks"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"packages/desktop": {
|
||||||
|
"name": "@paseo/desktop",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"devDependencies": {
|
||||||
|
"@tauri-apps/cli": "^2.9.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"packages/relay": {
|
"packages/relay": {
|
||||||
"name": "@paseo/relay",
|
"name": "@paseo/relay",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
@@ -24706,11 +24968,13 @@
|
|||||||
"@modelcontextprotocol/sdk": "^1.20.1",
|
"@modelcontextprotocol/sdk": "^1.20.1",
|
||||||
"@opencode-ai/sdk": "^1.1.12",
|
"@opencode-ai/sdk": "^1.1.12",
|
||||||
"@openrouter/ai-sdk-provider": "^1.2.0",
|
"@openrouter/ai-sdk-provider": "^1.2.0",
|
||||||
|
"@xterm/headless": "^6.0.0",
|
||||||
"ai": "^5.0.76",
|
"ai": "^5.0.76",
|
||||||
"dotenv": "^17.2.3",
|
"dotenv": "^17.2.3",
|
||||||
"express": "^4.18.2",
|
"express": "^4.18.2",
|
||||||
"express-basic-auth": "^1.2.1",
|
"express-basic-auth": "^1.2.1",
|
||||||
"mnemonic-id": "^3.2.7",
|
"mnemonic-id": "^3.2.7",
|
||||||
|
"node-pty": "^1.0.0",
|
||||||
"openai": "^4.20.0",
|
"openai": "^4.20.0",
|
||||||
"pino": "^10.2.0",
|
"pino": "^10.2.0",
|
||||||
"pino-pretty": "^13.1.3",
|
"pino-pretty": "^13.1.3",
|
||||||
@@ -24931,6 +25195,16 @@
|
|||||||
"node": ">= 0.6"
|
"node": ">= 0.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"packages/server/node_modules/node-pty": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/node-pty/-/node-pty-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-wtBMWWS7dFZm/VgqElrTvtfMq4GzJ6+edFI0Y0zyzygUSZMgZdraDUMUhCIvkjhJjme15qWmbyJbtAx4ot4uZA==",
|
||||||
|
"hasInstallScript": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"nan": "^2.17.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"packages/server/node_modules/qs": {
|
"packages/server/node_modules/qs": {
|
||||||
"version": "6.14.0",
|
"version": "6.14.0",
|
||||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz",
|
"resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz",
|
||||||
|
|||||||
@@ -6,7 +6,8 @@
|
|||||||
"packages/server",
|
"packages/server",
|
||||||
"packages/app",
|
"packages/app",
|
||||||
"packages/relay",
|
"packages/relay",
|
||||||
"packages/website"
|
"packages/website",
|
||||||
|
"packages/desktop"
|
||||||
],
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "NODE_ENV=development tsx packages/server/scripts/dev-runner.ts",
|
"dev": "NODE_ENV=development tsx packages/server/scripts/dev-runner.ts",
|
||||||
@@ -21,6 +22,8 @@
|
|||||||
"android:dev-release": "ANDROID_VARIANT=devRelease npm run android --workspace=@paseo/app",
|
"android:dev-release": "ANDROID_VARIANT=devRelease npm run android --workspace=@paseo/app",
|
||||||
"ios": "npm run ios --workspace=@paseo/app",
|
"ios": "npm run ios --workspace=@paseo/app",
|
||||||
"web": "npm run web --workspace=@paseo/app",
|
"web": "npm run web --workspace=@paseo/app",
|
||||||
|
"desktop": "npm run dev --workspace=@paseo/desktop",
|
||||||
|
"desktop:build": "npm run build --workspace=@paseo/desktop",
|
||||||
"postinstall": "patch-package"
|
"postinstall": "patch-package"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<svg width="48" height="48" viewBox="0 0 700 700" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg width="48" height="48" viewBox="0 0 700 700" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<rect width="700" height="700" rx="100" fill="black"/>
|
<rect width="700" height="700" rx="156" fill="black"/>
|
||||||
<path d="M291.495 91.399C333.897 104.892 379.155 135.075 416.229 173.191C453.389 211.394 484.429 259.725 495.708 311.251C497.555 319.693 498.865 328.216 499.586 336.776C509.755 326.554 519.867 317.815 529.89 311.547C540.647 304.821 553.808 299.297 568.641 299.785C584.29 300.299 597.395 307.326 607.747 317.632C632.173 341.947 629.612 372.898 619.872 397.936C610.185 422.833 591.557 447.826 572.732 469.124C553.591 490.78 532.713 510.308 516.779 524.318C508.775 531.355 501.936 537.073 497.07 541.052C494.635 543.043 492.689 544.603 491.334 545.679C490.657 546.217 490.126 546.635 489.756 546.926C489.571 547.071 489.425 547.184 489.321 547.265C489.269 547.305 489.227 547.338 489.196 547.362C489.181 547.374 489.168 547.385 489.157 547.393C489.153 547.397 489.147 547.401 489.144 547.403C489.134 547.4 488.837 547.06 473.001 528.499L489.135 547.411C478.157 555.911 462.033 554.334 453.122 543.89C444.213 533.448 445.887 518.094 456.861 509.592C456.863 509.591 456.865 509.588 456.869 509.586C456.88 509.577 456.902 509.561 456.933 509.536C456.997 509.487 457.101 509.404 457.245 509.292C457.533 509.066 457.979 508.715 458.569 508.247C459.749 507.31 461.506 505.901 463.742 504.073C468.216 500.414 474.589 495.088 482.073 488.508C497.114 475.284 516.315 457.282 533.578 437.75C551.157 417.862 565.26 398.01 571.859 381.048C578.403 364.227 575.681 356.302 570.724 351.367C568.928 349.579 567.744 348.902 567.267 348.676C566.888 348.496 566.811 348.52 566.804 348.52C566.605 348.513 563.971 348.537 557.953 352.3C545.161 360.299 528.815 377.492 506.807 403.867C494.927 418.106 481.871 434.435 467.547 451.957C463.709 457.28 459.503 462.538 454.91 467.717L454.702 467.549C420.808 508.347 380.37 553.856 332.335 593.848C301.853 619.226 262.656 622.597 228.642 614.743C194.834 606.936 162.658 587.448 142.217 561.686C108.054 518.631 100.57 469.801 108.223 427.836C115.56 387.606 137.391 351.005 166.502 331.557C161.248 315.813 156.813 299.49 153.519 283.013C142.593 228.368 143.239 167.031 174.28 119.619C186.922 100.31 205.846 89.1535 227.387 85.2773C248.1 81.5504 270.278 84.648 291.495 91.399ZM378.642 206.356C345.773 172.563 307.463 147.917 275.208 137.654C259.096 132.527 246.171 131.514 236.828 133.195C228.314 134.727 222.227 138.497 217.721 145.38C196.712 177.468 193.858 224.004 203.82 273.827C206.532 287.394 210.127 300.834 214.345 313.817C236.45 310.276 260.156 311.463 281.22 317.11C319.621 327.403 357.501 355.419 357.501 405.654C357.501 435.255 339.111 465.136 307.278 473.815C273.211 483.103 238.854 464.822 213.105 427.541C203.716 413.947 194.443 397.766 185.947 379.89C174.028 392.223 163.08 411.953 158.673 436.118C153.128 466.518 158.514 501.286 183.085 532.253C195.993 548.522 217.742 562.031 240.771 567.349C263.594 572.619 284.147 569.24 298.664 557.154C349.383 514.927 390.709 466.547 426.366 422.952C448.879 390.86 453.195 356.06 445.578 321.265C436.703 280.718 411.425 240.06 378.642 206.356ZM306.296 405.722C306.296 384.769 292.223 370.736 267.284 364.051C256.012 361.03 244.156 360.087 233.095 360.771C240.361 375.935 248.168 389.513 255.897 400.704C275.647 429.298 289.989 427.822 293.247 426.934C298.737 425.437 306.296 418.161 306.296 405.722Z" fill="white"/>
|
<path transform="translate(350,350) scale(1.05) translate(-350,-350)" d="M291.495 91.399C333.897 104.892 379.155 135.075 416.229 173.191C453.389 211.394 484.429 259.725 495.708 311.251C497.555 319.693 498.865 328.216 499.586 336.776C509.755 326.554 519.867 317.815 529.89 311.547C540.647 304.821 553.808 299.297 568.641 299.785C584.29 300.299 597.395 307.326 607.747 317.632C632.173 341.947 629.612 372.898 619.872 397.936C610.185 422.833 591.557 447.826 572.732 469.124C553.591 490.78 532.713 510.308 516.779 524.318C508.775 531.355 501.936 537.073 497.07 541.052C494.635 543.043 492.689 544.603 491.334 545.679C490.657 546.217 490.126 546.635 489.756 546.926C489.571 547.071 489.425 547.184 489.321 547.265C489.269 547.305 489.227 547.338 489.196 547.362C489.181 547.374 489.168 547.385 489.157 547.393C489.153 547.397 489.147 547.401 489.144 547.403C489.134 547.4 488.837 547.06 473.001 528.499L489.135 547.411C478.157 555.911 462.033 554.334 453.122 543.89C444.213 533.448 445.887 518.094 456.861 509.592C456.863 509.591 456.865 509.588 456.869 509.586C456.88 509.577 456.902 509.561 456.933 509.536C456.997 509.487 457.101 509.404 457.245 509.292C457.533 509.066 457.979 508.715 458.569 508.247C459.749 507.31 461.506 505.901 463.742 504.073C468.216 500.414 474.589 495.088 482.073 488.508C497.114 475.284 516.315 457.282 533.578 437.75C551.157 417.862 565.26 398.01 571.859 381.048C578.403 364.227 575.681 356.302 570.724 351.367C568.928 349.579 567.744 348.902 567.267 348.676C566.888 348.496 566.811 348.52 566.804 348.52C566.605 348.513 563.971 348.537 557.953 352.3C545.161 360.299 528.815 377.492 506.807 403.867C494.927 418.106 481.871 434.435 467.547 451.957C463.709 457.28 459.503 462.538 454.91 467.717L454.702 467.549C420.808 508.347 380.37 553.856 332.335 593.848C301.853 619.226 262.656 622.597 228.642 614.743C194.834 606.936 162.658 587.448 142.217 561.686C108.054 518.631 100.57 469.801 108.223 427.836C115.56 387.606 137.391 351.005 166.502 331.557C161.248 315.813 156.813 299.49 153.519 283.013C142.593 228.368 143.239 167.031 174.28 119.619C186.922 100.31 205.846 89.1535 227.387 85.2773C248.1 81.5504 270.278 84.648 291.495 91.399ZM378.642 206.356C345.773 172.563 307.463 147.917 275.208 137.654C259.096 132.527 246.171 131.514 236.828 133.195C228.314 134.727 222.227 138.497 217.721 145.38C196.712 177.468 193.858 224.004 203.82 273.827C206.532 287.394 210.127 300.834 214.345 313.817C236.45 310.276 260.156 311.463 281.22 317.11C319.621 327.403 357.501 355.419 357.501 405.654C357.501 435.255 339.111 465.136 307.278 473.815C273.211 483.103 238.854 464.822 213.105 427.541C203.716 413.947 194.443 397.766 185.947 379.89C174.028 392.223 163.08 411.953 158.673 436.118C153.128 466.518 158.514 501.286 183.085 532.253C195.993 548.522 217.742 562.031 240.771 567.349C263.594 572.619 284.147 569.24 298.664 557.154C349.383 514.927 390.709 466.547 426.366 422.952C448.879 390.86 453.195 356.06 445.578 321.265C436.703 280.718 411.425 240.06 378.642 206.356ZM306.296 405.722C306.296 384.769 292.223 370.736 267.284 364.051C256.012 361.03 244.156 360.087 233.095 360.771C240.361 375.935 248.168 389.513 255.897 400.704C275.647 429.298 289.989 427.822 293.247 426.934C298.737 425.437 306.296 418.161 306.296 405.722Z" fill="white"/>
|
||||||
<circle cx="570" cy="570" r="130" fill="#22c55e"/>
|
<circle cx="570" cy="570" r="130" fill="#22c55e"/>
|
||||||
</svg>
|
</svg>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 3.4 KiB |
@@ -1,5 +1,5 @@
|
|||||||
<svg width="48" height="48" viewBox="0 0 700 700" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg width="48" height="48" viewBox="0 0 700 700" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<rect width="700" height="700" rx="100" fill="black"/>
|
<rect width="700" height="700" rx="156" fill="black"/>
|
||||||
<path d="M291.495 91.399C333.897 104.892 379.155 135.075 416.229 173.191C453.389 211.394 484.429 259.725 495.708 311.251C497.555 319.693 498.865 328.216 499.586 336.776C509.755 326.554 519.867 317.815 529.89 311.547C540.647 304.821 553.808 299.297 568.641 299.785C584.29 300.299 597.395 307.326 607.747 317.632C632.173 341.947 629.612 372.898 619.872 397.936C610.185 422.833 591.557 447.826 572.732 469.124C553.591 490.78 532.713 510.308 516.779 524.318C508.775 531.355 501.936 537.073 497.07 541.052C494.635 543.043 492.689 544.603 491.334 545.679C490.657 546.217 490.126 546.635 489.756 546.926C489.571 547.071 489.425 547.184 489.321 547.265C489.269 547.305 489.227 547.338 489.196 547.362C489.181 547.374 489.168 547.385 489.157 547.393C489.153 547.397 489.147 547.401 489.144 547.403C489.134 547.4 488.837 547.06 473.001 528.499L489.135 547.411C478.157 555.911 462.033 554.334 453.122 543.89C444.213 533.448 445.887 518.094 456.861 509.592C456.863 509.591 456.865 509.588 456.869 509.586C456.88 509.577 456.902 509.561 456.933 509.536C456.997 509.487 457.101 509.404 457.245 509.292C457.533 509.066 457.979 508.715 458.569 508.247C459.749 507.31 461.506 505.901 463.742 504.073C468.216 500.414 474.589 495.088 482.073 488.508C497.114 475.284 516.315 457.282 533.578 437.75C551.157 417.862 565.26 398.01 571.859 381.048C578.403 364.227 575.681 356.302 570.724 351.367C568.928 349.579 567.744 348.902 567.267 348.676C566.888 348.496 566.811 348.52 566.804 348.52C566.605 348.513 563.971 348.537 557.953 352.3C545.161 360.299 528.815 377.492 506.807 403.867C494.927 418.106 481.871 434.435 467.547 451.957C463.709 457.28 459.503 462.538 454.91 467.717L454.702 467.549C420.808 508.347 380.37 553.856 332.335 593.848C301.853 619.226 262.656 622.597 228.642 614.743C194.834 606.936 162.658 587.448 142.217 561.686C108.054 518.631 100.57 469.801 108.223 427.836C115.56 387.606 137.391 351.005 166.502 331.557C161.248 315.813 156.813 299.49 153.519 283.013C142.593 228.368 143.239 167.031 174.28 119.619C186.922 100.31 205.846 89.1535 227.387 85.2773C248.1 81.5504 270.278 84.648 291.495 91.399ZM378.642 206.356C345.773 172.563 307.463 147.917 275.208 137.654C259.096 132.527 246.171 131.514 236.828 133.195C228.314 134.727 222.227 138.497 217.721 145.38C196.712 177.468 193.858 224.004 203.82 273.827C206.532 287.394 210.127 300.834 214.345 313.817C236.45 310.276 260.156 311.463 281.22 317.11C319.621 327.403 357.501 355.419 357.501 405.654C357.501 435.255 339.111 465.136 307.278 473.815C273.211 483.103 238.854 464.822 213.105 427.541C203.716 413.947 194.443 397.766 185.947 379.89C174.028 392.223 163.08 411.953 158.673 436.118C153.128 466.518 158.514 501.286 183.085 532.253C195.993 548.522 217.742 562.031 240.771 567.349C263.594 572.619 284.147 569.24 298.664 557.154C349.383 514.927 390.709 466.547 426.366 422.952C448.879 390.86 453.195 356.06 445.578 321.265C436.703 280.718 411.425 240.06 378.642 206.356ZM306.296 405.722C306.296 384.769 292.223 370.736 267.284 364.051C256.012 361.03 244.156 360.087 233.095 360.771C240.361 375.935 248.168 389.513 255.897 400.704C275.647 429.298 289.989 427.822 293.247 426.934C298.737 425.437 306.296 418.161 306.296 405.722Z" fill="white"/>
|
<path transform="translate(350,350) scale(1.05) translate(-350,-350)" d="M291.495 91.399C333.897 104.892 379.155 135.075 416.229 173.191C453.389 211.394 484.429 259.725 495.708 311.251C497.555 319.693 498.865 328.216 499.586 336.776C509.755 326.554 519.867 317.815 529.89 311.547C540.647 304.821 553.808 299.297 568.641 299.785C584.29 300.299 597.395 307.326 607.747 317.632C632.173 341.947 629.612 372.898 619.872 397.936C610.185 422.833 591.557 447.826 572.732 469.124C553.591 490.78 532.713 510.308 516.779 524.318C508.775 531.355 501.936 537.073 497.07 541.052C494.635 543.043 492.689 544.603 491.334 545.679C490.657 546.217 490.126 546.635 489.756 546.926C489.571 547.071 489.425 547.184 489.321 547.265C489.269 547.305 489.227 547.338 489.196 547.362C489.181 547.374 489.168 547.385 489.157 547.393C489.153 547.397 489.147 547.401 489.144 547.403C489.134 547.4 488.837 547.06 473.001 528.499L489.135 547.411C478.157 555.911 462.033 554.334 453.122 543.89C444.213 533.448 445.887 518.094 456.861 509.592C456.863 509.591 456.865 509.588 456.869 509.586C456.88 509.577 456.902 509.561 456.933 509.536C456.997 509.487 457.101 509.404 457.245 509.292C457.533 509.066 457.979 508.715 458.569 508.247C459.749 507.31 461.506 505.901 463.742 504.073C468.216 500.414 474.589 495.088 482.073 488.508C497.114 475.284 516.315 457.282 533.578 437.75C551.157 417.862 565.26 398.01 571.859 381.048C578.403 364.227 575.681 356.302 570.724 351.367C568.928 349.579 567.744 348.902 567.267 348.676C566.888 348.496 566.811 348.52 566.804 348.52C566.605 348.513 563.971 348.537 557.953 352.3C545.161 360.299 528.815 377.492 506.807 403.867C494.927 418.106 481.871 434.435 467.547 451.957C463.709 457.28 459.503 462.538 454.91 467.717L454.702 467.549C420.808 508.347 380.37 553.856 332.335 593.848C301.853 619.226 262.656 622.597 228.642 614.743C194.834 606.936 162.658 587.448 142.217 561.686C108.054 518.631 100.57 469.801 108.223 427.836C115.56 387.606 137.391 351.005 166.502 331.557C161.248 315.813 156.813 299.49 153.519 283.013C142.593 228.368 143.239 167.031 174.28 119.619C186.922 100.31 205.846 89.1535 227.387 85.2773C248.1 81.5504 270.278 84.648 291.495 91.399ZM378.642 206.356C345.773 172.563 307.463 147.917 275.208 137.654C259.096 132.527 246.171 131.514 236.828 133.195C228.314 134.727 222.227 138.497 217.721 145.38C196.712 177.468 193.858 224.004 203.82 273.827C206.532 287.394 210.127 300.834 214.345 313.817C236.45 310.276 260.156 311.463 281.22 317.11C319.621 327.403 357.501 355.419 357.501 405.654C357.501 435.255 339.111 465.136 307.278 473.815C273.211 483.103 238.854 464.822 213.105 427.541C203.716 413.947 194.443 397.766 185.947 379.89C174.028 392.223 163.08 411.953 158.673 436.118C153.128 466.518 158.514 501.286 183.085 532.253C195.993 548.522 217.742 562.031 240.771 567.349C263.594 572.619 284.147 569.24 298.664 557.154C349.383 514.927 390.709 466.547 426.366 422.952C448.879 390.86 453.195 356.06 445.578 321.265C436.703 280.718 411.425 240.06 378.642 206.356ZM306.296 405.722C306.296 384.769 292.223 370.736 267.284 364.051C256.012 361.03 244.156 360.087 233.095 360.771C240.361 375.935 248.168 389.513 255.897 400.704C275.647 429.298 289.989 427.822 293.247 426.934C298.737 425.437 306.296 418.161 306.296 405.722Z" fill="white"/>
|
||||||
<circle cx="570" cy="570" r="130" fill="#3b82f6"/>
|
<circle cx="570" cy="570" r="130" fill="#3b82f6"/>
|
||||||
</svg>
|
</svg>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 3.4 KiB |
@@ -1,4 +1,4 @@
|
|||||||
<svg width="48" height="48" viewBox="0 0 700 700" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg width="48" height="48" viewBox="0 0 700 700" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<rect width="700" height="700" rx="100" fill="black"/>
|
<rect width="700" height="700" rx="156" fill="black"/>
|
||||||
<path d="M291.495 91.399C333.897 104.892 379.155 135.075 416.229 173.191C453.389 211.394 484.429 259.725 495.708 311.251C497.555 319.693 498.865 328.216 499.586 336.776C509.755 326.554 519.867 317.815 529.89 311.547C540.647 304.821 553.808 299.297 568.641 299.785C584.29 300.299 597.395 307.326 607.747 317.632C632.173 341.947 629.612 372.898 619.872 397.936C610.185 422.833 591.557 447.826 572.732 469.124C553.591 490.78 532.713 510.308 516.779 524.318C508.775 531.355 501.936 537.073 497.07 541.052C494.635 543.043 492.689 544.603 491.334 545.679C490.657 546.217 490.126 546.635 489.756 546.926C489.571 547.071 489.425 547.184 489.321 547.265C489.269 547.305 489.227 547.338 489.196 547.362C489.181 547.374 489.168 547.385 489.157 547.393C489.153 547.397 489.147 547.401 489.144 547.403C489.134 547.4 488.837 547.06 473.001 528.499L489.135 547.411C478.157 555.911 462.033 554.334 453.122 543.89C444.213 533.448 445.887 518.094 456.861 509.592C456.863 509.591 456.865 509.588 456.869 509.586C456.88 509.577 456.902 509.561 456.933 509.536C456.997 509.487 457.101 509.404 457.245 509.292C457.533 509.066 457.979 508.715 458.569 508.247C459.749 507.31 461.506 505.901 463.742 504.073C468.216 500.414 474.589 495.088 482.073 488.508C497.114 475.284 516.315 457.282 533.578 437.75C551.157 417.862 565.26 398.01 571.859 381.048C578.403 364.227 575.681 356.302 570.724 351.367C568.928 349.579 567.744 348.902 567.267 348.676C566.888 348.496 566.811 348.52 566.804 348.52C566.605 348.513 563.971 348.537 557.953 352.3C545.161 360.299 528.815 377.492 506.807 403.867C494.927 418.106 481.871 434.435 467.547 451.957C463.709 457.28 459.503 462.538 454.91 467.717L454.702 467.549C420.808 508.347 380.37 553.856 332.335 593.848C301.853 619.226 262.656 622.597 228.642 614.743C194.834 606.936 162.658 587.448 142.217 561.686C108.054 518.631 100.57 469.801 108.223 427.836C115.56 387.606 137.391 351.005 166.502 331.557C161.248 315.813 156.813 299.49 153.519 283.013C142.593 228.368 143.239 167.031 174.28 119.619C186.922 100.31 205.846 89.1535 227.387 85.2773C248.1 81.5504 270.278 84.648 291.495 91.399ZM378.642 206.356C345.773 172.563 307.463 147.917 275.208 137.654C259.096 132.527 246.171 131.514 236.828 133.195C228.314 134.727 222.227 138.497 217.721 145.38C196.712 177.468 193.858 224.004 203.82 273.827C206.532 287.394 210.127 300.834 214.345 313.817C236.45 310.276 260.156 311.463 281.22 317.11C319.621 327.403 357.501 355.419 357.501 405.654C357.501 435.255 339.111 465.136 307.278 473.815C273.211 483.103 238.854 464.822 213.105 427.541C203.716 413.947 194.443 397.766 185.947 379.89C174.028 392.223 163.08 411.953 158.673 436.118C153.128 466.518 158.514 501.286 183.085 532.253C195.993 548.522 217.742 562.031 240.771 567.349C263.594 572.619 284.147 569.24 298.664 557.154C349.383 514.927 390.709 466.547 426.366 422.952C448.879 390.86 453.195 356.06 445.578 321.265C436.703 280.718 411.425 240.06 378.642 206.356ZM306.296 405.722C306.296 384.769 292.223 370.736 267.284 364.051C256.012 361.03 244.156 360.087 233.095 360.771C240.361 375.935 248.168 389.513 255.897 400.704C275.647 429.298 289.989 427.822 293.247 426.934C298.737 425.437 306.296 418.161 306.296 405.722Z" fill="white"/>
|
<path transform="translate(350,350) scale(1.05) translate(-350,-350)" d="M291.495 91.399C333.897 104.892 379.155 135.075 416.229 173.191C453.389 211.394 484.429 259.725 495.708 311.251C497.555 319.693 498.865 328.216 499.586 336.776C509.755 326.554 519.867 317.815 529.89 311.547C540.647 304.821 553.808 299.297 568.641 299.785C584.29 300.299 597.395 307.326 607.747 317.632C632.173 341.947 629.612 372.898 619.872 397.936C610.185 422.833 591.557 447.826 572.732 469.124C553.591 490.78 532.713 510.308 516.779 524.318C508.775 531.355 501.936 537.073 497.07 541.052C494.635 543.043 492.689 544.603 491.334 545.679C490.657 546.217 490.126 546.635 489.756 546.926C489.571 547.071 489.425 547.184 489.321 547.265C489.269 547.305 489.227 547.338 489.196 547.362C489.181 547.374 489.168 547.385 489.157 547.393C489.153 547.397 489.147 547.401 489.144 547.403C489.134 547.4 488.837 547.06 473.001 528.499L489.135 547.411C478.157 555.911 462.033 554.334 453.122 543.89C444.213 533.448 445.887 518.094 456.861 509.592C456.863 509.591 456.865 509.588 456.869 509.586C456.88 509.577 456.902 509.561 456.933 509.536C456.997 509.487 457.101 509.404 457.245 509.292C457.533 509.066 457.979 508.715 458.569 508.247C459.749 507.31 461.506 505.901 463.742 504.073C468.216 500.414 474.589 495.088 482.073 488.508C497.114 475.284 516.315 457.282 533.578 437.75C551.157 417.862 565.26 398.01 571.859 381.048C578.403 364.227 575.681 356.302 570.724 351.367C568.928 349.579 567.744 348.902 567.267 348.676C566.888 348.496 566.811 348.52 566.804 348.52C566.605 348.513 563.971 348.537 557.953 352.3C545.161 360.299 528.815 377.492 506.807 403.867C494.927 418.106 481.871 434.435 467.547 451.957C463.709 457.28 459.503 462.538 454.91 467.717L454.702 467.549C420.808 508.347 380.37 553.856 332.335 593.848C301.853 619.226 262.656 622.597 228.642 614.743C194.834 606.936 162.658 587.448 142.217 561.686C108.054 518.631 100.57 469.801 108.223 427.836C115.56 387.606 137.391 351.005 166.502 331.557C161.248 315.813 156.813 299.49 153.519 283.013C142.593 228.368 143.239 167.031 174.28 119.619C186.922 100.31 205.846 89.1535 227.387 85.2773C248.1 81.5504 270.278 84.648 291.495 91.399ZM378.642 206.356C345.773 172.563 307.463 147.917 275.208 137.654C259.096 132.527 246.171 131.514 236.828 133.195C228.314 134.727 222.227 138.497 217.721 145.38C196.712 177.468 193.858 224.004 203.82 273.827C206.532 287.394 210.127 300.834 214.345 313.817C236.45 310.276 260.156 311.463 281.22 317.11C319.621 327.403 357.501 355.419 357.501 405.654C357.501 435.255 339.111 465.136 307.278 473.815C273.211 483.103 238.854 464.822 213.105 427.541C203.716 413.947 194.443 397.766 185.947 379.89C174.028 392.223 163.08 411.953 158.673 436.118C153.128 466.518 158.514 501.286 183.085 532.253C195.993 548.522 217.742 562.031 240.771 567.349C263.594 572.619 284.147 569.24 298.664 557.154C349.383 514.927 390.709 466.547 426.366 422.952C448.879 390.86 453.195 356.06 445.578 321.265C436.703 280.718 411.425 240.06 378.642 206.356ZM306.296 405.722C306.296 384.769 292.223 370.736 267.284 364.051C256.012 361.03 244.156 360.087 233.095 360.771C240.361 375.935 248.168 389.513 255.897 400.704C275.647 429.298 289.989 427.822 293.247 426.934C298.737 425.437 306.296 418.161 306.296 405.722Z" fill="white"/>
|
||||||
</svg>
|
</svg>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 3.3 KiB |
@@ -1,5 +1,5 @@
|
|||||||
<svg width="48" height="48" viewBox="0 0 700 700" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg width="48" height="48" viewBox="0 0 700 700" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<rect width="700" height="700" rx="100" fill="black"/>
|
<rect width="700" height="700" rx="156" fill="black"/>
|
||||||
<path d="M291.495 91.399C333.897 104.892 379.155 135.075 416.229 173.191C453.389 211.394 484.429 259.725 495.708 311.251C497.555 319.693 498.865 328.216 499.586 336.776C509.755 326.554 519.867 317.815 529.89 311.547C540.647 304.821 553.808 299.297 568.641 299.785C584.29 300.299 597.395 307.326 607.747 317.632C632.173 341.947 629.612 372.898 619.872 397.936C610.185 422.833 591.557 447.826 572.732 469.124C553.591 490.78 532.713 510.308 516.779 524.318C508.775 531.355 501.936 537.073 497.07 541.052C494.635 543.043 492.689 544.603 491.334 545.679C490.657 546.217 490.126 546.635 489.756 546.926C489.571 547.071 489.425 547.184 489.321 547.265C489.269 547.305 489.227 547.338 489.196 547.362C489.181 547.374 489.168 547.385 489.157 547.393C489.153 547.397 489.147 547.401 489.144 547.403C489.134 547.4 488.837 547.06 473.001 528.499L489.135 547.411C478.157 555.911 462.033 554.334 453.122 543.89C444.213 533.448 445.887 518.094 456.861 509.592C456.863 509.591 456.865 509.588 456.869 509.586C456.88 509.577 456.902 509.561 456.933 509.536C456.997 509.487 457.101 509.404 457.245 509.292C457.533 509.066 457.979 508.715 458.569 508.247C459.749 507.31 461.506 505.901 463.742 504.073C468.216 500.414 474.589 495.088 482.073 488.508C497.114 475.284 516.315 457.282 533.578 437.75C551.157 417.862 565.26 398.01 571.859 381.048C578.403 364.227 575.681 356.302 570.724 351.367C568.928 349.579 567.744 348.902 567.267 348.676C566.888 348.496 566.811 348.52 566.804 348.52C566.605 348.513 563.971 348.537 557.953 352.3C545.161 360.299 528.815 377.492 506.807 403.867C494.927 418.106 481.871 434.435 467.547 451.957C463.709 457.28 459.503 462.538 454.91 467.717L454.702 467.549C420.808 508.347 380.37 553.856 332.335 593.848C301.853 619.226 262.656 622.597 228.642 614.743C194.834 606.936 162.658 587.448 142.217 561.686C108.054 518.631 100.57 469.801 108.223 427.836C115.56 387.606 137.391 351.005 166.502 331.557C161.248 315.813 156.813 299.49 153.519 283.013C142.593 228.368 143.239 167.031 174.28 119.619C186.922 100.31 205.846 89.1535 227.387 85.2773C248.1 81.5504 270.278 84.648 291.495 91.399ZM378.642 206.356C345.773 172.563 307.463 147.917 275.208 137.654C259.096 132.527 246.171 131.514 236.828 133.195C228.314 134.727 222.227 138.497 217.721 145.38C196.712 177.468 193.858 224.004 203.82 273.827C206.532 287.394 210.127 300.834 214.345 313.817C236.45 310.276 260.156 311.463 281.22 317.11C319.621 327.403 357.501 355.419 357.501 405.654C357.501 435.255 339.111 465.136 307.278 473.815C273.211 483.103 238.854 464.822 213.105 427.541C203.716 413.947 194.443 397.766 185.947 379.89C174.028 392.223 163.08 411.953 158.673 436.118C153.128 466.518 158.514 501.286 183.085 532.253C195.993 548.522 217.742 562.031 240.771 567.349C263.594 572.619 284.147 569.24 298.664 557.154C349.383 514.927 390.709 466.547 426.366 422.952C448.879 390.86 453.195 356.06 445.578 321.265C436.703 280.718 411.425 240.06 378.642 206.356ZM306.296 405.722C306.296 384.769 292.223 370.736 267.284 364.051C256.012 361.03 244.156 360.087 233.095 360.771C240.361 375.935 248.168 389.513 255.897 400.704C275.647 429.298 289.989 427.822 293.247 426.934C298.737 425.437 306.296 418.161 306.296 405.722Z" fill="white"/>
|
<path transform="translate(350,350) scale(1.05) translate(-350,-350)" d="M291.495 91.399C333.897 104.892 379.155 135.075 416.229 173.191C453.389 211.394 484.429 259.725 495.708 311.251C497.555 319.693 498.865 328.216 499.586 336.776C509.755 326.554 519.867 317.815 529.89 311.547C540.647 304.821 553.808 299.297 568.641 299.785C584.29 300.299 597.395 307.326 607.747 317.632C632.173 341.947 629.612 372.898 619.872 397.936C610.185 422.833 591.557 447.826 572.732 469.124C553.591 490.78 532.713 510.308 516.779 524.318C508.775 531.355 501.936 537.073 497.07 541.052C494.635 543.043 492.689 544.603 491.334 545.679C490.657 546.217 490.126 546.635 489.756 546.926C489.571 547.071 489.425 547.184 489.321 547.265C489.269 547.305 489.227 547.338 489.196 547.362C489.181 547.374 489.168 547.385 489.157 547.393C489.153 547.397 489.147 547.401 489.144 547.403C489.134 547.4 488.837 547.06 473.001 528.499L489.135 547.411C478.157 555.911 462.033 554.334 453.122 543.89C444.213 533.448 445.887 518.094 456.861 509.592C456.863 509.591 456.865 509.588 456.869 509.586C456.88 509.577 456.902 509.561 456.933 509.536C456.997 509.487 457.101 509.404 457.245 509.292C457.533 509.066 457.979 508.715 458.569 508.247C459.749 507.31 461.506 505.901 463.742 504.073C468.216 500.414 474.589 495.088 482.073 488.508C497.114 475.284 516.315 457.282 533.578 437.75C551.157 417.862 565.26 398.01 571.859 381.048C578.403 364.227 575.681 356.302 570.724 351.367C568.928 349.579 567.744 348.902 567.267 348.676C566.888 348.496 566.811 348.52 566.804 348.52C566.605 348.513 563.971 348.537 557.953 352.3C545.161 360.299 528.815 377.492 506.807 403.867C494.927 418.106 481.871 434.435 467.547 451.957C463.709 457.28 459.503 462.538 454.91 467.717L454.702 467.549C420.808 508.347 380.37 553.856 332.335 593.848C301.853 619.226 262.656 622.597 228.642 614.743C194.834 606.936 162.658 587.448 142.217 561.686C108.054 518.631 100.57 469.801 108.223 427.836C115.56 387.606 137.391 351.005 166.502 331.557C161.248 315.813 156.813 299.49 153.519 283.013C142.593 228.368 143.239 167.031 174.28 119.619C186.922 100.31 205.846 89.1535 227.387 85.2773C248.1 81.5504 270.278 84.648 291.495 91.399ZM378.642 206.356C345.773 172.563 307.463 147.917 275.208 137.654C259.096 132.527 246.171 131.514 236.828 133.195C228.314 134.727 222.227 138.497 217.721 145.38C196.712 177.468 193.858 224.004 203.82 273.827C206.532 287.394 210.127 300.834 214.345 313.817C236.45 310.276 260.156 311.463 281.22 317.11C319.621 327.403 357.501 355.419 357.501 405.654C357.501 435.255 339.111 465.136 307.278 473.815C273.211 483.103 238.854 464.822 213.105 427.541C203.716 413.947 194.443 397.766 185.947 379.89C174.028 392.223 163.08 411.953 158.673 436.118C153.128 466.518 158.514 501.286 183.085 532.253C195.993 548.522 217.742 562.031 240.771 567.349C263.594 572.619 284.147 569.24 298.664 557.154C349.383 514.927 390.709 466.547 426.366 422.952C448.879 390.86 453.195 356.06 445.578 321.265C436.703 280.718 411.425 240.06 378.642 206.356ZM306.296 405.722C306.296 384.769 292.223 370.736 267.284 364.051C256.012 361.03 244.156 360.087 233.095 360.771C240.361 375.935 248.168 389.513 255.897 400.704C275.647 429.298 289.989 427.822 293.247 426.934C298.737 425.437 306.296 418.161 306.296 405.722Z" fill="white"/>
|
||||||
<circle cx="570" cy="570" r="130" fill="#22c55e"/>
|
<circle cx="570" cy="570" r="130" fill="#22c55e"/>
|
||||||
</svg>
|
</svg>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 3.4 KiB |
@@ -1,5 +1,5 @@
|
|||||||
<svg width="48" height="48" viewBox="0 0 700 700" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg width="48" height="48" viewBox="0 0 700 700" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<rect width="700" height="700" rx="100" fill="black"/>
|
<rect width="700" height="700" rx="156" fill="black"/>
|
||||||
<path d="M291.495 91.399C333.897 104.892 379.155 135.075 416.229 173.191C453.389 211.394 484.429 259.725 495.708 311.251C497.555 319.693 498.865 328.216 499.586 336.776C509.755 326.554 519.867 317.815 529.89 311.547C540.647 304.821 553.808 299.297 568.641 299.785C584.29 300.299 597.395 307.326 607.747 317.632C632.173 341.947 629.612 372.898 619.872 397.936C610.185 422.833 591.557 447.826 572.732 469.124C553.591 490.78 532.713 510.308 516.779 524.318C508.775 531.355 501.936 537.073 497.07 541.052C494.635 543.043 492.689 544.603 491.334 545.679C490.657 546.217 490.126 546.635 489.756 546.926C489.571 547.071 489.425 547.184 489.321 547.265C489.269 547.305 489.227 547.338 489.196 547.362C489.181 547.374 489.168 547.385 489.157 547.393C489.153 547.397 489.147 547.401 489.144 547.403C489.134 547.4 488.837 547.06 473.001 528.499L489.135 547.411C478.157 555.911 462.033 554.334 453.122 543.89C444.213 533.448 445.887 518.094 456.861 509.592C456.863 509.591 456.865 509.588 456.869 509.586C456.88 509.577 456.902 509.561 456.933 509.536C456.997 509.487 457.101 509.404 457.245 509.292C457.533 509.066 457.979 508.715 458.569 508.247C459.749 507.31 461.506 505.901 463.742 504.073C468.216 500.414 474.589 495.088 482.073 488.508C497.114 475.284 516.315 457.282 533.578 437.75C551.157 417.862 565.26 398.01 571.859 381.048C578.403 364.227 575.681 356.302 570.724 351.367C568.928 349.579 567.744 348.902 567.267 348.676C566.888 348.496 566.811 348.52 566.804 348.52C566.605 348.513 563.971 348.537 557.953 352.3C545.161 360.299 528.815 377.492 506.807 403.867C494.927 418.106 481.871 434.435 467.547 451.957C463.709 457.28 459.503 462.538 454.91 467.717L454.702 467.549C420.808 508.347 380.37 553.856 332.335 593.848C301.853 619.226 262.656 622.597 228.642 614.743C194.834 606.936 162.658 587.448 142.217 561.686C108.054 518.631 100.57 469.801 108.223 427.836C115.56 387.606 137.391 351.005 166.502 331.557C161.248 315.813 156.813 299.49 153.519 283.013C142.593 228.368 143.239 167.031 174.28 119.619C186.922 100.31 205.846 89.1535 227.387 85.2773C248.1 81.5504 270.278 84.648 291.495 91.399ZM378.642 206.356C345.773 172.563 307.463 147.917 275.208 137.654C259.096 132.527 246.171 131.514 236.828 133.195C228.314 134.727 222.227 138.497 217.721 145.38C196.712 177.468 193.858 224.004 203.82 273.827C206.532 287.394 210.127 300.834 214.345 313.817C236.45 310.276 260.156 311.463 281.22 317.11C319.621 327.403 357.501 355.419 357.501 405.654C357.501 435.255 339.111 465.136 307.278 473.815C273.211 483.103 238.854 464.822 213.105 427.541C203.716 413.947 194.443 397.766 185.947 379.89C174.028 392.223 163.08 411.953 158.673 436.118C153.128 466.518 158.514 501.286 183.085 532.253C195.993 548.522 217.742 562.031 240.771 567.349C263.594 572.619 284.147 569.24 298.664 557.154C349.383 514.927 390.709 466.547 426.366 422.952C448.879 390.86 453.195 356.06 445.578 321.265C436.703 280.718 411.425 240.06 378.642 206.356ZM306.296 405.722C306.296 384.769 292.223 370.736 267.284 364.051C256.012 361.03 244.156 360.087 233.095 360.771C240.361 375.935 248.168 389.513 255.897 400.704C275.647 429.298 289.989 427.822 293.247 426.934C298.737 425.437 306.296 418.161 306.296 405.722Z" fill="white"/>
|
<path transform="translate(350,350) scale(1.05) translate(-350,-350)" d="M291.495 91.399C333.897 104.892 379.155 135.075 416.229 173.191C453.389 211.394 484.429 259.725 495.708 311.251C497.555 319.693 498.865 328.216 499.586 336.776C509.755 326.554 519.867 317.815 529.89 311.547C540.647 304.821 553.808 299.297 568.641 299.785C584.29 300.299 597.395 307.326 607.747 317.632C632.173 341.947 629.612 372.898 619.872 397.936C610.185 422.833 591.557 447.826 572.732 469.124C553.591 490.78 532.713 510.308 516.779 524.318C508.775 531.355 501.936 537.073 497.07 541.052C494.635 543.043 492.689 544.603 491.334 545.679C490.657 546.217 490.126 546.635 489.756 546.926C489.571 547.071 489.425 547.184 489.321 547.265C489.269 547.305 489.227 547.338 489.196 547.362C489.181 547.374 489.168 547.385 489.157 547.393C489.153 547.397 489.147 547.401 489.144 547.403C489.134 547.4 488.837 547.06 473.001 528.499L489.135 547.411C478.157 555.911 462.033 554.334 453.122 543.89C444.213 533.448 445.887 518.094 456.861 509.592C456.863 509.591 456.865 509.588 456.869 509.586C456.88 509.577 456.902 509.561 456.933 509.536C456.997 509.487 457.101 509.404 457.245 509.292C457.533 509.066 457.979 508.715 458.569 508.247C459.749 507.31 461.506 505.901 463.742 504.073C468.216 500.414 474.589 495.088 482.073 488.508C497.114 475.284 516.315 457.282 533.578 437.75C551.157 417.862 565.26 398.01 571.859 381.048C578.403 364.227 575.681 356.302 570.724 351.367C568.928 349.579 567.744 348.902 567.267 348.676C566.888 348.496 566.811 348.52 566.804 348.52C566.605 348.513 563.971 348.537 557.953 352.3C545.161 360.299 528.815 377.492 506.807 403.867C494.927 418.106 481.871 434.435 467.547 451.957C463.709 457.28 459.503 462.538 454.91 467.717L454.702 467.549C420.808 508.347 380.37 553.856 332.335 593.848C301.853 619.226 262.656 622.597 228.642 614.743C194.834 606.936 162.658 587.448 142.217 561.686C108.054 518.631 100.57 469.801 108.223 427.836C115.56 387.606 137.391 351.005 166.502 331.557C161.248 315.813 156.813 299.49 153.519 283.013C142.593 228.368 143.239 167.031 174.28 119.619C186.922 100.31 205.846 89.1535 227.387 85.2773C248.1 81.5504 270.278 84.648 291.495 91.399ZM378.642 206.356C345.773 172.563 307.463 147.917 275.208 137.654C259.096 132.527 246.171 131.514 236.828 133.195C228.314 134.727 222.227 138.497 217.721 145.38C196.712 177.468 193.858 224.004 203.82 273.827C206.532 287.394 210.127 300.834 214.345 313.817C236.45 310.276 260.156 311.463 281.22 317.11C319.621 327.403 357.501 355.419 357.501 405.654C357.501 435.255 339.111 465.136 307.278 473.815C273.211 483.103 238.854 464.822 213.105 427.541C203.716 413.947 194.443 397.766 185.947 379.89C174.028 392.223 163.08 411.953 158.673 436.118C153.128 466.518 158.514 501.286 183.085 532.253C195.993 548.522 217.742 562.031 240.771 567.349C263.594 572.619 284.147 569.24 298.664 557.154C349.383 514.927 390.709 466.547 426.366 422.952C448.879 390.86 453.195 356.06 445.578 321.265C436.703 280.718 411.425 240.06 378.642 206.356ZM306.296 405.722C306.296 384.769 292.223 370.736 267.284 364.051C256.012 361.03 244.156 360.087 233.095 360.771C240.361 375.935 248.168 389.513 255.897 400.704C275.647 429.298 289.989 427.822 293.247 426.934C298.737 425.437 306.296 418.161 306.296 405.722Z" fill="white"/>
|
||||||
<circle cx="570" cy="570" r="130" fill="#3b82f6"/>
|
<circle cx="570" cy="570" r="130" fill="#3b82f6"/>
|
||||||
</svg>
|
</svg>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 3.4 KiB |
@@ -1,4 +1,4 @@
|
|||||||
<svg width="48" height="48" viewBox="0 0 700 700" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg width="48" height="48" viewBox="0 0 700 700" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<rect width="700" height="700" rx="100" fill="black"/>
|
<rect width="700" height="700" rx="156" fill="black"/>
|
||||||
<path d="M291.495 91.399C333.897 104.892 379.155 135.075 416.229 173.191C453.389 211.394 484.429 259.725 495.708 311.251C497.555 319.693 498.865 328.216 499.586 336.776C509.755 326.554 519.867 317.815 529.89 311.547C540.647 304.821 553.808 299.297 568.641 299.785C584.29 300.299 597.395 307.326 607.747 317.632C632.173 341.947 629.612 372.898 619.872 397.936C610.185 422.833 591.557 447.826 572.732 469.124C553.591 490.78 532.713 510.308 516.779 524.318C508.775 531.355 501.936 537.073 497.07 541.052C494.635 543.043 492.689 544.603 491.334 545.679C490.657 546.217 490.126 546.635 489.756 546.926C489.571 547.071 489.425 547.184 489.321 547.265C489.269 547.305 489.227 547.338 489.196 547.362C489.181 547.374 489.168 547.385 489.157 547.393C489.153 547.397 489.147 547.401 489.144 547.403C489.134 547.4 488.837 547.06 473.001 528.499L489.135 547.411C478.157 555.911 462.033 554.334 453.122 543.89C444.213 533.448 445.887 518.094 456.861 509.592C456.863 509.591 456.865 509.588 456.869 509.586C456.88 509.577 456.902 509.561 456.933 509.536C456.997 509.487 457.101 509.404 457.245 509.292C457.533 509.066 457.979 508.715 458.569 508.247C459.749 507.31 461.506 505.901 463.742 504.073C468.216 500.414 474.589 495.088 482.073 488.508C497.114 475.284 516.315 457.282 533.578 437.75C551.157 417.862 565.26 398.01 571.859 381.048C578.403 364.227 575.681 356.302 570.724 351.367C568.928 349.579 567.744 348.902 567.267 348.676C566.888 348.496 566.811 348.52 566.804 348.52C566.605 348.513 563.971 348.537 557.953 352.3C545.161 360.299 528.815 377.492 506.807 403.867C494.927 418.106 481.871 434.435 467.547 451.957C463.709 457.28 459.503 462.538 454.91 467.717L454.702 467.549C420.808 508.347 380.37 553.856 332.335 593.848C301.853 619.226 262.656 622.597 228.642 614.743C194.834 606.936 162.658 587.448 142.217 561.686C108.054 518.631 100.57 469.801 108.223 427.836C115.56 387.606 137.391 351.005 166.502 331.557C161.248 315.813 156.813 299.49 153.519 283.013C142.593 228.368 143.239 167.031 174.28 119.619C186.922 100.31 205.846 89.1535 227.387 85.2773C248.1 81.5504 270.278 84.648 291.495 91.399ZM378.642 206.356C345.773 172.563 307.463 147.917 275.208 137.654C259.096 132.527 246.171 131.514 236.828 133.195C228.314 134.727 222.227 138.497 217.721 145.38C196.712 177.468 193.858 224.004 203.82 273.827C206.532 287.394 210.127 300.834 214.345 313.817C236.45 310.276 260.156 311.463 281.22 317.11C319.621 327.403 357.501 355.419 357.501 405.654C357.501 435.255 339.111 465.136 307.278 473.815C273.211 483.103 238.854 464.822 213.105 427.541C203.716 413.947 194.443 397.766 185.947 379.89C174.028 392.223 163.08 411.953 158.673 436.118C153.128 466.518 158.514 501.286 183.085 532.253C195.993 548.522 217.742 562.031 240.771 567.349C263.594 572.619 284.147 569.24 298.664 557.154C349.383 514.927 390.709 466.547 426.366 422.952C448.879 390.86 453.195 356.06 445.578 321.265C436.703 280.718 411.425 240.06 378.642 206.356ZM306.296 405.722C306.296 384.769 292.223 370.736 267.284 364.051C256.012 361.03 244.156 360.087 233.095 360.771C240.361 375.935 248.168 389.513 255.897 400.704C275.647 429.298 289.989 427.822 293.247 426.934C298.737 425.437 306.296 418.161 306.296 405.722Z" fill="white"/>
|
<path transform="translate(350,350) scale(1.05) translate(-350,-350)" d="M291.495 91.399C333.897 104.892 379.155 135.075 416.229 173.191C453.389 211.394 484.429 259.725 495.708 311.251C497.555 319.693 498.865 328.216 499.586 336.776C509.755 326.554 519.867 317.815 529.89 311.547C540.647 304.821 553.808 299.297 568.641 299.785C584.29 300.299 597.395 307.326 607.747 317.632C632.173 341.947 629.612 372.898 619.872 397.936C610.185 422.833 591.557 447.826 572.732 469.124C553.591 490.78 532.713 510.308 516.779 524.318C508.775 531.355 501.936 537.073 497.07 541.052C494.635 543.043 492.689 544.603 491.334 545.679C490.657 546.217 490.126 546.635 489.756 546.926C489.571 547.071 489.425 547.184 489.321 547.265C489.269 547.305 489.227 547.338 489.196 547.362C489.181 547.374 489.168 547.385 489.157 547.393C489.153 547.397 489.147 547.401 489.144 547.403C489.134 547.4 488.837 547.06 473.001 528.499L489.135 547.411C478.157 555.911 462.033 554.334 453.122 543.89C444.213 533.448 445.887 518.094 456.861 509.592C456.863 509.591 456.865 509.588 456.869 509.586C456.88 509.577 456.902 509.561 456.933 509.536C456.997 509.487 457.101 509.404 457.245 509.292C457.533 509.066 457.979 508.715 458.569 508.247C459.749 507.31 461.506 505.901 463.742 504.073C468.216 500.414 474.589 495.088 482.073 488.508C497.114 475.284 516.315 457.282 533.578 437.75C551.157 417.862 565.26 398.01 571.859 381.048C578.403 364.227 575.681 356.302 570.724 351.367C568.928 349.579 567.744 348.902 567.267 348.676C566.888 348.496 566.811 348.52 566.804 348.52C566.605 348.513 563.971 348.537 557.953 352.3C545.161 360.299 528.815 377.492 506.807 403.867C494.927 418.106 481.871 434.435 467.547 451.957C463.709 457.28 459.503 462.538 454.91 467.717L454.702 467.549C420.808 508.347 380.37 553.856 332.335 593.848C301.853 619.226 262.656 622.597 228.642 614.743C194.834 606.936 162.658 587.448 142.217 561.686C108.054 518.631 100.57 469.801 108.223 427.836C115.56 387.606 137.391 351.005 166.502 331.557C161.248 315.813 156.813 299.49 153.519 283.013C142.593 228.368 143.239 167.031 174.28 119.619C186.922 100.31 205.846 89.1535 227.387 85.2773C248.1 81.5504 270.278 84.648 291.495 91.399ZM378.642 206.356C345.773 172.563 307.463 147.917 275.208 137.654C259.096 132.527 246.171 131.514 236.828 133.195C228.314 134.727 222.227 138.497 217.721 145.38C196.712 177.468 193.858 224.004 203.82 273.827C206.532 287.394 210.127 300.834 214.345 313.817C236.45 310.276 260.156 311.463 281.22 317.11C319.621 327.403 357.501 355.419 357.501 405.654C357.501 435.255 339.111 465.136 307.278 473.815C273.211 483.103 238.854 464.822 213.105 427.541C203.716 413.947 194.443 397.766 185.947 379.89C174.028 392.223 163.08 411.953 158.673 436.118C153.128 466.518 158.514 501.286 183.085 532.253C195.993 548.522 217.742 562.031 240.771 567.349C263.594 572.619 284.147 569.24 298.664 557.154C349.383 514.927 390.709 466.547 426.366 422.952C448.879 390.86 453.195 356.06 445.578 321.265C436.703 280.718 411.425 240.06 378.642 206.356ZM306.296 405.722C306.296 384.769 292.223 370.736 267.284 364.051C256.012 361.03 244.156 360.087 233.095 360.771C240.361 375.935 248.168 389.513 255.897 400.704C275.647 429.298 289.989 427.822 293.247 426.934C298.737 425.437 306.296 418.161 306.296 405.722Z" fill="white"/>
|
||||||
</svg>
|
</svg>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 3.3 KiB |
|
Before Width: | Height: | Size: 57 KiB After Width: | Height: | Size: 62 KiB |
@@ -36,6 +36,7 @@
|
|||||||
"@react-navigation/elements": "^2.6.3",
|
"@react-navigation/elements": "^2.6.3",
|
||||||
"@react-navigation/native": "^7.1.8",
|
"@react-navigation/native": "^7.1.8",
|
||||||
"@tanstack/react-query": "^5.90.11",
|
"@tanstack/react-query": "^5.90.11",
|
||||||
|
"@tauri-apps/api": "^2.9.1",
|
||||||
"buffer": "^6.0.3",
|
"buffer": "^6.0.3",
|
||||||
"expo": "^54.0.18",
|
"expo": "^54.0.18",
|
||||||
"expo-audio": "~1.0.13",
|
"expo-audio": "~1.0.13",
|
||||||
@@ -78,6 +79,7 @@
|
|||||||
"react-native-svg": "^15.14.0",
|
"react-native-svg": "^15.14.0",
|
||||||
"react-native-unistyles": "^3.0.15",
|
"react-native-unistyles": "^3.0.15",
|
||||||
"react-native-web": "~0.21.0",
|
"react-native-web": "~0.21.0",
|
||||||
|
"react-native-webview": "^13.16.0",
|
||||||
"react-native-worklets": "0.5.1",
|
"react-native-worklets": "0.5.1",
|
||||||
"zod": "^3.23.8",
|
"zod": "^3.23.8",
|
||||||
"zustand": "^5.0.9"
|
"zustand": "^5.0.9"
|
||||||
|
|||||||
@@ -28,6 +28,8 @@ import {
|
|||||||
HorizontalScrollProvider,
|
HorizontalScrollProvider,
|
||||||
useHorizontalScrollOptional,
|
useHorizontalScrollOptional,
|
||||||
} from "@/contexts/horizontal-scroll-context";
|
} from "@/contexts/horizontal-scroll-context";
|
||||||
|
import { getIsTauriMac } from "@/constants/layout";
|
||||||
|
import { useTrafficLightPadding } from "@/utils/tauri-window";
|
||||||
|
|
||||||
function QueryProvider({ children }: { children: ReactNode }) {
|
function QueryProvider({ children }: { children: ReactNode }) {
|
||||||
const [queryClient] = useState(
|
const [queryClient] = useState(
|
||||||
@@ -152,11 +154,17 @@ function AppContainer({ children, selectedAgentId }: AppContainerProps) {
|
|||||||
[isMobile, isOpen, windowWidth, translateX, backdropOpacity, animateToOpen, animateToClose, openAgentList, isGesturing, horizontalScroll?.isAnyScrolledRight, touchStartX]
|
[isMobile, isOpen, windowWidth, translateX, backdropOpacity, animateToOpen, animateToClose, openAgentList, isGesturing, horizontalScroll?.isAnyScrolledRight, touchStartX]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// When sidebar is collapsed on desktop Tauri macOS, add left padding for traffic lights
|
||||||
|
const trafficLightPadding = useTrafficLightPadding();
|
||||||
|
const needsTrafficLightPadding = !isMobile && !isOpen && getIsTauriMac();
|
||||||
|
|
||||||
const content = (
|
const content = (
|
||||||
<View style={{ flex: 1, backgroundColor: theme.colors.surface0 }}>
|
<View style={{ flex: 1, backgroundColor: theme.colors.surface0 }}>
|
||||||
<View style={{ flex: 1, flexDirection: "row" }}>
|
<View style={{ flex: 1, flexDirection: "row" }}>
|
||||||
{!isMobile && <SlidingSidebar selectedAgentId={selectedAgentId} />}
|
{!isMobile && <SlidingSidebar selectedAgentId={selectedAgentId} />}
|
||||||
<View style={{ flex: 1 }}>{children}</View>
|
<View style={{ flex: 1, paddingLeft: needsTrafficLightPadding ? trafficLightPadding.left : 0 }}>
|
||||||
|
{children}
|
||||||
|
</View>
|
||||||
</View>
|
</View>
|
||||||
{isMobile && <SlidingSidebar selectedAgentId={selectedAgentId} />}
|
{isMobile && <SlidingSidebar selectedAgentId={selectedAgentId} />}
|
||||||
<DownloadToast />
|
<DownloadToast />
|
||||||
|
|||||||
@@ -30,8 +30,6 @@ import {
|
|||||||
GitBranch,
|
GitBranch,
|
||||||
Folder,
|
Folder,
|
||||||
RotateCcw,
|
RotateCcw,
|
||||||
Users,
|
|
||||||
ChevronRight,
|
|
||||||
PlusIcon,
|
PlusIcon,
|
||||||
PanelRight,
|
PanelRight,
|
||||||
} from "lucide-react-native";
|
} from "lucide-react-native";
|
||||||
@@ -51,7 +49,6 @@ import { useDaemonConnections } from "@/contexts/daemon-connections-context";
|
|||||||
import type { ConnectionStatus } from "@/contexts/daemon-connections-context";
|
import type { ConnectionStatus } from "@/contexts/daemon-connections-context";
|
||||||
import { formatConnectionStatus } from "@/utils/daemons";
|
import { formatConnectionStatus } from "@/utils/daemons";
|
||||||
import { useSessionStore } from "@/stores/session-store";
|
import { useSessionStore } from "@/stores/session-store";
|
||||||
import type { Agent } from "@/stores/session-store";
|
|
||||||
import type { StreamItem } from "@/types/stream";
|
import type { StreamItem } from "@/types/stream";
|
||||||
import {
|
import {
|
||||||
buildAgentNavigationKey,
|
buildAgentNavigationKey,
|
||||||
@@ -61,6 +58,7 @@ import {
|
|||||||
} from "@/utils/navigation-timing";
|
} from "@/utils/navigation-timing";
|
||||||
import { extractAgentModel } from "@/utils/extract-agent-model";
|
import { extractAgentModel } from "@/utils/extract-agent-model";
|
||||||
import { startPerfMonitor } from "@/utils/perf-monitor";
|
import { startPerfMonitor } from "@/utils/perf-monitor";
|
||||||
|
import { shortenPath } from "@/utils/shorten-path";
|
||||||
|
|
||||||
const DROPDOWN_WIDTH = 220;
|
const DROPDOWN_WIDTH = 220;
|
||||||
const EMPTY_STREAM_ITEMS: StreamItem[] = [];
|
const EMPTY_STREAM_ITEMS: StreamItem[] = [];
|
||||||
@@ -294,35 +292,6 @@ function AgentScreenContent({
|
|||||||
: undefined
|
: undefined
|
||||||
);
|
);
|
||||||
|
|
||||||
// Select the agents Map directly - this is a stable reference that only changes when agents are added/removed
|
|
||||||
const allAgents = useSessionStore(
|
|
||||||
(state) => state.sessions[serverId]?.agents
|
|
||||||
);
|
|
||||||
|
|
||||||
// Derive child agents in useMemo to avoid creating new references on every store update
|
|
||||||
// This prevents infinite loops caused by useShallow not doing deep equality on object arrays
|
|
||||||
const childAgents = useMemo(() => {
|
|
||||||
if (!resolvedAgentId || !allAgents) return [];
|
|
||||||
const children: Array<{
|
|
||||||
id: string;
|
|
||||||
title: string | null;
|
|
||||||
createdAt: Date;
|
|
||||||
status: Agent["status"];
|
|
||||||
}> = [];
|
|
||||||
for (const [id, a] of allAgents) {
|
|
||||||
if (a.parentAgentId === resolvedAgentId) {
|
|
||||||
children.push({
|
|
||||||
id,
|
|
||||||
title: a.title,
|
|
||||||
createdAt: a.createdAt,
|
|
||||||
status: a.status,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
children.sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime());
|
|
||||||
return children;
|
|
||||||
}, [allAgents, resolvedAgentId]);
|
|
||||||
|
|
||||||
// Select only the specific stream tail - use stable empty array to avoid infinite loop
|
// Select only the specific stream tail - use stable empty array to avoid infinite loop
|
||||||
const streamItemsRaw = useSessionStore((state) =>
|
const streamItemsRaw = useSessionStore((state) =>
|
||||||
resolvedAgentId
|
resolvedAgentId
|
||||||
@@ -613,20 +582,6 @@ function AgentScreenContent({
|
|||||||
router.push({ pathname: "/", params });
|
router.push({ pathname: "/", params });
|
||||||
}, [agent, agentModel, handleCloseMenu, router, serverId]);
|
}, [agent, agentModel, handleCloseMenu, router, serverId]);
|
||||||
|
|
||||||
const handleNavigateToChildAgent = useCallback(
|
|
||||||
(childAgentId: string) => {
|
|
||||||
handleCloseMenu();
|
|
||||||
router.push({
|
|
||||||
pathname: "/agent/[serverId]/[agentId]",
|
|
||||||
params: {
|
|
||||||
serverId: serverId,
|
|
||||||
agentId: childAgentId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
},
|
|
||||||
[handleCloseMenu, router, serverId]
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!agent) {
|
if (!agent) {
|
||||||
return (
|
return (
|
||||||
<View style={styles.container}>
|
<View style={styles.container}>
|
||||||
@@ -736,7 +691,7 @@ function AgentScreenContent({
|
|||||||
numberOfLines={2}
|
numberOfLines={2}
|
||||||
ellipsizeMode="middle"
|
ellipsizeMode="middle"
|
||||||
>
|
>
|
||||||
{agent.cwd}
|
{shortenPath(agent.cwd)}
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
@@ -784,50 +739,6 @@ function AgentScreenContent({
|
|||||||
|
|
||||||
<View style={styles.menuDivider} />
|
<View style={styles.menuDivider} />
|
||||||
|
|
||||||
{/* Sub-Agents Section */}
|
|
||||||
<View style={styles.menuSubAgentsSection}>
|
|
||||||
<View style={styles.menuSubAgentsHeader}>
|
|
||||||
<Users size={16} color={theme.colors.foregroundMuted} />
|
|
||||||
<Text style={styles.menuSubAgentsLabel}>Sub-Agents</Text>
|
|
||||||
</View>
|
|
||||||
{childAgents.length === 0 ? (
|
|
||||||
<Text style={styles.menuSubAgentsEmpty}>No sub-agents</Text>
|
|
||||||
) : (
|
|
||||||
<ScrollView
|
|
||||||
style={styles.menuSubAgentsList}
|
|
||||||
contentContainerStyle={styles.menuSubAgentsListContent}
|
|
||||||
showsVerticalScrollIndicator={childAgents.length > 3}
|
|
||||||
>
|
|
||||||
{childAgents.map((child) => (
|
|
||||||
<Pressable
|
|
||||||
key={child.id}
|
|
||||||
onPress={() => handleNavigateToChildAgent(child.id)}
|
|
||||||
style={styles.menuSubAgentItem}
|
|
||||||
>
|
|
||||||
<Text
|
|
||||||
style={styles.menuSubAgentText}
|
|
||||||
numberOfLines={1}
|
|
||||||
ellipsizeMode="tail"
|
|
||||||
>
|
|
||||||
{child.title || "Untitled Agent"}
|
|
||||||
</Text>
|
|
||||||
<View style={styles.menuSubAgentMeta}>
|
|
||||||
{child.status === "running" ? (
|
|
||||||
<View style={styles.menuSubAgentDot} />
|
|
||||||
) : null}
|
|
||||||
<ChevronRight
|
|
||||||
size={16}
|
|
||||||
color={theme.colors.foregroundMuted}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
</Pressable>
|
|
||||||
))}
|
|
||||||
</ScrollView>
|
|
||||||
)}
|
|
||||||
</View>
|
|
||||||
|
|
||||||
<View style={styles.menuDivider} />
|
|
||||||
|
|
||||||
<Pressable onPress={handleViewChanges} style={styles.menuItem}>
|
<Pressable onPress={handleViewChanges} style={styles.menuItem}>
|
||||||
<GitBranch size={16} color={theme.colors.foreground} />
|
<GitBranch size={16} color={theme.colors.foreground} />
|
||||||
<Text style={styles.menuItemText}>View Changes</Text>
|
<Text style={styles.menuItemText}>View Changes</Text>
|
||||||
@@ -1108,57 +1019,4 @@ const styles = StyleSheet.create((theme) => ({
|
|||||||
color: theme.colors.foreground,
|
color: theme.colors.foreground,
|
||||||
fontWeight: theme.fontWeight.normal,
|
fontWeight: theme.fontWeight.normal,
|
||||||
},
|
},
|
||||||
menuSubAgentsSection: {
|
|
||||||
gap: theme.spacing[2],
|
|
||||||
},
|
|
||||||
menuSubAgentsHeader: {
|
|
||||||
flexDirection: "row",
|
|
||||||
alignItems: "center",
|
|
||||||
gap: theme.spacing[2],
|
|
||||||
paddingHorizontal: theme.spacing[3],
|
|
||||||
paddingVertical: theme.spacing[2],
|
|
||||||
},
|
|
||||||
menuSubAgentsLabel: {
|
|
||||||
fontSize: theme.fontSize.xs,
|
|
||||||
color: theme.colors.foregroundMuted,
|
|
||||||
letterSpacing: 0.5,
|
|
||||||
textTransform: "uppercase",
|
|
||||||
},
|
|
||||||
menuSubAgentsEmpty: {
|
|
||||||
fontSize: theme.fontSize.base,
|
|
||||||
color: theme.colors.foregroundMuted,
|
|
||||||
paddingHorizontal: theme.spacing[3],
|
|
||||||
paddingVertical: theme.spacing[3],
|
|
||||||
},
|
|
||||||
menuSubAgentsList: {
|
|
||||||
maxHeight: (theme.spacing[2] * 2 + theme.fontSize.base) * 3,
|
|
||||||
},
|
|
||||||
menuSubAgentsListContent: {
|
|
||||||
gap: theme.spacing[1],
|
|
||||||
},
|
|
||||||
menuSubAgentItem: {
|
|
||||||
flexDirection: "row",
|
|
||||||
alignItems: "center",
|
|
||||||
justifyContent: "space-between",
|
|
||||||
paddingVertical: theme.spacing[3],
|
|
||||||
paddingHorizontal: theme.spacing[3],
|
|
||||||
borderRadius: theme.borderRadius.md,
|
|
||||||
},
|
|
||||||
menuSubAgentText: {
|
|
||||||
fontSize: theme.fontSize.base,
|
|
||||||
color: theme.colors.foreground,
|
|
||||||
flex: 1,
|
|
||||||
marginRight: theme.spacing[2],
|
|
||||||
},
|
|
||||||
menuSubAgentMeta: {
|
|
||||||
flexDirection: "row",
|
|
||||||
alignItems: "center",
|
|
||||||
gap: theme.spacing[1],
|
|
||||||
},
|
|
||||||
menuSubAgentDot: {
|
|
||||||
width: 6,
|
|
||||||
height: 6,
|
|
||||||
borderRadius: 999,
|
|
||||||
backgroundColor: "#3b82f6",
|
|
||||||
},
|
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -106,7 +106,7 @@ export default function HomeScreen() {
|
|||||||
const resolvedModel = getParamValue(params.model);
|
const resolvedModel = getParamValue(params.model);
|
||||||
const resolvedWorkingDir = getParamValue(params.workingDir);
|
const resolvedWorkingDir = getParamValue(params.workingDir);
|
||||||
|
|
||||||
const initialValues = useMemo(() => {
|
const initialValues = useMemo((): CreateAgentInitialValues => {
|
||||||
const values: CreateAgentInitialValues = {};
|
const values: CreateAgentInitialValues = {};
|
||||||
if (resolvedWorkingDir) {
|
if (resolvedWorkingDir) {
|
||||||
values.workingDir = resolvedWorkingDir;
|
values.workingDir = resolvedWorkingDir;
|
||||||
@@ -117,8 +117,12 @@ export default function HomeScreen() {
|
|||||||
if (resolvedMode) {
|
if (resolvedMode) {
|
||||||
values.modeId = resolvedMode;
|
values.modeId = resolvedMode;
|
||||||
}
|
}
|
||||||
|
if (resolvedModel) {
|
||||||
|
values.model = resolvedModel;
|
||||||
|
}
|
||||||
return values;
|
return values;
|
||||||
}, [resolvedMode, resolvedProvider, resolvedWorkingDir]);
|
}, [resolvedMode, resolvedModel, resolvedProvider, resolvedWorkingDir]);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
selectedServerId,
|
selectedServerId,
|
||||||
setSelectedServerIdFromUser,
|
setSelectedServerIdFromUser,
|
||||||
@@ -143,21 +147,6 @@ export default function HomeScreen() {
|
|||||||
isVisible: true,
|
isVisible: true,
|
||||||
isCreateFlow: true,
|
isCreateFlow: true,
|
||||||
});
|
});
|
||||||
const hasAppliedModelParamRef = useRef(false);
|
|
||||||
useEffect(() => {
|
|
||||||
if (!resolvedModel || hasAppliedModelParamRef.current) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (availableModels.length === 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const isValidModel = availableModels.some((model) => model.id === resolvedModel);
|
|
||||||
hasAppliedModelParamRef.current = true;
|
|
||||||
if (!isValidModel) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
setModelFromUser(resolvedModel);
|
|
||||||
}, [availableModels, resolvedModel, setModelFromUser]);
|
|
||||||
const hostEntry = selectedServerId
|
const hostEntry = selectedServerId
|
||||||
? connectionStates.get(selectedServerId)
|
? connectionStates.get(selectedServerId)
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|||||||
@@ -340,6 +340,37 @@ const styles = StyleSheet.create((theme) => ({
|
|||||||
themeToggleTextActive: {
|
themeToggleTextActive: {
|
||||||
color: theme.colors.foreground,
|
color: theme.colors.foreground,
|
||||||
},
|
},
|
||||||
|
// Dev section
|
||||||
|
devCard: {
|
||||||
|
backgroundColor: theme.colors.surface2,
|
||||||
|
borderRadius: theme.borderRadius.lg,
|
||||||
|
borderWidth: 1,
|
||||||
|
borderColor: theme.colors.border,
|
||||||
|
overflow: "hidden",
|
||||||
|
},
|
||||||
|
devButton: {
|
||||||
|
flexDirection: "row",
|
||||||
|
alignItems: "center",
|
||||||
|
gap: theme.spacing[3],
|
||||||
|
paddingVertical: theme.spacing[4],
|
||||||
|
paddingHorizontal: theme.spacing[4],
|
||||||
|
},
|
||||||
|
devButtonBorder: {
|
||||||
|
borderTopWidth: 1,
|
||||||
|
borderTopColor: theme.colors.border,
|
||||||
|
},
|
||||||
|
devButtonContent: {
|
||||||
|
flex: 1,
|
||||||
|
},
|
||||||
|
devButtonTitle: {
|
||||||
|
color: theme.colors.foreground,
|
||||||
|
fontSize: theme.fontSize.base,
|
||||||
|
},
|
||||||
|
devButtonDescription: {
|
||||||
|
color: theme.colors.foregroundMuted,
|
||||||
|
fontSize: theme.fontSize.sm,
|
||||||
|
marginTop: 2,
|
||||||
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
type DaemonTestState = {
|
type DaemonTestState = {
|
||||||
@@ -760,6 +791,24 @@ export default function SettingsScreen() {
|
|||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
|
{/* Developer */}
|
||||||
|
<View style={styles.section}>
|
||||||
|
<Text style={styles.sectionTitle}>Developer</Text>
|
||||||
|
<View style={styles.devCard}>
|
||||||
|
<Pressable
|
||||||
|
style={styles.devButton}
|
||||||
|
onPress={() => router.push("/audio-test")}
|
||||||
|
>
|
||||||
|
<View style={styles.devButtonContent}>
|
||||||
|
<Text style={styles.devButtonTitle}>Audio Test</Text>
|
||||||
|
<Text style={styles.devButtonDescription}>
|
||||||
|
Test audio recording and playback
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
</Pressable>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
{/* Footer */}
|
{/* Footer */}
|
||||||
<View style={styles.footer}>
|
<View style={styles.footer}>
|
||||||
<View style={styles.footerAppInfo}>
|
<View style={styles.footerAppInfo}>
|
||||||
|
|||||||
@@ -101,10 +101,6 @@ export function AgentInputArea({
|
|||||||
|
|
||||||
const setQueuedMessages = useSessionStore((state) => state.setQueuedMessages);
|
const setQueuedMessages = useSessionStore((state) => state.setQueuedMessages);
|
||||||
|
|
||||||
// Noop fallback for sendAgentAudio (required by MessageInput)
|
|
||||||
const noopSendAgentAudio = useCallback(async () => {}, []);
|
|
||||||
const sendAgentAudio = methods?.sendAgentAudio ?? noopSendAgentAudio;
|
|
||||||
|
|
||||||
const { startRealtime, stopRealtime, isRealtimeMode } = useRealtime();
|
const { startRealtime, stopRealtime, isRealtimeMode } = useRealtime();
|
||||||
|
|
||||||
const [internalInput, setInternalInput] = useState("");
|
const [internalInput, setInternalInput] = useState("");
|
||||||
@@ -448,52 +444,47 @@ export function AgentInputArea({
|
|||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
const rightContent = (
|
const rightContent = isAgentRunning ? (
|
||||||
<>
|
<Pressable
|
||||||
<Pressable
|
onPress={handleCancelAgent}
|
||||||
onPress={handleRealtimePress}
|
disabled={!isConnected || isCancellingAgent}
|
||||||
disabled={!isConnected && !isRealtimeMode}
|
accessibilityLabel={isCancellingAgent ? "Canceling agent" : "Stop agent"}
|
||||||
style={[
|
accessibilityRole="button"
|
||||||
styles.realtimeButton as any,
|
style={[
|
||||||
(isRealtimeMode ? styles.realtimeButtonActive : undefined) as any,
|
styles.cancelButton as any,
|
||||||
(!isConnected && !isRealtimeMode
|
(!isConnected || isCancellingAgent
|
||||||
? styles.buttonDisabled
|
? styles.buttonDisabled
|
||||||
: undefined) as any,
|
: undefined) as any,
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
{isRealtimeMode ? (
|
{isCancellingAgent ? (
|
||||||
<Square
|
<ActivityIndicator size="small" color="white" />
|
||||||
size={18}
|
) : (
|
||||||
color={theme.colors.background}
|
<Square size={18} color="white" fill="white" />
|
||||||
fill={theme.colors.background}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<AudioLines size={20} color={theme.colors.background} />
|
|
||||||
)}
|
|
||||||
</Pressable>
|
|
||||||
{isAgentRunning && (
|
|
||||||
<Pressable
|
|
||||||
onPress={handleCancelAgent}
|
|
||||||
disabled={!isConnected || isCancellingAgent}
|
|
||||||
accessibilityLabel={
|
|
||||||
isCancellingAgent ? "Canceling agent" : "Stop agent"
|
|
||||||
}
|
|
||||||
accessibilityRole="button"
|
|
||||||
style={[
|
|
||||||
styles.cancelButton as any,
|
|
||||||
(!isConnected || isCancellingAgent
|
|
||||||
? styles.buttonDisabled
|
|
||||||
: undefined) as any,
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
{isCancellingAgent ? (
|
|
||||||
<ActivityIndicator size="small" color="white" />
|
|
||||||
) : (
|
|
||||||
<Square size={18} color="white" fill="white" />
|
|
||||||
)}
|
|
||||||
</Pressable>
|
|
||||||
)}
|
)}
|
||||||
</>
|
</Pressable>
|
||||||
|
) : (
|
||||||
|
<Pressable
|
||||||
|
onPress={handleRealtimePress}
|
||||||
|
disabled={!isConnected && !isRealtimeMode}
|
||||||
|
style={[
|
||||||
|
styles.realtimeButton as any,
|
||||||
|
(isRealtimeMode ? styles.realtimeButtonActive : undefined) as any,
|
||||||
|
(!isConnected && !isRealtimeMode
|
||||||
|
? styles.buttonDisabled
|
||||||
|
: undefined) as any,
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
{isRealtimeMode ? (
|
||||||
|
<Square
|
||||||
|
size={18}
|
||||||
|
color={theme.colors.background}
|
||||||
|
fill={theme.colors.background}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<AudioLines size={20} color={theme.colors.background} />
|
||||||
|
)}
|
||||||
|
</Pressable>
|
||||||
);
|
);
|
||||||
|
|
||||||
const leftContent = <AgentStatusBar agentId={agentId} serverId={serverId} />;
|
const leftContent = <AgentStatusBar agentId={agentId} serverId={serverId} />;
|
||||||
@@ -574,7 +565,6 @@ export function AgentInputArea({
|
|||||||
onPickImages={handlePickImage}
|
onPickImages={handlePickImage}
|
||||||
onRemoveImage={handleRemoveImage}
|
onRemoveImage={handleRemoveImage}
|
||||||
client={client}
|
client={client}
|
||||||
sendAgentAudio={sendAgentAudio}
|
|
||||||
placeholder="Message agent..."
|
placeholder="Message agent..."
|
||||||
autoFocus={autoFocus}
|
autoFocus={autoFocus}
|
||||||
disabled={isRealtimeMode}
|
disabled={isRealtimeMode}
|
||||||
@@ -630,10 +620,10 @@ const styles = StyleSheet.create(((theme: Theme) => ({
|
|||||||
backgroundColor: theme.colors.palette.blue[600],
|
backgroundColor: theme.colors.palette.blue[600],
|
||||||
},
|
},
|
||||||
cancelButton: {
|
cancelButton: {
|
||||||
width: BUTTON_SIZE,
|
width: 34,
|
||||||
height: BUTTON_SIZE,
|
height: 34,
|
||||||
borderRadius: theme.borderRadius.full,
|
borderRadius: theme.borderRadius.full,
|
||||||
backgroundColor: theme.colors.palette.red[500],
|
backgroundColor: theme.colors.palette.red[600],
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
justifyContent: "center",
|
justifyContent: "center",
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import { useCallback, useState, type ReactElement } from "react";
|
|||||||
import { router, usePathname } from "expo-router";
|
import { router, usePathname } from "expo-router";
|
||||||
import { StyleSheet, useUnistyles } from "react-native-unistyles";
|
import { StyleSheet, useUnistyles } from "react-native-unistyles";
|
||||||
import { formatTimeAgo } from "@/utils/time";
|
import { formatTimeAgo } from "@/utils/time";
|
||||||
|
import { shortenPath } from "@/utils/shorten-path";
|
||||||
import { type AggregatedAgent } from "@/hooks/use-aggregated-agents";
|
import { type AggregatedAgent } from "@/hooks/use-aggregated-agents";
|
||||||
import { useSessionStore } from "@/stores/session-store";
|
import { useSessionStore } from "@/stores/session-store";
|
||||||
import {
|
import {
|
||||||
@@ -144,7 +145,7 @@ export function AgentList({
|
|||||||
</View>
|
</View>
|
||||||
|
|
||||||
<Text style={styles.secondaryRow} numberOfLines={1}>
|
<Text style={styles.secondaryRow} numberOfLines={1}>
|
||||||
{agent.cwd.replace(/^\/(?:Users|home)\/[^/]+/, "~")} · {timeAgo}
|
{shortenPath(agent.cwd)} · {timeAgo}
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -495,12 +495,6 @@ export function AgentStreamView({
|
|||||||
</View>
|
</View>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
{showWorkingIndicator ? (
|
|
||||||
<View style={stylesheet.workingIndicatorWrapper}>
|
|
||||||
<WorkingIndicator />
|
|
||||||
</View>
|
|
||||||
) : null}
|
|
||||||
|
|
||||||
{hasHeadItems
|
{hasHeadItems
|
||||||
? [...streamHead].reverse().map((item, index) => {
|
? [...streamHead].reverse().map((item, index) => {
|
||||||
const rendered = renderStreamItemContent(item, index);
|
const rendered = renderStreamItemContent(item, index);
|
||||||
@@ -511,6 +505,12 @@ export function AgentStreamView({
|
|||||||
) : null;
|
) : null;
|
||||||
})
|
})
|
||||||
: null}
|
: null}
|
||||||
|
|
||||||
|
{showWorkingIndicator ? (
|
||||||
|
<View style={stylesheet.workingIndicatorWrapper}>
|
||||||
|
<WorkingIndicator />
|
||||||
|
</View>
|
||||||
|
) : null}
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
@@ -555,10 +555,12 @@ export function AgentStreamView({
|
|||||||
}
|
}
|
||||||
ListHeaderComponent={listHeaderComponent}
|
ListHeaderComponent={listHeaderComponent}
|
||||||
extraData={flatListExtraData}
|
extraData={flatListExtraData}
|
||||||
maintainVisibleContentPosition={{
|
maintainVisibleContentPosition={
|
||||||
minIndexForVisible: 0,
|
// Disable when streaming and user is at bottom - we handle auto-scroll ourselves
|
||||||
autoscrollToTopThreshold: 40,
|
agent.status === "running" && isNearBottom
|
||||||
}}
|
? undefined
|
||||||
|
: { minIndexForVisible: 0, autoscrollToTopThreshold: 40 }
|
||||||
|
}
|
||||||
initialNumToRender={12}
|
initialNumToRender={12}
|
||||||
windowSize={10}
|
windowSize={10}
|
||||||
inverted
|
inverted
|
||||||
@@ -777,8 +779,8 @@ function PermissionRequestCard({
|
|||||||
}, [request]);
|
}, [request]);
|
||||||
|
|
||||||
const toolCallDisplay = useMemo(
|
const toolCallDisplay = useMemo(
|
||||||
() => parseToolCallDisplay(request.input, null),
|
() => parseToolCallDisplay(request.name ?? "unknown", request.input, null),
|
||||||
[request.input]
|
[request.name, request.input]
|
||||||
);
|
);
|
||||||
|
|
||||||
const markdownStyles = useMemo(() => createMarkdownStyles(theme), [theme]);
|
const markdownStyles = useMemo(() => createMarkdownStyles(theme), [theme]);
|
||||||
@@ -792,7 +794,7 @@ function PermissionRequestCard({
|
|||||||
styles: any,
|
styles: any,
|
||||||
inheritedStyles: any = {}
|
inheritedStyles: any = {}
|
||||||
) => (
|
) => (
|
||||||
<Text key={node.key} style={[inheritedStyles, styles.text]} selectable>
|
<Text key={node.key} style={[inheritedStyles, styles.text]}>
|
||||||
{node.content}
|
{node.content}
|
||||||
</Text>
|
</Text>
|
||||||
),
|
),
|
||||||
@@ -806,7 +808,6 @@ function PermissionRequestCard({
|
|||||||
<Text
|
<Text
|
||||||
key={node.key}
|
key={node.key}
|
||||||
style={[inheritedStyles, styles.textgroup]}
|
style={[inheritedStyles, styles.textgroup]}
|
||||||
selectable
|
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</Text>
|
</Text>
|
||||||
@@ -821,7 +822,6 @@ function PermissionRequestCard({
|
|||||||
<Text
|
<Text
|
||||||
key={node.key}
|
key={node.key}
|
||||||
style={[inheritedStyles, styles.code_block]}
|
style={[inheritedStyles, styles.code_block]}
|
||||||
selectable
|
|
||||||
>
|
>
|
||||||
{node.content}
|
{node.content}
|
||||||
</Text>
|
</Text>
|
||||||
@@ -833,7 +833,7 @@ function PermissionRequestCard({
|
|||||||
styles: any,
|
styles: any,
|
||||||
inheritedStyles: any = {}
|
inheritedStyles: any = {}
|
||||||
) => (
|
) => (
|
||||||
<Text key={node.key} style={[inheritedStyles, styles.fence]} selectable>
|
<Text key={node.key} style={[inheritedStyles, styles.fence]}>
|
||||||
{node.content}
|
{node.content}
|
||||||
</Text>
|
</Text>
|
||||||
),
|
),
|
||||||
@@ -847,7 +847,6 @@ function PermissionRequestCard({
|
|||||||
<Text
|
<Text
|
||||||
key={node.key}
|
key={node.key}
|
||||||
style={[inheritedStyles, styles.code_inline]}
|
style={[inheritedStyles, styles.code_inline]}
|
||||||
selectable
|
|
||||||
>
|
>
|
||||||
{node.content}
|
{node.content}
|
||||||
</Text>
|
</Text>
|
||||||
@@ -893,7 +892,6 @@ function PermissionRequestCard({
|
|||||||
<Text style={iconStyle}>{bullet}</Text>
|
<Text style={iconStyle}>{bullet}</Text>
|
||||||
<Text
|
<Text
|
||||||
style={[contentStyle, { flex: 1, flexShrink: 1, minWidth: 0 }]}
|
style={[contentStyle, { flex: 1, flexShrink: 1, minWidth: 0 }]}
|
||||||
selectable
|
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</Text>
|
</Text>
|
||||||
|
|||||||
@@ -117,13 +117,13 @@ const styles = StyleSheet.create((theme) => ({
|
|||||||
backgroundColor: "rgba(46, 160, 67, 0.15)",
|
backgroundColor: "rgba(46, 160, 67, 0.15)",
|
||||||
},
|
},
|
||||||
addText: {
|
addText: {
|
||||||
color: theme.colors.palette.green[800],
|
color: theme.colors.foreground,
|
||||||
},
|
},
|
||||||
removeLine: {
|
removeLine: {
|
||||||
backgroundColor: "rgba(248, 81, 73, 0.1)",
|
backgroundColor: "rgba(248, 81, 73, 0.1)",
|
||||||
},
|
},
|
||||||
removeText: {
|
removeText: {
|
||||||
color: theme.colors.palette.red[800],
|
color: theme.colors.foreground,
|
||||||
},
|
},
|
||||||
addHighlight: {
|
addHighlight: {
|
||||||
backgroundColor: "rgba(46, 160, 67, 0.4)",
|
backgroundColor: "rgba(46, 160, 67, 0.4)",
|
||||||
|
|||||||
@@ -14,14 +14,13 @@ import {
|
|||||||
MIN_EXPLORER_SIDEBAR_WIDTH,
|
MIN_EXPLORER_SIDEBAR_WIDTH,
|
||||||
MAX_EXPLORER_SIDEBAR_WIDTH,
|
MAX_EXPLORER_SIDEBAR_WIDTH,
|
||||||
type ViewMode,
|
type ViewMode,
|
||||||
|
type ExplorerTab,
|
||||||
} from "@/stores/panel-store";
|
} from "@/stores/panel-store";
|
||||||
import { useExplorerSidebarAnimation } from "@/contexts/explorer-sidebar-animation-context";
|
import { useExplorerSidebarAnimation } from "@/contexts/explorer-sidebar-animation-context";
|
||||||
import { HEADER_INNER_HEIGHT } from "@/constants/layout";
|
import { HEADER_INNER_HEIGHT } from "@/constants/layout";
|
||||||
import { GitDiffPane } from "./git-diff-pane";
|
import { GitDiffPane } from "./git-diff-pane";
|
||||||
import { FileExplorerPane } from "./file-explorer-pane";
|
import { FileExplorerPane } from "./file-explorer-pane";
|
||||||
|
|
||||||
type ExplorerTab = "changes" | "files";
|
|
||||||
|
|
||||||
interface ExplorerSidebarProps {
|
interface ExplorerSidebarProps {
|
||||||
serverId: string;
|
serverId: string;
|
||||||
agentId: string;
|
agentId: string;
|
||||||
@@ -309,13 +308,11 @@ function SidebarContent({
|
|||||||
|
|
||||||
{/* Content based on active tab */}
|
{/* Content based on active tab */}
|
||||||
<View style={styles.contentArea} testID="explorer-content-area">
|
<View style={styles.contentArea} testID="explorer-content-area">
|
||||||
{activeTab === "changes" ? (
|
{activeTab === "changes" && (
|
||||||
<GitDiffPane serverId={serverId} agentId={agentId} />
|
<GitDiffPane serverId={serverId} agentId={agentId} />
|
||||||
) : (
|
)}
|
||||||
<FileExplorerPane
|
{activeTab === "files" && (
|
||||||
serverId={serverId}
|
<FileExplorerPane serverId={serverId} agentId={agentId} />
|
||||||
agentId={agentId}
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
@@ -130,7 +130,6 @@ export function FileExplorerPane({
|
|||||||
return sorted;
|
return sorted;
|
||||||
}, [rawEntries, sortOption]);
|
}, [rawEntries, sortOption]);
|
||||||
const showInitialListLoading = isListingLoading && entries.length === 0;
|
const showInitialListLoading = isListingLoading && entries.length === 0;
|
||||||
const showListLoadingBanner = isListingLoading && entries.length > 0;
|
|
||||||
const isPreviewLoading = Boolean(
|
const isPreviewLoading = Boolean(
|
||||||
isExplorerLoading &&
|
isExplorerLoading &&
|
||||||
pendingRequest?.mode === "file" &&
|
pendingRequest?.mode === "file" &&
|
||||||
@@ -585,14 +584,9 @@ export function FileExplorerPane({
|
|||||||
<View style={styles.entryIcon}>
|
<View style={styles.entryIcon}>
|
||||||
{renderEntryIcon(displayKind, theme.colors)}
|
{renderEntryIcon(displayKind, theme.colors)}
|
||||||
</View>
|
</View>
|
||||||
<View style={styles.entryTextContainer}>
|
<Text style={styles.entryName} numberOfLines={1}>
|
||||||
<Text style={styles.entryName} numberOfLines={1}>
|
{item.name}
|
||||||
{item.name}
|
</Text>
|
||||||
</Text>
|
|
||||||
<Text style={styles.entryMeta} numberOfLines={1}>
|
|
||||||
{formatFileSize({ size: item.size })} · {formatTimeAgo(new Date(item.modifiedAt))}
|
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
</View>
|
</View>
|
||||||
<Pressable
|
<Pressable
|
||||||
onPress={(event) => handleOpenMenu(item, event)}
|
onPress={(event) => handleOpenMenu(item, event)}
|
||||||
@@ -642,17 +636,9 @@ export function FileExplorerPane({
|
|||||||
<ChevronDown size={14} color={theme.colors.foregroundMuted} />
|
<ChevronDown size={14} color={theme.colors.foregroundMuted} />
|
||||||
</Pressable>
|
</Pressable>
|
||||||
</View>
|
</View>
|
||||||
{showListLoadingBanner && (
|
|
||||||
<View style={styles.loadingBanner}>
|
|
||||||
<ActivityIndicator size="small" />
|
|
||||||
<Text style={styles.loadingBannerText}>
|
|
||||||
Loading {formatDirectoryLabel(activePath)}...
|
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
)}
|
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
}, [activePath, currentSortLabel, explorerState?.history?.length, handleNavigateBack, handleSortCycle, showListLoadingBanner, theme.colors.mutedForeground]);
|
}, [activePath, currentSortLabel, explorerState?.history?.length, handleNavigateBack, handleSortCycle]);
|
||||||
|
|
||||||
// Watch for completed file previews and process queue
|
// Watch for completed file previews and process queue
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -789,6 +775,15 @@ export function FileExplorerPane({
|
|||||||
]}
|
]}
|
||||||
onLayout={handleMenuLayout}
|
onLayout={handleMenuLayout}
|
||||||
>
|
>
|
||||||
|
<View style={styles.entryMenuHeader}>
|
||||||
|
<Text style={styles.entryMenuMeta}>
|
||||||
|
{formatFileSize({ size: menuEntry.size })}
|
||||||
|
</Text>
|
||||||
|
<Text style={styles.entryMenuMeta}>
|
||||||
|
{formatTimeAgo(new Date(menuEntry.modifiedAt))}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
<View style={styles.entryMenuDivider} />
|
||||||
<Pressable
|
<Pressable
|
||||||
style={styles.entryMenuItem}
|
style={styles.entryMenuItem}
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
@@ -866,7 +861,7 @@ export function FileExplorerPane({
|
|||||||
) : (
|
) : (
|
||||||
<View style={styles.sheetCenterState}>
|
<View style={styles.sheetCenterState}>
|
||||||
<Text style={styles.emptyText}>Binary preview unavailable</Text>
|
<Text style={styles.emptyText}>Binary preview unavailable</Text>
|
||||||
<Text style={styles.entryMeta}>
|
<Text style={styles.entryMenuMeta}>
|
||||||
{formatFileSize({ size: preview.size })}
|
{formatFileSize({ size: preview.size })}
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
@@ -1035,17 +1030,6 @@ const styles = StyleSheet.create((theme) => ({
|
|||||||
fontSize: theme.fontSize.lg,
|
fontSize: theme.fontSize.lg,
|
||||||
color: theme.colors.foreground,
|
color: theme.colors.foreground,
|
||||||
},
|
},
|
||||||
loadingBanner: {
|
|
||||||
flexDirection: "row",
|
|
||||||
alignItems: "center",
|
|
||||||
gap: theme.spacing[2],
|
|
||||||
paddingVertical: theme.spacing[2],
|
|
||||||
paddingHorizontal: theme.spacing[3],
|
|
||||||
},
|
|
||||||
loadingBannerText: {
|
|
||||||
color: theme.colors.foregroundMuted,
|
|
||||||
fontSize: theme.fontSize.sm,
|
|
||||||
},
|
|
||||||
sortButton: {
|
sortButton: {
|
||||||
flexDirection: "row",
|
flexDirection: "row",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
@@ -1114,8 +1098,8 @@ const styles = StyleSheet.create((theme) => ({
|
|||||||
flexDirection: "row",
|
flexDirection: "row",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
justifyContent: "space-between",
|
justifyContent: "space-between",
|
||||||
paddingVertical: theme.spacing[2],
|
paddingVertical: theme.spacing[1],
|
||||||
paddingHorizontal: theme.spacing[3],
|
paddingLeft: theme.spacing[2],
|
||||||
borderRadius: theme.borderRadius.md,
|
borderRadius: theme.borderRadius.md,
|
||||||
borderWidth: theme.borderWidth[1],
|
borderWidth: theme.borderWidth[1],
|
||||||
borderColor: theme.colors.border,
|
borderColor: theme.colors.border,
|
||||||
@@ -1128,24 +1112,16 @@ const styles = StyleSheet.create((theme) => ({
|
|||||||
flex: 1,
|
flex: 1,
|
||||||
flexDirection: "row",
|
flexDirection: "row",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
columnGap: theme.spacing[2],
|
gap: theme.spacing[2],
|
||||||
marginRight: theme.spacing[3],
|
minWidth: 0,
|
||||||
},
|
},
|
||||||
entryIcon: {
|
entryIcon: {
|
||||||
width: 28,
|
flexShrink: 0,
|
||||||
alignItems: "center",
|
|
||||||
},
|
|
||||||
entryTextContainer: {
|
|
||||||
flex: 1,
|
|
||||||
},
|
},
|
||||||
entryName: {
|
entryName: {
|
||||||
|
flex: 1,
|
||||||
color: theme.colors.foreground,
|
color: theme.colors.foreground,
|
||||||
fontSize: theme.fontSize.base,
|
fontSize: theme.fontSize.sm,
|
||||||
},
|
|
||||||
entryMeta: {
|
|
||||||
color: theme.colors.foregroundMuted,
|
|
||||||
fontSize: theme.fontSize.xs,
|
|
||||||
marginTop: theme.spacing[1],
|
|
||||||
},
|
},
|
||||||
menuButton: {
|
menuButton: {
|
||||||
width: 36,
|
width: 36,
|
||||||
@@ -1168,6 +1144,22 @@ const styles = StyleSheet.create((theme) => ({
|
|||||||
backgroundColor: theme.colors.surface2,
|
backgroundColor: theme.colors.surface2,
|
||||||
padding: theme.spacing[1],
|
padding: theme.spacing[1],
|
||||||
},
|
},
|
||||||
|
entryMenuHeader: {
|
||||||
|
flexDirection: "row",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
paddingVertical: theme.spacing[2],
|
||||||
|
paddingHorizontal: theme.spacing[3],
|
||||||
|
},
|
||||||
|
entryMenuMeta: {
|
||||||
|
color: theme.colors.foregroundMuted,
|
||||||
|
fontSize: theme.fontSize.xs,
|
||||||
|
},
|
||||||
|
entryMenuDivider: {
|
||||||
|
height: 1,
|
||||||
|
backgroundColor: theme.colors.border,
|
||||||
|
marginHorizontal: theme.spacing[2],
|
||||||
|
marginVertical: theme.spacing[1],
|
||||||
|
},
|
||||||
entryMenuItem: {
|
entryMenuItem: {
|
||||||
paddingVertical: theme.spacing[2],
|
paddingVertical: theme.spacing[2],
|
||||||
paddingHorizontal: theme.spacing[3],
|
paddingHorizontal: theme.spacing[3],
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { View, type StyleProp, type ViewStyle } from "react-native";
|
|||||||
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
||||||
import { StyleSheet, UnistylesRuntime } from "react-native-unistyles";
|
import { StyleSheet, UnistylesRuntime } from "react-native-unistyles";
|
||||||
import { HEADER_INNER_HEIGHT } from "@/constants/layout";
|
import { HEADER_INNER_HEIGHT } from "@/constants/layout";
|
||||||
|
import { useTauriDragHandlers } from "@/utils/tauri-window";
|
||||||
|
|
||||||
interface ScreenHeaderProps {
|
interface ScreenHeaderProps {
|
||||||
left?: ReactNode;
|
left?: ReactNode;
|
||||||
@@ -21,10 +22,14 @@ export function ScreenHeader({ left, right, leftStyle, rightStyle }: ScreenHeade
|
|||||||
// Only add extra padding on mobile for better touch targets; on desktop, only use safe area insets
|
// Only add extra padding on mobile for better touch targets; on desktop, only use safe area insets
|
||||||
const topPadding = isMobile ? 8 : 0;
|
const topPadding = isMobile ? 8 : 0;
|
||||||
|
|
||||||
|
// On Tauri macOS, enable window dragging and double-click to maximize
|
||||||
|
// Left padding for traffic lights is handled by _layout.tsx when sidebar is collapsed
|
||||||
|
const dragHandlers = useTauriDragHandlers();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={styles.header}>
|
<View style={styles.header}>
|
||||||
<View style={[styles.inner, { paddingTop: insets.top + topPadding }]}>
|
<View style={[styles.inner, { paddingTop: insets.top + topPadding }]}>
|
||||||
<View style={styles.row}>
|
<View style={styles.row} {...dragHandlers}>
|
||||||
<View style={[styles.left, leftStyle]}>{left}</View>
|
<View style={[styles.left, leftStyle]}>{left}</View>
|
||||||
<View style={[styles.right, rightStyle]}>{right}</View>
|
<View style={[styles.right, rightStyle]}>{right}</View>
|
||||||
</View>
|
</View>
|
||||||
@@ -46,6 +51,7 @@ const styles = StyleSheet.create((theme) => ({
|
|||||||
paddingHorizontal: theme.spacing[2],
|
paddingHorizontal: theme.spacing[2],
|
||||||
borderBottomWidth: theme.borderWidth[1],
|
borderBottomWidth: theme.borderWidth[1],
|
||||||
borderBottomColor: theme.colors.border,
|
borderBottomColor: theme.colors.border,
|
||||||
|
userSelect: "none",
|
||||||
},
|
},
|
||||||
left: {
|
left: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
|
|||||||
@@ -27,7 +27,6 @@ import Animated, {
|
|||||||
import { useDictation } from "@/hooks/use-dictation";
|
import { useDictation } from "@/hooks/use-dictation";
|
||||||
import { DictationOverlay } from "./dictation-controls";
|
import { DictationOverlay } from "./dictation-controls";
|
||||||
import type { DaemonClientV2 } from "@server/client/daemon-client-v2";
|
import type { DaemonClientV2 } from "@server/client/daemon-client-v2";
|
||||||
import type { SessionContextValue } from "@/contexts/session-context";
|
|
||||||
import { usePanelStore } from "@/stores/panel-store";
|
import { usePanelStore } from "@/stores/panel-store";
|
||||||
|
|
||||||
export interface ImageAttachment {
|
export interface ImageAttachment {
|
||||||
@@ -52,7 +51,6 @@ export interface MessageInputProps {
|
|||||||
onPickImages?: () => void;
|
onPickImages?: () => void;
|
||||||
onRemoveImage?: (index: number) => void;
|
onRemoveImage?: (index: number) => void;
|
||||||
client: DaemonClientV2 | null;
|
client: DaemonClientV2 | null;
|
||||||
sendAgentAudio: SessionContextValue["sendAgentAudio"];
|
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
autoFocus?: boolean;
|
autoFocus?: boolean;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
@@ -104,7 +102,6 @@ export const MessageInput = forwardRef<MessageInputRef, MessageInputProps>(
|
|||||||
onPickImages,
|
onPickImages,
|
||||||
onRemoveImage,
|
onRemoveImage,
|
||||||
client,
|
client,
|
||||||
sendAgentAudio,
|
|
||||||
placeholder = "Message...",
|
placeholder = "Message...",
|
||||||
autoFocus = false,
|
autoFocus = false,
|
||||||
disabled = false,
|
disabled = false,
|
||||||
@@ -200,9 +197,7 @@ export const MessageInput = forwardRef<MessageInputRef, MessageInputProps>(
|
|||||||
retryFailedDictation,
|
retryFailedDictation,
|
||||||
discardFailedDictation,
|
discardFailedDictation,
|
||||||
} = useDictation({
|
} = useDictation({
|
||||||
sendAgentAudio,
|
|
||||||
client,
|
client,
|
||||||
mode: "transcribe_only",
|
|
||||||
onTranscript: handleDictationTranscript,
|
onTranscript: handleDictationTranscript,
|
||||||
onError: handleDictationError,
|
onError: handleDictationError,
|
||||||
canStart: canStartDictation,
|
canStart: canStartDictation,
|
||||||
@@ -538,8 +533,23 @@ export const MessageInput = forwardRef<MessageInputRef, MessageInputProps>(
|
|||||||
{leftContent}
|
{leftContent}
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{/* Right: send/queue button, voice button, rightContent slot */}
|
{/* Right: voice button, contextual button (realtime/send/cancel) */}
|
||||||
<View style={styles.rightButtonGroup}>
|
<View style={styles.rightButtonGroup}>
|
||||||
|
<Pressable
|
||||||
|
onPress={handleVoicePress}
|
||||||
|
disabled={!isConnected || disabled}
|
||||||
|
style={[
|
||||||
|
styles.voiceButton,
|
||||||
|
(!isConnected || disabled) && styles.buttonDisabled,
|
||||||
|
isDictating && styles.voiceButtonRecording,
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
{isDictating ? (
|
||||||
|
<Square size={14} color="white" fill="white" />
|
||||||
|
) : (
|
||||||
|
<Mic size={20} color={theme.colors.foreground} />
|
||||||
|
)}
|
||||||
|
</Pressable>
|
||||||
{shouldShowSendButton && (
|
{shouldShowSendButton && (
|
||||||
<Pressable
|
<Pressable
|
||||||
onPress={handleSendMessage}
|
onPress={handleSendMessage}
|
||||||
@@ -561,22 +571,7 @@ export const MessageInput = forwardRef<MessageInputRef, MessageInputProps>(
|
|||||||
<ArrowUp size={20} color="white" />
|
<ArrowUp size={20} color="white" />
|
||||||
</Pressable>
|
</Pressable>
|
||||||
)}
|
)}
|
||||||
<Pressable
|
{!shouldShowSendButton && rightContent}
|
||||||
onPress={handleVoicePress}
|
|
||||||
disabled={!isConnected || disabled}
|
|
||||||
style={[
|
|
||||||
styles.voiceButton,
|
|
||||||
(!isConnected || disabled) && styles.buttonDisabled,
|
|
||||||
isDictating && styles.voiceButtonRecording,
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
{isDictating ? (
|
|
||||||
<Square size={14} color="white" fill="white" />
|
|
||||||
) : (
|
|
||||||
<Mic size={20} color={theme.colors.foreground} />
|
|
||||||
)}
|
|
||||||
</Pressable>
|
|
||||||
{rightContent}
|
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
</Animated.View>
|
</Animated.View>
|
||||||
|
|||||||
@@ -387,16 +387,21 @@ const expandableBadgeStylesheet = StyleSheet.create((theme) => ({
|
|||||||
flexShrink: 0,
|
flexShrink: 0,
|
||||||
},
|
},
|
||||||
detailWrapper: {
|
detailWrapper: {
|
||||||
marginTop: theme.spacing[2],
|
borderBottomLeftRadius: theme.borderRadius.lg,
|
||||||
borderRadius: theme.borderRadius.md,
|
borderBottomRightRadius: theme.borderRadius.lg,
|
||||||
borderWidth: theme.borderWidth[1],
|
borderWidth: theme.borderWidth[1],
|
||||||
borderColor: theme.colors.border,
|
borderTopWidth: 0,
|
||||||
|
borderColor: theme.colors.borderAccent,
|
||||||
backgroundColor: theme.colors.surface0,
|
backgroundColor: theme.colors.surface0,
|
||||||
padding: theme.spacing[2],
|
padding: theme.spacing[2],
|
||||||
gap: theme.spacing[2],
|
gap: theme.spacing[2],
|
||||||
flexShrink: 1,
|
flexShrink: 1,
|
||||||
minWidth: 0,
|
minWidth: 0,
|
||||||
},
|
},
|
||||||
|
pressableExpanded: {
|
||||||
|
borderBottomLeftRadius: 0,
|
||||||
|
borderBottomRightRadius: 0,
|
||||||
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
function isLikelyPathToken(value: string): boolean {
|
function isLikelyPathToken(value: string): boolean {
|
||||||
@@ -511,7 +516,7 @@ export const AssistantMessage = memo(function AssistantMessage({
|
|||||||
styles: any,
|
styles: any,
|
||||||
inheritedStyles: any = {}
|
inheritedStyles: any = {}
|
||||||
) => (
|
) => (
|
||||||
<Text key={node.key} style={[inheritedStyles, styles.text]} selectable>
|
<Text key={node.key} style={[inheritedStyles, styles.text]}>
|
||||||
{node.content}
|
{node.content}
|
||||||
</Text>
|
</Text>
|
||||||
),
|
),
|
||||||
@@ -525,7 +530,6 @@ export const AssistantMessage = memo(function AssistantMessage({
|
|||||||
<Text
|
<Text
|
||||||
key={node.key}
|
key={node.key}
|
||||||
style={[inheritedStyles, styles.textgroup]}
|
style={[inheritedStyles, styles.textgroup]}
|
||||||
selectable
|
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</Text>
|
</Text>
|
||||||
@@ -540,7 +544,6 @@ export const AssistantMessage = memo(function AssistantMessage({
|
|||||||
<Text
|
<Text
|
||||||
key={node.key}
|
key={node.key}
|
||||||
style={[inheritedStyles, styles.code_block]}
|
style={[inheritedStyles, styles.code_block]}
|
||||||
selectable
|
|
||||||
>
|
>
|
||||||
{node.content}
|
{node.content}
|
||||||
</Text>
|
</Text>
|
||||||
@@ -552,7 +555,7 @@ export const AssistantMessage = memo(function AssistantMessage({
|
|||||||
styles: any,
|
styles: any,
|
||||||
inheritedStyles: any = {}
|
inheritedStyles: any = {}
|
||||||
) => (
|
) => (
|
||||||
<Text key={node.key} style={[inheritedStyles, styles.fence]} selectable>
|
<Text key={node.key} style={[inheritedStyles, styles.fence]}>
|
||||||
{node.content}
|
{node.content}
|
||||||
</Text>
|
</Text>
|
||||||
),
|
),
|
||||||
@@ -576,7 +579,6 @@ export const AssistantMessage = memo(function AssistantMessage({
|
|||||||
inheritedStyles,
|
inheritedStyles,
|
||||||
assistantMessageStylesheet.markdownCodeInline,
|
assistantMessageStylesheet.markdownCodeInline,
|
||||||
]}
|
]}
|
||||||
selectable
|
|
||||||
>
|
>
|
||||||
{content}
|
{content}
|
||||||
</Text>
|
</Text>
|
||||||
@@ -1153,6 +1155,7 @@ const ExpandableBadge = memo(function ExpandableBadge({
|
|||||||
pressed && hasDetails
|
pressed && hasDetails
|
||||||
? expandableBadgeStylesheet.pressablePressed
|
? expandableBadgeStylesheet.pressablePressed
|
||||||
: null,
|
: null,
|
||||||
|
isExpanded && expandableBadgeStylesheet.pressableExpanded,
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<View style={expandableBadgeStylesheet.headerRow}>
|
<View style={expandableBadgeStylesheet.headerRow}>
|
||||||
@@ -1181,12 +1184,12 @@ const ExpandableBadge = memo(function ExpandableBadge({
|
|||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
</View>
|
</View>
|
||||||
{detailContent ? (
|
|
||||||
<View style={expandableBadgeStylesheet.detailWrapper}>
|
|
||||||
{detailContent}
|
|
||||||
</View>
|
|
||||||
) : null}
|
|
||||||
</Pressable>
|
</Pressable>
|
||||||
|
{detailContent ? (
|
||||||
|
<View style={expandableBadgeStylesheet.detailWrapper}>
|
||||||
|
{detailContent}
|
||||||
|
</View>
|
||||||
|
) : null}
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -1226,7 +1229,7 @@ export const AgentThoughtMessage = memo(function AgentThoughtMessage({
|
|||||||
styles: any,
|
styles: any,
|
||||||
inheritedStyles: any = {}
|
inheritedStyles: any = {}
|
||||||
) => (
|
) => (
|
||||||
<Text key={node.key} style={[inheritedStyles, styles.text]} selectable>
|
<Text key={node.key} style={[inheritedStyles, styles.text]}>
|
||||||
{node.content}
|
{node.content}
|
||||||
</Text>
|
</Text>
|
||||||
),
|
),
|
||||||
@@ -1240,7 +1243,6 @@ export const AgentThoughtMessage = memo(function AgentThoughtMessage({
|
|||||||
<Text
|
<Text
|
||||||
key={node.key}
|
key={node.key}
|
||||||
style={[inheritedStyles, styles.textgroup]}
|
style={[inheritedStyles, styles.textgroup]}
|
||||||
selectable
|
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</Text>
|
</Text>
|
||||||
@@ -1255,7 +1257,6 @@ export const AgentThoughtMessage = memo(function AgentThoughtMessage({
|
|||||||
<Text
|
<Text
|
||||||
key={node.key}
|
key={node.key}
|
||||||
style={[inheritedStyles, styles.code_block]}
|
style={[inheritedStyles, styles.code_block]}
|
||||||
selectable
|
|
||||||
>
|
>
|
||||||
{node.content}
|
{node.content}
|
||||||
</Text>
|
</Text>
|
||||||
@@ -1267,7 +1268,7 @@ export const AgentThoughtMessage = memo(function AgentThoughtMessage({
|
|||||||
styles: any,
|
styles: any,
|
||||||
inheritedStyles: any = {}
|
inheritedStyles: any = {}
|
||||||
) => (
|
) => (
|
||||||
<Text key={node.key} style={[inheritedStyles, styles.fence]} selectable>
|
<Text key={node.key} style={[inheritedStyles, styles.fence]}>
|
||||||
{node.content}
|
{node.content}
|
||||||
</Text>
|
</Text>
|
||||||
),
|
),
|
||||||
@@ -1281,7 +1282,6 @@ export const AgentThoughtMessage = memo(function AgentThoughtMessage({
|
|||||||
<Text
|
<Text
|
||||||
key={node.key}
|
key={node.key}
|
||||||
style={[inheritedStyles, styles.code_inline]}
|
style={[inheritedStyles, styles.code_inline]}
|
||||||
selectable
|
|
||||||
>
|
>
|
||||||
{node.content}
|
{node.content}
|
||||||
</Text>
|
</Text>
|
||||||
@@ -1432,7 +1432,7 @@ export const ToolCall = memo(function ToolCall({
|
|||||||
args !== undefined || result !== undefined || error !== undefined;
|
args !== undefined || result !== undefined || error !== undefined;
|
||||||
|
|
||||||
// Parse tool call details for inline rendering
|
// Parse tool call details for inline rendering
|
||||||
const { display, errorText } = useToolCallDetails({ args, result, error });
|
const { display, errorText } = useToolCallDetails({ toolName, args, result, error });
|
||||||
|
|
||||||
const handleToggle = useCallback(() => {
|
const handleToggle = useCallback(() => {
|
||||||
if (!isMobile && isPerfLoggingEnabled()) {
|
if (!isMobile && isPerfLoggingEnabled()) {
|
||||||
@@ -1498,7 +1498,7 @@ export const ToolCall = memo(function ToolCall({
|
|||||||
return (
|
return (
|
||||||
<ExpandableBadge
|
<ExpandableBadge
|
||||||
testID="tool-call-badge"
|
testID="tool-call-badge"
|
||||||
label={toolName}
|
label={display.toolName}
|
||||||
secondaryLabel={principalParam}
|
secondaryLabel={principalParam}
|
||||||
icon={IconComponent}
|
icon={IconComponent}
|
||||||
isExpanded={!isMobile && isExpanded}
|
isExpanded={!isMobile && isExpanded}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import { usePanelStore } from "@/stores/panel-store";
|
|||||||
import { AgentList } from "./agent-list";
|
import { AgentList } from "./agent-list";
|
||||||
import { useAggregatedAgents } from "@/hooks/use-aggregated-agents";
|
import { useAggregatedAgents } from "@/hooks/use-aggregated-agents";
|
||||||
import { useSidebarAnimation } from "@/contexts/sidebar-animation-context";
|
import { useSidebarAnimation } from "@/contexts/sidebar-animation-context";
|
||||||
|
import { useTauriDragHandlers, useTrafficLightPadding } from "@/utils/tauri-window";
|
||||||
|
|
||||||
const DESKTOP_SIDEBAR_WIDTH = 320;
|
const DESKTOP_SIDEBAR_WIDTH = 320;
|
||||||
const SIDEBAR_AGENT_LIMIT = 15;
|
const SIDEBAR_AGENT_LIMIT = 15;
|
||||||
@@ -44,6 +45,8 @@ export function SlidingSidebar({ selectedAgentId }: SlidingSidebarProps) {
|
|||||||
animateToClose,
|
animateToClose,
|
||||||
isGesturing,
|
isGesturing,
|
||||||
} = useSidebarAnimation();
|
} = useSidebarAnimation();
|
||||||
|
const trafficLightPadding = useTrafficLightPadding();
|
||||||
|
const dragHandlers = useTauriDragHandlers();
|
||||||
|
|
||||||
// Track user-initiated refresh to avoid showing spinner on background revalidation
|
// Track user-initiated refresh to avoid showing spinner on background revalidation
|
||||||
const [isManualRefresh, setIsManualRefresh] = useState(false);
|
const [isManualRefresh, setIsManualRefresh] = useState(false);
|
||||||
@@ -249,8 +252,11 @@ export function SlidingSidebar({ selectedAgentId }: SlidingSidebarProps) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={[styles.desktopSidebar, { width: DESKTOP_SIDEBAR_WIDTH }]}>
|
<View style={[styles.desktopSidebar, { width: DESKTOP_SIDEBAR_WIDTH }]}>
|
||||||
{/* Header: New Agent button */}
|
{/* Header: New Agent button - top padding area is draggable on Tauri */}
|
||||||
<View style={styles.sidebarHeader}>
|
<View
|
||||||
|
style={[styles.sidebarHeader, { paddingTop: trafficLightPadding.top || styles.sidebarHeader.paddingTop }]}
|
||||||
|
{...dragHandlers}
|
||||||
|
>
|
||||||
<Pressable
|
<Pressable
|
||||||
style={({ hovered }) => [
|
style={({ hovered }) => [
|
||||||
styles.newAgentButton,
|
styles.newAgentButton,
|
||||||
@@ -316,6 +322,7 @@ const styles = StyleSheet.create((theme) => ({
|
|||||||
paddingHorizontal: theme.spacing[4],
|
paddingHorizontal: theme.spacing[4],
|
||||||
paddingTop: theme.spacing[4],
|
paddingTop: theme.spacing[4],
|
||||||
paddingBottom: theme.spacing[2],
|
paddingBottom: theme.spacing[2],
|
||||||
|
userSelect: "none",
|
||||||
},
|
},
|
||||||
newAgentButton: {
|
newAgentButton: {
|
||||||
flexDirection: "row",
|
flexDirection: "row",
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { getNowMs, isPerfLoggingEnabled, perfLog } from "@/utils/perf";
|
|||||||
import {
|
import {
|
||||||
parseToolCallDisplay,
|
parseToolCallDisplay,
|
||||||
buildLineDiff,
|
buildLineDiff,
|
||||||
|
parseUnifiedDiff,
|
||||||
type ToolCallDisplay,
|
type ToolCallDisplay,
|
||||||
} from "@/utils/tool-call-parsers";
|
} from "@/utils/tool-call-parsers";
|
||||||
import { DiffViewer } from "./diff-viewer";
|
import { DiffViewer } from "./diff-viewer";
|
||||||
@@ -14,6 +15,7 @@ import { DiffViewer } from "./diff-viewer";
|
|||||||
// ---- Types ----
|
// ---- Types ----
|
||||||
|
|
||||||
export interface ToolCallDetailsData {
|
export interface ToolCallDetailsData {
|
||||||
|
toolName: string;
|
||||||
args?: unknown;
|
args?: unknown;
|
||||||
result?: unknown;
|
result?: unknown;
|
||||||
error?: unknown;
|
error?: unknown;
|
||||||
@@ -139,6 +141,10 @@ export function ToolCallDetailsContent({
|
|||||||
// Compute diff lines for edit type
|
// Compute diff lines for edit type
|
||||||
const diffLines = useMemo(() => {
|
const diffLines = useMemo(() => {
|
||||||
if (display.type !== "edit") return undefined;
|
if (display.type !== "edit") return undefined;
|
||||||
|
// Use pre-computed unified diff if available (e.g., from apply_patch)
|
||||||
|
if (display.unifiedDiff) {
|
||||||
|
return parseUnifiedDiff(display.unifiedDiff);
|
||||||
|
}
|
||||||
return buildLineDiff(display.oldString, display.newString);
|
return buildLineDiff(display.oldString, display.newString);
|
||||||
}, [display]);
|
}, [display]);
|
||||||
|
|
||||||
@@ -304,12 +310,12 @@ export function ToolCallDetailsContent({
|
|||||||
// ---- Hook for parsing tool call data ----
|
// ---- Hook for parsing tool call data ----
|
||||||
|
|
||||||
export function useToolCallDetails(data: ToolCallDetailsData) {
|
export function useToolCallDetails(data: ToolCallDetailsData) {
|
||||||
const { args, result, error } = data;
|
const { toolName, args, result, error } = data;
|
||||||
|
|
||||||
return useMemo(() => {
|
return useMemo(() => {
|
||||||
const shouldLog = isPerfLoggingEnabled();
|
const shouldLog = isPerfLoggingEnabled();
|
||||||
const startMs = shouldLog ? getNowMs() : 0;
|
const startMs = shouldLog ? getNowMs() : 0;
|
||||||
const display = parseToolCallDisplay(args, result);
|
const display = parseToolCallDisplay(toolName, args, result);
|
||||||
const errorText = error !== undefined ? formatValue(error) : undefined;
|
const errorText = error !== undefined ? formatValue(error) : undefined;
|
||||||
if (shouldLog) {
|
if (shouldLog) {
|
||||||
const durationMs = getNowMs() - startMs;
|
const durationMs = getNowMs() - startMs;
|
||||||
@@ -329,7 +335,7 @@ export function useToolCallDetails(data: ToolCallDetailsData) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return { display, errorText };
|
return { display, errorText };
|
||||||
}, [args, result, error]);
|
}, [toolName, args, result, error]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---- Styles ----
|
// ---- Styles ----
|
||||||
|
|||||||
@@ -144,7 +144,7 @@ function ToolCallSheetContent({ data, onClose }: ToolCallSheetContentProps) {
|
|||||||
? toolKindIcons[kind.toLowerCase()] || Wrench
|
? toolKindIcons[kind.toLowerCase()] || Wrench
|
||||||
: Wrench;
|
: Wrench;
|
||||||
|
|
||||||
const { display, errorText } = useToolCallDetails({ args, result, error });
|
const { display, errorText } = useToolCallDetails({ toolName, args, result, error });
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={styles.container}>
|
<View style={styles.container}>
|
||||||
@@ -153,7 +153,7 @@ function ToolCallSheetContent({ data, onClose }: ToolCallSheetContentProps) {
|
|||||||
<View style={styles.headerLeft}>
|
<View style={styles.headerLeft}>
|
||||||
<IconComponent size={20} color={styles.headerIcon.color} />
|
<IconComponent size={20} color={styles.headerIcon.color} />
|
||||||
<Text style={styles.headerTitle} numberOfLines={1}>
|
<Text style={styles.headerTitle} numberOfLines={1}>
|
||||||
{toolName}
|
{display.toolName}
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
<Pressable onPress={onClose} style={styles.closeButton}>
|
<Pressable onPress={onClose} style={styles.closeButton}>
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { Platform } from "react-native";
|
||||||
|
|
||||||
export const FOOTER_HEIGHT = 75;
|
export const FOOTER_HEIGHT = 75;
|
||||||
|
|
||||||
// Shared header inner height (excluding safe area insets and border)
|
// Shared header inner height (excluding safe area insets and border)
|
||||||
@@ -7,3 +9,43 @@ export const HEADER_INNER_HEIGHT = 48;
|
|||||||
|
|
||||||
// Max width for chat content (stream view, input area, new agent form)
|
// Max width for chat content (stream view, input area, new agent form)
|
||||||
export const MAX_CONTENT_WIDTH = 820;
|
export const MAX_CONTENT_WIDTH = 820;
|
||||||
|
|
||||||
|
// Tauri desktop app constants for macOS traffic light buttons
|
||||||
|
// These buttons (close/minimize/maximize) overlay the top-left corner
|
||||||
|
export const TAURI_TRAFFIC_LIGHT_WIDTH = 78;
|
||||||
|
export const TAURI_TRAFFIC_LIGHT_HEIGHT = 56;
|
||||||
|
|
||||||
|
// Check if running in Tauri desktop app on macOS
|
||||||
|
function isTauriMac(): boolean {
|
||||||
|
if (Platform.OS !== "web") return false;
|
||||||
|
if (typeof window === "undefined") return false;
|
||||||
|
if (!("__TAURI__" in window)) return false;
|
||||||
|
// Check for macOS via user agent
|
||||||
|
const ua = navigator.userAgent;
|
||||||
|
return ua.includes("Mac OS") || ua.includes("Macintosh");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cached result - only cache true, keep checking if false (in case __TAURI__ loads later)
|
||||||
|
let _isTauriMacCached: boolean | null = null;
|
||||||
|
|
||||||
|
export function getIsTauriMac(): boolean {
|
||||||
|
if (_isTauriMacCached === true) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
const result = isTauriMac();
|
||||||
|
if (result) {
|
||||||
|
_isTauriMacCached = true;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get traffic light padding values (only non-zero on Tauri macOS)
|
||||||
|
export function getTrafficLightPadding(): { left: number; top: number } {
|
||||||
|
if (!getIsTauriMac()) {
|
||||||
|
return { left: 0, top: 0 };
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
left: TAURI_TRAFFIC_LIGHT_WIDTH,
|
||||||
|
top: TAURI_TRAFFIC_LIGHT_HEIGHT,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
@@ -352,12 +352,6 @@ export interface SessionContextValue {
|
|||||||
message: string,
|
message: string,
|
||||||
images?: Array<{ uri: string; mimeType?: string }>
|
images?: Array<{ uri: string; mimeType?: string }>
|
||||||
) => Promise<void>;
|
) => Promise<void>;
|
||||||
sendAgentAudio: (
|
|
||||||
agentId: string | undefined,
|
|
||||||
audioBlob: Blob,
|
|
||||||
requestId?: string,
|
|
||||||
options?: { mode?: "transcribe_only" | "auto_run" }
|
|
||||||
) => Promise<void>;
|
|
||||||
deleteAgent: (agentId: string) => void;
|
deleteAgent: (agentId: string) => void;
|
||||||
createAgent: (options: {
|
createAgent: (options: {
|
||||||
config: any;
|
config: any;
|
||||||
@@ -1972,69 +1966,6 @@ export function SessionProvider({
|
|||||||
[client]
|
[client]
|
||||||
);
|
);
|
||||||
|
|
||||||
const sendAgentAudio = useCallback(
|
|
||||||
async (
|
|
||||||
agentId: string | undefined,
|
|
||||||
audioBlob: Blob,
|
|
||||||
requestId?: string,
|
|
||||||
options?: { mode?: "transcribe_only" | "auto_run" }
|
|
||||||
) => {
|
|
||||||
try {
|
|
||||||
if (!client) {
|
|
||||||
throw new Error("Daemon client unavailable");
|
|
||||||
}
|
|
||||||
if (!client.isConnected) {
|
|
||||||
throw new Error("Daemon client is disconnected");
|
|
||||||
}
|
|
||||||
const resolvedRequestId = requestId ?? generateMessageId();
|
|
||||||
const arrayBuffer = await audioBlob.arrayBuffer();
|
|
||||||
const bytes = new Uint8Array(arrayBuffer);
|
|
||||||
let binary = "";
|
|
||||||
for (let i = 0; i < bytes.length; i++) {
|
|
||||||
binary += String.fromCharCode(bytes[i]);
|
|
||||||
}
|
|
||||||
const base64Audio = btoa(binary);
|
|
||||||
|
|
||||||
const deriveFormat = (mimeType: string | undefined): string => {
|
|
||||||
if (!mimeType || mimeType.length === 0) {
|
|
||||||
return "webm";
|
|
||||||
}
|
|
||||||
const slashIndex = mimeType.indexOf("/");
|
|
||||||
let formatPart =
|
|
||||||
slashIndex >= 0 ? mimeType.slice(slashIndex + 1) : mimeType;
|
|
||||||
const semicolonIndex = formatPart.indexOf(";");
|
|
||||||
if (semicolonIndex >= 0) {
|
|
||||||
formatPart = formatPart.slice(0, semicolonIndex);
|
|
||||||
}
|
|
||||||
return formatPart.trim().length > 0 ? formatPart.trim() : "webm";
|
|
||||||
};
|
|
||||||
|
|
||||||
const format = deriveFormat(audioBlob.type);
|
|
||||||
await client.sendAgentAudio({
|
|
||||||
...(agentId ? { agentId } : {}),
|
|
||||||
audio: base64Audio,
|
|
||||||
format,
|
|
||||||
isLast: true,
|
|
||||||
requestId: resolvedRequestId,
|
|
||||||
...(options?.mode ? { mode: options.mode } : {}),
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log(
|
|
||||||
"[Session] Sent audio:",
|
|
||||||
agentId ?? "(no agent)",
|
|
||||||
format,
|
|
||||||
audioBlob.size,
|
|
||||||
"bytes",
|
|
||||||
`(requestId: ${resolvedRequestId})`
|
|
||||||
);
|
|
||||||
} catch (error) {
|
|
||||||
console.error("[Session] Failed to send audio:", error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[client]
|
|
||||||
);
|
|
||||||
|
|
||||||
const createAgent = useCallback(
|
const createAgent = useCallback(
|
||||||
async ({
|
async ({
|
||||||
config,
|
config,
|
||||||
@@ -2344,7 +2275,6 @@ export function SessionProvider({
|
|||||||
cancelAgentRun,
|
cancelAgentRun,
|
||||||
deleteAgent,
|
deleteAgent,
|
||||||
sendAgentMessage,
|
sendAgentMessage,
|
||||||
sendAgentAudio,
|
|
||||||
createAgent,
|
createAgent,
|
||||||
setAgentMode,
|
setAgentMode,
|
||||||
respondToPermission,
|
respondToPermission,
|
||||||
@@ -2368,7 +2298,6 @@ export function SessionProvider({
|
|||||||
cancelAgentRun,
|
cancelAgentRun,
|
||||||
deleteAgent,
|
deleteAgent,
|
||||||
sendAgentMessage,
|
sendAgentMessage,
|
||||||
sendAgentAudio,
|
|
||||||
createAgent,
|
createAgent,
|
||||||
setAgentMode,
|
setAgentMode,
|
||||||
respondToPermission,
|
respondToPermission,
|
||||||
@@ -2394,7 +2323,6 @@ export function SessionProvider({
|
|||||||
refreshSession,
|
refreshSession,
|
||||||
cancelAgentRun,
|
cancelAgentRun,
|
||||||
sendAgentMessage,
|
sendAgentMessage,
|
||||||
sendAgentAudio,
|
|
||||||
deleteAgent,
|
deleteAgent,
|
||||||
createAgent,
|
createAgent,
|
||||||
setAgentMode,
|
setAgentMode,
|
||||||
@@ -2415,7 +2343,6 @@ export function SessionProvider({
|
|||||||
refreshSession,
|
refreshSession,
|
||||||
cancelAgentRun,
|
cancelAgentRun,
|
||||||
sendAgentMessage,
|
sendAgentMessage,
|
||||||
sendAgentAudio,
|
|
||||||
deleteAgent,
|
deleteAgent,
|
||||||
createAgent,
|
createAgent,
|
||||||
setAgentMode,
|
setAgentMode,
|
||||||
|
|||||||
@@ -8,20 +8,47 @@ import type {
|
|||||||
AgentModelDefinition,
|
AgentModelDefinition,
|
||||||
AgentProvider,
|
AgentProvider,
|
||||||
} from "@server/server/agent/agent-sdk-types";
|
} from "@server/server/agent/agent-sdk-types";
|
||||||
import type { ProviderModelState } from "@/stores/session-store";
|
|
||||||
import { useSessionStore } from "@/stores/session-store";
|
import { useSessionStore } from "@/stores/session-store";
|
||||||
import { useFormPreferences } from "./use-form-preferences";
|
import { useFormPreferences, type FormPreferences } from "./use-form-preferences";
|
||||||
|
|
||||||
export type CreateAgentInitialValues = {
|
// Explicit overrides from URL params or "New Agent" button
|
||||||
workingDir?: string;
|
export interface FormInitialValues {
|
||||||
|
serverId?: string | null;
|
||||||
provider?: AgentProvider;
|
provider?: AgentProvider;
|
||||||
modeId?: string | null;
|
modeId?: string | null;
|
||||||
model?: string | null;
|
model?: string | null;
|
||||||
|
workingDir?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tracks which fields the user has explicitly modified in this session
|
||||||
|
interface UserModifiedFields {
|
||||||
|
serverId: boolean;
|
||||||
|
provider: boolean;
|
||||||
|
modeId: boolean;
|
||||||
|
model: boolean;
|
||||||
|
workingDir: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const INITIAL_USER_MODIFIED: UserModifiedFields = {
|
||||||
|
serverId: false,
|
||||||
|
provider: false,
|
||||||
|
modeId: false,
|
||||||
|
model: false,
|
||||||
|
workingDir: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Internal form state
|
||||||
|
interface FormState {
|
||||||
|
serverId: string | null;
|
||||||
|
provider: AgentProvider;
|
||||||
|
modeId: string;
|
||||||
|
model: string;
|
||||||
|
workingDir: string;
|
||||||
|
}
|
||||||
|
|
||||||
type UseAgentFormStateOptions = {
|
type UseAgentFormStateOptions = {
|
||||||
initialServerId?: string | null;
|
initialServerId?: string | null;
|
||||||
initialValues?: CreateAgentInitialValues;
|
initialValues?: FormInitialValues;
|
||||||
isVisible?: boolean;
|
isVisible?: boolean;
|
||||||
isCreateFlow?: boolean;
|
isCreateFlow?: boolean;
|
||||||
isTargetDaemonReady?: boolean;
|
isTargetDaemonReady?: boolean;
|
||||||
@@ -66,6 +93,106 @@ const DEFAULT_PROVIDER: AgentProvider = fallbackDefinition?.id ?? "claude";
|
|||||||
const DEFAULT_MODE_FOR_DEFAULT_PROVIDER =
|
const DEFAULT_MODE_FOR_DEFAULT_PROVIDER =
|
||||||
fallbackDefinition?.defaultModeId ?? "";
|
fallbackDefinition?.defaultModeId ?? "";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pure function that resolves form state from multiple data sources.
|
||||||
|
* Priority: explicit (URL params) > preferences > provider defaults > fallback
|
||||||
|
*
|
||||||
|
* Only resolves fields that haven't been user-modified.
|
||||||
|
*/
|
||||||
|
function resolveFormState(
|
||||||
|
initialValues: FormInitialValues | undefined,
|
||||||
|
preferences: FormPreferences | null,
|
||||||
|
availableModels: AgentModelDefinition[] | null,
|
||||||
|
userModified: UserModifiedFields,
|
||||||
|
currentState: FormState
|
||||||
|
): FormState {
|
||||||
|
// Start with current state - we only update non-user-modified fields
|
||||||
|
const result = { ...currentState };
|
||||||
|
|
||||||
|
// 1. Resolve provider first (other fields depend on it)
|
||||||
|
if (!userModified.provider) {
|
||||||
|
if (initialValues?.provider && providerDefinitionMap.has(initialValues.provider)) {
|
||||||
|
result.provider = initialValues.provider;
|
||||||
|
} else if (
|
||||||
|
preferences?.provider &&
|
||||||
|
providerDefinitionMap.has(preferences.provider as AgentProvider)
|
||||||
|
) {
|
||||||
|
result.provider = preferences.provider as AgentProvider;
|
||||||
|
}
|
||||||
|
// else keep current (initialized to DEFAULT_PROVIDER)
|
||||||
|
}
|
||||||
|
|
||||||
|
const providerDef = providerDefinitionMap.get(result.provider);
|
||||||
|
const providerPrefs = preferences?.providerPreferences?.[result.provider];
|
||||||
|
|
||||||
|
// 2. Resolve modeId (depends on provider)
|
||||||
|
if (!userModified.modeId) {
|
||||||
|
const validModeIds = providerDef?.modes.map((m) => m.id) ?? [];
|
||||||
|
|
||||||
|
if (
|
||||||
|
typeof initialValues?.modeId === "string" &&
|
||||||
|
initialValues.modeId.length > 0 &&
|
||||||
|
validModeIds.includes(initialValues.modeId)
|
||||||
|
) {
|
||||||
|
result.modeId = initialValues.modeId;
|
||||||
|
} else if (
|
||||||
|
providerPrefs?.mode &&
|
||||||
|
validModeIds.includes(providerPrefs.mode)
|
||||||
|
) {
|
||||||
|
result.modeId = providerPrefs.mode;
|
||||||
|
} else {
|
||||||
|
result.modeId = providerDef?.defaultModeId ?? validModeIds[0] ?? "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Resolve model (depends on provider + availableModels)
|
||||||
|
if (!userModified.model) {
|
||||||
|
const isValidModel = (m: string) =>
|
||||||
|
availableModels?.some((am) => am.id === m) ?? false;
|
||||||
|
|
||||||
|
if (
|
||||||
|
typeof initialValues?.model === "string" &&
|
||||||
|
initialValues.model.length > 0
|
||||||
|
) {
|
||||||
|
// If models aren't loaded yet, trust the initial value
|
||||||
|
// It will be validated once models load
|
||||||
|
if (!availableModels || isValidModel(initialValues.model)) {
|
||||||
|
result.model = initialValues.model;
|
||||||
|
} else if (providerPrefs?.model && isValidModel(providerPrefs.model)) {
|
||||||
|
result.model = providerPrefs.model;
|
||||||
|
} else {
|
||||||
|
result.model = "";
|
||||||
|
}
|
||||||
|
} else if (providerPrefs?.model && isValidModel(providerPrefs.model)) {
|
||||||
|
result.model = providerPrefs.model;
|
||||||
|
} else {
|
||||||
|
result.model = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Resolve serverId (independent)
|
||||||
|
if (!userModified.serverId) {
|
||||||
|
if (initialValues?.serverId !== undefined) {
|
||||||
|
result.serverId = initialValues.serverId;
|
||||||
|
} else if (preferences?.serverId) {
|
||||||
|
result.serverId = preferences.serverId;
|
||||||
|
}
|
||||||
|
// else keep current
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. Resolve workingDir (independent)
|
||||||
|
if (!userModified.workingDir) {
|
||||||
|
if (initialValues?.workingDir !== undefined) {
|
||||||
|
result.workingDir = initialValues.workingDir;
|
||||||
|
} else if (preferences?.workingDir) {
|
||||||
|
result.workingDir = preferences.workingDir;
|
||||||
|
}
|
||||||
|
// else keep current (empty string)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
export function useAgentFormState(
|
export function useAgentFormState(
|
||||||
options: UseAgentFormStateOptions = {}
|
options: UseAgentFormStateOptions = {}
|
||||||
): UseAgentFormStateResult {
|
): UseAgentFormStateResult {
|
||||||
@@ -80,29 +207,36 @@ export function useAgentFormState(
|
|||||||
const {
|
const {
|
||||||
preferences,
|
preferences,
|
||||||
isLoading: isPreferencesLoading,
|
isLoading: isPreferencesLoading,
|
||||||
getProviderPreferences,
|
|
||||||
updatePreferences,
|
updatePreferences,
|
||||||
updateProviderPreferences,
|
updateProviderPreferences,
|
||||||
} = useFormPreferences();
|
} = useFormPreferences();
|
||||||
|
|
||||||
const [selectedServerId, setSelectedServerId] = useState<string | null>(
|
// Track which fields the user has explicitly modified
|
||||||
initialServerId
|
const [userModified, setUserModified] = useState<UserModifiedFields>(INITIAL_USER_MODIFIED);
|
||||||
);
|
|
||||||
const [workingDir, setWorkingDir] = useState("");
|
|
||||||
const [selectedProvider, setSelectedProvider] =
|
|
||||||
useState<AgentProvider>(DEFAULT_PROVIDER);
|
|
||||||
const [selectedMode, setSelectedMode] = useState(
|
|
||||||
DEFAULT_MODE_FOR_DEFAULT_PROVIDER
|
|
||||||
);
|
|
||||||
const [selectedModel, setSelectedModel] = useState("");
|
|
||||||
|
|
||||||
const hasHydratedRef = useRef(false);
|
// Form state
|
||||||
const hasAppliedInitialValuesRef = useRef(false);
|
const [formState, setFormState] = useState<FormState>(() => ({
|
||||||
const providerModelRequestTimersRef = useRef<
|
serverId: initialServerId,
|
||||||
Map<string, ReturnType<typeof setTimeout>>
|
provider: DEFAULT_PROVIDER,
|
||||||
>(new Map());
|
modeId: DEFAULT_MODE_FOR_DEFAULT_PROVIDER,
|
||||||
|
model: "",
|
||||||
|
workingDir: "",
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Track if we've done initial resolution (to avoid flickering)
|
||||||
|
const hasResolvedRef = useRef(false);
|
||||||
|
|
||||||
|
// Reset user modifications when form becomes invisible
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isVisible) {
|
||||||
|
setUserModified(INITIAL_USER_MODIFIED);
|
||||||
|
hasResolvedRef.current = false;
|
||||||
|
}
|
||||||
|
}, [isVisible]);
|
||||||
|
|
||||||
|
// Get session state for provider models
|
||||||
const sessionState = useSessionStore((state) =>
|
const sessionState = useSessionStore((state) =>
|
||||||
selectedServerId ? state.sessions[selectedServerId] : undefined
|
formState.serverId ? state.sessions[formState.serverId] : undefined
|
||||||
);
|
);
|
||||||
const providerModels = sessionState?.providerModels;
|
const providerModels = sessionState?.providerModels;
|
||||||
const requestProviderModels = sessionState?.methods?.requestProviderModels;
|
const requestProviderModels = sessionState?.methods?.requestProviderModels;
|
||||||
@@ -111,9 +245,73 @@ export function useAgentFormState(
|
|||||||
[]
|
[]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Get available models for current provider
|
||||||
|
const modelState = providerModels?.get(formState.provider);
|
||||||
|
const availableModels = modelState?.models ?? null;
|
||||||
|
|
||||||
|
// Combine initialValues with initialServerId for resolution
|
||||||
|
const combinedInitialValues = useMemo((): FormInitialValues | undefined => {
|
||||||
|
if (!initialValues && initialServerId === null) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
...initialValues,
|
||||||
|
serverId: initialValues?.serverId ?? initialServerId,
|
||||||
|
};
|
||||||
|
}, [initialValues, initialServerId]);
|
||||||
|
|
||||||
|
// Resolve form state when data sources change
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isVisible || !isCreateFlow) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for preferences to load before first resolution
|
||||||
|
if (isPreferencesLoading && !hasResolvedRef.current) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const resolved = resolveFormState(
|
||||||
|
combinedInitialValues,
|
||||||
|
preferences,
|
||||||
|
availableModels,
|
||||||
|
userModified,
|
||||||
|
formState
|
||||||
|
);
|
||||||
|
|
||||||
|
// Only update if something changed
|
||||||
|
if (
|
||||||
|
resolved.serverId !== formState.serverId ||
|
||||||
|
resolved.provider !== formState.provider ||
|
||||||
|
resolved.modeId !== formState.modeId ||
|
||||||
|
resolved.model !== formState.model ||
|
||||||
|
resolved.workingDir !== formState.workingDir
|
||||||
|
) {
|
||||||
|
setFormState(resolved);
|
||||||
|
}
|
||||||
|
|
||||||
|
hasResolvedRef.current = true;
|
||||||
|
}, [
|
||||||
|
isVisible,
|
||||||
|
isCreateFlow,
|
||||||
|
isPreferencesLoading,
|
||||||
|
combinedInitialValues,
|
||||||
|
preferences,
|
||||||
|
availableModels,
|
||||||
|
userModified,
|
||||||
|
formState,
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Provider model request timers
|
||||||
|
const providerModelRequestTimersRef = useRef<
|
||||||
|
Map<string, ReturnType<typeof setTimeout>>
|
||||||
|
>(new Map());
|
||||||
|
|
||||||
|
// User setters - mark fields as modified and persist to preferences
|
||||||
const setSelectedServerIdFromUser = useCallback(
|
const setSelectedServerIdFromUser = useCallback(
|
||||||
(value: string | null) => {
|
(value: string | null) => {
|
||||||
setSelectedServerId(value);
|
setFormState((prev) => ({ ...prev, serverId: value }));
|
||||||
|
setUserModified((prev) => ({ ...prev, serverId: true }));
|
||||||
void updatePreferences({ serverId: value ?? undefined });
|
void updatePreferences({ serverId: value ?? undefined });
|
||||||
},
|
},
|
||||||
[updatePreferences]
|
[updatePreferences]
|
||||||
@@ -121,124 +319,86 @@ export function useAgentFormState(
|
|||||||
|
|
||||||
const setProviderFromUser = useCallback(
|
const setProviderFromUser = useCallback(
|
||||||
(provider: AgentProvider) => {
|
(provider: AgentProvider) => {
|
||||||
setSelectedProvider(provider);
|
setFormState((prev) => ({ ...prev, provider }));
|
||||||
|
setUserModified((prev) => ({ ...prev, provider: true }));
|
||||||
void updatePreferences({ provider });
|
void updatePreferences({ provider });
|
||||||
|
|
||||||
// Restore per-provider preferences if available
|
// When provider changes, reset mode and model to provider defaults
|
||||||
const providerPrefs = getProviderPreferences(provider);
|
// (unless user has explicitly set them)
|
||||||
const providerDef = providerDefinitionMap.get(provider);
|
const providerDef = providerDefinitionMap.get(provider);
|
||||||
|
const providerPrefs = preferences?.providerPreferences?.[provider];
|
||||||
|
|
||||||
setSelectedModel(providerPrefs?.model ?? "");
|
setFormState((prev) => ({
|
||||||
setSelectedMode(providerPrefs?.mode ?? providerDef?.defaultModeId ?? "");
|
...prev,
|
||||||
|
provider,
|
||||||
|
modeId: providerPrefs?.mode ?? providerDef?.defaultModeId ?? "",
|
||||||
|
model: providerPrefs?.model ?? "",
|
||||||
|
}));
|
||||||
},
|
},
|
||||||
[getProviderPreferences, updatePreferences]
|
[preferences?.providerPreferences, updatePreferences]
|
||||||
);
|
);
|
||||||
|
|
||||||
const setModeFromUser = useCallback(
|
const setModeFromUser = useCallback(
|
||||||
(modeId: string) => {
|
(modeId: string) => {
|
||||||
setSelectedMode(modeId);
|
setFormState((prev) => ({ ...prev, modeId }));
|
||||||
void updateProviderPreferences(selectedProvider, { mode: modeId });
|
setUserModified((prev) => ({ ...prev, modeId: true }));
|
||||||
|
void updateProviderPreferences(formState.provider, { mode: modeId });
|
||||||
},
|
},
|
||||||
[selectedProvider, updateProviderPreferences]
|
[formState.provider, updateProviderPreferences]
|
||||||
);
|
);
|
||||||
|
|
||||||
const setModelFromUser = useCallback(
|
const setModelFromUser = useCallback(
|
||||||
(modelId: string) => {
|
(modelId: string) => {
|
||||||
setSelectedModel(modelId);
|
setFormState((prev) => ({ ...prev, model: modelId }));
|
||||||
void updateProviderPreferences(selectedProvider, { model: modelId });
|
setUserModified((prev) => ({ ...prev, model: true }));
|
||||||
|
void updateProviderPreferences(formState.provider, { model: modelId });
|
||||||
},
|
},
|
||||||
[selectedProvider, updateProviderPreferences]
|
[formState.provider, updateProviderPreferences]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const setWorkingDir = useCallback((value: string) => {
|
||||||
|
setFormState((prev) => ({ ...prev, workingDir: value }));
|
||||||
|
}, []);
|
||||||
|
|
||||||
const setWorkingDirFromUser = useCallback(
|
const setWorkingDirFromUser = useCallback(
|
||||||
(value: string) => {
|
(value: string) => {
|
||||||
setWorkingDir(value);
|
setFormState((prev) => ({ ...prev, workingDir: value }));
|
||||||
|
setUserModified((prev) => ({ ...prev, workingDir: true }));
|
||||||
void updatePreferences({ workingDir: value });
|
void updatePreferences({ workingDir: value });
|
||||||
},
|
},
|
||||||
[updatePreferences]
|
[updatePreferences]
|
||||||
);
|
);
|
||||||
|
|
||||||
const applyInitialValues = useCallback(() => {
|
const setSelectedServerId = useCallback((value: string | null) => {
|
||||||
if (!isCreateFlow || !initialValues) {
|
setFormState((prev) => ({ ...prev, serverId: value }));
|
||||||
return;
|
}, []);
|
||||||
}
|
|
||||||
|
|
||||||
if (Object.prototype.hasOwnProperty.call(initialValues, "workingDir")) {
|
|
||||||
setWorkingDir(initialValues.workingDir ?? "");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (initialValues.provider && providerDefinitionMap.has(initialValues.provider)) {
|
|
||||||
setSelectedProvider(initialValues.provider);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof initialValues.modeId === "string" && initialValues.modeId.length > 0) {
|
|
||||||
setSelectedMode(initialValues.modeId);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof initialValues.model === "string" && initialValues.model.length > 0) {
|
|
||||||
setSelectedModel(initialValues.model);
|
|
||||||
}
|
|
||||||
}, [initialValues, isCreateFlow]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!isVisible) {
|
|
||||||
hasAppliedInitialValuesRef.current = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (hasAppliedInitialValuesRef.current) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
applyInitialValues();
|
|
||||||
hasAppliedInitialValuesRef.current = true;
|
|
||||||
}, [applyInitialValues, isVisible]);
|
|
||||||
|
|
||||||
const refreshProviderModels = useCallback(() => {
|
const refreshProviderModels = useCallback(() => {
|
||||||
if (!requestProviderModels) {
|
if (!requestProviderModels) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const trimmed = workingDir.trim();
|
const trimmed = formState.workingDir.trim();
|
||||||
requestProviderModels(selectedProvider, {
|
requestProviderModels(formState.provider, {
|
||||||
cwd: trimmed.length > 0 ? trimmed : undefined,
|
cwd: trimmed.length > 0 ? trimmed : undefined,
|
||||||
});
|
});
|
||||||
}, [requestProviderModels, selectedProvider, workingDir]);
|
}, [requestProviderModels, formState.provider, formState.workingDir]);
|
||||||
|
|
||||||
// Hydrate form state from preferences once loaded
|
|
||||||
useEffect(() => {
|
|
||||||
if (isPreferencesLoading || hasHydratedRef.current) return;
|
|
||||||
hasHydratedRef.current = true;
|
|
||||||
|
|
||||||
const activeProvider =
|
|
||||||
preferences.provider &&
|
|
||||||
providerDefinitionMap.has(preferences.provider as AgentProvider)
|
|
||||||
? (preferences.provider as AgentProvider)
|
|
||||||
: DEFAULT_PROVIDER;
|
|
||||||
|
|
||||||
const providerPrefs = preferences.providerPreferences?.[activeProvider];
|
|
||||||
const providerDef = providerDefinitionMap.get(activeProvider);
|
|
||||||
|
|
||||||
setSelectedProvider(activeProvider);
|
|
||||||
if (preferences.workingDir) setWorkingDir(preferences.workingDir);
|
|
||||||
setSelectedMode(providerPrefs?.mode ?? providerDef?.defaultModeId ?? "");
|
|
||||||
if (providerPrefs?.model) setSelectedModel(providerPrefs.model);
|
|
||||||
if (preferences.serverId) setSelectedServerId(preferences.serverId);
|
|
||||||
}, [isPreferencesLoading, preferences]);
|
|
||||||
|
|
||||||
const persistFormPreferences = useCallback(async () => {
|
const persistFormPreferences = useCallback(async () => {
|
||||||
await updatePreferences({
|
await updatePreferences({
|
||||||
workingDir,
|
workingDir: formState.workingDir,
|
||||||
provider: selectedProvider,
|
provider: formState.provider,
|
||||||
serverId: selectedServerId ?? undefined,
|
serverId: formState.serverId ?? undefined,
|
||||||
});
|
});
|
||||||
await updateProviderPreferences(selectedProvider, {
|
await updateProviderPreferences(formState.provider, {
|
||||||
mode: selectedMode,
|
mode: formState.modeId,
|
||||||
model: selectedModel,
|
model: formState.model,
|
||||||
});
|
});
|
||||||
}, [
|
}, [
|
||||||
selectedMode,
|
formState.modeId,
|
||||||
selectedModel,
|
formState.model,
|
||||||
selectedProvider,
|
formState.provider,
|
||||||
selectedServerId,
|
formState.serverId,
|
||||||
workingDir,
|
formState.workingDir,
|
||||||
updatePreferences,
|
updatePreferences,
|
||||||
updateProviderPreferences,
|
updateProviderPreferences,
|
||||||
]);
|
]);
|
||||||
@@ -270,7 +430,7 @@ export function useAgentFormState(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentState = sessionState.providerModels?.get(selectedProvider);
|
const currentState = sessionState.providerModels?.get(formState.provider);
|
||||||
if (currentState?.models?.length || currentState?.isLoading) {
|
if (currentState?.models?.length || currentState?.isLoading) {
|
||||||
clearQueuedProviderModelRequest(serverId);
|
clearQueuedProviderModelRequest(serverId);
|
||||||
return;
|
return;
|
||||||
@@ -279,7 +439,7 @@ export function useAgentFormState(
|
|||||||
const delayMs = options?.delayMs ?? 0;
|
const delayMs = options?.delayMs ?? 0;
|
||||||
const trigger = () => {
|
const trigger = () => {
|
||||||
providerModelRequestTimersRef.current.delete(serverId);
|
providerModelRequestTimersRef.current.delete(serverId);
|
||||||
sessionState.methods?.requestProviderModels(selectedProvider, {
|
sessionState.methods?.requestProviderModels(formState.provider, {
|
||||||
...(options?.cwd ? { cwd: options.cwd } : {}),
|
...(options?.cwd ? { cwd: options.cwd } : {}),
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -290,7 +450,7 @@ export function useAgentFormState(
|
|||||||
trigger();
|
trigger();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[clearQueuedProviderModelRequest, getSessionState, selectedProvider]
|
[clearQueuedProviderModelRequest, getSessionState, formState.provider]
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -303,74 +463,53 @@ export function useAgentFormState(
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isVisible || !isTargetDaemonReady || !selectedServerId) {
|
if (!isVisible || !isTargetDaemonReady || !formState.serverId) {
|
||||||
clearQueuedProviderModelRequest(selectedServerId);
|
clearQueuedProviderModelRequest(formState.serverId);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const trimmed = workingDir.trim();
|
const trimmed = formState.workingDir.trim();
|
||||||
queueProviderModelFetch(selectedServerId, {
|
queueProviderModelFetch(formState.serverId, {
|
||||||
cwd: trimmed.length > 0 ? trimmed : undefined,
|
cwd: trimmed.length > 0 ? trimmed : undefined,
|
||||||
delayMs: 180,
|
delayMs: 180,
|
||||||
});
|
});
|
||||||
return () => {
|
return () => {
|
||||||
clearQueuedProviderModelRequest(selectedServerId);
|
clearQueuedProviderModelRequest(formState.serverId);
|
||||||
};
|
};
|
||||||
}, [
|
}, [
|
||||||
clearQueuedProviderModelRequest,
|
clearQueuedProviderModelRequest,
|
||||||
isTargetDaemonReady,
|
isTargetDaemonReady,
|
||||||
isVisible,
|
isVisible,
|
||||||
queueProviderModelFetch,
|
queueProviderModelFetch,
|
||||||
selectedServerId,
|
formState.serverId,
|
||||||
workingDir,
|
formState.workingDir,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const agentDefinition = providerDefinitionMap.get(selectedProvider);
|
const agentDefinition = providerDefinitionMap.get(formState.provider);
|
||||||
const modeOptions = agentDefinition?.modes ?? [];
|
const modeOptions = agentDefinition?.modes ?? [];
|
||||||
const modelState = providerModels?.get(selectedProvider);
|
|
||||||
const availableModels = modelState?.models ?? [];
|
|
||||||
const isModelLoading = modelState?.isLoading ?? false;
|
const isModelLoading = modelState?.isLoading ?? false;
|
||||||
const modelError = modelState?.error ?? null;
|
const modelError = modelState?.error ?? null;
|
||||||
|
|
||||||
useEffect(() => {
|
const workingDirIsEmpty = !formState.workingDir.trim();
|
||||||
if (!agentDefinition) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (modeOptions.length === 0) {
|
|
||||||
if (selectedMode !== "") {
|
|
||||||
setSelectedMode("");
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const availableModeIds = modeOptions.map((mode) => mode.id);
|
|
||||||
if (!availableModeIds.includes(selectedMode)) {
|
|
||||||
const fallbackModeId = agentDefinition.defaultModeId ?? availableModeIds[0];
|
|
||||||
setSelectedMode(fallbackModeId);
|
|
||||||
}
|
|
||||||
}, [agentDefinition, modeOptions, selectedMode]);
|
|
||||||
|
|
||||||
const workingDirIsEmpty = !workingDir.trim();
|
|
||||||
|
|
||||||
return useMemo(
|
return useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
selectedServerId,
|
selectedServerId: formState.serverId,
|
||||||
setSelectedServerId,
|
setSelectedServerId,
|
||||||
setSelectedServerIdFromUser,
|
setSelectedServerIdFromUser,
|
||||||
selectedProvider,
|
selectedProvider: formState.provider,
|
||||||
setProviderFromUser,
|
setProviderFromUser,
|
||||||
selectedMode,
|
selectedMode: formState.modeId,
|
||||||
setModeFromUser,
|
setModeFromUser,
|
||||||
selectedModel,
|
selectedModel: formState.model,
|
||||||
setModelFromUser,
|
setModelFromUser,
|
||||||
workingDir,
|
workingDir: formState.workingDir,
|
||||||
setWorkingDir,
|
setWorkingDir,
|
||||||
setWorkingDirFromUser,
|
setWorkingDirFromUser,
|
||||||
providerDefinitions,
|
providerDefinitions,
|
||||||
providerDefinitionMap,
|
providerDefinitionMap,
|
||||||
agentDefinition,
|
agentDefinition,
|
||||||
modeOptions,
|
modeOptions,
|
||||||
availableModels,
|
availableModels: availableModels ?? [],
|
||||||
isModelLoading,
|
isModelLoading,
|
||||||
modelError,
|
modelError,
|
||||||
refreshProviderModels,
|
refreshProviderModels,
|
||||||
@@ -380,26 +519,31 @@ export function useAgentFormState(
|
|||||||
persistFormPreferences,
|
persistFormPreferences,
|
||||||
}),
|
}),
|
||||||
[
|
[
|
||||||
agentDefinition,
|
formState.serverId,
|
||||||
availableModels,
|
formState.provider,
|
||||||
isModelLoading,
|
formState.modeId,
|
||||||
modelError,
|
formState.model,
|
||||||
modeOptions,
|
formState.workingDir,
|
||||||
queueProviderModelFetch,
|
setSelectedServerId,
|
||||||
clearQueuedProviderModelRequest,
|
|
||||||
refreshProviderModels,
|
|
||||||
selectedMode,
|
|
||||||
selectedModel,
|
|
||||||
selectedProvider,
|
|
||||||
selectedServerId,
|
|
||||||
setSelectedServerIdFromUser,
|
setSelectedServerIdFromUser,
|
||||||
setProviderFromUser,
|
setProviderFromUser,
|
||||||
setModeFromUser,
|
setModeFromUser,
|
||||||
setModelFromUser,
|
setModelFromUser,
|
||||||
|
setWorkingDir,
|
||||||
setWorkingDirFromUser,
|
setWorkingDirFromUser,
|
||||||
workingDir,
|
agentDefinition,
|
||||||
|
modeOptions,
|
||||||
|
availableModels,
|
||||||
|
isModelLoading,
|
||||||
|
modelError,
|
||||||
|
refreshProviderModels,
|
||||||
|
queueProviderModelFetch,
|
||||||
|
clearQueuedProviderModelRequest,
|
||||||
workingDirIsEmpty,
|
workingDirIsEmpty,
|
||||||
persistFormPreferences,
|
persistFormPreferences,
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Re-export for backwards compatibility
|
||||||
|
export type CreateAgentInitialValues = FormInitialValues;
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { useCallback, useEffect, useRef, useState } from "react";
|
import { useCallback, useEffect, useRef, useState } from "react";
|
||||||
import { useMutation } from "@tanstack/react-query";
|
import { useMutation } from "@tanstack/react-query";
|
||||||
|
|
||||||
import type { SessionContextValue } from "@/contexts/session-context";
|
|
||||||
import { useAudioRecorder } from "@/hooks/use-audio-recorder";
|
import { useAudioRecorder } from "@/hooks/use-audio-recorder";
|
||||||
import type { DaemonClientV2 } from "@server/client/daemon-client-v2";
|
import type { DaemonClientV2 } from "@server/client/daemon-client-v2";
|
||||||
import type { TranscriptionResultMessage } from "@server/shared/messages";
|
import type { TranscriptionResultMessage } from "@server/shared/messages";
|
||||||
@@ -33,10 +32,7 @@ export type DictationOutcome =
|
|||||||
| { type: "failure"; requestId: string; errorMessage: string; timestamp: number };
|
| { type: "failure"; requestId: string; errorMessage: string; timestamp: number };
|
||||||
|
|
||||||
export type UseDictationOptions = {
|
export type UseDictationOptions = {
|
||||||
agentId?: string;
|
|
||||||
sendAgentAudio: SessionContextValue["sendAgentAudio"];
|
|
||||||
client: DaemonClientV2 | null;
|
client: DaemonClientV2 | null;
|
||||||
mode?: "transcribe_only" | "auto_run";
|
|
||||||
onTranscript: (text: string, meta: { requestId: string }) => void;
|
onTranscript: (text: string, meta: { requestId: string }) => void;
|
||||||
onError?: (error: Error) => void;
|
onError?: (error: Error) => void;
|
||||||
onRetryAttempt?: (info: DictationRetryInfo) => void;
|
onRetryAttempt?: (info: DictationRetryInfo) => void;
|
||||||
@@ -121,6 +117,16 @@ type CapturedAudioPayload = {
|
|||||||
|
|
||||||
type TranscriptionPayload = TranscriptionResultMessage["payload"];
|
type TranscriptionPayload = TranscriptionResultMessage["payload"];
|
||||||
|
|
||||||
|
const blobToBase64 = async (blob: Blob): Promise<string> => {
|
||||||
|
const arrayBuffer = await blob.arrayBuffer();
|
||||||
|
const bytes = new Uint8Array(arrayBuffer);
|
||||||
|
let binary = "";
|
||||||
|
for (let i = 0; i < bytes.length; i++) {
|
||||||
|
binary += String.fromCharCode(bytes[i]);
|
||||||
|
}
|
||||||
|
return btoa(binary);
|
||||||
|
};
|
||||||
|
|
||||||
const deriveFormatFromMime = (mimeType?: string): string => {
|
const deriveFormatFromMime = (mimeType?: string): string => {
|
||||||
if (!mimeType || mimeType.length === 0) {
|
if (!mimeType || mimeType.length === 0) {
|
||||||
return "webm";
|
return "webm";
|
||||||
@@ -144,10 +150,7 @@ const buildCapturedAudioPayload = (blob: Blob, durationSeconds: number): Capture
|
|||||||
|
|
||||||
export function useDictation(options: UseDictationOptions): UseDictationResult {
|
export function useDictation(options: UseDictationOptions): UseDictationResult {
|
||||||
const {
|
const {
|
||||||
agentId,
|
|
||||||
sendAgentAudio,
|
|
||||||
client,
|
client,
|
||||||
mode = "transcribe_only",
|
|
||||||
onTranscript,
|
onTranscript,
|
||||||
onError,
|
onError,
|
||||||
onRetryAttempt,
|
onRetryAttempt,
|
||||||
@@ -172,7 +175,7 @@ export function useDictation(options: UseDictationOptions): UseDictationResult {
|
|||||||
const maxRetryAttempts = MAX_AUTO_RETRY_ATTEMPTS;
|
const maxRetryAttempts = MAX_AUTO_RETRY_ATTEMPTS;
|
||||||
|
|
||||||
const transcriptionMutation = useMutation({
|
const transcriptionMutation = useMutation({
|
||||||
mutationFn: async (requestId: string): Promise<TranscriptionPayload> => {
|
mutationFn: async (): Promise<TranscriptionPayload> => {
|
||||||
const capturedAudio = pendingAudioRef.current;
|
const capturedAudio = pendingAudioRef.current;
|
||||||
if (!capturedAudio) {
|
if (!capturedAudio) {
|
||||||
throw new Error("No recorded audio available for transcription");
|
throw new Error("No recorded audio available for transcription");
|
||||||
@@ -199,9 +202,7 @@ export function useDictation(options: UseDictationOptions): UseDictationResult {
|
|||||||
}
|
}
|
||||||
|
|
||||||
console.info("[useDictation] sending transcription request", {
|
console.info("[useDictation] sending transcription request", {
|
||||||
requestId,
|
|
||||||
attempt,
|
attempt,
|
||||||
agentId,
|
|
||||||
size: capturedAudio.sizeBytes,
|
size: capturedAudio.sizeBytes,
|
||||||
durationSeconds: capturedAudio.durationSeconds,
|
durationSeconds: capturedAudio.durationSeconds,
|
||||||
});
|
});
|
||||||
@@ -210,20 +211,12 @@ export function useDictation(options: UseDictationOptions): UseDictationResult {
|
|||||||
setRetryAttempt(attempt);
|
setRetryAttempt(attempt);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const transcriptionPromise = client.waitForTranscriptionResult(
|
const base64Audio = await blobToBase64(capturedAudio.blob);
|
||||||
requestId,
|
return await client.transcribeAudio({
|
||||||
TRANSCRIPTION_TIMEOUT_MS
|
audio: base64Audio,
|
||||||
);
|
format: capturedAudio.format,
|
||||||
try {
|
timeout: TRANSCRIPTION_TIMEOUT_MS,
|
||||||
await sendAgentAudio(agentId, capturedAudio.blob, requestId, {
|
});
|
||||||
mode,
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
transcriptionPromise.catch(() => {});
|
|
||||||
throw new DictationAttemptError("dispatch", toError(error));
|
|
||||||
}
|
|
||||||
|
|
||||||
return await transcriptionPromise;
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const normalized = toError(error);
|
const normalized = toError(error);
|
||||||
const reason: DictationRetryReason = isTimeoutError(normalized)
|
const reason: DictationRetryReason = isTimeoutError(normalized)
|
||||||
@@ -256,7 +249,6 @@ export function useDictation(options: UseDictationOptions): UseDictationResult {
|
|||||||
setRetryInfo(info);
|
setRetryInfo(info);
|
||||||
onRetryAttemptRef.current?.(info);
|
onRetryAttemptRef.current?.(info);
|
||||||
console.warn("[useDictation] retry scheduled", {
|
console.warn("[useDictation] retry scheduled", {
|
||||||
requestId,
|
|
||||||
attempt: nextAttempt,
|
attempt: nextAttempt,
|
||||||
maxAttempts: MAX_AUTO_RETRY_ATTEMPTS,
|
maxAttempts: MAX_AUTO_RETRY_ATTEMPTS,
|
||||||
reason: attemptError.reason,
|
reason: attemptError.reason,
|
||||||
@@ -383,12 +375,13 @@ export function useDictation(options: UseDictationOptions): UseDictationResult {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const transmitDictation = useCallback(
|
const transmitDictation = useCallback(
|
||||||
async (requestId: string) => runTranscription(requestId),
|
async () => runTranscription(),
|
||||||
[runTranscription]
|
[runTranscription]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleTranscriptionSuccess = useCallback(
|
const handleTranscriptionSuccess = useCallback(
|
||||||
(transcription: TranscriptionPayload, requestId: string) => {
|
(transcription: TranscriptionPayload) => {
|
||||||
|
const requestId = transcription.requestId;
|
||||||
pendingRequestIdRef.current = null;
|
pendingRequestIdRef.current = null;
|
||||||
setPendingRequestId(null);
|
setPendingRequestId(null);
|
||||||
setIsProcessing(false);
|
setIsProcessing(false);
|
||||||
@@ -409,16 +402,15 @@ export function useDictation(options: UseDictationOptions): UseDictationResult {
|
|||||||
requestId,
|
requestId,
|
||||||
textLength: transcriptText.length,
|
textLength: transcriptText.length,
|
||||||
});
|
});
|
||||||
onTranscriptRef.current?.(transcriptText, {
|
onTranscriptRef.current?.(transcriptText, { requestId });
|
||||||
requestId: transcription.requestId ?? requestId,
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
[onTranscriptRef]
|
[onTranscriptRef]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleDictationFailure = useCallback(
|
const handleDictationFailure = useCallback(
|
||||||
(failure: unknown, requestId: string | null) => {
|
(failure: unknown) => {
|
||||||
const normalized = toError(failure);
|
const normalized = toError(failure);
|
||||||
|
const failureId = generateMessageId();
|
||||||
pendingRequestIdRef.current = null;
|
pendingRequestIdRef.current = null;
|
||||||
setPendingRequestId(null);
|
setPendingRequestId(null);
|
||||||
setIsProcessing(false);
|
setIsProcessing(false);
|
||||||
@@ -431,16 +423,14 @@ export function useDictation(options: UseDictationOptions): UseDictationResult {
|
|||||||
if (capturedAudio) {
|
if (capturedAudio) {
|
||||||
setStatus("failed");
|
setStatus("failed");
|
||||||
setFailedRecording({
|
setFailedRecording({
|
||||||
requestId: requestId ?? generateMessageId(),
|
requestId: failureId,
|
||||||
durationSeconds: capturedAudio.durationSeconds,
|
durationSeconds: capturedAudio.durationSeconds,
|
||||||
sizeBytes: capturedAudio.sizeBytes,
|
sizeBytes: capturedAudio.sizeBytes,
|
||||||
format: capturedAudio.format,
|
format: capturedAudio.format,
|
||||||
recordedAt: capturedAudio.recordedAt,
|
recordedAt: capturedAudio.recordedAt,
|
||||||
errorMessage: normalized.message,
|
errorMessage: normalized.message,
|
||||||
});
|
});
|
||||||
if (requestId) {
|
onPermanentFailureRef.current?.(normalized, { requestId: failureId });
|
||||||
onPermanentFailureRef.current?.(normalized, { requestId });
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
setStatus("idle");
|
setStatus("idle");
|
||||||
}
|
}
|
||||||
@@ -448,7 +438,7 @@ export function useDictation(options: UseDictationOptions): UseDictationResult {
|
|||||||
setRetryAttempt(0);
|
setRetryAttempt(0);
|
||||||
setLastOutcome({
|
setLastOutcome({
|
||||||
type: "failure",
|
type: "failure",
|
||||||
requestId: requestId ?? generateMessageId(),
|
requestId: failureId,
|
||||||
errorMessage: normalized.message,
|
errorMessage: normalized.message,
|
||||||
timestamp: Date.now(),
|
timestamp: Date.now(),
|
||||||
});
|
});
|
||||||
@@ -571,7 +561,6 @@ export function useDictation(options: UseDictationOptions): UseDictationResult {
|
|||||||
setRetryAttempt(0);
|
setRetryAttempt(0);
|
||||||
setLastOutcome(null);
|
setLastOutcome(null);
|
||||||
|
|
||||||
let requestId: string | null = null;
|
|
||||||
try {
|
try {
|
||||||
const audioData = await stopRecorder();
|
const audioData = await stopRecorder();
|
||||||
const recordedDurationSeconds = durationRef.current;
|
const recordedDurationSeconds = durationRef.current;
|
||||||
@@ -581,21 +570,15 @@ export function useDictation(options: UseDictationOptions): UseDictationResult {
|
|||||||
setIsRecording(false);
|
setIsRecording(false);
|
||||||
setVolume(0);
|
setVolume(0);
|
||||||
|
|
||||||
requestId = generateMessageId();
|
const transcription = await transmitDictation();
|
||||||
pendingRequestIdRef.current = requestId;
|
handleTranscriptionSuccess(transcription);
|
||||||
setPendingRequestId(requestId);
|
|
||||||
|
|
||||||
const transcription = await transmitDictation(requestId);
|
|
||||||
handleTranscriptionSuccess(transcription, requestId);
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
resetTranscriptionMutation();
|
resetTranscriptionMutation();
|
||||||
handleDictationFailure(err, requestId);
|
handleDictationFailure(err);
|
||||||
}
|
}
|
||||||
}, [
|
}, [
|
||||||
agentId,
|
|
||||||
canConfirm,
|
canConfirm,
|
||||||
isProcessing,
|
isProcessing,
|
||||||
mode,
|
|
||||||
handleDictationFailure,
|
handleDictationFailure,
|
||||||
handleTranscriptionSuccess,
|
handleTranscriptionSuccess,
|
||||||
resetTranscriptionMutation,
|
resetTranscriptionMutation,
|
||||||
@@ -615,16 +598,12 @@ export function useDictation(options: UseDictationOptions): UseDictationResult {
|
|||||||
setIsProcessing(true);
|
setIsProcessing(true);
|
||||||
setLastOutcome(null);
|
setLastOutcome(null);
|
||||||
|
|
||||||
const requestId = generateMessageId();
|
|
||||||
pendingRequestIdRef.current = requestId;
|
|
||||||
setPendingRequestId(requestId);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const transcription = await transmitDictation(requestId);
|
const transcription = await transmitDictation();
|
||||||
handleTranscriptionSuccess(transcription, requestId);
|
handleTranscriptionSuccess(transcription);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
resetTranscriptionMutation();
|
resetTranscriptionMutation();
|
||||||
handleDictationFailure(err, requestId);
|
handleDictationFailure(err);
|
||||||
}
|
}
|
||||||
}, [
|
}, [
|
||||||
handleDictationFailure,
|
handleDictationFailure,
|
||||||
@@ -687,17 +666,6 @@ export function useDictation(options: UseDictationOptions): UseDictationResult {
|
|||||||
}
|
}
|
||||||
}, [autoStopWhenHidden?.isVisible]);
|
}, [autoStopWhenHidden?.isVisible]);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
pendingRequestIdRef.current = null;
|
|
||||||
setPendingRequestId(null);
|
|
||||||
setIsProcessing(false);
|
|
||||||
pendingAudioRef.current = null;
|
|
||||||
setStatus("idle");
|
|
||||||
setRetryAttempt(0);
|
|
||||||
setRetryInfo(null);
|
|
||||||
setFailedRecording(null);
|
|
||||||
resetTranscriptionMutation();
|
|
||||||
}, [agentId, resetTranscriptionMutation]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
return () => {
|
return () => {
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ interface DesktopSidebarState {
|
|||||||
fileExplorerOpen: boolean;
|
fileExplorerOpen: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
type ExplorerTab = "changes" | "files";
|
export type ExplorerTab = "changes" | "files";
|
||||||
export type ViewMode = "list" | "grid";
|
export type ViewMode = "list" | "grid";
|
||||||
export type SortOption = "name" | "modified" | "size";
|
export type SortOption = "name" | "modified" | "size";
|
||||||
|
|
||||||
|
|||||||
@@ -182,12 +182,6 @@ export interface SessionState {
|
|||||||
message: string,
|
message: string,
|
||||||
images?: Array<{ uri: string; mimeType?: string }>
|
images?: Array<{ uri: string; mimeType?: string }>
|
||||||
) => Promise<void>;
|
) => Promise<void>;
|
||||||
sendAgentAudio: (
|
|
||||||
agentId: string | undefined,
|
|
||||||
audioBlob: Blob,
|
|
||||||
requestId?: string,
|
|
||||||
options?: { mode?: "transcribe_only" | "auto_run" }
|
|
||||||
) => Promise<void>;
|
|
||||||
deleteAgent: (agentId: string) => void;
|
deleteAgent: (agentId: string) => void;
|
||||||
createAgent: (options: {
|
createAgent: (options: {
|
||||||
config: any;
|
config: any;
|
||||||
|
|||||||
10
packages/app/src/utils/shorten-path.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
/**
|
||||||
|
* Shortens a file path by replacing the home directory prefix with ~.
|
||||||
|
* Handles both macOS (/Users/username) and Linux (/home/username) paths.
|
||||||
|
*/
|
||||||
|
export function shortenPath(path: string | undefined | null): string {
|
||||||
|
if (!path) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return path.replace(/^\/(?:Users|home)\/[^/]+/, "~");
|
||||||
|
}
|
||||||
104
packages/app/src/utils/tauri-window.ts
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
import { Platform } from "react-native";
|
||||||
|
import { useState, useEffect } from "react";
|
||||||
|
import { getIsTauriMac, TAURI_TRAFFIC_LIGHT_WIDTH, TAURI_TRAFFIC_LIGHT_HEIGHT } from "@/constants/layout";
|
||||||
|
|
||||||
|
let tauriWindow: any = null;
|
||||||
|
|
||||||
|
async function getTauriWindow() {
|
||||||
|
if (tauriWindow) return tauriWindow;
|
||||||
|
if (!getIsTauriMac()) return null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { getCurrentWindow } = await import("@tauri-apps/api/window");
|
||||||
|
tauriWindow = getCurrentWindow();
|
||||||
|
return tauriWindow;
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function startDragging() {
|
||||||
|
const win = await getTauriWindow();
|
||||||
|
if (win) {
|
||||||
|
await win.startDragging();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function toggleMaximize() {
|
||||||
|
const win = await getTauriWindow();
|
||||||
|
if (win) {
|
||||||
|
await win.toggleMaximize();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns event handlers for drag region behavior
|
||||||
|
export function useTauriDragHandlers() {
|
||||||
|
if (Platform.OS !== "web" || !getIsTauriMac()) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
onMouseDown: (e: React.MouseEvent) => {
|
||||||
|
// Only handle left click, ignore if clicking on interactive elements
|
||||||
|
if (e.button !== 0) return;
|
||||||
|
const target = e.target as HTMLElement;
|
||||||
|
if (target.closest("button, a, input, [role='button']")) return;
|
||||||
|
|
||||||
|
// Prevent text selection when dragging
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
// Start dragging on mouse down
|
||||||
|
startDragging();
|
||||||
|
},
|
||||||
|
onDoubleClick: (e: React.MouseEvent) => {
|
||||||
|
// Ignore if clicking on interactive elements
|
||||||
|
const target = e.target as HTMLElement;
|
||||||
|
if (target.closest("button, a, input, [role='button']")) return;
|
||||||
|
|
||||||
|
e.preventDefault();
|
||||||
|
toggleMaximize();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hook that returns traffic light padding, accounting for fullscreen state
|
||||||
|
// In fullscreen, traffic lights are hidden so no padding is needed
|
||||||
|
export function useTrafficLightPadding(): { left: number; top: number } {
|
||||||
|
const [isFullscreen, setIsFullscreen] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (Platform.OS !== "web" || !getIsTauriMac()) return;
|
||||||
|
|
||||||
|
let unlisten: (() => void) | undefined;
|
||||||
|
|
||||||
|
async function setup() {
|
||||||
|
const win = await getTauriWindow();
|
||||||
|
if (!win) return;
|
||||||
|
|
||||||
|
// Check initial fullscreen state
|
||||||
|
const fullscreen = await win.isFullscreen();
|
||||||
|
setIsFullscreen(fullscreen);
|
||||||
|
|
||||||
|
// Listen for resize events which include fullscreen changes
|
||||||
|
unlisten = await win.onResized(async () => {
|
||||||
|
const fs = await win.isFullscreen();
|
||||||
|
setIsFullscreen(fs);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
setup();
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
unlisten?.();
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
if (!getIsTauriMac() || isFullscreen) {
|
||||||
|
return { left: 0, top: 0 };
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
left: TAURI_TRAFFIC_LIGHT_WIDTH,
|
||||||
|
top: TAURI_TRAFFIC_LIGHT_HEIGHT,
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -42,8 +42,9 @@ describe("parseToolCallDisplay", () => {
|
|||||||
const input = { command: "pwd", description: "Print working directory" };
|
const input = { command: "pwd", description: "Print working directory" };
|
||||||
const result = { type: "command", command: "pwd", output: "/some/path" };
|
const result = { type: "command", command: "pwd", output: "/some/path" };
|
||||||
|
|
||||||
const display: ToolCallDisplay = parseToolCallDisplay(input, result);
|
const display: ToolCallDisplay = parseToolCallDisplay("Bash", input, result);
|
||||||
expect(display.type).toBe("shell");
|
expect(display.type).toBe("shell");
|
||||||
|
expect(display.toolName).toBe("Shell");
|
||||||
if (display.type === "shell") {
|
if (display.type === "shell") {
|
||||||
expect(display.command).toBe("pwd");
|
expect(display.command).toBe("pwd");
|
||||||
expect(display.output).toBe("/some/path");
|
expect(display.output).toBe("/some/path");
|
||||||
@@ -55,8 +56,9 @@ describe("parseToolCallDisplay", () => {
|
|||||||
const input = { command: "pwd", description: "Print working directory" };
|
const input = { command: "pwd", description: "Print working directory" };
|
||||||
const result = undefined;
|
const result = undefined;
|
||||||
|
|
||||||
const display: ToolCallDisplay = parseToolCallDisplay(input, result);
|
const display: ToolCallDisplay = parseToolCallDisplay("Bash", input, result);
|
||||||
expect(display.type).toBe("shell");
|
expect(display.type).toBe("shell");
|
||||||
|
expect(display.toolName).toBe("Shell");
|
||||||
if (display.type === "shell") {
|
if (display.type === "shell") {
|
||||||
expect(display.command).toBe("pwd");
|
expect(display.command).toBe("pwd");
|
||||||
expect(display.output).toBe("");
|
expect(display.output).toBe("");
|
||||||
@@ -67,19 +69,44 @@ describe("parseToolCallDisplay", () => {
|
|||||||
const input = { command: ["git", "status"] };
|
const input = { command: ["git", "status"] };
|
||||||
const result = { type: "command", output: "On branch main" };
|
const result = { type: "command", output: "On branch main" };
|
||||||
|
|
||||||
const display: ToolCallDisplay = parseToolCallDisplay(input, result);
|
const display: ToolCallDisplay = parseToolCallDisplay("shell", input, result);
|
||||||
expect(display.type).toBe("shell");
|
expect(display.type).toBe("shell");
|
||||||
|
expect(display.toolName).toBe("Shell");
|
||||||
if (display.type === "shell") {
|
if (display.type === "shell") {
|
||||||
expect(display.command).toBe("git status");
|
expect(display.command).toBe("git status");
|
||||||
expect(display.output).toBe("On branch main");
|
expect(display.output).toBe("On branch main");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("normalizes tool names - shell to Shell", () => {
|
||||||
|
const input = { command: "pwd" };
|
||||||
|
const display = parseToolCallDisplay("shell", input, undefined);
|
||||||
|
expect(display.toolName).toBe("Shell");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("normalizes tool names - Bash to Shell", () => {
|
||||||
|
const input = { command: "pwd" };
|
||||||
|
const display = parseToolCallDisplay("Bash", input, undefined);
|
||||||
|
expect(display.toolName).toBe("Shell");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("normalizes tool names - read_file to Read", () => {
|
||||||
|
const input = { file_path: "/some/file.txt" };
|
||||||
|
const display = parseToolCallDisplay("read_file", input, undefined);
|
||||||
|
expect(display.toolName).toBe("Read");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("preserves unknown tool names", () => {
|
||||||
|
const input = { some_arg: "value" };
|
||||||
|
const display = parseToolCallDisplay("MyCustomTool", input, undefined);
|
||||||
|
expect(display.toolName).toBe("MyCustomTool");
|
||||||
|
});
|
||||||
|
|
||||||
test("parses non-command tool call into generic type", () => {
|
test("parses non-command tool call into generic type", () => {
|
||||||
const input = { file_path: "/some/file.txt" };
|
const input = { file_path: "/some/file.txt" };
|
||||||
const result = { content: "file contents here", lineCount: 42 };
|
const result = { content: "file contents here", lineCount: 42 };
|
||||||
|
|
||||||
const display: ToolCallDisplay = parseToolCallDisplay(input, result);
|
const display: ToolCallDisplay = parseToolCallDisplay("SomeTool", input, result);
|
||||||
expect(display.type).toBe("generic");
|
expect(display.type).toBe("generic");
|
||||||
if (display.type === "generic") {
|
if (display.type === "generic") {
|
||||||
expect(display.input).toContainEqual({ key: "file_path", value: "/some/file.txt" });
|
expect(display.input).toContainEqual({ key: "file_path", value: "/some/file.txt" });
|
||||||
@@ -92,12 +119,12 @@ describe("parseToolCallDisplay", () => {
|
|||||||
const input = { file_path: "/some/file.txt", content: "new content" };
|
const input = { file_path: "/some/file.txt", content: "new content" };
|
||||||
const result = { type: "file_write", filePath: "/some/file.txt" };
|
const result = { type: "file_write", filePath: "/some/file.txt" };
|
||||||
|
|
||||||
const display: ToolCallDisplay = parseToolCallDisplay(input, result);
|
const display: ToolCallDisplay = parseToolCallDisplay("Write", input, result);
|
||||||
expect(display.type).toBe("generic");
|
expect(display.type).toBe("generic");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("handles undefined input and result gracefully", () => {
|
test("handles undefined input and result gracefully", () => {
|
||||||
const display: ToolCallDisplay = parseToolCallDisplay(undefined, undefined);
|
const display: ToolCallDisplay = parseToolCallDisplay("unknown", undefined, undefined);
|
||||||
expect(display.type).toBe("generic");
|
expect(display.type).toBe("generic");
|
||||||
if (display.type === "generic") {
|
if (display.type === "generic") {
|
||||||
expect(display.input).toEqual([]);
|
expect(display.input).toEqual([]);
|
||||||
@@ -116,7 +143,7 @@ describe("parseToolCallDisplay", () => {
|
|||||||
filePath: "/some/file.txt",
|
filePath: "/some/file.txt",
|
||||||
};
|
};
|
||||||
|
|
||||||
const display: ToolCallDisplay = parseToolCallDisplay(input, result);
|
const display: ToolCallDisplay = parseToolCallDisplay("Edit", input, result);
|
||||||
expect(display.type).toBe("edit");
|
expect(display.type).toBe("edit");
|
||||||
if (display.type === "edit") {
|
if (display.type === "edit") {
|
||||||
expect(display.filePath).toBe("/some/file.txt");
|
expect(display.filePath).toBe("/some/file.txt");
|
||||||
@@ -133,7 +160,7 @@ describe("parseToolCallDisplay", () => {
|
|||||||
};
|
};
|
||||||
const result = undefined;
|
const result = undefined;
|
||||||
|
|
||||||
const display: ToolCallDisplay = parseToolCallDisplay(input, result);
|
const display: ToolCallDisplay = parseToolCallDisplay("Edit", input, result);
|
||||||
expect(display.type).toBe("edit");
|
expect(display.type).toBe("edit");
|
||||||
if (display.type === "edit") {
|
if (display.type === "edit") {
|
||||||
expect(display.filePath).toBe("/some/file.txt");
|
expect(display.filePath).toBe("/some/file.txt");
|
||||||
@@ -149,7 +176,7 @@ describe("parseToolCallDisplay", () => {
|
|||||||
new_string: "new content",
|
new_string: "new content",
|
||||||
};
|
};
|
||||||
|
|
||||||
const display: ToolCallDisplay = parseToolCallDisplay(input, undefined);
|
const display: ToolCallDisplay = parseToolCallDisplay("Edit", input, undefined);
|
||||||
expect(display.type).toBe("edit");
|
expect(display.type).toBe("edit");
|
||||||
if (display.type === "edit") {
|
if (display.type === "edit") {
|
||||||
expect(display.filePath).toBe("/some/file.txt");
|
expect(display.filePath).toBe("/some/file.txt");
|
||||||
@@ -158,3 +185,111 @@ describe("parseToolCallDisplay", () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("parseToolCallDisplay - apply_patch (Codex)", () => {
|
||||||
|
test("parses apply_patch into edit type with unified diff", () => {
|
||||||
|
const input = {
|
||||||
|
files: [
|
||||||
|
{
|
||||||
|
path: "/Users/moboudra/dev/blankpage/editor/.tasks/578561c8.md",
|
||||||
|
kind: "update",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
const result = {
|
||||||
|
files: [
|
||||||
|
{
|
||||||
|
path: "/Users/moboudra/dev/blankpage/editor/.tasks/578561c8.md",
|
||||||
|
patch: "@@ -15,3 +15,2 @@\n-This task defines the **design philosophy**\n+This task defines the **updated philosophy**",
|
||||||
|
kind: "update",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
message: "Success. Updated the following files:\nM .tasks/578561c8.md",
|
||||||
|
success: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
const display = parseToolCallDisplay("apply_patch", input, result);
|
||||||
|
expect(display.type).toBe("edit");
|
||||||
|
expect(display.toolName).toBe("Edit");
|
||||||
|
if (display.type === "edit") {
|
||||||
|
expect(display.filePath).toBe("/Users/moboudra/dev/blankpage/editor/.tasks/578561c8.md");
|
||||||
|
expect(display.unifiedDiff).toBe("@@ -15,3 +15,2 @@\n-This task defines the **design philosophy**\n+This task defines the **updated philosophy**");
|
||||||
|
expect(display.oldString).toBe("");
|
||||||
|
expect(display.newString).toBe("");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test("parses pending apply_patch (no result yet)", () => {
|
||||||
|
const input = {
|
||||||
|
files: [
|
||||||
|
{
|
||||||
|
path: "/some/file.txt",
|
||||||
|
kind: "create",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const display = parseToolCallDisplay("apply_patch", input, undefined);
|
||||||
|
expect(display.type).toBe("edit");
|
||||||
|
expect(display.toolName).toBe("Edit");
|
||||||
|
if (display.type === "edit") {
|
||||||
|
expect(display.filePath).toBe("/some/file.txt");
|
||||||
|
expect(display.unifiedDiff).toBeUndefined();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test("handles apply_patch with multiple files (uses first file)", () => {
|
||||||
|
const input = {
|
||||||
|
files: [
|
||||||
|
{ path: "/first/file.txt", kind: "update" },
|
||||||
|
{ path: "/second/file.txt", kind: "create" },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
const result = {
|
||||||
|
files: [
|
||||||
|
{ path: "/first/file.txt", patch: "@@ -1 +1 @@\n-old\n+new", kind: "update" },
|
||||||
|
{ path: "/second/file.txt", patch: "@@ -0,0 +1 @@\n+content", kind: "create" },
|
||||||
|
],
|
||||||
|
success: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
const display = parseToolCallDisplay("apply_patch", input, result);
|
||||||
|
expect(display.type).toBe("edit");
|
||||||
|
if (display.type === "edit") {
|
||||||
|
expect(display.filePath).toBe("/first/file.txt");
|
||||||
|
expect(display.unifiedDiff).toBe("@@ -1 +1 @@\n-old\n+new");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("parseToolCallDisplay - read_file (Codex)", () => {
|
||||||
|
test("parses Codex read_file into read type", () => {
|
||||||
|
const input = {
|
||||||
|
path: "/Users/moboudra/dev/blankpage/editor/.tasks/578561c8.md",
|
||||||
|
};
|
||||||
|
const result = {
|
||||||
|
type: "read_file",
|
||||||
|
path: "/Users/moboudra/dev/blankpage/editor/.tasks/578561c8.md",
|
||||||
|
content: "260 - Source: `**bold**|`\n261 - Action: `Shift+ArrowLeft`",
|
||||||
|
};
|
||||||
|
|
||||||
|
const display = parseToolCallDisplay("read_file", input, result);
|
||||||
|
expect(display.type).toBe("read");
|
||||||
|
expect(display.toolName).toBe("Read");
|
||||||
|
if (display.type === "read") {
|
||||||
|
expect(display.filePath).toBe("/Users/moboudra/dev/blankpage/editor/.tasks/578561c8.md");
|
||||||
|
expect(display.content).toBe("260 - Source: `**bold**|`\n261 - Action: `Shift+ArrowLeft`");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Codex read_file falls through to generic when result is missing", () => {
|
||||||
|
const input = {
|
||||||
|
path: "/some/file.txt",
|
||||||
|
};
|
||||||
|
|
||||||
|
const display = parseToolCallDisplay("read_file", input, undefined);
|
||||||
|
// Without result, it can't match the schema so falls through to generic
|
||||||
|
expect(display.type).toBe("generic");
|
||||||
|
expect(display.toolName).toBe("Read");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@@ -270,7 +270,7 @@ export function buildLineDiff(originalText: string, updatedText: string): DiffLi
|
|||||||
return diff;
|
return diff;
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseUnifiedDiff(diffText?: string): DiffLine[] {
|
export function parseUnifiedDiff(diffText?: string): DiffLine[] {
|
||||||
if (!diffText) {
|
if (!diffText) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
@@ -1023,7 +1023,7 @@ const EditToolCallSchema = z
|
|||||||
input: EditInputSchema,
|
input: EditInputSchema,
|
||||||
result: z.unknown(),
|
result: z.unknown(),
|
||||||
})
|
})
|
||||||
.transform((data): { type: "edit"; filePath: string; oldString: string; newString: string } => {
|
.transform((data): { type: "edit"; filePath: string; oldString: string; newString: string; unifiedDiff?: string } => {
|
||||||
const filePath = data.input.file_path;
|
const filePath = data.input.file_path;
|
||||||
const oldString = "old_string" in data.input
|
const oldString = "old_string" in data.input
|
||||||
? (data.input as { old_string: string }).old_string
|
? (data.input as { old_string: string }).old_string
|
||||||
@@ -1040,6 +1040,52 @@ const EditToolCallSchema = z
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Codex apply_patch input: { files: [{ path: string, kind: string }] }
|
||||||
|
const ApplyPatchInputSchema = z.object({
|
||||||
|
files: z.array(z.object({
|
||||||
|
path: z.string(),
|
||||||
|
kind: z.string().optional(),
|
||||||
|
})).min(1),
|
||||||
|
}).passthrough();
|
||||||
|
|
||||||
|
// Codex apply_patch result: { files: [{ path: string, patch: string, kind: string }], message: string, success: boolean }
|
||||||
|
const ApplyPatchResultSchema = z.object({
|
||||||
|
files: z.array(z.object({
|
||||||
|
path: z.string(),
|
||||||
|
patch: z.string().optional(),
|
||||||
|
kind: z.string().optional(),
|
||||||
|
})).optional(),
|
||||||
|
message: z.string().optional(),
|
||||||
|
success: z.boolean().optional(),
|
||||||
|
}).passthrough();
|
||||||
|
|
||||||
|
// Apply patch tool call display schema - transforms to edit type with unified diff
|
||||||
|
const ApplyPatchToolCallSchema = z
|
||||||
|
.object({
|
||||||
|
input: ApplyPatchInputSchema,
|
||||||
|
result: z.unknown(),
|
||||||
|
})
|
||||||
|
.transform((data): { type: "edit"; filePath: string; oldString: string; newString: string; unifiedDiff?: string } => {
|
||||||
|
const firstFile = data.input.files[0];
|
||||||
|
const filePath = firstFile.path;
|
||||||
|
|
||||||
|
// Try to get the patch from the result
|
||||||
|
const resultParsed = ApplyPatchResultSchema.safeParse(data.result);
|
||||||
|
let unifiedDiff: string | undefined;
|
||||||
|
if (resultParsed.success && resultParsed.data.files) {
|
||||||
|
const resultFile = resultParsed.data.files.find(f => f.path === filePath) ?? resultParsed.data.files[0];
|
||||||
|
unifiedDiff = resultFile?.patch;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
type: "edit",
|
||||||
|
filePath,
|
||||||
|
oldString: "",
|
||||||
|
newString: "",
|
||||||
|
unifiedDiff,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
// Read input: { file_path: string, offset?: number, limit?: number }
|
// Read input: { file_path: string, offset?: number, limit?: number }
|
||||||
const ReadInputSchema = z.object({
|
const ReadInputSchema = z.object({
|
||||||
file_path: z.string(),
|
file_path: z.string(),
|
||||||
@@ -1054,7 +1100,7 @@ const ReadResultSchema = z.object({
|
|||||||
content: z.string(),
|
content: z.string(),
|
||||||
}).passthrough();
|
}).passthrough();
|
||||||
|
|
||||||
// Read tool call display schema
|
// Read tool call display schema (Claude)
|
||||||
const ReadToolCallSchema = z
|
const ReadToolCallSchema = z
|
||||||
.object({
|
.object({
|
||||||
input: ReadInputSchema,
|
input: ReadInputSchema,
|
||||||
@@ -1068,6 +1114,30 @@ const ReadToolCallSchema = z
|
|||||||
limit: data.input.limit,
|
limit: data.input.limit,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
// Codex read_file input: { path: string }
|
||||||
|
const CodexReadInputSchema = z.object({
|
||||||
|
path: z.string(),
|
||||||
|
}).passthrough();
|
||||||
|
|
||||||
|
// Codex read_file result: { type: "read_file", path: string, content: string }
|
||||||
|
const CodexReadResultSchema = z.object({
|
||||||
|
type: z.literal("read_file"),
|
||||||
|
path: z.string(),
|
||||||
|
content: z.string(),
|
||||||
|
}).passthrough();
|
||||||
|
|
||||||
|
// Codex read_file tool call display schema
|
||||||
|
const CodexReadToolCallSchema = z
|
||||||
|
.object({
|
||||||
|
input: CodexReadInputSchema,
|
||||||
|
result: CodexReadResultSchema,
|
||||||
|
})
|
||||||
|
.transform((data): { type: "read"; filePath: string; content: string; offset?: number; limit?: number } => ({
|
||||||
|
type: "read",
|
||||||
|
filePath: data.input.path,
|
||||||
|
content: data.result.content,
|
||||||
|
}));
|
||||||
|
|
||||||
// Generic tool call display schema (fallback)
|
// Generic tool call display schema (fallback)
|
||||||
const GenericToolCallSchema = z
|
const GenericToolCallSchema = z
|
||||||
.object({
|
.object({
|
||||||
@@ -1085,12 +1155,60 @@ const GenericToolCallSchema = z
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
const ToolCallDisplaySchema = z.union([ShellToolCallSchema, EditToolCallSchema, ReadToolCallSchema, GenericToolCallSchema]);
|
// Normalizes tool names for consistent display across agents
|
||||||
|
const TOOL_NAME_MAP: Record<string, string> = {
|
||||||
|
shell: "Shell",
|
||||||
|
Bash: "Shell",
|
||||||
|
read_file: "Read",
|
||||||
|
apply_patch: "Edit",
|
||||||
|
};
|
||||||
|
|
||||||
|
const ToolCallDisplaySchema = z
|
||||||
|
.object({
|
||||||
|
toolName: z.string(),
|
||||||
|
input: z.unknown(),
|
||||||
|
result: z.unknown(),
|
||||||
|
})
|
||||||
|
.transform((data) => {
|
||||||
|
const normalizedToolName = TOOL_NAME_MAP[data.toolName] ?? data.toolName;
|
||||||
|
|
||||||
|
// Try each schema in order
|
||||||
|
const shellParsed = ShellToolCallSchema.safeParse({ input: data.input, result: data.result });
|
||||||
|
if (shellParsed.success) {
|
||||||
|
return { ...shellParsed.data, toolName: normalizedToolName };
|
||||||
|
}
|
||||||
|
|
||||||
|
const editParsed = EditToolCallSchema.safeParse({ input: data.input, result: data.result });
|
||||||
|
if (editParsed.success) {
|
||||||
|
return { ...editParsed.data, toolName: normalizedToolName };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Codex apply_patch - try before read since it also uses files array
|
||||||
|
const applyPatchParsed = ApplyPatchToolCallSchema.safeParse({ input: data.input, result: data.result });
|
||||||
|
if (applyPatchParsed.success) {
|
||||||
|
return { ...applyPatchParsed.data, toolName: normalizedToolName };
|
||||||
|
}
|
||||||
|
|
||||||
|
const readParsed = ReadToolCallSchema.safeParse({ input: data.input, result: data.result });
|
||||||
|
if (readParsed.success) {
|
||||||
|
return { ...readParsed.data, toolName: normalizedToolName };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Codex read_file
|
||||||
|
const codexReadParsed = CodexReadToolCallSchema.safeParse({ input: data.input, result: data.result });
|
||||||
|
if (codexReadParsed.success) {
|
||||||
|
return { ...codexReadParsed.data, toolName: normalizedToolName };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback to generic
|
||||||
|
const genericParsed = GenericToolCallSchema.parse({ input: data.input, result: data.result });
|
||||||
|
return { ...genericParsed, toolName: normalizedToolName };
|
||||||
|
});
|
||||||
|
|
||||||
export type ToolCallDisplay = z.infer<typeof ToolCallDisplaySchema>;
|
export type ToolCallDisplay = z.infer<typeof ToolCallDisplaySchema>;
|
||||||
|
|
||||||
export function parseToolCallDisplay(input: unknown, result: unknown): ToolCallDisplay {
|
export function parseToolCallDisplay(toolName: string, input: unknown, result: unknown): ToolCallDisplay {
|
||||||
return ToolCallDisplaySchema.parse({ input, result });
|
return ToolCallDisplaySchema.parse({ toolName, input, result });
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---- Principal Parameter Extraction ----
|
// ---- Principal Parameter Extraction ----
|
||||||
|
|||||||
233
packages/desktop/package-lock.json
generated
Normal file
@@ -0,0 +1,233 @@
|
|||||||
|
{
|
||||||
|
"name": "desktop",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"lockfileVersion": 3,
|
||||||
|
"requires": true,
|
||||||
|
"packages": {
|
||||||
|
"": {
|
||||||
|
"name": "desktop",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"license": "ISC",
|
||||||
|
"devDependencies": {
|
||||||
|
"@tauri-apps/cli": "^2.9.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@tauri-apps/cli": {
|
||||||
|
"version": "2.9.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tauri-apps/cli/-/cli-2.9.6.tgz",
|
||||||
|
"integrity": "sha512-3xDdXL5omQ3sPfBfdC8fCtDKcnyV7OqyzQgfyT5P3+zY6lcPqIYKQBvUasNvppi21RSdfhy44ttvJmftb0PCDw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0 OR MIT",
|
||||||
|
"bin": {
|
||||||
|
"tauri": "tauri.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/tauri"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"@tauri-apps/cli-darwin-arm64": "2.9.6",
|
||||||
|
"@tauri-apps/cli-darwin-x64": "2.9.6",
|
||||||
|
"@tauri-apps/cli-linux-arm-gnueabihf": "2.9.6",
|
||||||
|
"@tauri-apps/cli-linux-arm64-gnu": "2.9.6",
|
||||||
|
"@tauri-apps/cli-linux-arm64-musl": "2.9.6",
|
||||||
|
"@tauri-apps/cli-linux-riscv64-gnu": "2.9.6",
|
||||||
|
"@tauri-apps/cli-linux-x64-gnu": "2.9.6",
|
||||||
|
"@tauri-apps/cli-linux-x64-musl": "2.9.6",
|
||||||
|
"@tauri-apps/cli-win32-arm64-msvc": "2.9.6",
|
||||||
|
"@tauri-apps/cli-win32-ia32-msvc": "2.9.6",
|
||||||
|
"@tauri-apps/cli-win32-x64-msvc": "2.9.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@tauri-apps/cli-darwin-arm64": {
|
||||||
|
"version": "2.9.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-arm64/-/cli-darwin-arm64-2.9.6.tgz",
|
||||||
|
"integrity": "sha512-gf5no6N9FCk1qMrti4lfwP77JHP5haASZgVbBgpZG7BUepB3fhiLCXGUK8LvuOjP36HivXewjg72LTnPDScnQQ==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0 OR MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@tauri-apps/cli-darwin-x64": {
|
||||||
|
"version": "2.9.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-x64/-/cli-darwin-x64-2.9.6.tgz",
|
||||||
|
"integrity": "sha512-oWh74WmqbERwwrwcueJyY6HYhgCksUc6NT7WKeXyrlY/FPmNgdyQAgcLuTSkhRFuQ6zh4Np1HZpOqCTpeZBDcw==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0 OR MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@tauri-apps/cli-linux-arm-gnueabihf": {
|
||||||
|
"version": "2.9.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm-gnueabihf/-/cli-linux-arm-gnueabihf-2.9.6.tgz",
|
||||||
|
"integrity": "sha512-/zde3bFroFsNXOHN204DC2qUxAcAanUjVXXSdEGmhwMUZeAQalNj5cz2Qli2elsRjKN/hVbZOJj0gQ5zaYUjSg==",
|
||||||
|
"cpu": [
|
||||||
|
"arm"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0 OR MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@tauri-apps/cli-linux-arm64-gnu": {
|
||||||
|
"version": "2.9.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-gnu/-/cli-linux-arm64-gnu-2.9.6.tgz",
|
||||||
|
"integrity": "sha512-pvbljdhp9VOo4RnID5ywSxgBs7qiylTPlK56cTk7InR3kYSTJKYMqv/4Q/4rGo/mG8cVppesKIeBMH42fw6wjg==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0 OR MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@tauri-apps/cli-linux-arm64-musl": {
|
||||||
|
"version": "2.9.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.9.6.tgz",
|
||||||
|
"integrity": "sha512-02TKUndpodXBCR0oP//6dZWGYcc22Upf2eP27NvC6z0DIqvkBBFziQUcvi2n6SrwTRL0yGgQjkm9K5NIn8s6jw==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0 OR MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@tauri-apps/cli-linux-riscv64-gnu": {
|
||||||
|
"version": "2.9.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-riscv64-gnu/-/cli-linux-riscv64-gnu-2.9.6.tgz",
|
||||||
|
"integrity": "sha512-fmp1hnulbqzl1GkXl4aTX9fV+ubHw2LqlLH1PE3BxZ11EQk+l/TmiEongjnxF0ie4kV8DQfDNJ1KGiIdWe1GvQ==",
|
||||||
|
"cpu": [
|
||||||
|
"riscv64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0 OR MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@tauri-apps/cli-linux-x64-gnu": {
|
||||||
|
"version": "2.9.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-gnu/-/cli-linux-x64-gnu-2.9.6.tgz",
|
||||||
|
"integrity": "sha512-vY0le8ad2KaV1PJr+jCd8fUF9VOjwwQP/uBuTJvhvKTloEwxYA/kAjKK9OpIslGA9m/zcnSo74czI6bBrm2sYA==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0 OR MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@tauri-apps/cli-linux-x64-musl": {
|
||||||
|
"version": "2.9.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-musl/-/cli-linux-x64-musl-2.9.6.tgz",
|
||||||
|
"integrity": "sha512-TOEuB8YCFZTWVDzsO2yW0+zGcoMiPPwcUgdnW1ODnmgfwccpnihDRoks+ABT1e3fHb1ol8QQWsHSCovb3o2ENQ==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0 OR MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@tauri-apps/cli-win32-arm64-msvc": {
|
||||||
|
"version": "2.9.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-arm64-msvc/-/cli-win32-arm64-msvc-2.9.6.tgz",
|
||||||
|
"integrity": "sha512-ujmDGMRc4qRLAnj8nNG26Rlz9klJ0I0jmZs2BPpmNNf0gM/rcVHhqbEkAaHPTBVIrtUdf7bGvQAD2pyIiUrBHQ==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0 OR MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@tauri-apps/cli-win32-ia32-msvc": {
|
||||||
|
"version": "2.9.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-ia32-msvc/-/cli-win32-ia32-msvc-2.9.6.tgz",
|
||||||
|
"integrity": "sha512-S4pT0yAJgFX8QRCyKA1iKjZ9Q/oPjCZf66A/VlG5Yw54Nnr88J1uBpmenINbXxzyhduWrIXBaUbEY1K80ZbpMg==",
|
||||||
|
"cpu": [
|
||||||
|
"ia32"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0 OR MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@tauri-apps/cli-win32-x64-msvc": {
|
||||||
|
"version": "2.9.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-x64-msvc/-/cli-win32-x64-msvc-2.9.6.tgz",
|
||||||
|
"integrity": "sha512-ldWuWSSkWbKOPjQMJoYVj9wLHcOniv7diyI5UAJ4XsBdtaFB0pKHQsqw/ItUma0VXGC7vB4E9fZjivmxur60aw==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0 OR MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
14
packages/desktop/package.json
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"name": "@paseo/desktop",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"private": true,
|
||||||
|
"description": "Paseo desktop app (Tauri wrapper)",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "tauri dev",
|
||||||
|
"build": "tauri build",
|
||||||
|
"tauri": "tauri"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@tauri-apps/cli": "^2.9.6"
|
||||||
|
}
|
||||||
|
}
|
||||||
4
packages/desktop/src-tauri/.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
# Generated by Cargo
|
||||||
|
# will have compiled files and executables
|
||||||
|
/target/
|
||||||
|
/gen/schemas
|
||||||
4952
packages/desktop/src-tauri/Cargo.lock
generated
Normal file
29
packages/desktop/src-tauri/Cargo.toml
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
[package]
|
||||||
|
name = "paseo"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = "Paseo Desktop"
|
||||||
|
authors = ["moboudra"]
|
||||||
|
license = "MIT"
|
||||||
|
repository = "https://github.com/moboudra/paseo"
|
||||||
|
edition = "2021"
|
||||||
|
rust-version = "1.77.2"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
name = "paseo_lib"
|
||||||
|
crate-type = ["staticlib", "cdylib", "rlib"]
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "Paseo"
|
||||||
|
path = "src/main.rs"
|
||||||
|
|
||||||
|
[build-dependencies]
|
||||||
|
tauri-build = { version = "2.5.3", features = [] }
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
serde_json = "1.0"
|
||||||
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
|
log = "0.4"
|
||||||
|
tauri = { version = "2.9.5", features = [] }
|
||||||
|
tauri-plugin-log = "2"
|
||||||
3
packages/desktop/src-tauri/build.rs
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
fn main() {
|
||||||
|
tauri_build::build()
|
||||||
|
}
|
||||||
13
packages/desktop/src-tauri/capabilities/default.json
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"$schema": "../gen/schemas/desktop-schema.json",
|
||||||
|
"identifier": "default",
|
||||||
|
"description": "enables the default permissions",
|
||||||
|
"windows": [
|
||||||
|
"main"
|
||||||
|
],
|
||||||
|
"permissions": [
|
||||||
|
"core:default",
|
||||||
|
"core:window:allow-start-dragging",
|
||||||
|
"core:window:allow-toggle-maximize"
|
||||||
|
]
|
||||||
|
}
|
||||||
BIN
packages/desktop/src-tauri/icons/128x128.png
Normal file
|
After Width: | Height: | Size: 6.9 KiB |
BIN
packages/desktop/src-tauri/icons/128x128@2x.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
packages/desktop/src-tauri/icons/32x32.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
packages/desktop/src-tauri/icons/64x64.png
Normal file
|
After Width: | Height: | Size: 3.3 KiB |
BIN
packages/desktop/src-tauri/icons/Square107x107Logo.png
Normal file
|
After Width: | Height: | Size: 5.6 KiB |
BIN
packages/desktop/src-tauri/icons/Square142x142Logo.png
Normal file
|
After Width: | Height: | Size: 7.6 KiB |
BIN
packages/desktop/src-tauri/icons/Square150x150Logo.png
Normal file
|
After Width: | Height: | Size: 8.1 KiB |
BIN
packages/desktop/src-tauri/icons/Square284x284Logo.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
packages/desktop/src-tauri/icons/Square30x30Logo.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
packages/desktop/src-tauri/icons/Square310x310Logo.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
packages/desktop/src-tauri/icons/Square44x44Logo.png
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
BIN
packages/desktop/src-tauri/icons/Square71x71Logo.png
Normal file
|
After Width: | Height: | Size: 3.7 KiB |
BIN
packages/desktop/src-tauri/icons/Square89x89Logo.png
Normal file
|
After Width: | Height: | Size: 4.8 KiB |
BIN
packages/desktop/src-tauri/icons/StoreLogo.png
Normal file
|
After Width: | Height: | Size: 2.5 KiB |
@@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||||
|
<background android:drawable="@color/ic_launcher_background"/>
|
||||||
|
</adaptive-icon>
|
||||||
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 8.8 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 5.7 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 4.7 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 4.5 KiB |
|
After Width: | Height: | Size: 7.2 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 6.8 KiB |
|
After Width: | Height: | Size: 9.8 KiB |
|
After Width: | Height: | Size: 26 KiB |
|
After Width: | Height: | Size: 9.2 KiB |
@@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<color name="ic_launcher_background">#fff</color>
|
||||||
|
</resources>
|
||||||
BIN
packages/desktop/src-tauri/icons/icon.icns
Normal file
BIN
packages/desktop/src-tauri/icons/icon.ico
Normal file
|
After Width: | Height: | Size: 27 KiB |
BIN
packages/desktop/src-tauri/icons/icon.png
Normal file
|
After Width: | Height: | Size: 31 KiB |
BIN
packages/desktop/src-tauri/icons/ios/AppIcon-20x20@1x.png
Normal file
|
After Width: | Height: | Size: 873 B |
BIN
packages/desktop/src-tauri/icons/ios/AppIcon-20x20@2x-1.png
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
packages/desktop/src-tauri/icons/ios/AppIcon-20x20@2x.png
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
packages/desktop/src-tauri/icons/ios/AppIcon-20x20@3x.png
Normal file
|
After Width: | Height: | Size: 3.0 KiB |
BIN
packages/desktop/src-tauri/icons/ios/AppIcon-29x29@1x.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
packages/desktop/src-tauri/icons/ios/AppIcon-29x29@2x-1.png
Normal file
|
After Width: | Height: | Size: 2.9 KiB |
BIN
packages/desktop/src-tauri/icons/ios/AppIcon-29x29@2x.png
Normal file
|
After Width: | Height: | Size: 2.9 KiB |
BIN
packages/desktop/src-tauri/icons/ios/AppIcon-29x29@3x.png
Normal file
|
After Width: | Height: | Size: 4.6 KiB |
BIN
packages/desktop/src-tauri/icons/ios/AppIcon-40x40@1x.png
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
packages/desktop/src-tauri/icons/ios/AppIcon-40x40@2x-1.png
Normal file
|
After Width: | Height: | Size: 4.2 KiB |
BIN
packages/desktop/src-tauri/icons/ios/AppIcon-40x40@2x.png
Normal file
|
After Width: | Height: | Size: 4.2 KiB |
BIN
packages/desktop/src-tauri/icons/ios/AppIcon-40x40@3x.png
Normal file
|
After Width: | Height: | Size: 6.4 KiB |
BIN
packages/desktop/src-tauri/icons/ios/AppIcon-512@2x.png
Normal file
|
After Width: | Height: | Size: 63 KiB |
BIN
packages/desktop/src-tauri/icons/ios/AppIcon-60x60@2x.png
Normal file
|
After Width: | Height: | Size: 6.4 KiB |
BIN
packages/desktop/src-tauri/icons/ios/AppIcon-60x60@3x.png
Normal file
|
After Width: | Height: | Size: 9.8 KiB |
BIN
packages/desktop/src-tauri/icons/ios/AppIcon-76x76@1x.png
Normal file
|
After Width: | Height: | Size: 3.9 KiB |
BIN
packages/desktop/src-tauri/icons/ios/AppIcon-76x76@2x.png
Normal file
|
After Width: | Height: | Size: 8.1 KiB |
BIN
packages/desktop/src-tauri/icons/ios/AppIcon-83.5x83.5@2x.png
Normal file
|
After Width: | Height: | Size: 9.0 KiB |
72
packages/desktop/src-tauri/src/lib.rs
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
use std::sync::atomic::{AtomicU64, Ordering};
|
||||||
|
use tauri::menu::{Menu, MenuItemBuilder, Submenu};
|
||||||
|
use tauri::Manager;
|
||||||
|
use tauri::WebviewWindow;
|
||||||
|
|
||||||
|
// Store zoom as u64 bits (f64 * 100 as integer for atomic ops)
|
||||||
|
static ZOOM_LEVEL: AtomicU64 = AtomicU64::new(100);
|
||||||
|
|
||||||
|
fn get_zoom_factor() -> f64 {
|
||||||
|
ZOOM_LEVEL.load(Ordering::Relaxed) as f64 / 100.0
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_zoom_factor(webview: &WebviewWindow, factor: f64) {
|
||||||
|
let clamped = factor.clamp(0.5, 3.0);
|
||||||
|
ZOOM_LEVEL.store((clamped * 100.0) as u64, Ordering::Relaxed);
|
||||||
|
let _ = webview.set_zoom(clamped);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||||
|
pub fn run() {
|
||||||
|
tauri::Builder::default()
|
||||||
|
.setup(|app| {
|
||||||
|
if cfg!(debug_assertions) {
|
||||||
|
app.handle().plugin(
|
||||||
|
tauri_plugin_log::Builder::default()
|
||||||
|
.level(log::LevelFilter::Info)
|
||||||
|
.build(),
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build the View menu with zoom controls
|
||||||
|
let zoom_in = MenuItemBuilder::with_id("zoom_in", "Zoom In")
|
||||||
|
.accelerator("CmdOrCtrl+=")
|
||||||
|
.build(app)?;
|
||||||
|
let zoom_out = MenuItemBuilder::with_id("zoom_out", "Zoom Out")
|
||||||
|
.accelerator("CmdOrCtrl+-")
|
||||||
|
.build(app)?;
|
||||||
|
let zoom_reset = MenuItemBuilder::with_id("zoom_reset", "Actual Size")
|
||||||
|
.accelerator("CmdOrCtrl+0")
|
||||||
|
.build(app)?;
|
||||||
|
|
||||||
|
let view_menu = Submenu::with_items(
|
||||||
|
app,
|
||||||
|
"View",
|
||||||
|
true,
|
||||||
|
&[&zoom_in, &zoom_out, &zoom_reset],
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let menu = Menu::with_items(app, &[&view_menu])?;
|
||||||
|
app.set_menu(menu)?;
|
||||||
|
|
||||||
|
let window = app.get_webview_window("main").unwrap();
|
||||||
|
let window_clone = window.clone();
|
||||||
|
|
||||||
|
app.on_menu_event(move |_app, event| {
|
||||||
|
let id = event.id().as_ref();
|
||||||
|
if id == "zoom_in" {
|
||||||
|
let current = get_zoom_factor();
|
||||||
|
set_zoom_factor(&window_clone, current + 0.1);
|
||||||
|
} else if id == "zoom_out" {
|
||||||
|
let current = get_zoom_factor();
|
||||||
|
set_zoom_factor(&window_clone, current - 0.1);
|
||||||
|
} else if id == "zoom_reset" {
|
||||||
|
set_zoom_factor(&window_clone, 1.0);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
.run(tauri::generate_context!())
|
||||||
|
.expect("error while running tauri application");
|
||||||
|
}
|
||||||
6
packages/desktop/src-tauri/src/main.rs
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
// Prevents additional console window on Windows in release, DO NOT REMOVE!!
|
||||||
|
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
paseo_lib::run();
|
||||||
|
}
|
||||||
41
packages/desktop/src-tauri/tauri.conf.json
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
{
|
||||||
|
"$schema": "../node_modules/@tauri-apps/cli/config.schema.json",
|
||||||
|
"productName": "Paseo",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"identifier": "dev.paseo.desktop",
|
||||||
|
"build": {
|
||||||
|
"frontendDist": "../../app/dist",
|
||||||
|
"devUrl": "http://localhost:8081"
|
||||||
|
},
|
||||||
|
"app": {
|
||||||
|
"withGlobalTauri": true,
|
||||||
|
"windows": [
|
||||||
|
{
|
||||||
|
"title": "Paseo",
|
||||||
|
"width": 1200,
|
||||||
|
"height": 800,
|
||||||
|
"minWidth": 800,
|
||||||
|
"minHeight": 600,
|
||||||
|
"resizable": true,
|
||||||
|
"fullscreen": false,
|
||||||
|
"titleBarStyle": "Overlay",
|
||||||
|
"hiddenTitle": true,
|
||||||
|
"trafficLightPosition": { "x": 16, "y": 22 }
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"security": {
|
||||||
|
"csp": null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"bundle": {
|
||||||
|
"active": true,
|
||||||
|
"targets": "all",
|
||||||
|
"icon": [
|
||||||
|
"icons/32x32.png",
|
||||||
|
"icons/128x128.png",
|
||||||
|
"icons/128x128@2x.png",
|
||||||
|
"icons/icon.icns",
|
||||||
|
"icons/icon.ico"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||