feat: initialize ZCLAW project with core systems and Tauri desktop
- Created backend core systems: - Remote Execution System (远程执行系统) - Task Orchestration Engine (任务编排引擎) - Persistent Memory System (持续记忆系统) - Proactive Service System (主动服务系统) - Created Tauri desktop app: - Three-column layout based on AutoClaw design - React + TypeScript + Tailwind CSS - Zustand state management - Lucide React icons - Components: - Sidebar (Agent list, IM channels, scheduled tasks) - ChatArea (Chat interface with message bubbles) - RightPanel (Task progress, statistics, next actions) Next: Test Tauri dev server and integrate with OpenClaw backend
37
.gitignore
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
# Dependencies
|
||||
node_modules/
|
||||
.pnpm-store/
|
||||
|
||||
# Build
|
||||
dist/
|
||||
build/
|
||||
*.tsbuildinfo
|
||||
|
||||
# Environment
|
||||
.env
|
||||
.env.local
|
||||
.env.*.local
|
||||
|
||||
# Logs
|
||||
logs/
|
||||
*.log
|
||||
npm-debug.log*
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# IDE
|
||||
.vscode/
|
||||
.idea/
|
||||
*.swp
|
||||
*.swo
|
||||
|
||||
# Database
|
||||
*.db
|
||||
*.sqlite
|
||||
*.sqlite3
|
||||
|
||||
# Tauri
|
||||
desktop/src-tauri/target/
|
||||
desktop/dist/
|
||||
62
README.md
Normal file
@@ -0,0 +1,62 @@
|
||||
# ZCLAW - AI Agent Platform
|
||||
|
||||
基于 OpenClaw 的 AI 代理平台,实现"随时随地、一个 IM 入口搞定一切"。
|
||||
|
||||
## 核心功能
|
||||
|
||||
- **远程执行系统**: 手机发消息 → 电脑执行 → 结果返回
|
||||
- **任务编排引擎**: 复杂任务自动拆解、多步骤执行
|
||||
- **多 Agent 协作**: Planner + Executor + Combiner 协作模式
|
||||
- **持续记忆系统**: 用户画像、行为学习、关系图谱
|
||||
- **主动服务系统**: 定时任务、智能提醒、主动推荐
|
||||
|
||||
## 技术栈
|
||||
|
||||
- TypeScript 5.x
|
||||
- Node.js 22 LTS
|
||||
- OpenClaw SDK
|
||||
- SQLite + sqlite-vec
|
||||
- BullMQ (任务队列)
|
||||
- Koishi (IM 集成)
|
||||
|
||||
## 快速开始
|
||||
|
||||
```bash
|
||||
# 安装依赖
|
||||
pnpm install
|
||||
|
||||
# 开发模式
|
||||
pnpm dev
|
||||
|
||||
# 构建
|
||||
pnpm build
|
||||
```
|
||||
|
||||
## 项目结构
|
||||
|
||||
```
|
||||
zclaw/
|
||||
├── src/
|
||||
│ ├── core/ # 核心系统
|
||||
│ │ ├── remote-execution/ # 远程执行
|
||||
│ │ ├── task-orchestration/ # 任务编排
|
||||
│ │ ├── multi-agent/ # 多 Agent 协作
|
||||
│ │ ├── memory/ # 持续记忆
|
||||
│ │ └── proactive/ # 主动服务
|
||||
│ ├── im/ # IM 集成
|
||||
│ │ ├── feishu/ # 飞书
|
||||
│ │ ├── wecom/ # 企业微信
|
||||
│ │ └── telegram/ # Telegram
|
||||
│ ├── skills/ # 场景化 Skills
|
||||
│ └── index.ts # 入口
|
||||
├── tests/ # 测试
|
||||
└── docs/ # 文档
|
||||
```
|
||||
|
||||
## 开发计划
|
||||
|
||||
详见: temp/zclaw-final-plan.md
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
24
desktop/.gitignore
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
7
desktop/README.md
Normal file
@@ -0,0 +1,7 @@
|
||||
# Tauri + React + Typescript
|
||||
|
||||
This template should help get you started developing with Tauri, React and Typescript in Vite.
|
||||
|
||||
## Recommended IDE Setup
|
||||
|
||||
- [VS Code](https://code.visualstudio.com/) + [Tauri](https://marketplace.visualstudio.com/items?itemName=tauri-apps.tauri-vscode) + [rust-analyzer](https://marketplace.visualstudio.com/items?itemName=rust-lang.rust-analyzer)
|
||||
14
desktop/index.html
Normal file
@@ -0,0 +1,14 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Tauri + React + Typescript</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
31
desktop/package.json
Normal file
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"name": "desktop",
|
||||
"private": true,
|
||||
"version": "0.1.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "tsc && vite build",
|
||||
"preview": "vite preview",
|
||||
"tauri": "tauri"
|
||||
},
|
||||
"dependencies": {
|
||||
"@tauri-apps/api": "^2",
|
||||
"@tauri-apps/plugin-opener": "^2",
|
||||
"lucide-react": "^0.577.0",
|
||||
"react": "^19.1.0",
|
||||
"react-dom": "^19.1.0",
|
||||
"zustand": "^5.0.11"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tauri-apps/cli": "^2",
|
||||
"@types/react": "^19.1.8",
|
||||
"@types/react-dom": "^19.1.6",
|
||||
"@vitejs/plugin-react": "^4.6.0",
|
||||
"autoprefixer": "^10.4.27",
|
||||
"postcss": "^8.5.8",
|
||||
"tailwindcss": "^4.2.1",
|
||||
"typescript": "~5.8.3",
|
||||
"vite": "^7.0.4"
|
||||
}
|
||||
}
|
||||
1355
desktop/pnpm-lock.yaml
generated
Normal file
6
desktop/public/tauri.svg
Normal file
@@ -0,0 +1,6 @@
|
||||
<svg width="206" height="231" viewBox="0 0 206 231" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M143.143 84C143.143 96.1503 133.293 106 121.143 106C108.992 106 99.1426 96.1503 99.1426 84C99.1426 71.8497 108.992 62 121.143 62C133.293 62 143.143 71.8497 143.143 84Z" fill="#FFC131"/>
|
||||
<ellipse cx="84.1426" cy="147" rx="22" ry="22" transform="rotate(180 84.1426 147)" fill="#24C8DB"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M166.738 154.548C157.86 160.286 148.023 164.269 137.757 166.341C139.858 160.282 141 153.774 141 147C141 144.543 140.85 142.121 140.558 139.743C144.975 138.204 149.215 136.139 153.183 133.575C162.73 127.404 170.292 118.608 174.961 108.244C179.63 97.8797 181.207 86.3876 179.502 75.1487C177.798 63.9098 172.884 53.4021 165.352 44.8883C157.82 36.3744 147.99 30.2165 137.042 27.1546C126.095 24.0926 114.496 24.2568 103.64 27.6274C92.7839 30.998 83.1319 37.4317 75.8437 46.1553C74.9102 47.2727 74.0206 48.4216 73.176 49.5993C61.9292 50.8488 51.0363 54.0318 40.9629 58.9556C44.2417 48.4586 49.5653 38.6591 56.679 30.1442C67.0505 17.7298 80.7861 8.57426 96.2354 3.77762C111.685 -1.01901 128.19 -1.25267 143.769 3.10474C159.348 7.46215 173.337 16.2252 184.056 28.3411C194.775 40.457 201.767 55.4101 204.193 71.404C206.619 87.3978 204.374 103.752 197.73 118.501C191.086 133.25 180.324 145.767 166.738 154.548ZM41.9631 74.275L62.5557 76.8042C63.0459 72.813 63.9401 68.9018 65.2138 65.1274C57.0465 67.0016 49.2088 70.087 41.9631 74.275Z" fill="#FFC131"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M38.4045 76.4519C47.3493 70.6709 57.2677 66.6712 67.6171 64.6132C65.2774 70.9669 64 77.8343 64 85.0001C64 87.1434 64.1143 89.26 64.3371 91.3442C60.0093 92.8732 55.8533 94.9092 51.9599 97.4256C42.4128 103.596 34.8505 112.392 30.1816 122.756C25.5126 133.12 23.9357 144.612 25.6403 155.851C27.3449 167.09 32.2584 177.598 39.7906 186.112C47.3227 194.626 57.153 200.784 68.1003 203.846C79.0476 206.907 90.6462 206.743 101.502 203.373C112.359 200.002 122.011 193.568 129.299 184.845C130.237 183.722 131.131 182.567 131.979 181.383C143.235 180.114 154.132 176.91 164.205 171.962C160.929 182.49 155.596 192.319 148.464 200.856C138.092 213.27 124.357 222.426 108.907 227.222C93.458 232.019 76.9524 232.253 61.3736 227.895C45.7948 223.538 31.8055 214.775 21.0867 202.659C10.3679 190.543 3.37557 175.59 0.949823 159.596C-1.47592 143.602 0.768139 127.248 7.41237 112.499C14.0566 97.7497 24.8183 85.2327 38.4045 76.4519ZM163.062 156.711L163.062 156.711C162.954 156.773 162.846 156.835 162.738 156.897C162.846 156.835 162.954 156.773 163.062 156.711Z" fill="#24C8DB"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.5 KiB |
1
desktop/public/vite.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
7
desktop/src-tauri/.gitignore
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
# Generated by Cargo
|
||||
# will have compiled files and executables
|
||||
/target/
|
||||
|
||||
# Generated by Tauri
|
||||
# will have schema files for capabilities auto-completion
|
||||
/gen/schemas
|
||||
25
desktop/src-tauri/Cargo.toml
Normal file
@@ -0,0 +1,25 @@
|
||||
[package]
|
||||
name = "desktop"
|
||||
version = "0.1.0"
|
||||
description = "A Tauri App"
|
||||
authors = ["you"]
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[lib]
|
||||
# The `_lib` suffix may seem redundant but it is necessary
|
||||
# to make the lib name unique and wouldn't conflict with the bin name.
|
||||
# This seems to be only an issue on Windows, see https://github.com/rust-lang/cargo/issues/8519
|
||||
name = "desktop_lib"
|
||||
crate-type = ["staticlib", "cdylib", "rlib"]
|
||||
|
||||
[build-dependencies]
|
||||
tauri-build = { version = "2", features = [] }
|
||||
|
||||
[dependencies]
|
||||
tauri = { version = "2", features = [] }
|
||||
tauri-plugin-opener = "2"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
|
||||
3
desktop/src-tauri/build.rs
Normal file
@@ -0,0 +1,3 @@
|
||||
fn main() {
|
||||
tauri_build::build()
|
||||
}
|
||||
10
desktop/src-tauri/capabilities/default.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"$schema": "../gen/schemas/desktop-schema.json",
|
||||
"identifier": "default",
|
||||
"description": "Capability for the main window",
|
||||
"windows": ["main"],
|
||||
"permissions": [
|
||||
"core:default",
|
||||
"opener:default"
|
||||
]
|
||||
}
|
||||
BIN
desktop/src-tauri/icons/128x128.png
Normal file
|
After Width: | Height: | Size: 3.4 KiB |
BIN
desktop/src-tauri/icons/128x128@2x.png
Normal file
|
After Width: | Height: | Size: 6.8 KiB |
BIN
desktop/src-tauri/icons/32x32.png
Normal file
|
After Width: | Height: | Size: 974 B |
BIN
desktop/src-tauri/icons/Square107x107Logo.png
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
BIN
desktop/src-tauri/icons/Square142x142Logo.png
Normal file
|
After Width: | Height: | Size: 3.8 KiB |
BIN
desktop/src-tauri/icons/Square150x150Logo.png
Normal file
|
After Width: | Height: | Size: 3.9 KiB |
BIN
desktop/src-tauri/icons/Square284x284Logo.png
Normal file
|
After Width: | Height: | Size: 7.6 KiB |
BIN
desktop/src-tauri/icons/Square30x30Logo.png
Normal file
|
After Width: | Height: | Size: 903 B |
BIN
desktop/src-tauri/icons/Square310x310Logo.png
Normal file
|
After Width: | Height: | Size: 8.4 KiB |
BIN
desktop/src-tauri/icons/Square44x44Logo.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
desktop/src-tauri/icons/Square71x71Logo.png
Normal file
|
After Width: | Height: | Size: 2.0 KiB |
BIN
desktop/src-tauri/icons/Square89x89Logo.png
Normal file
|
After Width: | Height: | Size: 2.4 KiB |
BIN
desktop/src-tauri/icons/StoreLogo.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
desktop/src-tauri/icons/icon.icns
Normal file
BIN
desktop/src-tauri/icons/icon.ico
Normal file
|
After Width: | Height: | Size: 85 KiB |
BIN
desktop/src-tauri/icons/icon.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
14
desktop/src-tauri/src/lib.rs
Normal file
@@ -0,0 +1,14 @@
|
||||
// Learn more about Tauri commands at https://tauri.app/develop/calling-rust/
|
||||
#[tauri::command]
|
||||
fn greet(name: &str) -> String {
|
||||
format!("Hello, {}! You've been greeted from Rust!", name)
|
||||
}
|
||||
|
||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||
pub fn run() {
|
||||
tauri::Builder::default()
|
||||
.plugin(tauri_plugin_opener::init())
|
||||
.invoke_handler(tauri::generate_handler![greet])
|
||||
.run(tauri::generate_context!())
|
||||
.expect("error while running tauri application");
|
||||
}
|
||||
6
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() {
|
||||
desktop_lib::run()
|
||||
}
|
||||
35
desktop/src-tauri/tauri.conf.json
Normal file
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"$schema": "https://schema.tauri.app/config/2",
|
||||
"productName": "desktop",
|
||||
"version": "0.1.0",
|
||||
"identifier": "com.szend.desktop",
|
||||
"build": {
|
||||
"beforeDevCommand": "pnpm dev",
|
||||
"devUrl": "http://localhost:1420",
|
||||
"beforeBuildCommand": "pnpm build",
|
||||
"frontendDist": "../dist"
|
||||
},
|
||||
"app": {
|
||||
"windows": [
|
||||
{
|
||||
"title": "desktop",
|
||||
"width": 800,
|
||||
"height": 600
|
||||
}
|
||||
],
|
||||
"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"
|
||||
]
|
||||
}
|
||||
}
|
||||
116
desktop/src/App.css
Normal file
@@ -0,0 +1,116 @@
|
||||
.logo.vite:hover {
|
||||
filter: drop-shadow(0 0 2em #747bff);
|
||||
}
|
||||
|
||||
.logo.react:hover {
|
||||
filter: drop-shadow(0 0 2em #61dafb);
|
||||
}
|
||||
:root {
|
||||
font-family: Inter, Avenir, Helvetica, Arial, sans-serif;
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
font-weight: 400;
|
||||
|
||||
color: #0f0f0f;
|
||||
background-color: #f6f6f6;
|
||||
|
||||
font-synthesis: none;
|
||||
text-rendering: optimizeLegibility;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
}
|
||||
|
||||
.container {
|
||||
margin: 0;
|
||||
padding-top: 10vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.logo {
|
||||
height: 6em;
|
||||
padding: 1.5em;
|
||||
will-change: filter;
|
||||
transition: 0.75s;
|
||||
}
|
||||
|
||||
.logo.tauri:hover {
|
||||
filter: drop-shadow(0 0 2em #24c8db);
|
||||
}
|
||||
|
||||
.row {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
a {
|
||||
font-weight: 500;
|
||||
color: #646cff;
|
||||
text-decoration: inherit;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: #535bf2;
|
||||
}
|
||||
|
||||
h1 {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
input,
|
||||
button {
|
||||
border-radius: 8px;
|
||||
border: 1px solid transparent;
|
||||
padding: 0.6em 1.2em;
|
||||
font-size: 1em;
|
||||
font-weight: 500;
|
||||
font-family: inherit;
|
||||
color: #0f0f0f;
|
||||
background-color: #ffffff;
|
||||
transition: border-color 0.25s;
|
||||
box-shadow: 0 2px 2px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
button {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
border-color: #396cd8;
|
||||
}
|
||||
button:active {
|
||||
border-color: #396cd8;
|
||||
background-color: #e8e8e8;
|
||||
}
|
||||
|
||||
input,
|
||||
button {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
#greet-input {
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
color: #f6f6f6;
|
||||
background-color: #2f2f2f;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: #24c8db;
|
||||
}
|
||||
|
||||
input,
|
||||
button {
|
||||
color: #ffffff;
|
||||
background-color: #0f0f0f98;
|
||||
}
|
||||
button:active {
|
||||
background-color: #0f0f0f69;
|
||||
}
|
||||
}
|
||||
23
desktop/src/App.tsx
Normal file
@@ -0,0 +1,23 @@
|
||||
import './index.css';
|
||||
import { Sidebar } from './components/Sidebar';
|
||||
import { ChatArea } from './components/ChatArea';
|
||||
import { RightPanel } from './components/RightPanel';
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<div className="h-screen flex overflow-hidden text-gray-800 text-sm">
|
||||
{/* 左侧边栏 */}
|
||||
<Sidebar />
|
||||
|
||||
{/* 中间对话区域 */}
|
||||
<main className="flex-1 flex flex-col bg-white relative">
|
||||
<ChatArea />
|
||||
</main>
|
||||
|
||||
{/* 右侧边栏 */}
|
||||
<RightPanel />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
1
desktop/src/assets/react.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="35.93" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 228"><path fill="#00D8FF" d="M210.483 73.824a171.49 171.49 0 0 0-8.24-2.597c.465-1.9.893-3.777 1.273-5.621c6.238-30.281 2.16-54.676-11.769-62.708c-13.355-7.7-35.196.329-57.254 19.526a171.23 171.23 0 0 0-6.375 5.848a155.866 155.866 0 0 0-4.241-3.917C100.759 3.829 77.587-4.822 63.673 3.233C50.33 10.957 46.379 33.89 51.995 62.588a170.974 170.974 0 0 0 1.892 8.48c-3.28.932-6.445 1.924-9.474 2.98C17.309 83.498 0 98.307 0 113.668c0 15.865 18.582 31.778 46.812 41.427a145.52 145.52 0 0 0 6.921 2.165a167.467 167.467 0 0 0-2.01 9.138c-5.354 28.2-1.173 50.591 12.134 58.266c13.744 7.926 36.812-.22 59.273-19.855a145.567 145.567 0 0 0 5.342-4.923a168.064 168.064 0 0 0 6.92 6.314c21.758 18.722 43.246 26.282 56.54 18.586c13.731-7.949 18.194-32.003 12.4-61.268a145.016 145.016 0 0 0-1.535-6.842c1.62-.48 3.21-.974 4.76-1.488c29.348-9.723 48.443-25.443 48.443-41.52c0-15.417-17.868-30.326-45.517-39.844Zm-6.365 70.984c-1.4.463-2.836.91-4.3 1.345c-3.24-10.257-7.612-21.163-12.963-32.432c5.106-11 9.31-21.767 12.459-31.957c2.619.758 5.16 1.557 7.61 2.4c23.69 8.156 38.14 20.213 38.14 29.504c0 9.896-15.606 22.743-40.946 31.14Zm-10.514 20.834c2.562 12.94 2.927 24.64 1.23 33.787c-1.524 8.219-4.59 13.698-8.382 15.893c-8.067 4.67-25.32-1.4-43.927-17.412a156.726 156.726 0 0 1-6.437-5.87c7.214-7.889 14.423-17.06 21.459-27.246c12.376-1.098 24.068-2.894 34.671-5.345a134.17 134.17 0 0 1 1.386 6.193ZM87.276 214.515c-7.882 2.783-14.16 2.863-17.955.675c-8.075-4.657-11.432-22.636-6.853-46.752a156.923 156.923 0 0 1 1.869-8.499c10.486 2.32 22.093 3.988 34.498 4.994c7.084 9.967 14.501 19.128 21.976 27.15a134.668 134.668 0 0 1-4.877 4.492c-9.933 8.682-19.886 14.842-28.658 17.94ZM50.35 144.747c-12.483-4.267-22.792-9.812-29.858-15.863c-6.35-5.437-9.555-10.836-9.555-15.216c0-9.322 13.897-21.212 37.076-29.293c2.813-.98 5.757-1.905 8.812-2.773c3.204 10.42 7.406 21.315 12.477 32.332c-5.137 11.18-9.399 22.249-12.634 32.792a134.718 134.718 0 0 1-6.318-1.979Zm12.378-84.26c-4.811-24.587-1.616-43.134 6.425-47.789c8.564-4.958 27.502 2.111 47.463 19.835a144.318 144.318 0 0 1 3.841 3.545c-7.438 7.987-14.787 17.08-21.808 26.988c-12.04 1.116-23.565 2.908-34.161 5.309a160.342 160.342 0 0 1-1.76-7.887Zm110.427 27.268a347.8 347.8 0 0 0-7.785-12.803c8.168 1.033 15.994 2.404 23.343 4.08c-2.206 7.072-4.956 14.465-8.193 22.045a381.151 381.151 0 0 0-7.365-13.322Zm-45.032-43.861c5.044 5.465 10.096 11.566 15.065 18.186a322.04 322.04 0 0 0-30.257-.006c4.974-6.559 10.069-12.652 15.192-18.18ZM82.802 87.83a323.167 323.167 0 0 0-7.227 13.238c-3.184-7.553-5.909-14.98-8.134-22.152c7.304-1.634 15.093-2.97 23.209-3.984a321.524 321.524 0 0 0-7.848 12.897Zm8.081 65.352c-8.385-.936-16.291-2.203-23.593-3.793c2.26-7.3 5.045-14.885 8.298-22.6a321.187 321.187 0 0 0 7.257 13.246c2.594 4.48 5.28 8.868 8.038 13.147Zm37.542 31.03c-5.184-5.592-10.354-11.779-15.403-18.433c4.902.192 9.899.29 14.978.29c5.218 0 10.376-.117 15.453-.343c-4.985 6.774-10.018 12.97-15.028 18.486Zm52.198-57.817c3.422 7.8 6.306 15.345 8.596 22.52c-7.422 1.694-15.436 3.058-23.88 4.071a382.417 382.417 0 0 0 7.859-13.026a347.403 347.403 0 0 0 7.425-13.565Zm-16.898 8.101a358.557 358.557 0 0 1-12.281 19.815a329.4 329.4 0 0 1-23.444.823c-7.967 0-15.716-.248-23.178-.732a310.202 310.202 0 0 1-12.513-19.846h.001a307.41 307.41 0 0 1-10.923-20.627a310.278 310.278 0 0 1 10.89-20.637l-.001.001a307.318 307.318 0 0 1 12.413-19.761c7.613-.576 15.42-.876 23.31-.876H128c7.926 0 15.743.303 23.354.883a329.357 329.357 0 0 1 12.335 19.695a358.489 358.489 0 0 1 11.036 20.54a329.472 329.472 0 0 1-11 20.722Zm22.56-122.124c8.572 4.944 11.906 24.881 6.52 51.026c-.344 1.668-.73 3.367-1.15 5.09c-10.622-2.452-22.155-4.275-34.23-5.408c-7.034-10.017-14.323-19.124-21.64-27.008a160.789 160.789 0 0 1 5.888-5.4c18.9-16.447 36.564-22.941 44.612-18.3ZM128 90.808c12.625 0 22.86 10.235 22.86 22.86s-10.235 22.86-22.86 22.86s-22.86-10.235-22.86-22.86s10.235-22.86 22.86-22.86Z"></path></svg>
|
||||
|
After Width: | Height: | Size: 4.0 KiB |
112
desktop/src/components/ChatArea.tsx
Normal file
@@ -0,0 +1,112 @@
|
||||
import { useState } from 'react';
|
||||
import { useChatStore } from '../store/chatStore';
|
||||
import { Send, Paperclip, ChevronDown } from 'lucide-react';
|
||||
|
||||
export function ChatArea() {
|
||||
const { messages, currentAgent, addMessage } = useChatStore();
|
||||
const [input, setInput] = useState('');
|
||||
|
||||
const sendMessage = () => {
|
||||
if (!input.trim()) return;
|
||||
|
||||
const userMessage = {
|
||||
id: Date.now().toString(),
|
||||
role: 'user' as const,
|
||||
content: input,
|
||||
timestamp: new Date(),
|
||||
};
|
||||
|
||||
addMessage(userMessage);
|
||||
setInput('');
|
||||
|
||||
// TODO: 调用后端 API
|
||||
setTimeout(() => {
|
||||
const aiMessage = {
|
||||
id: (Date.now() + 1).toString(),
|
||||
role: 'assistant' as const,
|
||||
content: '收到你的消息了!正在处理中...',
|
||||
timestamp: new Date(),
|
||||
};
|
||||
addMessage(aiMessage);
|
||||
}, 1000);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* 顶部标题栏 */}
|
||||
<div className="h-14 border-b border-gray-100 flex items-center justify-between px-6 flex-shrink-0">
|
||||
<div className="flex items-center gap-2">
|
||||
<h2 className="font-semibold text-gray-900">{currentAgent?.name || 'ZCLAW'}</h2>
|
||||
<span className="text-xs text-gray-400 flex items-center gap-1">
|
||||
<span className="w-1.5 h-1.5 bg-green-400 rounded-full"></span>
|
||||
在线
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 聊天内容区 */}
|
||||
<div className="flex-1 overflow-y-auto custom-scrollbar p-6 space-y-6">
|
||||
{messages.length === 0 && (
|
||||
<div className="text-center text-gray-400 py-20">
|
||||
<p className="text-lg mb-2">欢迎使用 ZCLAW 🦞</p>
|
||||
<p className="text-sm">发送消息开始对话</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{messages.map((message) => (
|
||||
<div
|
||||
key={message.id}
|
||||
className={lex gap-4 }
|
||||
>
|
||||
<div
|
||||
className={w-8 h-8 rounded-lg flex items-center justify-center flex-shrink-0 }
|
||||
>
|
||||
{message.role === 'user' ? '用' : '🦞'}
|
||||
</div>
|
||||
<div className={message.role === 'user' ? 'max-w-2xl' : 'flex-1 max-w-3xl'}>
|
||||
<div className={message.role === 'user' ? 'chat-bubble-user p-4 shadow-md' : 'chat-bubble-assistant p-4 shadow-sm'}>
|
||||
<p className="leading-relaxed text-gray-700">{message.content}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* 底部输入区 */}
|
||||
<div className="border-t border-gray-100 p-4 bg-white">
|
||||
<div className="max-w-4xl mx-auto">
|
||||
<div className="relative flex items-end gap-2 bg-gray-50 rounded-2xl border border-gray-200 p-2 focus-within:border-orange-300 focus-within:ring-2 focus-within:ring-orange-100 transition-all">
|
||||
<button className="p-2 text-gray-400 hover:text-gray-600 rounded-lg">
|
||||
<Paperclip className="w-5 h-5" />
|
||||
</button>
|
||||
<div className="flex-1 py-2">
|
||||
<input
|
||||
type="text"
|
||||
value={input}
|
||||
onChange={(e) => setInput(e.target.value)}
|
||||
onKeyPress={(e) => e.key === 'Enter' && sendMessage()}
|
||||
placeholder="发送给 ZCLAW"
|
||||
className="w-full bg-transparent border-none focus:outline-none text-gray-700 placeholder-gray-400"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 pr-2 pb-1">
|
||||
<button className="flex items-center gap-1 px-2 py-1 text-xs text-gray-500 hover:bg-gray-200 rounded-md transition-colors">
|
||||
<span>glm5</span>
|
||||
<ChevronDown className="w-3 h-3" />
|
||||
</button>
|
||||
<button
|
||||
onClick={sendMessage}
|
||||
className="w-8 h-8 bg-gray-900 text-white rounded-full flex items-center justify-center hover:bg-gray-800 transition-colors"
|
||||
>
|
||||
<Send className="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-center mt-2 text-xs text-gray-400">
|
||||
Agent 在本地运行,内容由 AI 生成
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
106
desktop/src/components/RightPanel.tsx
Normal file
@@ -0,0 +1,106 @@
|
||||
import { FileText, User, Target, CheckSquare } from 'lucide-react';
|
||||
|
||||
export function RightPanel() {
|
||||
return (
|
||||
<aside className="w-80 bg-white border-l border-gray-200 flex flex-col flex-shrink-0">
|
||||
{/* 顶部工具栏 */}
|
||||
<div className="h-14 border-b border-gray-100 flex items-center justify-between px-4 flex-shrink-0">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="flex items-center gap-1 text-gray-600">
|
||||
<Target className="w-4 h-4" />
|
||||
<span className="font-medium">2268</span>
|
||||
</div>
|
||||
<button className="text-xs text-orange-600 hover:underline">去购买</button>
|
||||
</div>
|
||||
<div className="flex items-center gap-3 text-gray-500">
|
||||
<button className="hover:text-gray-700 flex items-center gap-1 text-xs">
|
||||
<FileText className="w-4 h-4" />
|
||||
<span>文件</span>
|
||||
</button>
|
||||
<button className="hover:text-gray-700 flex items-center gap-1 text-xs">
|
||||
<User className="w-4 h-4" />
|
||||
<span>Agent</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 内容区 */}
|
||||
<div className="flex-1 overflow-y-auto custom-scrollbar p-4">
|
||||
{/* 任务进度 */}
|
||||
<div className="bg-gray-50 rounded-lg border border-gray-100 mb-6 overflow-hidden">
|
||||
<div className="p-3">
|
||||
<div className="flex justify-between text-xs mb-2">
|
||||
<span className="text-gray-600">任务进度</span>
|
||||
<span className="font-medium text-gray-900">65%</span>
|
||||
</div>
|
||||
<div className="w-full bg-gray-200 rounded-full h-2">
|
||||
<div className="bg-orange-500 h-2 rounded-full transition-all" style={{ width: '65%' }}></div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="border-t border-gray-100 divide-y divide-gray-100 text-xs">
|
||||
<div className="py-2 px-3 flex justify-between">
|
||||
<span className="text-gray-600">远程执行</span>
|
||||
<span className="text-green-600">✅ 完成</span>
|
||||
</div>
|
||||
<div className="py-2 px-3 flex justify-between">
|
||||
<span className="text-gray-600">任务编排</span>
|
||||
<span className="text-orange-600">🔄 进行中</span>
|
||||
</div>
|
||||
<div className="py-2 px-3 flex justify-between">
|
||||
<span className="text-gray-600">多 Agent 协作</span>
|
||||
<span className="text-gray-400">⏳ 待开始</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 今日统计 */}
|
||||
<div className="mb-6">
|
||||
<h3 className="font-bold text-gray-900 mb-3 text-sm">今日统计</h3>
|
||||
<div className="bg-gray-50 rounded-lg border border-gray-100 p-3">
|
||||
<div className="space-y-2 text-xs">
|
||||
<div className="flex justify-between">
|
||||
<span className="text-gray-600">任务数</span>
|
||||
<span className="font-medium text-gray-900">8 个</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-gray-600">成功</span>
|
||||
<span className="font-medium text-green-600">6 个</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-gray-600">进行中</span>
|
||||
<span className="font-medium text-orange-600">2 个</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 下一步行动 */}
|
||||
<div>
|
||||
<h3 className="font-bold text-gray-900 mb-3 text-sm flex items-center gap-2">
|
||||
<span className="w-5 h-5 bg-orange-500 rounded-full flex items-center justify-center text-white text-xs">
|
||||
<Target className="w-3 h-3" />
|
||||
</span>
|
||||
下一步行动
|
||||
</h3>
|
||||
<div className="ml-7 space-y-2">
|
||||
<h4 className="text-xs font-semibold text-gray-900 mb-2">立即执行 (今天)</h4>
|
||||
<ul className="space-y-2">
|
||||
<li className="flex items-start gap-2 text-xs text-gray-600">
|
||||
<div className="w-3 h-3 border border-gray-300 rounded-sm mt-0.5 flex-shrink-0"></div>
|
||||
<span>初始化项目结构</span>
|
||||
</li>
|
||||
<li className="flex items-start gap-2 text-xs text-gray-600">
|
||||
<div className="w-3 h-3 border border-gray-300 rounded-sm mt-0.5 flex-shrink-0"></div>
|
||||
<span>创建核心系统代码</span>
|
||||
</li>
|
||||
<li className="flex items-start gap-2 text-xs text-gray-600">
|
||||
<div className="w-3 h-3 border border-gray-300 rounded-sm mt-0.5 flex-shrink-0"></div>
|
||||
<span>集成 OpenClaw SDK</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
);
|
||||
}
|
||||
70
desktop/src/components/Sidebar.tsx
Normal file
@@ -0,0 +1,70 @@
|
||||
import { useChatStore } from '../store/chatStore';
|
||||
import { Settings, Cat, Search, Globe, BarChart } from 'lucide-react';
|
||||
|
||||
export function Sidebar() {
|
||||
const { agents, currentAgent, setCurrentAgent } = useChatStore();
|
||||
const [activeTab, setActiveTab] = React.useState('agents');
|
||||
|
||||
return (
|
||||
<aside className="w-64 bg-gray-50 border-r border-gray-200 flex flex-col flex-shrink-0">
|
||||
{/* 顶部标签 */}
|
||||
<div className="flex border-b border-gray-200 bg-white">
|
||||
<button
|
||||
className={lex-1 py-3 px-4 text-xs font-medium }
|
||||
onClick={() => setActiveTab('agents')}
|
||||
>
|
||||
分身
|
||||
</button>
|
||||
<button
|
||||
className={lex-1 py-3 px-4 text-xs font-medium }
|
||||
onClick={() => setActiveTab('channels')}
|
||||
>
|
||||
IM 频道
|
||||
</button>
|
||||
<button
|
||||
className={lex-1 py-3 px-4 text-xs font-medium }
|
||||
onClick={() => setActiveTab('tasks')}
|
||||
>
|
||||
定时任务
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Agent 列表 */}
|
||||
<div className="flex-1 overflow-y-auto custom-scrollbar py-2">
|
||||
{agents.map((agent) => (
|
||||
<div
|
||||
key={agent.id}
|
||||
className={sidebar-item mx-2 px-3 py-3 rounded-lg cursor-pointer mb-1 }
|
||||
onClick={() => setCurrentAgent(agent)}
|
||||
>
|
||||
<div className="flex items-start gap-3">
|
||||
<div className={w-10 h-10 rounded-xl flex items-center justify-center text-white flex-shrink-0}>
|
||||
<span className="text-xl">{agent.icon}</span>
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex justify-between items-center mb-0.5">
|
||||
<span className="font-semibold text-gray-900 truncate">{agent.name}</span>
|
||||
<span className="text-xs text-gray-400">{agent.time}</span>
|
||||
</div>
|
||||
<p className="text-xs text-gray-500 truncate leading-relaxed">{agent.lastMessage}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* 底部用户 */}
|
||||
<div className="p-3 border-t border-gray-200 bg-gray-50">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-8 h-8 bg-gradient-to-br from-orange-400 to-red-500 rounded-full flex items-center justify-center text-white text-xs font-bold">
|
||||
🦞
|
||||
</div>
|
||||
<span className="font-medium text-gray-700">用户7141</span>
|
||||
<button className="ml-auto text-gray-400 hover:text-gray-600">
|
||||
<Settings className="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
);
|
||||
}
|
||||
48
desktop/src/index.css
Normal file
@@ -0,0 +1,48 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600&display=swap');
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
||||
background-color: #f9fafb;
|
||||
}
|
||||
|
||||
.custom-scrollbar::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
.custom-scrollbar::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
.custom-scrollbar::-webkit-scrollbar-thumb {
|
||||
background: #d1d5db;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.agent-avatar {
|
||||
background: linear-gradient(135deg, #f97316 0%, #ea580c 100%);
|
||||
}
|
||||
|
||||
.chat-bubble-assistant {
|
||||
background: white;
|
||||
border: 1px solid #e5e7eb;
|
||||
border-radius: 12px;
|
||||
border-bottom-left-radius: 4px;
|
||||
}
|
||||
|
||||
.chat-bubble-user {
|
||||
background: #f97316;
|
||||
color: white;
|
||||
border-radius: 12px;
|
||||
border-bottom-right-radius: 4px;
|
||||
}
|
||||
|
||||
.thinking-dot {
|
||||
animation: pulse 1.5s infinite;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0%, 100% { opacity: 0.4; }
|
||||
50% { opacity: 1; }
|
||||
}
|
||||
10
desktop/src/main.tsx
Normal file
@@ -0,0 +1,10 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom/client';
|
||||
import App from './App';
|
||||
import './index.css';
|
||||
|
||||
ReactDOM.createRoot(document.getElementById('root')!).render(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
</React.StrictMode>,
|
||||
);
|
||||
42
desktop/src/store/chatStore.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import { create } from 'zustand';
|
||||
|
||||
export interface Message {
|
||||
id: string;
|
||||
role: 'user' | 'assistant';
|
||||
content: string;
|
||||
timestamp: Date;
|
||||
}
|
||||
|
||||
export interface Agent {
|
||||
id: string;
|
||||
name: string;
|
||||
icon: string;
|
||||
color: string;
|
||||
lastMessage: string;
|
||||
time: string;
|
||||
}
|
||||
|
||||
interface ChatState {
|
||||
messages: Message[];
|
||||
agents: Agent[];
|
||||
currentAgent: Agent | null;
|
||||
addMessage: (message: Message) => void;
|
||||
setCurrentAgent: (agent: Agent) => void;
|
||||
}
|
||||
|
||||
export const useChatStore = create<ChatState>((set) => ({
|
||||
messages: [],
|
||||
agents: [
|
||||
{
|
||||
id: '1',
|
||||
name: 'ZCLAW',
|
||||
icon: '🦞',
|
||||
color: 'bg-gradient-to-br from-orange-500 to-red-500',
|
||||
lastMessage: '好的!选项 A 确认...',
|
||||
time: '21:58',
|
||||
},
|
||||
],
|
||||
currentAgent: null,
|
||||
addMessage: (message) => set((state) => ({ messages: [...state.messages, message] })),
|
||||
setCurrentAgent: (agent) => set({ currentAgent: agent }),
|
||||
}));
|
||||
1
desktop/src/vite-env.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/// <reference types="vite/client" />
|
||||
11
desktop/tailwind.config.js
Normal file
@@ -0,0 +1,11 @@
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
export default {
|
||||
content: [
|
||||
"./index.html",
|
||||
"./src/**/*.{js,ts,jsx,tsx}",
|
||||
],
|
||||
theme: {
|
||||
extend: {},
|
||||
},
|
||||
plugins: [],
|
||||
}
|
||||
25
desktop/tsconfig.json
Normal file
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"useDefineForClassFields": true,
|
||||
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
||||
"module": "ESNext",
|
||||
"skipLibCheck": true,
|
||||
|
||||
/* Bundler mode */
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx",
|
||||
|
||||
/* Linting */
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noFallthroughCasesInSwitch": true
|
||||
},
|
||||
"include": ["src"],
|
||||
"references": [{ "path": "./tsconfig.node.json" }]
|
||||
}
|
||||
10
desktop/tsconfig.node.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"skipLibCheck": true,
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
"allowSyntheticDefaultImports": true
|
||||
},
|
||||
"include": ["vite.config.ts"]
|
||||
}
|
||||
32
desktop/vite.config.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import { defineConfig } from "vite";
|
||||
import react from "@vitejs/plugin-react";
|
||||
|
||||
// @ts-expect-error process is a nodejs global
|
||||
const host = process.env.TAURI_DEV_HOST;
|
||||
|
||||
// https://vite.dev/config/
|
||||
export default defineConfig(async () => ({
|
||||
plugins: [react()],
|
||||
|
||||
// Vite options tailored for Tauri development and only applied in `tauri dev` or `tauri build`
|
||||
//
|
||||
// 1. prevent Vite from obscuring rust errors
|
||||
clearScreen: false,
|
||||
// 2. tauri expects a fixed port, fail if that port is not available
|
||||
server: {
|
||||
port: 1420,
|
||||
strictPort: true,
|
||||
host: host || false,
|
||||
hmr: host
|
||||
? {
|
||||
protocol: "ws",
|
||||
host,
|
||||
port: 1421,
|
||||
}
|
||||
: undefined,
|
||||
watch: {
|
||||
// 3. tell Vite to ignore watching `src-tauri`
|
||||
ignored: ["**/src-tauri/**"],
|
||||
},
|
||||
},
|
||||
}));
|
||||
31
package.json
Normal file
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"name": "zclaw",
|
||||
"version": "0.1.0",
|
||||
"description": "ZCLAW - AI Agent Platform based on OpenClaw",
|
||||
"main": "dist/index.js",
|
||||
"scripts": {
|
||||
"dev": "tsx watch src/index.ts",
|
||||
"build": "tsc",
|
||||
"test": "jest"
|
||||
},
|
||||
"keywords": ["ai", "agent", "openclaw", "automation"],
|
||||
"author": "ZCLAW Team",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@openclaw/sdk": "latest",
|
||||
"bullmq": "^5.0.0",
|
||||
"ioredis": "^5.3.0",
|
||||
"better-sqlite3": "^9.4.0",
|
||||
"sqlite-vec": "^0.1.0",
|
||||
"node-cron": "^3.0.3",
|
||||
"koishi": "^4.17.0",
|
||||
"zod": "^3.22.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.11.0",
|
||||
"@types/better-sqlite3": "^7.6.8",
|
||||
"typescript": "^5.3.0",
|
||||
"tsx": "^4.7.0",
|
||||
"jest": "^29.7.0"
|
||||
}
|
||||
}
|
||||
1
src/core/memory/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './memory';
|
||||
54
src/core/memory/memory.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
// 持续记忆系统
|
||||
export interface UserProfile {
|
||||
id: string;
|
||||
preferences: {
|
||||
language: 'zh' | 'en';
|
||||
timezone: string;
|
||||
responseStyle: 'concise' | 'detailed';
|
||||
};
|
||||
patterns: {
|
||||
activeHours: number[];
|
||||
frequentCommands: string[];
|
||||
};
|
||||
}
|
||||
|
||||
export interface MemoryEvent {
|
||||
id: string;
|
||||
userId: string;
|
||||
type: string;
|
||||
content: any;
|
||||
timestamp: Date;
|
||||
embedding?: number[];
|
||||
}
|
||||
|
||||
export class PersistentMemorySystem {
|
||||
private profiles: Map<string, UserProfile> = new Map();
|
||||
private events: MemoryEvent[] = [];
|
||||
|
||||
async remember(userId: string, event: MemoryEvent): Promise<void> {
|
||||
event.id = event.id || this.generateId();
|
||||
event.timestamp = new Date();
|
||||
this.events.push(event);
|
||||
console.log([Memory] Event remembered: );
|
||||
}
|
||||
|
||||
async recall(userId: string, query: string, limit: number = 10): Promise<MemoryEvent[]> {
|
||||
// TODO: 实现向量搜索(后续实现)
|
||||
return this.events.filter(e => e.userId === userId).slice(0, limit);
|
||||
}
|
||||
|
||||
async getProfile(userId: string): Promise<UserProfile | undefined> {
|
||||
return this.profiles.get(userId);
|
||||
}
|
||||
|
||||
async updateProfile(userId: string, updates: Partial<UserProfile>): Promise<void> {
|
||||
const profile = this.profiles.get(userId);
|
||||
if (profile) {
|
||||
Object.assign(profile, updates);
|
||||
}
|
||||
}
|
||||
|
||||
private generateId(): string {
|
||||
return mem__;
|
||||
}
|
||||
}
|
||||
1
src/core/proactive/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './proactive';
|
||||
49
src/core/proactive/proactive.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
// 主动服务系统
|
||||
export interface ScheduledTask {
|
||||
id: string;
|
||||
userId: string;
|
||||
channel: string;
|
||||
schedule: {
|
||||
type: 'once' | 'daily' | 'weekly' | 'cron';
|
||||
time: string;
|
||||
timezone: string;
|
||||
};
|
||||
task: {
|
||||
type: string;
|
||||
prompt: string;
|
||||
};
|
||||
status: 'active' | 'paused' | 'completed';
|
||||
lastRun?: Date;
|
||||
nextRun?: Date;
|
||||
}
|
||||
|
||||
export class ProactiveServiceSystem {
|
||||
private tasks: Map<string, ScheduledTask> = new Map();
|
||||
private cronJobs: Map<string, any> = new Map();
|
||||
|
||||
async scheduleTask(task: ScheduledTask): Promise<void> {
|
||||
task.id = task.id || this.generateId();
|
||||
task.status = 'active';
|
||||
|
||||
this.tasks.set(task.id, task);
|
||||
|
||||
// TODO: 使用 node-cron 设置定时任务(后续实现)
|
||||
console.log([Proactive] Task scheduled: );
|
||||
}
|
||||
|
||||
async cancelTask(taskId: string): Promise<void> {
|
||||
const task = this.tasks.get(taskId);
|
||||
if (task) {
|
||||
task.status = 'paused';
|
||||
// TODO: 取消 cron job
|
||||
}
|
||||
}
|
||||
|
||||
async listTasks(userId: string): Promise<ScheduledTask[]> {
|
||||
return Array.from(this.tasks.values()).filter(t => t.userId === userId);
|
||||
}
|
||||
|
||||
private generateId(): string {
|
||||
return cron__;
|
||||
}
|
||||
}
|
||||
125
src/core/remote-execution/engine.ts
Normal file
@@ -0,0 +1,125 @@
|
||||
// 远程执行系统 - 实现类
|
||||
import type {
|
||||
RemoteExecutionSystem,
|
||||
Device,
|
||||
Task,
|
||||
TaskStatus,
|
||||
StatusHandler,
|
||||
Result,
|
||||
DeviceStatus
|
||||
} from './types';
|
||||
|
||||
export class RemoteExecutionEngine implements RemoteExecutionSystem {
|
||||
private devices: Map<string, Device> = new Map();
|
||||
private tasks: Map<string, Task> = new Map();
|
||||
private subscriptions: Map<string, StatusHandler[]> = new Map();
|
||||
private taskQueue: Task[] = [];
|
||||
|
||||
async registerDevice(device: Device): Promise<void> {
|
||||
this.devices.set(device.id, device);
|
||||
console.log([RemoteExecution] Device registered: ());
|
||||
}
|
||||
|
||||
async heartbeat(deviceId: string): Promise<DeviceStatus> {
|
||||
const device = this.devices.get(deviceId);
|
||||
if (!device) {
|
||||
throw new Error(Device not found: );
|
||||
}
|
||||
|
||||
device.lastHeartbeat = new Date();
|
||||
return device.status;
|
||||
}
|
||||
|
||||
async submitTask(task: Task): Promise<string> {
|
||||
task.id = task.id || this.generateId();
|
||||
task.status = 'pending';
|
||||
task.createdAt = new Date();
|
||||
|
||||
this.tasks.set(task.id, task);
|
||||
this.taskQueue.push(task);
|
||||
|
||||
console.log([RemoteExecution] Task submitted: );
|
||||
|
||||
// 立即执行(后续会改为队列处理)
|
||||
this.executeTask(task).catch(console.error);
|
||||
|
||||
return task.id;
|
||||
}
|
||||
|
||||
async cancelTask(taskId: string): Promise<void> {
|
||||
const task = this.tasks.get(taskId);
|
||||
if (!task) {
|
||||
throw new Error(Task not found: );
|
||||
}
|
||||
|
||||
task.status = 'cancelled';
|
||||
this.notifySubscribers(taskId, 'cancelled');
|
||||
}
|
||||
|
||||
async getStatus(taskId: string): Promise<TaskStatus> {
|
||||
const task = this.tasks.get(taskId);
|
||||
if (!task) {
|
||||
throw new Error(Task not found: );
|
||||
}
|
||||
|
||||
return task.status;
|
||||
}
|
||||
|
||||
subscribe(taskId: string, handler: StatusHandler): void {
|
||||
if (!this.subscriptions.has(taskId)) {
|
||||
this.subscriptions.set(taskId, []);
|
||||
}
|
||||
this.subscriptions.get(taskId)!.push(handler);
|
||||
}
|
||||
|
||||
async pushResult(taskId: string, result: Result): Promise<void> {
|
||||
const task = this.tasks.get(taskId);
|
||||
if (!task) {
|
||||
throw new Error(Task not found: );
|
||||
}
|
||||
|
||||
task.result = result;
|
||||
task.status = result.success ? 'completed' : 'failed';
|
||||
task.completedAt = new Date();
|
||||
|
||||
this.notifySubscribers(taskId, task.status);
|
||||
}
|
||||
|
||||
private async executeTask(task: Task): Promise<void> {
|
||||
try {
|
||||
task.status = 'running';
|
||||
task.startedAt = new Date();
|
||||
this.notifySubscribers(task.id, 'running');
|
||||
|
||||
// TODO: 实际执行逻辑(调用 OpenClaw SDK)
|
||||
console.log([RemoteExecution] Executing task: );
|
||||
|
||||
// 模拟执行
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
|
||||
await this.pushResult(task.id, {
|
||||
taskId: task.id,
|
||||
success: true,
|
||||
data: { message: 'Task completed successfully' }
|
||||
});
|
||||
|
||||
} catch (error: any) {
|
||||
await this.pushResult(task.id, {
|
||||
taskId: task.id,
|
||||
success: false,
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private notifySubscribers(taskId: string, status: TaskStatus, progress?: number): void {
|
||||
const handlers = this.subscriptions.get(taskId);
|
||||
if (handlers) {
|
||||
handlers.forEach(handler => handler(status, progress));
|
||||
}
|
||||
}
|
||||
|
||||
private generateId(): string {
|
||||
return ask__;
|
||||
}
|
||||
}
|
||||
2
src/core/remote-execution/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from './types';
|
||||
export * from './engine';
|
||||
46
src/core/remote-execution/types.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
// 远程执行系统 - 核心接口
|
||||
export interface Device {
|
||||
id: string;
|
||||
name: string;
|
||||
userId: string;
|
||||
platform: 'macos' | 'windows' | 'linux';
|
||||
capabilities: string[];
|
||||
status: 'online' | 'offline' | 'busy';
|
||||
lastHeartbeat: Date;
|
||||
}
|
||||
|
||||
export interface Task {
|
||||
id: string;
|
||||
userId: string;
|
||||
deviceId: string;
|
||||
channel: string;
|
||||
type: 'immediate' | 'scheduled';
|
||||
priority: 'high' | 'normal' | 'low';
|
||||
payload: any;
|
||||
status: TaskStatus;
|
||||
result?: any;
|
||||
createdAt: Date;
|
||||
startedAt?: Date;
|
||||
completedAt?: Date;
|
||||
}
|
||||
|
||||
export type TaskStatus = 'pending' | 'running' | 'completed' | 'failed' | 'cancelled';
|
||||
|
||||
export interface RemoteExecutionSystem {
|
||||
registerDevice(device: Device): Promise<void>;
|
||||
heartbeat(deviceId: string): Promise<DeviceStatus>;
|
||||
submitTask(task: Task): Promise<string>;
|
||||
cancelTask(taskId: string): Promise<void>;
|
||||
getStatus(taskId: string): Promise<TaskStatus>;
|
||||
subscribe(taskId: string, handler: StatusHandler): void;
|
||||
pushResult(taskId: string, result: Result): Promise<void>;
|
||||
}
|
||||
|
||||
export type StatusHandler = (status: TaskStatus, progress?: number) => void;
|
||||
export type DeviceStatus = 'online' | 'offline' | 'busy';
|
||||
export interface Result {
|
||||
taskId: string;
|
||||
success: boolean;
|
||||
data?: any;
|
||||
error?: string;
|
||||
}
|
||||
2
src/core/task-orchestration/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from './types';
|
||||
export * from './orchestrator';
|
||||
186
src/core/task-orchestration/orchestrator.ts
Normal file
@@ -0,0 +1,186 @@
|
||||
// 任务编排引擎 - 实现类
|
||||
import type {
|
||||
TaskOrchestrationEngine,
|
||||
TaskPlan,
|
||||
TaskStep,
|
||||
ExecutionResult,
|
||||
Progress,
|
||||
StepStatus
|
||||
} from './types';
|
||||
|
||||
export class TaskOrchestrator implements TaskOrchestrationEngine {
|
||||
private plans: Map<string, TaskPlan> = new Map();
|
||||
|
||||
async plan(goal: string, context: any): Promise<TaskPlan> {
|
||||
// TODO: 使用 AI 规划任务(后续实现)
|
||||
// 这里先返回一个简单的示例计划
|
||||
const steps: TaskStep[] = [
|
||||
{
|
||||
id: 'step_1',
|
||||
description: '分析任务需求',
|
||||
tool: 'ai',
|
||||
params: { goal },
|
||||
dependencies: [],
|
||||
status: 'pending'
|
||||
},
|
||||
{
|
||||
id: 'step_2',
|
||||
description: '执行任务',
|
||||
tool: 'executor',
|
||||
params: { goal },
|
||||
dependencies: ['step_1'],
|
||||
status: 'pending'
|
||||
},
|
||||
{
|
||||
id: 'step_3',
|
||||
description: '整理结果',
|
||||
tool: 'combiner',
|
||||
params: {},
|
||||
dependencies: ['step_2'],
|
||||
status: 'pending'
|
||||
}
|
||||
];
|
||||
|
||||
const plan: TaskPlan = {
|
||||
id: this.generateId(),
|
||||
goal,
|
||||
steps,
|
||||
dependencies: new Map(),
|
||||
context: new Map(Object.entries(context)),
|
||||
status: 'planned',
|
||||
progress: 0
|
||||
};
|
||||
|
||||
this.plans.set(plan.id, plan);
|
||||
console.log([TaskOrchestrator] Plan created: );
|
||||
|
||||
return plan;
|
||||
}
|
||||
|
||||
async execute(plan: TaskPlan): Promise<ExecutionResult> {
|
||||
plan.status = 'executing';
|
||||
|
||||
try {
|
||||
// 拓扑排序
|
||||
const sortedSteps = this.topologicalSort(plan.steps);
|
||||
|
||||
for (const step of sortedSteps) {
|
||||
// 检查依赖
|
||||
await this.waitForDependencies(step, plan);
|
||||
|
||||
// 执行步骤
|
||||
step.status = 'running';
|
||||
plan.progress = this.calculateProgress(plan);
|
||||
|
||||
console.log([TaskOrchestrator] Executing step: );
|
||||
|
||||
// TODO: 实际执行逻辑(后续实现)
|
||||
await new Promise(resolve => setTimeout(resolve, 500));
|
||||
|
||||
step.status = 'completed';
|
||||
step.result = { message: 'Step completed' };
|
||||
|
||||
plan.progress = this.calculateProgress(plan);
|
||||
}
|
||||
|
||||
plan.status = 'completed';
|
||||
plan.progress = 1;
|
||||
|
||||
return {
|
||||
planId: plan.id,
|
||||
status: 'completed',
|
||||
results: plan.steps.map(s => s.result)
|
||||
};
|
||||
|
||||
} catch (error: any) {
|
||||
plan.status = 'failed';
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async getProgress(planId: string): Promise<Progress> {
|
||||
const plan = this.plans.get(planId);
|
||||
if (!plan) {
|
||||
throw new Error(Plan not found: );
|
||||
}
|
||||
|
||||
const completed = plan.steps.filter(s => s.status === 'completed').length;
|
||||
const percentage = (plan.progress * 100).toFixed(1) + '%';
|
||||
|
||||
return {
|
||||
planId: plan.id,
|
||||
goal: plan.goal,
|
||||
total: plan.steps.length,
|
||||
completed,
|
||||
current: plan.steps.find(s => s.status === 'running')?.description || '',
|
||||
percentage,
|
||||
steps: plan.steps.map(s => ({
|
||||
description: s.description,
|
||||
status: s.status,
|
||||
result: s.result
|
||||
}))
|
||||
};
|
||||
}
|
||||
|
||||
async pause(planId: string): Promise<void> {
|
||||
const plan = this.plans.get(planId);
|
||||
if (plan) {
|
||||
plan.status = 'paused';
|
||||
}
|
||||
}
|
||||
|
||||
async resume(planId: string): Promise<void> {
|
||||
const plan = this.plans.get(planId);
|
||||
if (plan && plan.status === 'paused') {
|
||||
plan.status = 'executing';
|
||||
// 继续执行(后续实现)
|
||||
}
|
||||
}
|
||||
|
||||
async cancel(planId: string): Promise<void> {
|
||||
const plan = this.plans.get(planId);
|
||||
if (plan) {
|
||||
plan.status = 'failed';
|
||||
}
|
||||
}
|
||||
|
||||
private topologicalSort(steps: TaskStep[]): TaskStep[] {
|
||||
// 简单实现:按依赖顺序排序
|
||||
const sorted: TaskStep[] = [];
|
||||
const visited = new Set<string>();
|
||||
|
||||
const visit = (step: TaskStep) => {
|
||||
if (visited.has(step.id)) return;
|
||||
visited.add(step.id);
|
||||
|
||||
for (const depId of step.dependencies) {
|
||||
const dep = steps.find(s => s.id === depId);
|
||||
if (dep) visit(dep);
|
||||
}
|
||||
|
||||
sorted.push(step);
|
||||
};
|
||||
|
||||
steps.forEach(visit);
|
||||
return sorted;
|
||||
}
|
||||
|
||||
private async waitForDependencies(step: TaskStep, plan: TaskPlan): Promise<void> {
|
||||
for (const depId of step.dependencies) {
|
||||
const dep = plan.steps.find(s => s.id === depId);
|
||||
if (dep && dep.status !== 'completed') {
|
||||
// 等待依赖完成(简单实现)
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private calculateProgress(plan: TaskPlan): number {
|
||||
const completed = plan.steps.filter(s => s.status === 'completed').length;
|
||||
return completed / plan.steps.length;
|
||||
}
|
||||
|
||||
private generateId(): string {
|
||||
return plan__;
|
||||
}
|
||||
}
|
||||
53
src/core/task-orchestration/types.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
// 任务编排引擎 - 类型定义
|
||||
export interface TaskPlan {
|
||||
id: string;
|
||||
goal: string;
|
||||
steps: TaskStep[];
|
||||
dependencies: Map<string, string[]>;
|
||||
context: Map<string, any>;
|
||||
status: PlanStatus;
|
||||
progress: number;
|
||||
}
|
||||
|
||||
export interface TaskStep {
|
||||
id: string;
|
||||
description: string;
|
||||
tool: string;
|
||||
params: any;
|
||||
dependencies: string[];
|
||||
status: StepStatus;
|
||||
result?: any;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
export type PlanStatus = 'planned' | 'executing' | 'completed' | 'failed' | 'paused';
|
||||
export type StepStatus = 'pending' | 'running' | 'completed' | 'failed' | 'skipped';
|
||||
|
||||
export interface ExecutionResult {
|
||||
planId: string;
|
||||
status: PlanStatus;
|
||||
results: any[];
|
||||
}
|
||||
|
||||
export interface Progress {
|
||||
planId: string;
|
||||
goal: string;
|
||||
total: number;
|
||||
completed: number;
|
||||
current: string;
|
||||
percentage: string;
|
||||
steps: Array<{
|
||||
description: string;
|
||||
status: StepStatus;
|
||||
result?: any;
|
||||
}>;
|
||||
}
|
||||
|
||||
export interface TaskOrchestrationEngine {
|
||||
plan(goal: string, context: any): Promise<TaskPlan>;
|
||||
execute(plan: TaskPlan): Promise<ExecutionResult>;
|
||||
getProgress(planId: string): Promise<Progress>;
|
||||
pause(planId: string): Promise<void>;
|
||||
resume(planId: string): Promise<void>;
|
||||
cancel(planId: string): Promise<void>;
|
||||
}
|
||||
5
src/index.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
// ZCLAW 入口文件
|
||||
export * from './core/remote-execution';
|
||||
export * from './core/task-orchestration';
|
||||
export * from './core/memory';
|
||||
export * from './core/proactive';
|
||||
20
tsconfig.json
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"module": "NodeNext",
|
||||
"moduleResolution": "NodeNext",
|
||||
"lib": ["ES2022"],
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"resolveJsonModule": true,
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"sourceMap": true
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "dist", "tests"]
|
||||
}
|
||||