@ww-ai-lab/openclaw-office 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +241 -0
- package/README.zh.md +241 -0
- package/bin/openclaw-office.mjs +92 -0
- package/dist/assets/ActivityHeatmap-CbbZRmIr.js +1 -0
- package/dist/assets/CostPieChart-CEdqGGQC.js +4 -0
- package/dist/assets/NetworkGraph-CsG_yO_d.js +1 -0
- package/dist/assets/Scene3D-DuZCBa3W.js +4712 -0
- package/dist/assets/TokenLineChart-c0WS1KHK.js +2 -0
- package/dist/assets/generateCategoricalChart-8RMAKiEE.js +69 -0
- package/dist/assets/index-C877jght.css +1 -0
- package/dist/assets/index-CoF0NRzA.js +399 -0
- package/dist/assets/ws-adapter-sYZJya-9.js +1 -0
- package/dist/favicon.svg +4 -0
- package/dist/icons/.gitkeep +0 -0
- package/dist/index.html +14 -0
- package/dist/models/.gitkeep +0 -0
- package/package.json +84 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 OpenClaw
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
# OpenClaw Office
|
|
2
|
+
|
|
3
|
+
> [中文文档](./README.zh.md)
|
|
4
|
+
|
|
5
|
+
> Visualize AI agent collaboration as a real-time digital twin office.
|
|
6
|
+
|
|
7
|
+
**OpenClaw Office** is the visual monitoring and management frontend for the [OpenClaw](https://github.com/openclaw/openclaw) Multi-Agent system. It renders Agent work status, collaboration links, tool calls, and resource consumption through an isometric-style virtual office scene, along with a full-featured console for system management.
|
|
8
|
+
|
|
9
|
+
**Core Metaphor:** Agent = Digital Employee | Office = Agent Runtime | Desk = Session | Meeting Pod = Collaboration Context
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Features
|
|
14
|
+
|
|
15
|
+
### Virtual Office
|
|
16
|
+
|
|
17
|
+
- **2D Floor Plan** — SVG-rendered isometric office with desk zones, hot desks, meeting areas, and rich furniture (desks, chairs, sofas, plants, coffee cups)
|
|
18
|
+
- **3D Scene** — React Three Fiber 3D office with character models, skill holograms, spawn portal effects, and post-processing
|
|
19
|
+
- **Agent Avatars** — Deterministically generated SVG avatars from agent IDs with real-time status animations (idle, working, speaking, tool calling, error)
|
|
20
|
+
- **Collaboration Lines** — Visual connections showing inter-Agent message flow
|
|
21
|
+
- **Speech Bubbles** — Live Markdown text streaming and tool call display
|
|
22
|
+
- **Side Panels** — Agent details, Token line charts, cost pie charts, activity heatmaps, SubAgent relationship graphs, event timelines
|
|
23
|
+
|
|
24
|
+
### Chat
|
|
25
|
+
|
|
26
|
+
- Bottom-docked chat bar for real-time conversations with Agents
|
|
27
|
+
- Agent selector, streaming message display, Markdown rendering
|
|
28
|
+
- Chat history drawer with timeline view
|
|
29
|
+
|
|
30
|
+

|
|
31
|
+
|
|
32
|
+

|
|
33
|
+
|
|
34
|
+
### Console
|
|
35
|
+
|
|
36
|
+
Full system management interface with dedicated pages:
|
|
37
|
+
|
|
38
|
+
| Page | Features |
|
|
39
|
+
|------|----------|
|
|
40
|
+
| **Dashboard** | Overview stats, alert banners, Channel/Skill overview, quick navigation |
|
|
41
|
+
| **Agents** | Agent list/create/delete, detail tabs (Overview, Channels, Cron, Skills, Tools, Files) |
|
|
42
|
+
| **Channels** | Channel cards, configuration dialogs, stats, WhatsApp QR binding |
|
|
43
|
+
| **Skills** | Skill marketplace, install options, skill detail dialogs |
|
|
44
|
+
| **Cron** | Scheduled task management and statistics |
|
|
45
|
+
| **Settings** | Provider management (add/edit/model editor), appearance, Gateway, developer, advanced, about, update |
|
|
46
|
+
|
|
47
|
+

|
|
48
|
+
|
|
49
|
+

|
|
50
|
+
|
|
51
|
+

|
|
52
|
+
|
|
53
|
+
### Other
|
|
54
|
+
|
|
55
|
+
- **i18n** — Full Chinese/English bilingual support with runtime language switching
|
|
56
|
+
- **Mock Mode** — Develop without a live Gateway connection
|
|
57
|
+
- **Responsive** — Mobile-optimized with automatic 2D fallback
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## Tech Stack
|
|
62
|
+
|
|
63
|
+
| Layer | Technology |
|
|
64
|
+
|-------|-----------|
|
|
65
|
+
| Build Tool | Vite 6 |
|
|
66
|
+
| UI Framework | React 19 |
|
|
67
|
+
| 2D Rendering | SVG + CSS Animations |
|
|
68
|
+
| 3D Rendering | React Three Fiber (R3F) + @react-three/drei |
|
|
69
|
+
| State Management | Zustand 5 + Immer |
|
|
70
|
+
| Styling | Tailwind CSS 4 |
|
|
71
|
+
| Routing | React Router 7 |
|
|
72
|
+
| Charts | Recharts |
|
|
73
|
+
| i18n | i18next + react-i18next |
|
|
74
|
+
| Real-time | Native WebSocket (connects to OpenClaw Gateway) |
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## Prerequisites
|
|
79
|
+
|
|
80
|
+
- **Node.js 22+**
|
|
81
|
+
- **pnpm** (package manager)
|
|
82
|
+
- **[OpenClaw](https://github.com/openclaw/openclaw)** installed and configured
|
|
83
|
+
|
|
84
|
+
OpenClaw Office is a companion frontend that connects to a running OpenClaw Gateway. It does **not** start or manage the Gateway itself.
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
## Quick Start
|
|
89
|
+
|
|
90
|
+
### 1. Install Dependencies
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
pnpm install
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### 2. Configure Gateway Connection
|
|
97
|
+
|
|
98
|
+
Create a `.env.local` file (gitignored) with your Gateway connection details:
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
cat > .env.local << 'EOF'
|
|
102
|
+
VITE_GATEWAY_URL=ws://localhost:18789
|
|
103
|
+
VITE_GATEWAY_TOKEN=<your-gateway-token>
|
|
104
|
+
EOF
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Get your Gateway token:
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
openclaw config get gateway.auth.token
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### 3. Enable Device Auth Bypass (Required)
|
|
114
|
+
|
|
115
|
+
OpenClaw Office is a pure web application and cannot provide Ed25519 device identity signatures that Gateway 2026.2.15+ requires for operator scopes. You must configure the Gateway to bypass this requirement:
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
openclaw config set gateway.controlUi.dangerouslyDisableDeviceAuth true
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
**Restart the Gateway** after this configuration change.
|
|
122
|
+
|
|
123
|
+
> **Security Note:** This bypass is intended for local development. In production, use a reverse proxy or other secure authentication mechanism.
|
|
124
|
+
|
|
125
|
+
### 4. Start the Gateway
|
|
126
|
+
|
|
127
|
+
Ensure the OpenClaw Gateway is running on the configured address (default `localhost:18789`). You can start it via:
|
|
128
|
+
|
|
129
|
+
- The OpenClaw macOS app
|
|
130
|
+
- `openclaw gateway run` CLI command
|
|
131
|
+
- Other deployment methods (see [OpenClaw documentation](https://github.com/openclaw/openclaw))
|
|
132
|
+
|
|
133
|
+
### 5. Start the Dev Server
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
pnpm dev
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Open `http://localhost:5180` in your browser.
|
|
140
|
+
|
|
141
|
+
### Environment Variables
|
|
142
|
+
|
|
143
|
+
| Variable | Required | Default | Description |
|
|
144
|
+
|----------|----------|---------|-------------|
|
|
145
|
+
| `VITE_GATEWAY_URL` | No | `ws://localhost:18789` | Gateway WebSocket address |
|
|
146
|
+
| `VITE_GATEWAY_TOKEN` | Yes (when connecting to real Gateway) | — | Gateway auth token |
|
|
147
|
+
| `VITE_MOCK` | No | `false` | Enable mock mode (no Gateway needed) |
|
|
148
|
+
|
|
149
|
+
### Mock Mode (No Gateway)
|
|
150
|
+
|
|
151
|
+
To develop without a running Gateway, enable mock mode:
|
|
152
|
+
|
|
153
|
+
```bash
|
|
154
|
+
VITE_MOCK=true pnpm dev
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
This uses simulated Agent data for UI development.
|
|
158
|
+
|
|
159
|
+
---
|
|
160
|
+
|
|
161
|
+
## Project Structure
|
|
162
|
+
|
|
163
|
+
```
|
|
164
|
+
OpenClaw-Office/
|
|
165
|
+
├── src/
|
|
166
|
+
│ ├── main.tsx / App.tsx # Entry point and routing
|
|
167
|
+
│ ├── i18n/ # Internationalization (zh/en)
|
|
168
|
+
│ ├── gateway/ # Gateway communication layer
|
|
169
|
+
│ │ ├── ws-client.ts # WebSocket client + auth + reconnect
|
|
170
|
+
│ │ ├── rpc-client.ts # RPC request wrapper
|
|
171
|
+
│ │ ├── event-parser.ts # Event parsing + state mapping
|
|
172
|
+
│ │ └── mock-adapter.ts # Mock mode adapter
|
|
173
|
+
│ ├── store/ # Zustand state management
|
|
174
|
+
│ │ ├── office-store.ts # Main store (Agent state, connection, UI)
|
|
175
|
+
│ │ └── console-stores/ # Per-page console stores
|
|
176
|
+
│ ├── components/
|
|
177
|
+
│ │ ├── layout/ # AppShell, ConsoleLayout, Sidebar, TopBar
|
|
178
|
+
│ │ ├── office-2d/ # 2D SVG floor plan + furniture
|
|
179
|
+
│ │ ├── office-3d/ # 3D R3F scene
|
|
180
|
+
│ │ ├── overlays/ # HTML overlays (speech bubbles)
|
|
181
|
+
│ │ ├── panels/ # Detail/metrics/chart panels
|
|
182
|
+
│ │ ├── chat/ # Chat dock bar
|
|
183
|
+
│ │ ├── console/ # Console feature components
|
|
184
|
+
│ │ ├── pages/ # Console route pages
|
|
185
|
+
│ │ └── shared/ # Shared components
|
|
186
|
+
│ ├── hooks/ # Custom React hooks
|
|
187
|
+
│ ├── lib/ # Utility library
|
|
188
|
+
│ └── styles/ # Global styles
|
|
189
|
+
├── public/ # Static assets
|
|
190
|
+
├── tests/ # Test files
|
|
191
|
+
├── package.json
|
|
192
|
+
├── vite.config.ts
|
|
193
|
+
└── tsconfig.json
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
---
|
|
197
|
+
|
|
198
|
+
## Development
|
|
199
|
+
|
|
200
|
+
### Commands
|
|
201
|
+
|
|
202
|
+
```bash
|
|
203
|
+
pnpm install # Install dependencies
|
|
204
|
+
pnpm dev # Start dev server (port 5180)
|
|
205
|
+
pnpm build # Production build
|
|
206
|
+
pnpm test # Run tests
|
|
207
|
+
pnpm test:watch # Test watch mode
|
|
208
|
+
pnpm typecheck # TypeScript type check
|
|
209
|
+
pnpm lint # Oxlint linting
|
|
210
|
+
pnpm format # Oxfmt formatting
|
|
211
|
+
pnpm check # lint + format check
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
### Architecture
|
|
215
|
+
|
|
216
|
+
OpenClaw Office connects to the Gateway via WebSocket and follows this data flow:
|
|
217
|
+
|
|
218
|
+
```
|
|
219
|
+
OpenClaw Gateway ──WebSocket──> ws-client.ts ──> event-parser.ts ──> Zustand Store ──> React Components
|
|
220
|
+
│ │
|
|
221
|
+
└── RPC (agents.list, chat.send, ...) ──> rpc-client.ts ──────────────>─┘
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
The Gateway broadcasts real-time events (`agent`, `presence`, `health`, `heartbeat`) and responds to RPC requests. The frontend maps Agent lifecycle events to visual states (idle, working, speaking, tool_calling, error) and renders them in the office scene.
|
|
225
|
+
|
|
226
|
+
---
|
|
227
|
+
|
|
228
|
+
## Contributing
|
|
229
|
+
|
|
230
|
+
Contributions are welcome! Whether it's new visualization effects, 3D model improvements, console features, or performance optimizations.
|
|
231
|
+
|
|
232
|
+
1. Fork this repository
|
|
233
|
+
2. Create a feature branch (`git checkout -b feature/cool-effect`)
|
|
234
|
+
3. Commit your changes (use [Conventional Commits](https://www.conventionalcommits.org/))
|
|
235
|
+
4. Open a Pull Request
|
|
236
|
+
|
|
237
|
+
---
|
|
238
|
+
|
|
239
|
+
## License
|
|
240
|
+
|
|
241
|
+
[MIT](./LICENSE)
|
package/README.zh.md
ADDED
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
# OpenClaw Office
|
|
2
|
+
|
|
3
|
+
> [English](./README.md)
|
|
4
|
+
|
|
5
|
+
> 将 AI 智能体的协作逻辑具象化为实时的数字孪生办公室。
|
|
6
|
+
|
|
7
|
+
**OpenClaw Office** 是 [OpenClaw](https://github.com/openclaw/openclaw) Multi-Agent 系统的可视化监控与管理前端。它通过等距投影(Isometric)风格的虚拟办公室场景,实时展示 Agent 的工作状态、协作链路、工具调用和资源消耗,同时提供完整的控制台管理界面。
|
|
8
|
+
|
|
9
|
+
**核心隐喻:** Agent = 数字员工 | 办公室 = Agent 运行时 | 工位 = Session | 会议室 = 协作上下文
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## 功能概览
|
|
14
|
+
|
|
15
|
+
### 虚拟办公室
|
|
16
|
+
|
|
17
|
+
- **2D 平面图** — SVG 渲染的等距办公室场景,包含工位区、临时工位、会议区和丰富的家具(桌椅/沙发/植物/咖啡杯)
|
|
18
|
+
- **3D 场景** — React Three Fiber 3D 办公室,含角色模型、技能全息面板、传送门特效和后处理效果
|
|
19
|
+
- **Agent 头像** — 基于 agentId 确定性生成的 SVG 头像,支持实时状态动画(空闲/工作中/发言/工具调用/错误)
|
|
20
|
+
- **协作连线** — Agent 间消息传递的可视化连接
|
|
21
|
+
- **气泡面板** — 实时 Markdown 文本流和工具调用展示
|
|
22
|
+
- **侧边面板** — Agent 详情、Token 折线图、成本饼图、活跃热力图、子 Agent 关系图、事件时间轴
|
|
23
|
+
|
|
24
|
+
### Chat 聊天
|
|
25
|
+
|
|
26
|
+
- 底部停靠的聊天栏,支持与 Agent 实时对话
|
|
27
|
+
- Agent 选择器、流式消息展示、Markdown 渲染
|
|
28
|
+
- 聊天历史抽屉和时间轴视图
|
|
29
|
+
|
|
30
|
+

|
|
31
|
+
|
|
32
|
+

|
|
33
|
+
|
|
34
|
+
### 控制台
|
|
35
|
+
|
|
36
|
+
完整的系统管理界面:
|
|
37
|
+
|
|
38
|
+
| 页面 | 功能 |
|
|
39
|
+
|------|------|
|
|
40
|
+
| **Dashboard** | 概览统计卡片、告警横幅、Channel/Skill 概览、快捷导航 |
|
|
41
|
+
| **Agents** | Agent 列表/创建/删除,详情多 Tab(Overview/Channels/Cron/Skills/Tools/Files) |
|
|
42
|
+
| **Channels** | 渠道卡片、配置对话框、统计、WhatsApp QR 绑定流程 |
|
|
43
|
+
| **Skills** | 技能市场、安装选项、技能详情 |
|
|
44
|
+
| **Cron** | 定时任务管理和统计 |
|
|
45
|
+
| **Settings** | Provider 管理(添加/编辑/模型编辑器)、外观/Gateway/开发者/高级/关于/更新 |
|
|
46
|
+
|
|
47
|
+

|
|
48
|
+
|
|
49
|
+

|
|
50
|
+
|
|
51
|
+

|
|
52
|
+
|
|
53
|
+
### 其他特性
|
|
54
|
+
|
|
55
|
+
- **国际化** — 完整的中英文双语支持,运行时语言切换
|
|
56
|
+
- **Mock 模式** — 无需连接 Gateway 即可开发
|
|
57
|
+
- **响应式** — 移动端优化,自动切换 2D 模式
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## 技术栈
|
|
62
|
+
|
|
63
|
+
| 层 | 技术 |
|
|
64
|
+
|-----|------|
|
|
65
|
+
| 构建工具 | Vite 6 |
|
|
66
|
+
| UI 框架 | React 19 |
|
|
67
|
+
| 2D 渲染 | SVG + CSS Animations |
|
|
68
|
+
| 3D 渲染 | React Three Fiber (R3F) + @react-three/drei |
|
|
69
|
+
| 状态管理 | Zustand 5 + Immer |
|
|
70
|
+
| 样式 | Tailwind CSS 4 |
|
|
71
|
+
| 路由 | React Router 7 |
|
|
72
|
+
| 图表 | Recharts |
|
|
73
|
+
| 国际化 | i18next + react-i18next |
|
|
74
|
+
| 实时通信 | 原生 WebSocket(对接 OpenClaw Gateway) |
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## 前提条件
|
|
79
|
+
|
|
80
|
+
- **Node.js 22+**
|
|
81
|
+
- **pnpm**(包管理器)
|
|
82
|
+
- **[OpenClaw](https://github.com/openclaw/openclaw)** 已安装并配置
|
|
83
|
+
|
|
84
|
+
OpenClaw Office 是一个配套前端,连接到正在运行的 OpenClaw Gateway。它**不会**启动或管理 Gateway。
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
## 快速开始
|
|
89
|
+
|
|
90
|
+
### 1. 安装依赖
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
pnpm install
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### 2. 配置 Gateway 连接
|
|
97
|
+
|
|
98
|
+
创建 `.env.local` 文件(已在 `.gitignore` 中,不会被提交),填入 Gateway 连接信息:
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
cat > .env.local << 'EOF'
|
|
102
|
+
VITE_GATEWAY_URL=ws://localhost:18789
|
|
103
|
+
VITE_GATEWAY_TOKEN=<你的 gateway token>
|
|
104
|
+
EOF
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
获取 Gateway token:
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
openclaw config get gateway.auth.token
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### 3. 启用 Device Auth Bypass(必须)
|
|
114
|
+
|
|
115
|
+
OpenClaw Office 是纯 Web 应用,无法提供 Gateway 2026.2.15+ 要求的 Ed25519 device identity 签名。需要配置 Gateway 绕过此要求:
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
openclaw config set gateway.controlUi.dangerouslyDisableDeviceAuth true
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
配置后需**重启 Gateway**。
|
|
122
|
+
|
|
123
|
+
> **安全提示:** 此 bypass 配置仅建议在本地开发环境使用。生产环境应通过反向代理或其他安全机制处理认证。
|
|
124
|
+
|
|
125
|
+
### 4. 启动 Gateway
|
|
126
|
+
|
|
127
|
+
确保 OpenClaw Gateway 在配置的地址上运行(默认 `localhost:18789`)。可通过以下方式启动:
|
|
128
|
+
|
|
129
|
+
- OpenClaw macOS 应用
|
|
130
|
+
- `openclaw gateway run` CLI 命令
|
|
131
|
+
- 其他部署方式(参见 [OpenClaw 文档](https://github.com/openclaw/openclaw))
|
|
132
|
+
|
|
133
|
+
### 5. 启动开发服务器
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
pnpm dev
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
在浏览器中打开 `http://localhost:5180`。
|
|
140
|
+
|
|
141
|
+
### 环境变量
|
|
142
|
+
|
|
143
|
+
| 变量 | 必须 | 默认值 | 说明 |
|
|
144
|
+
|------|------|--------|------|
|
|
145
|
+
| `VITE_GATEWAY_URL` | 否 | `ws://localhost:18789` | Gateway WebSocket 地址 |
|
|
146
|
+
| `VITE_GATEWAY_TOKEN` | 是(连接真实 Gateway 时) | — | Gateway 认证 token |
|
|
147
|
+
| `VITE_MOCK` | 否 | `false` | 启用 Mock 模式(不需要 Gateway) |
|
|
148
|
+
|
|
149
|
+
### Mock 模式(无需 Gateway)
|
|
150
|
+
|
|
151
|
+
如需在没有运行中的 Gateway 的情况下开发,启用 Mock 模式:
|
|
152
|
+
|
|
153
|
+
```bash
|
|
154
|
+
VITE_MOCK=true pnpm dev
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
这会使用模拟的 Agent 数据进行 UI 开发。
|
|
158
|
+
|
|
159
|
+
---
|
|
160
|
+
|
|
161
|
+
## 项目结构
|
|
162
|
+
|
|
163
|
+
```
|
|
164
|
+
OpenClaw-Office/
|
|
165
|
+
├── src/
|
|
166
|
+
│ ├── main.tsx / App.tsx # 入口与路由
|
|
167
|
+
│ ├── i18n/ # 国际化(zh/en)
|
|
168
|
+
│ ├── gateway/ # Gateway 通信层
|
|
169
|
+
│ │ ├── ws-client.ts # WebSocket 客户端 + 认证 + 重连
|
|
170
|
+
│ │ ├── rpc-client.ts # RPC 请求封装
|
|
171
|
+
│ │ ├── event-parser.ts # 事件解析 + 状态映射
|
|
172
|
+
│ │ └── mock-adapter.ts # Mock 模式适配器
|
|
173
|
+
│ ├── store/ # Zustand 状态管理
|
|
174
|
+
│ │ ├── office-store.ts # 主 Store(Agent 状态、连接、UI)
|
|
175
|
+
│ │ └── console-stores/ # 控制台各页面 Store
|
|
176
|
+
│ ├── components/
|
|
177
|
+
│ │ ├── layout/ # AppShell / ConsoleLayout / Sidebar / TopBar
|
|
178
|
+
│ │ ├── office-2d/ # 2D SVG 平面图 + 家具
|
|
179
|
+
│ │ ├── office-3d/ # 3D R3F 场景
|
|
180
|
+
│ │ ├── overlays/ # HTML Overlay(气泡等)
|
|
181
|
+
│ │ ├── panels/ # 详情/指标/图表面板
|
|
182
|
+
│ │ ├── chat/ # Chat 停靠栏
|
|
183
|
+
│ │ ├── console/ # 控制台功能组件
|
|
184
|
+
│ │ ├── pages/ # 控制台路由页面
|
|
185
|
+
│ │ └── shared/ # 公共组件
|
|
186
|
+
│ ├── hooks/ # 自定义 Hooks
|
|
187
|
+
│ ├── lib/ # 工具库
|
|
188
|
+
│ └── styles/ # 全局样式
|
|
189
|
+
├── public/ # 静态资源
|
|
190
|
+
├── tests/ # 测试文件
|
|
191
|
+
├── package.json
|
|
192
|
+
├── vite.config.ts
|
|
193
|
+
└── tsconfig.json
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
---
|
|
197
|
+
|
|
198
|
+
## 开发
|
|
199
|
+
|
|
200
|
+
### 命令
|
|
201
|
+
|
|
202
|
+
```bash
|
|
203
|
+
pnpm install # 安装依赖
|
|
204
|
+
pnpm dev # 启动开发服务器 (port 5180)
|
|
205
|
+
pnpm build # 构建生产版本
|
|
206
|
+
pnpm test # 运行测试
|
|
207
|
+
pnpm test:watch # 测试 watch 模式
|
|
208
|
+
pnpm typecheck # TypeScript 类型检查
|
|
209
|
+
pnpm lint # Oxlint 检查
|
|
210
|
+
pnpm format # Oxfmt 格式化
|
|
211
|
+
pnpm check # lint + format 检查
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
### 架构
|
|
215
|
+
|
|
216
|
+
OpenClaw Office 通过 WebSocket 连接 Gateway,数据流如下:
|
|
217
|
+
|
|
218
|
+
```
|
|
219
|
+
OpenClaw Gateway ──WebSocket──> ws-client.ts ──> event-parser.ts ──> Zustand Store ──> React 组件
|
|
220
|
+
│ │
|
|
221
|
+
└── RPC (agents.list, chat.send, ...) ──> rpc-client.ts ──────────────>─┘
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
Gateway 广播实时事件(`agent`、`presence`、`health`、`heartbeat`)并响应 RPC 请求。前端将 Agent 生命周期事件映射为可视化状态(idle/working/speaking/tool_calling/error),在办公室场景中渲染。
|
|
225
|
+
|
|
226
|
+
---
|
|
227
|
+
|
|
228
|
+
## 贡献
|
|
229
|
+
|
|
230
|
+
欢迎任何贡献!无论是新的可视化效果、3D 模型改进、控制台功能还是性能优化。
|
|
231
|
+
|
|
232
|
+
1. Fork 本仓库
|
|
233
|
+
2. 创建特性分支 (`git checkout -b feature/cool-effect`)
|
|
234
|
+
3. 提交更改(使用 [Conventional Commits](https://www.conventionalcommits.org/) 格式)
|
|
235
|
+
4. 开启 Pull Request
|
|
236
|
+
|
|
237
|
+
---
|
|
238
|
+
|
|
239
|
+
## 许可证
|
|
240
|
+
|
|
241
|
+
[MIT](./LICENSE)
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { createServer } from "node:http";
|
|
4
|
+
import { readFile, access } from "node:fs/promises";
|
|
5
|
+
import { resolve, join, extname } from "node:path";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
7
|
+
import { networkInterfaces } from "node:os";
|
|
8
|
+
|
|
9
|
+
const __dirname = fileURLToPath(new URL(".", import.meta.url));
|
|
10
|
+
const distDir = resolve(__dirname, "..", "dist");
|
|
11
|
+
|
|
12
|
+
const MIME_TYPES = {
|
|
13
|
+
".html": "text/html; charset=utf-8",
|
|
14
|
+
".js": "application/javascript; charset=utf-8",
|
|
15
|
+
".css": "text/css; charset=utf-8",
|
|
16
|
+
".json": "application/json; charset=utf-8",
|
|
17
|
+
".svg": "image/svg+xml",
|
|
18
|
+
".png": "image/png",
|
|
19
|
+
".jpg": "image/jpeg",
|
|
20
|
+
".jpeg": "image/jpeg",
|
|
21
|
+
".gif": "image/gif",
|
|
22
|
+
".ico": "image/x-icon",
|
|
23
|
+
".woff": "font/woff",
|
|
24
|
+
".woff2": "font/woff2",
|
|
25
|
+
".ttf": "font/ttf",
|
|
26
|
+
".webp": "image/webp",
|
|
27
|
+
".glb": "model/gltf-binary",
|
|
28
|
+
".gltf": "model/gltf+json",
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const port = parseInt(process.env.PORT || "5180", 10);
|
|
32
|
+
const host = process.env.HOST || "0.0.0.0";
|
|
33
|
+
|
|
34
|
+
async function tryReadFile(filePath) {
|
|
35
|
+
try {
|
|
36
|
+
await access(filePath);
|
|
37
|
+
return await readFile(filePath);
|
|
38
|
+
} catch {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const server = createServer(async (req, res) => {
|
|
44
|
+
const url = new URL(req.url || "/", `http://${req.headers.host}`);
|
|
45
|
+
let pathname = decodeURIComponent(url.pathname);
|
|
46
|
+
|
|
47
|
+
let filePath = join(distDir, pathname);
|
|
48
|
+
let content = await tryReadFile(filePath);
|
|
49
|
+
|
|
50
|
+
if (!content && !extname(pathname)) {
|
|
51
|
+
filePath = join(distDir, pathname, "index.html");
|
|
52
|
+
content = await tryReadFile(filePath);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// SPA fallback: serve index.html for client-side routes
|
|
56
|
+
if (!content) {
|
|
57
|
+
filePath = join(distDir, "index.html");
|
|
58
|
+
content = await tryReadFile(filePath);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (!content) {
|
|
62
|
+
res.writeHead(404, { "Content-Type": "text/plain" });
|
|
63
|
+
res.end("Not Found");
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const ext = extname(filePath).toLowerCase();
|
|
68
|
+
const mime = MIME_TYPES[ext] || "application/octet-stream";
|
|
69
|
+
res.writeHead(200, { "Content-Type": mime });
|
|
70
|
+
res.end(content);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
server.listen(port, host, () => {
|
|
74
|
+
const url = `http://localhost:${port}`;
|
|
75
|
+
console.log();
|
|
76
|
+
console.log(" \x1b[36m\u{1F3E2} OpenClaw Office\x1b[0m");
|
|
77
|
+
console.log();
|
|
78
|
+
console.log(` \x1b[32m\u{27A1}\x1b[0m Local: \x1b[36m${url}\x1b[0m`);
|
|
79
|
+
if (host === "0.0.0.0") {
|
|
80
|
+
const nets = networkInterfaces();
|
|
81
|
+
for (const name of Object.keys(nets)) {
|
|
82
|
+
for (const net of nets[name] || []) {
|
|
83
|
+
if (net.family === "IPv4" && !net.internal) {
|
|
84
|
+
console.log(` \x1b[32m\u{27A1}\x1b[0m Network: \x1b[36mhttp://${net.address}:${port}\x1b[0m`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
console.log();
|
|
90
|
+
console.log(" Press \x1b[1mCtrl+C\x1b[0m to stop");
|
|
91
|
+
console.log();
|
|
92
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{u as H,h as v,d as b,j as r}from"./index-CoF0NRzA.js";const N=10,d=24,x=10,u=14,w=[{max:0,color:"#f3f4f6"},{max:5,color:"#bbf7d0"},{max:10,color:"#4ade80"},{max:1/0,color:"#16a34a"}],D=[{max:0,color:"#1e293b"},{max:5,color:"#14532d"},{max:10,color:"#166534"},{max:1/0,color:"#22c55e"}];function E(c,i){const l=i?D:w;for(const s of l)if(c<=s.max)return s.color;return l[l.length-1].color}function M(c){const i=Date.now(),l=3600*1e3,s=new Map;for(const t of c){const a=t.timestamp,n=(i-a)/l;if(n<0||n>=d)continue;const m=23-Math.min(23,Math.floor(n));let o=s.get(t.agentId);o||(o={name:t.agentName,counts:Array.from({length:d},()=>0)},s.set(t.agentId,o)),m>=0&&m<d&&o.counts[m]++}return Array.from(s.entries()).map(([t,{name:a,counts:n}])=>({agentId:t,agentName:a,counts:n})).toSorted((t,a)=>{const n=t.counts.reduce((o,e)=>o+e,0);return a.counts.reduce((o,e)=>o+e,0)-n}).slice(0,N)}function O(){const{t:c}=H("panels"),i=v(e=>e.eventHistory),s=v(e=>e.theme)==="dark",[t,a]=b.useState(null),n=b.useMemo(()=>M(i),[i]);if(n.length===0)return r.jsx("div",{className:"flex h-48 items-center justify-center text-sm text-gray-500 dark:text-gray-400",children:c("common:empty.noActivityData")});const m=Date.now(),o=3600*1e3;return r.jsxs("div",{className:"relative",children:[r.jsx("svg",{width:d*x+60,height:N*u+24,className:"overflow-visible",children:n.map((e,f)=>r.jsxs("g",{children:[r.jsx("text",{x:0,y:f*u+u/2+4,fontSize:9,fill:s?"#e2e8f0":"#374151",children:e.agentName.length>10?`${e.agentName.slice(0,8)}…`:e.agentName}),e.counts.map((h,g)=>{const L=55+g*x,j=f*u+4,y=m-(d-g)*o,S=y+o,A=`${new Date(y).getHours()}:00-${new Date(S).getHours()}:00`;return r.jsx("rect",{x:L,y:j,width:x-1,height:u-1,fill:E(h,s),onMouseEnter:C=>{const p=C.target.getBoundingClientRect();a({agentName:e.agentName,hour:A,count:h,x:p.left,y:p.top})},onMouseLeave:()=>a(null)},g)})]},e.agentId))}),t&&r.jsxs("div",{className:"fixed z-10 rounded border border-gray-200 bg-white px-2 py-1 text-xs shadow dark:border-gray-700 dark:bg-gray-900 dark:text-gray-200",style:{left:t.x,top:t.y-32},children:[t.agentName," | ",t.hour," | ",t.count," ",c("activityHeatmap.eventsUnit")]})]})}export{O as ActivityHeatmap};
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import{e as f,g as Ce,d as me,u as Qe,h as xe,j as S,i as Xe}from"./index-CoF0NRzA.js";import{c as I,f as A,H as Be,I as Ye,J as Fe,K as et,b as D,T as he,M as T,L as _,N as Ke,e as ye,h as Me,D as tt,C as rt,n as z,q as K,S as nt,A as it,d as at,k as Oe,l as ot,i as M,p as st,u as ct,G as lt,m as ee,j as ut,O as Ve,P as ft,Q as Y,U as Pe,z as pt,V as dt,R as vt,F as mt}from"./generateCategoricalChart-8RMAKiEE.js";var ht=["points","className","baseLinePoints","connectNulls"];function V(){return V=Object.assign?Object.assign.bind():function(r){for(var e=1;e<arguments.length;e++){var n=arguments[e];for(var t in n)Object.prototype.hasOwnProperty.call(n,t)&&(r[t]=n[t])}return r},V.apply(this,arguments)}function yt(r,e){if(r==null)return{};var n=gt(r,e),t,i;if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(r);for(i=0;i<o.length;i++)t=o[i],!(e.indexOf(t)>=0)&&Object.prototype.propertyIsEnumerable.call(r,t)&&(n[t]=r[t])}return n}function gt(r,e){if(r==null)return{};var n={};for(var t in r)if(Object.prototype.hasOwnProperty.call(r,t)){if(e.indexOf(t)>=0)continue;n[t]=r[t]}return n}function ke(r){return Ot(r)||xt(r)||At(r)||bt()}function bt(){throw new TypeError(`Invalid attempt to spread non-iterable instance.
|
|
2
|
+
In order to be iterable, non-array objects must have a [Symbol.iterator]() method.`)}function At(r,e){if(r){if(typeof r=="string")return fe(r,e);var n=Object.prototype.toString.call(r).slice(8,-1);if(n==="Object"&&r.constructor&&(n=r.constructor.name),n==="Map"||n==="Set")return Array.from(r);if(n==="Arguments"||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n))return fe(r,e)}}function xt(r){if(typeof Symbol<"u"&&r[Symbol.iterator]!=null||r["@@iterator"]!=null)return Array.from(r)}function Ot(r){if(Array.isArray(r))return fe(r)}function fe(r,e){(e==null||e>r.length)&&(e=r.length);for(var n=0,t=new Array(e);n<e;n++)t[n]=r[n];return t}var _e=function(e){return e&&e.x===+e.x&&e.y===+e.y},Pt=function(){var e=arguments.length>0&&arguments[0]!==void 0?arguments[0]:[],n=[[]];return e.forEach(function(t){_e(t)?n[n.length-1].push(t):n[n.length-1].length>0&&n.push([])}),_e(e[0])&&n[n.length-1].push(e[0]),n[n.length-1].length<=0&&(n=n.slice(0,-1)),n},U=function(e,n){var t=Pt(e);n&&(t=[t.reduce(function(o,a){return[].concat(ke(o),ke(a))},[])]);var i=t.map(function(o){return o.reduce(function(a,s,l){return"".concat(a).concat(l===0?"M":"L").concat(s.x,",").concat(s.y)},"")}).join("");return t.length===1?"".concat(i,"Z"):i},kt=function(e,n,t){var i=U(e,t);return"".concat(i.slice(-1)==="Z"?i.slice(0,-1):i,"L").concat(U(n.reverse(),t).slice(1))},_t=function(e){var n=e.points,t=e.className,i=e.baseLinePoints,o=e.connectNulls,a=yt(e,ht);if(!n||!n.length)return null;var s=I("recharts-polygon",t);if(i&&i.length){var l=a.stroke&&a.stroke!=="none",c=kt(n,i,o);return f.createElement("g",{className:s},f.createElement("path",V({},A(a,!0),{fill:c.slice(-1)==="Z"?a.fill:"none",stroke:"none",d:c})),l?f.createElement("path",V({},A(a,!0),{fill:"none",d:U(n,o)})):null,l?f.createElement("path",V({},A(a,!0),{fill:"none",d:U(i,o)})):null)}var p=U(n,o);return f.createElement("path",V({},A(a,!0),{fill:p.slice(-1)==="Z"?a.fill:"none",className:s,d:p}))},le,je;function jt(){if(je)return le;je=1;var r=Be(),e=Ye(),n=Fe();function t(i,o){return i&&i.length?r(i,n(o,2),e):void 0}return le=t,le}var wt=jt();const St=Ce(wt);var ue,we;function Tt(){if(we)return ue;we=1;var r=Be(),e=Fe(),n=et();function t(i,o){return i&&i.length?r(i,e(o,2),n):void 0}return ue=t,ue}var Et=Tt();const Rt=Ce(Et);var It=["cx","cy","angle","ticks","axisLine"],Lt=["ticks","tick","angle","tickFormatter","stroke"];function W(r){"@babel/helpers - typeof";return W=typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?function(e){return typeof e}:function(e){return e&&typeof Symbol=="function"&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},W(r)}function J(){return J=Object.assign?Object.assign.bind():function(r){for(var e=1;e<arguments.length;e++){var n=arguments[e];for(var t in n)Object.prototype.hasOwnProperty.call(n,t)&&(r[t]=n[t])}return r},J.apply(this,arguments)}function Se(r,e){var n=Object.keys(r);if(Object.getOwnPropertySymbols){var t=Object.getOwnPropertySymbols(r);e&&(t=t.filter(function(i){return Object.getOwnPropertyDescriptor(r,i).enumerable})),n.push.apply(n,t)}return n}function $(r){for(var e=1;e<arguments.length;e++){var n=arguments[e]!=null?arguments[e]:{};e%2?Se(Object(n),!0).forEach(function(t){ae(r,t,n[t])}):Object.getOwnPropertyDescriptors?Object.defineProperties(r,Object.getOwnPropertyDescriptors(n)):Se(Object(n)).forEach(function(t){Object.defineProperty(r,t,Object.getOwnPropertyDescriptor(n,t))})}return r}function Te(r,e){if(r==null)return{};var n=$t(r,e),t,i;if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(r);for(i=0;i<o.length;i++)t=o[i],!(e.indexOf(t)>=0)&&Object.prototype.propertyIsEnumerable.call(r,t)&&(n[t]=r[t])}return n}function $t(r,e){if(r==null)return{};var n={};for(var t in r)if(Object.prototype.hasOwnProperty.call(r,t)){if(e.indexOf(t)>=0)continue;n[t]=r[t]}return n}function Nt(r,e){if(!(r instanceof e))throw new TypeError("Cannot call a class as a function")}function Ee(r,e){for(var n=0;n<e.length;n++){var t=e[n];t.enumerable=t.enumerable||!1,t.configurable=!0,"value"in t&&(t.writable=!0),Object.defineProperty(r,ze(t.key),t)}}function Dt(r,e,n){return e&&Ee(r.prototype,e),n&&Ee(r,n),Object.defineProperty(r,"prototype",{writable:!1}),r}function Ct(r,e,n){return e=re(e),Bt(r,qe()?Reflect.construct(e,n||[],re(r).constructor):e.apply(r,n))}function Bt(r,e){if(e&&(W(e)==="object"||typeof e=="function"))return e;if(e!==void 0)throw new TypeError("Derived constructors may only return object or undefined");return Ft(r)}function Ft(r){if(r===void 0)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return r}function qe(){try{var r=!Boolean.prototype.valueOf.call(Reflect.construct(Boolean,[],function(){}))}catch{}return(qe=function(){return!!r})()}function re(r){return re=Object.setPrototypeOf?Object.getPrototypeOf.bind():function(n){return n.__proto__||Object.getPrototypeOf(n)},re(r)}function Kt(r,e){if(typeof e!="function"&&e!==null)throw new TypeError("Super expression must either be null or a function");r.prototype=Object.create(e&&e.prototype,{constructor:{value:r,writable:!0,configurable:!0}}),Object.defineProperty(r,"prototype",{writable:!1}),e&&pe(r,e)}function pe(r,e){return pe=Object.setPrototypeOf?Object.setPrototypeOf.bind():function(t,i){return t.__proto__=i,t},pe(r,e)}function ae(r,e,n){return e=ze(e),e in r?Object.defineProperty(r,e,{value:n,enumerable:!0,configurable:!0,writable:!0}):r[e]=n,r}function ze(r){var e=Mt(r,"string");return W(e)=="symbol"?e:e+""}function Mt(r,e){if(W(r)!="object"||!r)return r;var n=r[Symbol.toPrimitive];if(n!==void 0){var t=n.call(r,e);if(W(t)!="object")return t;throw new TypeError("@@toPrimitive must return a primitive value.")}return(e==="string"?String:Number)(r)}var oe=(function(r){function e(){return Nt(this,e),Ct(this,e,arguments)}return Kt(e,r),Dt(e,[{key:"getTickValueCoord",value:function(t){var i=t.coordinate,o=this.props,a=o.angle,s=o.cx,l=o.cy;return T(s,l,i,a)}},{key:"getTickTextAnchor",value:function(){var t=this.props.orientation,i;switch(t){case"left":i="end";break;case"right":i="start";break;default:i="middle";break}return i}},{key:"getViewBox",value:function(){var t=this.props,i=t.cx,o=t.cy,a=t.angle,s=t.ticks,l=St(s,function(p){return p.coordinate||0}),c=Rt(s,function(p){return p.coordinate||0});return{cx:i,cy:o,startAngle:a,endAngle:a,innerRadius:c.coordinate||0,outerRadius:l.coordinate||0}}},{key:"renderAxisLine",value:function(){var t=this.props,i=t.cx,o=t.cy,a=t.angle,s=t.ticks,l=t.axisLine,c=Te(t,It),p=s.reduce(function(d,u){return[Math.min(d[0],u.coordinate),Math.max(d[1],u.coordinate)]},[1/0,-1/0]),v=T(i,o,p[0],a),y=T(i,o,p[1],a),x=$($($({},A(c,!1)),{},{fill:"none"},A(l,!1)),{},{x1:v.x,y1:v.y,x2:y.x,y2:y.y});return f.createElement("line",J({className:"recharts-polar-radius-axis-line"},x))}},{key:"renderTicks",value:function(){var t=this,i=this.props,o=i.ticks,a=i.tick,s=i.angle,l=i.tickFormatter,c=i.stroke,p=Te(i,Lt),v=this.getTickTextAnchor(),y=A(p,!1),x=A(a,!1),d=o.map(function(u,h){var g=t.getTickValueCoord(u),b=$($($($({textAnchor:v,transform:"rotate(".concat(90-s,", ").concat(g.x,", ").concat(g.y,")")},y),{},{stroke:"none",fill:c},x),{},{index:h},g),{},{payload:u});return f.createElement(_,J({className:I("recharts-polar-radius-axis-tick",Ke(a)),key:"tick-".concat(u.coordinate)},ye(t.props,u,h)),e.renderTickItem(a,b,l?l(u.value,h):u.value))});return f.createElement(_,{className:"recharts-polar-radius-axis-ticks"},d)}},{key:"render",value:function(){var t=this.props,i=t.ticks,o=t.axisLine,a=t.tick;return!i||!i.length?null:f.createElement(_,{className:I("recharts-polar-radius-axis",this.props.className)},o&&this.renderAxisLine(),a&&this.renderTicks(),Me.renderCallByParent(this.props,this.getViewBox()))}}],[{key:"renderTickItem",value:function(t,i,o){var a;return f.isValidElement(t)?a=f.cloneElement(t,i):D(t)?a=t(i):a=f.createElement(he,J({},i,{className:"recharts-polar-radius-axis-tick-value"}),o),a}}])})(me.PureComponent);ae(oe,"displayName","PolarRadiusAxis");ae(oe,"axisType","radiusAxis");ae(oe,"defaultProps",{type:"number",radiusAxisId:0,cx:0,cy:0,angle:0,orientation:"right",stroke:"#ccc",axisLine:!0,tick:!0,tickCount:5,allowDataOverflow:!1,scale:"auto",allowDuplicatedCategory:!0});function G(r){"@babel/helpers - typeof";return G=typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?function(e){return typeof e}:function(e){return e&&typeof Symbol=="function"&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},G(r)}function C(){return C=Object.assign?Object.assign.bind():function(r){for(var e=1;e<arguments.length;e++){var n=arguments[e];for(var t in n)Object.prototype.hasOwnProperty.call(n,t)&&(r[t]=n[t])}return r},C.apply(this,arguments)}function Re(r,e){var n=Object.keys(r);if(Object.getOwnPropertySymbols){var t=Object.getOwnPropertySymbols(r);e&&(t=t.filter(function(i){return Object.getOwnPropertyDescriptor(r,i).enumerable})),n.push.apply(n,t)}return n}function N(r){for(var e=1;e<arguments.length;e++){var n=arguments[e]!=null?arguments[e]:{};e%2?Re(Object(n),!0).forEach(function(t){se(r,t,n[t])}):Object.getOwnPropertyDescriptors?Object.defineProperties(r,Object.getOwnPropertyDescriptors(n)):Re(Object(n)).forEach(function(t){Object.defineProperty(r,t,Object.getOwnPropertyDescriptor(n,t))})}return r}function Vt(r,e){if(!(r instanceof e))throw new TypeError("Cannot call a class as a function")}function Ie(r,e){for(var n=0;n<e.length;n++){var t=e[n];t.enumerable=t.enumerable||!1,t.configurable=!0,"value"in t&&(t.writable=!0),Object.defineProperty(r,Ge(t.key),t)}}function qt(r,e,n){return e&&Ie(r.prototype,e),n&&Ie(r,n),Object.defineProperty(r,"prototype",{writable:!1}),r}function zt(r,e,n){return e=ne(e),Wt(r,We()?Reflect.construct(e,n||[],ne(r).constructor):e.apply(r,n))}function Wt(r,e){if(e&&(G(e)==="object"||typeof e=="function"))return e;if(e!==void 0)throw new TypeError("Derived constructors may only return object or undefined");return Gt(r)}function Gt(r){if(r===void 0)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return r}function We(){try{var r=!Boolean.prototype.valueOf.call(Reflect.construct(Boolean,[],function(){}))}catch{}return(We=function(){return!!r})()}function ne(r){return ne=Object.setPrototypeOf?Object.getPrototypeOf.bind():function(n){return n.__proto__||Object.getPrototypeOf(n)},ne(r)}function Ht(r,e){if(typeof e!="function"&&e!==null)throw new TypeError("Super expression must either be null or a function");r.prototype=Object.create(e&&e.prototype,{constructor:{value:r,writable:!0,configurable:!0}}),Object.defineProperty(r,"prototype",{writable:!1}),e&&de(r,e)}function de(r,e){return de=Object.setPrototypeOf?Object.setPrototypeOf.bind():function(t,i){return t.__proto__=i,t},de(r,e)}function se(r,e,n){return e=Ge(e),e in r?Object.defineProperty(r,e,{value:n,enumerable:!0,configurable:!0,writable:!0}):r[e]=n,r}function Ge(r){var e=Zt(r,"string");return G(e)=="symbol"?e:e+""}function Zt(r,e){if(G(r)!="object"||!r)return r;var n=r[Symbol.toPrimitive];if(n!==void 0){var t=n.call(r,e);if(G(t)!="object")return t;throw new TypeError("@@toPrimitive must return a primitive value.")}return(e==="string"?String:Number)(r)}var Ut=Math.PI/180,Le=1e-5,ce=(function(r){function e(){return Vt(this,e),zt(this,e,arguments)}return Ht(e,r),qt(e,[{key:"getTickLineCoord",value:function(t){var i=this.props,o=i.cx,a=i.cy,s=i.radius,l=i.orientation,c=i.tickSize,p=c||8,v=T(o,a,s,t.coordinate),y=T(o,a,s+(l==="inner"?-1:1)*p,t.coordinate);return{x1:v.x,y1:v.y,x2:y.x,y2:y.y}}},{key:"getTickTextAnchor",value:function(t){var i=this.props.orientation,o=Math.cos(-t.coordinate*Ut),a;return o>Le?a=i==="outer"?"start":"end":o<-Le?a=i==="outer"?"end":"start":a="middle",a}},{key:"renderAxisLine",value:function(){var t=this.props,i=t.cx,o=t.cy,a=t.radius,s=t.axisLine,l=t.axisLineType,c=N(N({},A(this.props,!1)),{},{fill:"none"},A(s,!1));if(l==="circle")return f.createElement(tt,C({className:"recharts-polar-angle-axis-line"},c,{cx:i,cy:o,r:a}));var p=this.props.ticks,v=p.map(function(y){return T(i,o,a,y.coordinate)});return f.createElement(_t,C({className:"recharts-polar-angle-axis-line"},c,{points:v}))}},{key:"renderTicks",value:function(){var t=this,i=this.props,o=i.ticks,a=i.tick,s=i.tickLine,l=i.tickFormatter,c=i.stroke,p=A(this.props,!1),v=A(a,!1),y=N(N({},p),{},{fill:"none"},A(s,!1)),x=o.map(function(d,u){var h=t.getTickLineCoord(d),g=t.getTickTextAnchor(d),b=N(N(N({textAnchor:g},p),{},{stroke:"none",fill:c},v),{},{index:u,payload:d,x:h.x2,y:h.y2});return f.createElement(_,C({className:I("recharts-polar-angle-axis-tick",Ke(a)),key:"tick-".concat(d.coordinate)},ye(t.props,d,u)),s&&f.createElement("line",C({className:"recharts-polar-angle-axis-tick-line"},y,h)),a&&e.renderTickItem(a,b,l?l(d.value,u):d.value))});return f.createElement(_,{className:"recharts-polar-angle-axis-ticks"},x)}},{key:"render",value:function(){var t=this.props,i=t.ticks,o=t.radius,a=t.axisLine;return o<=0||!i||!i.length?null:f.createElement(_,{className:I("recharts-polar-angle-axis",this.props.className)},a&&this.renderAxisLine(),this.renderTicks())}}],[{key:"renderTickItem",value:function(t,i,o){var a;return f.isValidElement(t)?a=f.cloneElement(t,i):D(t)?a=t(i):a=f.createElement(he,C({},i,{className:"recharts-polar-angle-axis-tick-value"}),o),a}}])})(me.PureComponent);se(ce,"displayName","PolarAngleAxis");se(ce,"axisType","angleAxis");se(ce,"defaultProps",{type:"category",angleAxisId:0,scale:"auto",cx:0,cy:0,orientation:"outer",axisLine:!0,tickLine:!0,tickSize:8,tick:!0,hide:!1,allowDuplicatedCategory:!0});var te;function H(r){"@babel/helpers - typeof";return H=typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?function(e){return typeof e}:function(e){return e&&typeof Symbol=="function"&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},H(r)}function q(){return q=Object.assign?Object.assign.bind():function(r){for(var e=1;e<arguments.length;e++){var n=arguments[e];for(var t in n)Object.prototype.hasOwnProperty.call(n,t)&&(r[t]=n[t])}return r},q.apply(this,arguments)}function $e(r,e){var n=Object.keys(r);if(Object.getOwnPropertySymbols){var t=Object.getOwnPropertySymbols(r);e&&(t=t.filter(function(i){return Object.getOwnPropertyDescriptor(r,i).enumerable})),n.push.apply(n,t)}return n}function m(r){for(var e=1;e<arguments.length;e++){var n=arguments[e]!=null?arguments[e]:{};e%2?$e(Object(n),!0).forEach(function(t){P(r,t,n[t])}):Object.getOwnPropertyDescriptors?Object.defineProperties(r,Object.getOwnPropertyDescriptors(n)):$e(Object(n)).forEach(function(t){Object.defineProperty(r,t,Object.getOwnPropertyDescriptor(n,t))})}return r}function Jt(r,e){if(!(r instanceof e))throw new TypeError("Cannot call a class as a function")}function Ne(r,e){for(var n=0;n<e.length;n++){var t=e[n];t.enumerable=t.enumerable||!1,t.configurable=!0,"value"in t&&(t.writable=!0),Object.defineProperty(r,Ze(t.key),t)}}function Qt(r,e,n){return e&&Ne(r.prototype,e),n&&Ne(r,n),Object.defineProperty(r,"prototype",{writable:!1}),r}function Xt(r,e,n){return e=ie(e),Yt(r,He()?Reflect.construct(e,n||[],ie(r).constructor):e.apply(r,n))}function Yt(r,e){if(e&&(H(e)==="object"||typeof e=="function"))return e;if(e!==void 0)throw new TypeError("Derived constructors may only return object or undefined");return er(r)}function er(r){if(r===void 0)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return r}function He(){try{var r=!Boolean.prototype.valueOf.call(Reflect.construct(Boolean,[],function(){}))}catch{}return(He=function(){return!!r})()}function ie(r){return ie=Object.setPrototypeOf?Object.getPrototypeOf.bind():function(n){return n.__proto__||Object.getPrototypeOf(n)},ie(r)}function tr(r,e){if(typeof e!="function"&&e!==null)throw new TypeError("Super expression must either be null or a function");r.prototype=Object.create(e&&e.prototype,{constructor:{value:r,writable:!0,configurable:!0}}),Object.defineProperty(r,"prototype",{writable:!1}),e&&ve(r,e)}function ve(r,e){return ve=Object.setPrototypeOf?Object.setPrototypeOf.bind():function(t,i){return t.__proto__=i,t},ve(r,e)}function P(r,e,n){return e=Ze(e),e in r?Object.defineProperty(r,e,{value:n,enumerable:!0,configurable:!0,writable:!0}):r[e]=n,r}function Ze(r){var e=rr(r,"string");return H(e)=="symbol"?e:e+""}function rr(r,e){if(H(r)!="object"||!r)return r;var n=r[Symbol.toPrimitive];if(n!==void 0){var t=n.call(r,e);if(H(t)!="object")return t;throw new TypeError("@@toPrimitive must return a primitive value.")}return String(r)}var E=(function(r){function e(n){var t;return Jt(this,e),t=Xt(this,e,[n]),P(t,"pieRef",null),P(t,"sectorRefs",[]),P(t,"id",ct("recharts-pie-")),P(t,"handleAnimationEnd",function(){var i=t.props.onAnimationEnd;t.setState({isAnimationFinished:!0}),D(i)&&i()}),P(t,"handleAnimationStart",function(){var i=t.props.onAnimationStart;t.setState({isAnimationFinished:!1}),D(i)&&i()}),t.state={isAnimationFinished:!n.isAnimationActive,prevIsAnimationActive:n.isAnimationActive,prevAnimationId:n.animationId,sectorToFocus:0},t}return tr(e,r),Qt(e,[{key:"isActiveIndex",value:function(t){var i=this.props.activeIndex;return Array.isArray(i)?i.indexOf(t)!==-1:t===i}},{key:"hasActiveIndex",value:function(){var t=this.props.activeIndex;return Array.isArray(t)?t.length!==0:t||t===0}},{key:"renderLabels",value:function(t){var i=this.props.isAnimationActive;if(i&&!this.state.isAnimationFinished)return null;var o=this.props,a=o.label,s=o.labelLine,l=o.dataKey,c=o.valueKey,p=A(this.props,!1),v=A(a,!1),y=A(s,!1),x=a&&a.offsetRadius||20,d=t.map(function(u,h){var g=(u.startAngle+u.endAngle)/2,b=T(u.cx,u.cy,u.outerRadius+x,g),k=m(m(m(m({},p),u),{},{stroke:"none"},v),{},{index:h,textAnchor:e.getTextAnchor(b.x,u.cx)},b),B=m(m(m(m({},p),u),{},{fill:"none",stroke:u.fill},y),{},{index:h,points:[T(u.cx,u.cy,u.outerRadius,g),b]}),j=l;return z(l)&&z(c)?j="value":z(l)&&(j=c),f.createElement(_,{key:"label-".concat(u.startAngle,"-").concat(u.endAngle,"-").concat(u.midAngle,"-").concat(h)},s&&e.renderLabelLineItem(s,B,"line"),e.renderLabelItem(a,k,K(u,j)))});return f.createElement(_,{className:"recharts-pie-labels"},d)}},{key:"renderSectorsStatically",value:function(t){var i=this,o=this.props,a=o.activeShape,s=o.blendStroke,l=o.inactiveShape;return t.map(function(c,p){if((c==null?void 0:c.startAngle)===0&&(c==null?void 0:c.endAngle)===0&&t.length!==1)return null;var v=i.isActiveIndex(p),y=l&&i.hasActiveIndex()?l:null,x=v?a:y,d=m(m({},c),{},{stroke:s?c.fill:c.stroke,tabIndex:-1});return f.createElement(_,q({ref:function(h){h&&!i.sectorRefs.includes(h)&&i.sectorRefs.push(h)},tabIndex:-1,className:"recharts-pie-sector"},ye(i.props,c,p),{key:"sector-".concat(c==null?void 0:c.startAngle,"-").concat(c==null?void 0:c.endAngle,"-").concat(c.midAngle,"-").concat(p)}),f.createElement(nt,q({option:x,isActive:v,shapeType:"sector"},d)))})}},{key:"renderSectorsWithAnimation",value:function(){var t=this,i=this.props,o=i.sectors,a=i.isAnimationActive,s=i.animationBegin,l=i.animationDuration,c=i.animationEasing,p=i.animationId,v=this.state,y=v.prevSectors,x=v.prevIsAnimationActive;return f.createElement(it,{begin:s,duration:l,isActive:a,easing:c,from:{t:0},to:{t:1},key:"pie-".concat(p,"-").concat(x),onAnimationStart:this.handleAnimationStart,onAnimationEnd:this.handleAnimationEnd},function(d){var u=d.t,h=[],g=o&&o[0],b=g.startAngle;return o.forEach(function(k,B){var j=y&&y[B],L=B>0?at(k,"paddingAngle",0):0;if(j){var Z=Oe(j.endAngle-j.startAngle,k.endAngle-k.startAngle),O=m(m({},k),{},{startAngle:b+L,endAngle:b+Z(u)+L});h.push(O),b=O.endAngle}else{var F=k.endAngle,w=k.startAngle,Q=Oe(0,F-w),X=Q(u),R=m(m({},k),{},{startAngle:b+L,endAngle:b+X+L});h.push(R),b=R.endAngle}}),f.createElement(_,null,t.renderSectorsStatically(h))})}},{key:"attachKeyboardHandlers",value:function(t){var i=this;t.onkeydown=function(o){if(!o.altKey)switch(o.key){case"ArrowLeft":{var a=++i.state.sectorToFocus%i.sectorRefs.length;i.sectorRefs[a].focus(),i.setState({sectorToFocus:a});break}case"ArrowRight":{var s=--i.state.sectorToFocus<0?i.sectorRefs.length-1:i.state.sectorToFocus%i.sectorRefs.length;i.sectorRefs[s].focus(),i.setState({sectorToFocus:s});break}case"Escape":{i.sectorRefs[i.state.sectorToFocus].blur(),i.setState({sectorToFocus:0});break}}}}},{key:"renderSectors",value:function(){var t=this.props,i=t.sectors,o=t.isAnimationActive,a=this.state.prevSectors;return o&&i&&i.length&&(!a||!ot(a,i))?this.renderSectorsWithAnimation():this.renderSectorsStatically(i)}},{key:"componentDidMount",value:function(){this.pieRef&&this.attachKeyboardHandlers(this.pieRef)}},{key:"render",value:function(){var t=this,i=this.props,o=i.hide,a=i.sectors,s=i.className,l=i.label,c=i.cx,p=i.cy,v=i.innerRadius,y=i.outerRadius,x=i.isAnimationActive,d=this.state.isAnimationFinished;if(o||!a||!a.length||!M(c)||!M(p)||!M(v)||!M(y))return null;var u=I("recharts-pie",s);return f.createElement(_,{tabIndex:this.props.rootTabIndex,className:u,ref:function(g){t.pieRef=g}},this.renderSectors(),l&&this.renderLabels(a),Me.renderCallByParent(this.props,null,!1),(!x||d)&&st.renderCallByParent(this.props,a,!1))}}],[{key:"getDerivedStateFromProps",value:function(t,i){return i.prevIsAnimationActive!==t.isAnimationActive?{prevIsAnimationActive:t.isAnimationActive,prevAnimationId:t.animationId,curSectors:t.sectors,prevSectors:[],isAnimationFinished:!0}:t.isAnimationActive&&t.animationId!==i.prevAnimationId?{prevAnimationId:t.animationId,curSectors:t.sectors,prevSectors:i.curSectors,isAnimationFinished:!0}:t.sectors!==i.curSectors?{curSectors:t.sectors,isAnimationFinished:!0}:null}},{key:"getTextAnchor",value:function(t,i){return t>i?"start":t<i?"end":"middle"}},{key:"renderLabelLineItem",value:function(t,i,o){if(f.isValidElement(t))return f.cloneElement(t,i);if(D(t))return t(i);var a=I("recharts-pie-label-line",typeof t!="boolean"?t.className:"");return f.createElement(rt,q({},i,{key:o,type:"linear",className:a}))}},{key:"renderLabelItem",value:function(t,i,o){if(f.isValidElement(t))return f.cloneElement(t,i);var a=o;if(D(t)&&(a=t(i),f.isValidElement(a)))return a;var s=I("recharts-pie-label-text",typeof t!="boolean"&&!D(t)?t.className:"");return f.createElement(he,q({},i,{alignmentBaseline:"middle",className:s}),a)}}])})(me.PureComponent);te=E;P(E,"displayName","Pie");P(E,"defaultProps",{stroke:"#fff",fill:"#808080",legendType:"rect",cx:"50%",cy:"50%",startAngle:0,endAngle:360,innerRadius:0,outerRadius:"80%",paddingAngle:0,labelLine:!0,hide:!1,minAngle:0,isAnimationActive:!lt.isSsr,animationBegin:400,animationDuration:1500,animationEasing:"ease",nameKey:"name",blendStroke:!1,rootTabIndex:0});P(E,"parseDeltaAngle",function(r,e){var n=ee(e-r),t=Math.min(Math.abs(e-r),360);return n*t});P(E,"getRealPieData",function(r){var e=r.data,n=r.children,t=A(r,!1),i=ut(n,Ve);return e&&e.length?e.map(function(o,a){return m(m(m({payload:o},t),o),i&&i[a]&&i[a].props)}):i&&i.length?i.map(function(o){return m(m({},t),o.props)}):[]});P(E,"parseCoordinateOfPie",function(r,e){var n=e.top,t=e.left,i=e.width,o=e.height,a=ft(i,o),s=t+Y(r.cx,i,i/2),l=n+Y(r.cy,o,o/2),c=Y(r.innerRadius,a,0),p=Y(r.outerRadius,a,a*.8),v=r.maxRadius||Math.sqrt(i*i+o*o)/2;return{cx:s,cy:l,innerRadius:c,outerRadius:p,maxRadius:v}});P(E,"getComposedData",function(r){var e=r.item,n=r.offset,t=e.type.defaultProps!==void 0?m(m({},e.type.defaultProps),e.props):e.props,i=te.getRealPieData(t);if(!i||!i.length)return null;var o=t.cornerRadius,a=t.startAngle,s=t.endAngle,l=t.paddingAngle,c=t.dataKey,p=t.nameKey,v=t.valueKey,y=t.tooltipType,x=Math.abs(t.minAngle),d=te.parseCoordinateOfPie(t,n),u=te.parseDeltaAngle(a,s),h=Math.abs(u),g=c;z(c)&&z(v)?(Pe(!1,`Use "dataKey" to specify the value of pie,
|
|
3
|
+
the props "valueKey" will be deprecated in 1.1.0`),g="value"):z(c)&&(Pe(!1,`Use "dataKey" to specify the value of pie,
|
|
4
|
+
the props "valueKey" will be deprecated in 1.1.0`),g=v);var b=i.filter(function(O){return K(O,g,0)!==0}).length,k=(h>=360?b:b-1)*l,B=h-b*x-k,j=i.reduce(function(O,F){var w=K(F,g,0);return O+(M(w)?w:0)},0),L;if(j>0){var Z;L=i.map(function(O,F){var w=K(O,g,0),Q=K(O,p,F),X=(M(w)?w:0)/j,R;F?R=Z.endAngle+ee(u)*l*(w!==0?1:0):R=a;var ge=R+ee(u)*((w!==0?x:0)+X*B),be=(R+ge)/2,Ae=(d.innerRadius+d.outerRadius)/2,Ue=[{name:Q,value:w,payload:O,dataKey:g,type:y}],Je=T(d.cx,d.cy,Ae,be);return Z=m(m(m({percent:X,cornerRadius:o,name:Q,tooltipPayload:Ue,midAngle:be,middleRadius:Ae,tooltipPosition:Je},O),d),{},{value:K(O,g),startAngle:R,endAngle:ge,payload:O,paddingAngle:ee(u)*l}),Z})}return m(m({},d),{},{sectors:L,data:i})});var nr=pt({chartName:"PieChart",GraphicalChild:E,validateTooltipEventTypes:["item"],defaultTooltipEventType:"item",legendContent:"children",axisComponents:[{axisType:"angleAxis",AxisComp:ce},{axisType:"radiusAxis",AxisComp:oe}],formatAxisMap:dt,defaultProps:{layout:"centric",startAngle:0,endAngle:360,cx:"50%",cy:"50%",innerRadius:0,outerRadius:"80%"}});function De(r){return r>=1e6?`${(r/1e6).toFixed(1)}M`:r>=1e3?`${(r/1e3).toFixed(1)}k`:String(r)}function or(){const{t:r}=Qe(),e=xe(a=>a.agentCosts),n=xe(a=>a.agents),t=Object.entries(e).filter(([,a])=>a>0),i=t.reduce((a,[,s])=>a+s,0);if(t.length===0)return S.jsx("div",{className:"flex h-24 items-center justify-center text-sm text-gray-500 dark:text-gray-400",children:r("empty.noCostData")});const o=t.map(([a,s])=>{var l;return{name:((l=n.get(a))==null?void 0:l.name)??a,agentId:a,value:s,color:Xe(a)}});return S.jsxs("div",{className:"relative h-[200px] w-full",children:[S.jsx(vt,{width:"100%",height:"100%",children:S.jsxs(nr,{children:[S.jsx(E,{data:o,dataKey:"value",nameKey:"name",cx:"50%",cy:"50%",innerRadius:50,outerRadius:80,paddingAngle:2,children:o.map((a,s)=>S.jsx(Ve,{fill:a.color},s))}),S.jsx(mt,{formatter:(a,s)=>{const l=typeof a=="number"?a:0,c=i>0?(l/i*100).toFixed(0):"0";return`${s}: ${De(l)} (${c}%)`}})]})}),S.jsx("div",{className:"absolute inset-0 flex items-center justify-center pointer-events-none",children:S.jsx("span",{className:"text-sm font-bold text-gray-700 dark:text-gray-300",children:De(i)})})]})}export{or as CostPieChart};
|