@patrax/office-viz 1.0.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 +225 -0
- package/dist/index.d.ts +111 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +372 -0
- package/dist/index.js.map +1 -0
- package/openclaw.plugin.json +59 -0
- package/package.json +64 -0
- package/ui/dist/assets/index-DglNlTuL.js +44 -0
- package/ui/dist/assets/index-DglNlTuL.js.map +1 -0
- package/ui/dist/assets/index-Dm9OFqqZ.css +1 -0
- package/ui/dist/index.html +24 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 OpenClaw Community
|
|
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,225 @@
|
|
|
1
|
+
# 🏢 OpenClaw Office Visualization Plugin
|
|
2
|
+
|
|
3
|
+
A pixel art office visualization plugin for OpenClaw that displays your running agents as animated characters in a virtual office environment.
|
|
4
|
+
|
|
5
|
+

|
|
6
|
+

|
|
7
|
+
|
|
8
|
+
## ✨ Features
|
|
9
|
+
|
|
10
|
+
- **Real-time Agent Visualization**: See your OpenClaw agents as animated pixel art characters
|
|
11
|
+
- **Status Indicators**: Visual representation of agent states (active, idle, waiting, offline)
|
|
12
|
+
- **Interactive UI**: Hover over characters to see agent details, click for more info
|
|
13
|
+
- **Office Environment**: Desks, furniture, plants, and more create an immersive office scene
|
|
14
|
+
- **Automatic Updates**: Agents appear/disappear as sessions start and end
|
|
15
|
+
- **Responsive Design**: Scales to fit any screen size
|
|
16
|
+
- **Dark Theme**: Easy on the eyes, matches the OpenClaw dashboard aesthetic
|
|
17
|
+
|
|
18
|
+
## 📸 Preview
|
|
19
|
+
|
|
20
|
+
```
|
|
21
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
22
|
+
│ 🏢 OpenClaw Office ● 3 active ● 2 idle ○ 1 off │
|
|
23
|
+
├─────────────────────────────────────────────────────────────┤
|
|
24
|
+
│ 🌿 🌿 │
|
|
25
|
+
│ ┌───┐ ┌───┐ ┌───┐ │
|
|
26
|
+
│ │ 💻│ │ 💻│ │ 💻│ ☕ │
|
|
27
|
+
│ └───┘ └───┘ └───┘ │
|
|
28
|
+
│ 👤 👤 👤 💧 │
|
|
29
|
+
│ active active idle │
|
|
30
|
+
│ │
|
|
31
|
+
│ ┌───┐ ┌───┐ ┌───┐ │
|
|
32
|
+
│ │ 💻│ │ 💻│ │ │ │
|
|
33
|
+
│ └───┘ └───┘ └───┘ │
|
|
34
|
+
│ 👤 👤 │
|
|
35
|
+
│ idle waiting 🛋️ │
|
|
36
|
+
└─────────────────────────────────────────────────────────────┘
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## 🚀 Installation
|
|
40
|
+
|
|
41
|
+
### From npm (recommended)
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
npm install @openclaw-community/office-viz
|
|
45
|
+
# or
|
|
46
|
+
pnpm add @openclaw-community/office-viz
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### From source
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
git clone https://github.com/openclaw-community/office-viz.git
|
|
53
|
+
cd office-viz
|
|
54
|
+
pnpm install
|
|
55
|
+
pnpm build
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Enable in OpenClaw
|
|
59
|
+
|
|
60
|
+
Add to your `openclaw.yaml`:
|
|
61
|
+
|
|
62
|
+
```yaml
|
|
63
|
+
plugins:
|
|
64
|
+
office-viz:
|
|
65
|
+
enabled: true
|
|
66
|
+
basePath: /office
|
|
67
|
+
theme: default
|
|
68
|
+
showInactiveAgents: true
|
|
69
|
+
animationSpeed: 1.0
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Restart the OpenClaw Gateway, then visit `http://your-gateway:4242/office`
|
|
73
|
+
|
|
74
|
+
## ⚙️ Configuration
|
|
75
|
+
|
|
76
|
+
| Option | Type | Default | Description |
|
|
77
|
+
|--------|------|---------|-------------|
|
|
78
|
+
| `enabled` | boolean | `true` | Enable/disable the plugin |
|
|
79
|
+
| `basePath` | string | `/office` | URL path for the visualization |
|
|
80
|
+
| `theme` | string | `default` | Color theme (`default`, `dark`, `light`) |
|
|
81
|
+
| `showInactiveAgents` | boolean | `true` | Show offline agents in the office |
|
|
82
|
+
| `animationSpeed` | number | `1.0` | Animation speed multiplier (0.5 - 2.0) |
|
|
83
|
+
|
|
84
|
+
## 🛠️ Development
|
|
85
|
+
|
|
86
|
+
### Prerequisites
|
|
87
|
+
|
|
88
|
+
- Node.js 18+
|
|
89
|
+
- pnpm 8+
|
|
90
|
+
|
|
91
|
+
### Setup
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
# Clone the repository
|
|
95
|
+
git clone https://github.com/openclaw-community/office-viz.git
|
|
96
|
+
cd office-viz
|
|
97
|
+
|
|
98
|
+
# Install dependencies
|
|
99
|
+
pnpm install
|
|
100
|
+
|
|
101
|
+
# Build everything
|
|
102
|
+
pnpm build
|
|
103
|
+
|
|
104
|
+
# Or develop with hot reload
|
|
105
|
+
pnpm dev:ui
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### Project Structure
|
|
109
|
+
|
|
110
|
+
```
|
|
111
|
+
office-viz/
|
|
112
|
+
├── src/ # Plugin backend
|
|
113
|
+
│ └── index.ts # Main plugin entry point
|
|
114
|
+
├── ui/ # Frontend application
|
|
115
|
+
│ ├── src/
|
|
116
|
+
│ │ ├── main.ts # UI entry point
|
|
117
|
+
│ │ ├── styles.css # Global styles
|
|
118
|
+
│ │ └── office/ # Core visualization
|
|
119
|
+
│ │ ├── OfficeApp.ts # Main application class
|
|
120
|
+
│ │ ├── Renderer.ts # Canvas rendering
|
|
121
|
+
│ │ ├── SpriteManager.ts # Character sprites
|
|
122
|
+
│ │ ├── ApiClient.ts # API communication
|
|
123
|
+
│ │ └── types.ts # TypeScript definitions
|
|
124
|
+
│ ├── index.html
|
|
125
|
+
│ └── vite.config.ts
|
|
126
|
+
├── openclaw.plugin.json # Plugin manifest
|
|
127
|
+
├── package.json
|
|
128
|
+
└── README.md
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### API Endpoints
|
|
132
|
+
|
|
133
|
+
The plugin exposes these endpoints:
|
|
134
|
+
|
|
135
|
+
| Endpoint | Method | Description |
|
|
136
|
+
|----------|--------|-------------|
|
|
137
|
+
| `/office` | GET | Main visualization UI |
|
|
138
|
+
| `/office/api/agents` | GET | List all agent states |
|
|
139
|
+
| `/office/api/presence` | GET | Get presence information |
|
|
140
|
+
| `/office/api/sessions` | GET | Get active sessions |
|
|
141
|
+
| `/office/api/config` | GET | Get plugin configuration |
|
|
142
|
+
|
|
143
|
+
### Testing Locally
|
|
144
|
+
|
|
145
|
+
1. Build the plugin:
|
|
146
|
+
```bash
|
|
147
|
+
pnpm build
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
2. Link to your OpenClaw installation:
|
|
151
|
+
```bash
|
|
152
|
+
cd /path/to/openclaw
|
|
153
|
+
pnpm link /path/to/office-viz
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
3. Add to `openclaw.yaml` and restart
|
|
157
|
+
|
|
158
|
+
4. For UI development with hot reload:
|
|
159
|
+
```bash
|
|
160
|
+
pnpm dev:ui
|
|
161
|
+
```
|
|
162
|
+
Then visit `http://localhost:5173/office/`
|
|
163
|
+
|
|
164
|
+
## 🎨 Customization
|
|
165
|
+
|
|
166
|
+
### Adding Custom Sprites
|
|
167
|
+
|
|
168
|
+
You can replace the procedural character sprites with custom pixel art:
|
|
169
|
+
|
|
170
|
+
1. Create sprite sheets (32x48px per frame)
|
|
171
|
+
2. Place in `ui/public/sprites/`
|
|
172
|
+
3. Update `SpriteManager.ts` to load from files
|
|
173
|
+
|
|
174
|
+
### Modifying Office Layout
|
|
175
|
+
|
|
176
|
+
Edit the `DEFAULT_LAYOUT` in `OfficeApp.ts`:
|
|
177
|
+
|
|
178
|
+
```typescript
|
|
179
|
+
const DEFAULT_LAYOUT: OfficeLayout = {
|
|
180
|
+
width: 12, // Grid width
|
|
181
|
+
height: 10, // Grid height
|
|
182
|
+
desks: [
|
|
183
|
+
{ id: 1, x: 2, y: 2, occupied: false },
|
|
184
|
+
// Add more desks...
|
|
185
|
+
],
|
|
186
|
+
furniture: [
|
|
187
|
+
{ type: 'plant', x: 0.5, y: 1, width: 1, height: 1 },
|
|
188
|
+
// Add more furniture...
|
|
189
|
+
]
|
|
190
|
+
}
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
## 🤝 Contributing
|
|
194
|
+
|
|
195
|
+
Contributions are welcome! See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
|
|
196
|
+
|
|
197
|
+
### Ideas for Contributions
|
|
198
|
+
|
|
199
|
+
- 🎨 New character designs/animations
|
|
200
|
+
- 🪑 Additional furniture types
|
|
201
|
+
- 🎵 Sound effects
|
|
202
|
+
- 📱 Mobile-optimized layout
|
|
203
|
+
- 🌙 Additional themes
|
|
204
|
+
- 🗺️ Layout editor
|
|
205
|
+
- 📊 Agent activity graphs
|
|
206
|
+
|
|
207
|
+
## 📄 License
|
|
208
|
+
|
|
209
|
+
MIT License - see [LICENSE](LICENSE) for details.
|
|
210
|
+
|
|
211
|
+
## 🙏 Acknowledgments
|
|
212
|
+
|
|
213
|
+
- Inspired by [pixel-agents](https://github.com/pablodelucca/pixel-agents) for VS Code
|
|
214
|
+
- Built for the [OpenClaw](https://openclaw.ai) community
|
|
215
|
+
- Pixel art rendering techniques from various game dev resources
|
|
216
|
+
|
|
217
|
+
## 📚 Related
|
|
218
|
+
|
|
219
|
+
- [OpenClaw Documentation](https://docs.openclaw.ai)
|
|
220
|
+
- [OpenClaw Plugins Guide](https://docs.openclaw.ai/tools/plugin)
|
|
221
|
+
- [Community Plugins](https://docs.openclaw.ai/plugins/community)
|
|
222
|
+
|
|
223
|
+
---
|
|
224
|
+
|
|
225
|
+
Made with ❤️ for the OpenClaw community
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenClaw Office Visualization Plugin
|
|
3
|
+
*
|
|
4
|
+
* Displays running agents as animated pixel art characters in a virtual office.
|
|
5
|
+
*/
|
|
6
|
+
export interface OfficeVizConfig {
|
|
7
|
+
enabled: boolean;
|
|
8
|
+
basePath: string;
|
|
9
|
+
theme: 'default' | 'dark' | 'light';
|
|
10
|
+
showInactiveAgents: boolean;
|
|
11
|
+
animationSpeed: number;
|
|
12
|
+
}
|
|
13
|
+
export interface AgentState {
|
|
14
|
+
id: string;
|
|
15
|
+
name: string;
|
|
16
|
+
status: 'active' | 'idle' | 'waiting' | 'offline';
|
|
17
|
+
sessionId?: string;
|
|
18
|
+
lastActivity: number;
|
|
19
|
+
currentTool?: string;
|
|
20
|
+
model?: string;
|
|
21
|
+
channel?: string;
|
|
22
|
+
}
|
|
23
|
+
export interface OpenClawContext {
|
|
24
|
+
config: OfficeVizConfig;
|
|
25
|
+
http: {
|
|
26
|
+
get(path: string, handler: (req: Request) => Response | Promise<Response>): void;
|
|
27
|
+
post(path: string, handler: (req: Request) => Response | Promise<Response>): void;
|
|
28
|
+
};
|
|
29
|
+
events: {
|
|
30
|
+
on(event: string, handler: (data: unknown) => void): void;
|
|
31
|
+
off(event: string, handler: (data: unknown) => void): void;
|
|
32
|
+
};
|
|
33
|
+
sessions: {
|
|
34
|
+
list(): Promise<Array<{
|
|
35
|
+
id: string;
|
|
36
|
+
channel?: string;
|
|
37
|
+
model?: string;
|
|
38
|
+
status: string;
|
|
39
|
+
lastActivity?: number;
|
|
40
|
+
}>>;
|
|
41
|
+
};
|
|
42
|
+
presence: {
|
|
43
|
+
list(): Promise<Array<{
|
|
44
|
+
instanceId?: string;
|
|
45
|
+
deviceId?: string;
|
|
46
|
+
host?: string;
|
|
47
|
+
mode?: string;
|
|
48
|
+
lastInputSeconds?: number;
|
|
49
|
+
}>>;
|
|
50
|
+
};
|
|
51
|
+
logger: {
|
|
52
|
+
info(msg: string): void;
|
|
53
|
+
warn(msg: string): void;
|
|
54
|
+
error(msg: string): void;
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Office Visualization Plugin
|
|
59
|
+
*/
|
|
60
|
+
export declare class OfficeVizPlugin {
|
|
61
|
+
private ctx;
|
|
62
|
+
private agents;
|
|
63
|
+
private uiDistPath;
|
|
64
|
+
constructor(ctx: OpenClawContext);
|
|
65
|
+
/**
|
|
66
|
+
* Initialize the plugin
|
|
67
|
+
*/
|
|
68
|
+
init(): Promise<void>;
|
|
69
|
+
/**
|
|
70
|
+
* Register HTTP routes for the visualization
|
|
71
|
+
*/
|
|
72
|
+
private registerRoutes;
|
|
73
|
+
/**
|
|
74
|
+
* Serve static files from the UI dist folder
|
|
75
|
+
*/
|
|
76
|
+
private serveStaticFile;
|
|
77
|
+
/**
|
|
78
|
+
* Get content type for file extension
|
|
79
|
+
*/
|
|
80
|
+
private getContentType;
|
|
81
|
+
/**
|
|
82
|
+
* Serve a fallback UI when the dist folder doesn't exist
|
|
83
|
+
*/
|
|
84
|
+
private serveFallbackUI;
|
|
85
|
+
/**
|
|
86
|
+
* Subscribe to OpenClaw events for real-time updates
|
|
87
|
+
*/
|
|
88
|
+
private subscribeToEvents;
|
|
89
|
+
/**
|
|
90
|
+
* Generate a friendly name for an agent
|
|
91
|
+
*/
|
|
92
|
+
private generateAgentName;
|
|
93
|
+
/**
|
|
94
|
+
* Sync agents from current sessions
|
|
95
|
+
*/
|
|
96
|
+
private syncAgents;
|
|
97
|
+
/**
|
|
98
|
+
* Add or update an agent
|
|
99
|
+
*/
|
|
100
|
+
private addOrUpdateAgent;
|
|
101
|
+
/**
|
|
102
|
+
* Cleanup on shutdown
|
|
103
|
+
*/
|
|
104
|
+
destroy(): Promise<void>;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Plugin entry point
|
|
108
|
+
* Called by OpenClaw when loading the plugin
|
|
109
|
+
*/
|
|
110
|
+
export default function (ctx: OpenClawContext): Promise<OfficeVizPlugin>;
|
|
111
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenClaw Office Visualization Plugin
|
|
3
|
+
*
|
|
4
|
+
* Displays running agents as animated pixel art characters in a virtual office.
|
|
5
|
+
*/
|
|
6
|
+
import * as fs from 'fs';
|
|
7
|
+
import * as path from 'path';
|
|
8
|
+
import { fileURLToPath } from 'url';
|
|
9
|
+
// Get __dirname equivalent in ESM
|
|
10
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
11
|
+
const __dirname = path.dirname(__filename);
|
|
12
|
+
/**
|
|
13
|
+
* Office Visualization Plugin
|
|
14
|
+
*/
|
|
15
|
+
export class OfficeVizPlugin {
|
|
16
|
+
ctx;
|
|
17
|
+
agents = new Map();
|
|
18
|
+
uiDistPath;
|
|
19
|
+
constructor(ctx) {
|
|
20
|
+
this.ctx = ctx;
|
|
21
|
+
// UI dist is relative to the plugin dist folder
|
|
22
|
+
this.uiDistPath = path.resolve(__dirname, '..', 'ui', 'dist');
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Initialize the plugin
|
|
26
|
+
*/
|
|
27
|
+
async init() {
|
|
28
|
+
const { config, logger } = this.ctx;
|
|
29
|
+
if (!config.enabled) {
|
|
30
|
+
logger.info('Office Visualization plugin is disabled');
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
logger.info(`Initializing Office Visualization at ${config.basePath}`);
|
|
34
|
+
// Register HTTP routes
|
|
35
|
+
this.registerRoutes();
|
|
36
|
+
// Subscribe to events
|
|
37
|
+
this.subscribeToEvents();
|
|
38
|
+
// Initial agent sync
|
|
39
|
+
await this.syncAgents();
|
|
40
|
+
logger.info('Office Visualization plugin initialized');
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Register HTTP routes for the visualization
|
|
44
|
+
*/
|
|
45
|
+
registerRoutes() {
|
|
46
|
+
const { http, config } = this.ctx;
|
|
47
|
+
const basePath = config.basePath || '/office';
|
|
48
|
+
// API: Get agents
|
|
49
|
+
http.get(`${basePath}/api/agents`, async () => {
|
|
50
|
+
const agents = Array.from(this.agents.values());
|
|
51
|
+
return new Response(JSON.stringify({
|
|
52
|
+
agents,
|
|
53
|
+
timestamp: Date.now()
|
|
54
|
+
}), {
|
|
55
|
+
headers: { 'Content-Type': 'application/json' }
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
// API: Get presence
|
|
59
|
+
http.get(`${basePath}/api/presence`, async () => {
|
|
60
|
+
try {
|
|
61
|
+
const entries = await this.ctx.presence.list();
|
|
62
|
+
return new Response(JSON.stringify({ entries }), {
|
|
63
|
+
headers: { 'Content-Type': 'application/json' }
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
return new Response(JSON.stringify({ entries: [] }), {
|
|
68
|
+
headers: { 'Content-Type': 'application/json' }
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
// API: Get sessions
|
|
73
|
+
http.get(`${basePath}/api/sessions`, async () => {
|
|
74
|
+
try {
|
|
75
|
+
const sessions = await this.ctx.sessions.list();
|
|
76
|
+
return new Response(JSON.stringify({ sessions }), {
|
|
77
|
+
headers: { 'Content-Type': 'application/json' }
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
return new Response(JSON.stringify({ sessions: [] }), {
|
|
82
|
+
headers: { 'Content-Type': 'application/json' }
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
// API: Get config
|
|
87
|
+
http.get(`${basePath}/api/config`, () => {
|
|
88
|
+
const { theme, showInactiveAgents, animationSpeed } = this.ctx.config;
|
|
89
|
+
return new Response(JSON.stringify({
|
|
90
|
+
theme,
|
|
91
|
+
showInactiveAgents,
|
|
92
|
+
animationSpeed
|
|
93
|
+
}), {
|
|
94
|
+
headers: { 'Content-Type': 'application/json' }
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
// Serve static UI files
|
|
98
|
+
http.get(`${basePath}/*`, (req) => {
|
|
99
|
+
return this.serveStaticFile(req, basePath);
|
|
100
|
+
});
|
|
101
|
+
// Serve index for base path
|
|
102
|
+
http.get(basePath, () => {
|
|
103
|
+
return this.serveStaticFile(null, basePath);
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Serve static files from the UI dist folder
|
|
108
|
+
*/
|
|
109
|
+
serveStaticFile(req, basePath) {
|
|
110
|
+
let filePath = 'index.html';
|
|
111
|
+
if (req) {
|
|
112
|
+
const url = new URL(req.url);
|
|
113
|
+
const relativePath = url.pathname.replace(basePath, '').replace(/^\//, '');
|
|
114
|
+
if (relativePath && !relativePath.startsWith('api/')) {
|
|
115
|
+
filePath = relativePath;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
const fullPath = path.join(this.uiDistPath, filePath);
|
|
119
|
+
// Check if UI is built
|
|
120
|
+
if (!fs.existsSync(this.uiDistPath)) {
|
|
121
|
+
return this.serveFallbackUI();
|
|
122
|
+
}
|
|
123
|
+
// Check if file exists
|
|
124
|
+
if (!fs.existsSync(fullPath)) {
|
|
125
|
+
// For SPA routing, serve index.html for non-asset paths
|
|
126
|
+
if (!filePath.includes('.')) {
|
|
127
|
+
const indexPath = path.join(this.uiDistPath, 'index.html');
|
|
128
|
+
if (fs.existsSync(indexPath)) {
|
|
129
|
+
const content = fs.readFileSync(indexPath);
|
|
130
|
+
return new Response(content, {
|
|
131
|
+
headers: { 'Content-Type': 'text/html' }
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return new Response('Not Found', { status: 404 });
|
|
136
|
+
}
|
|
137
|
+
// Read and serve file
|
|
138
|
+
const content = fs.readFileSync(fullPath);
|
|
139
|
+
const contentType = this.getContentType(filePath);
|
|
140
|
+
return new Response(content, {
|
|
141
|
+
headers: { 'Content-Type': contentType }
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Get content type for file extension
|
|
146
|
+
*/
|
|
147
|
+
getContentType(filePath) {
|
|
148
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
149
|
+
const types = {
|
|
150
|
+
'.html': 'text/html',
|
|
151
|
+
'.css': 'text/css',
|
|
152
|
+
'.js': 'application/javascript',
|
|
153
|
+
'.json': 'application/json',
|
|
154
|
+
'.png': 'image/png',
|
|
155
|
+
'.jpg': 'image/jpeg',
|
|
156
|
+
'.svg': 'image/svg+xml',
|
|
157
|
+
'.ico': 'image/x-icon',
|
|
158
|
+
'.woff': 'font/woff',
|
|
159
|
+
'.woff2': 'font/woff2'
|
|
160
|
+
};
|
|
161
|
+
return types[ext] || 'application/octet-stream';
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Serve a fallback UI when the dist folder doesn't exist
|
|
165
|
+
*/
|
|
166
|
+
serveFallbackUI() {
|
|
167
|
+
const html = `<!DOCTYPE html>
|
|
168
|
+
<html lang="en">
|
|
169
|
+
<head>
|
|
170
|
+
<meta charset="UTF-8">
|
|
171
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
172
|
+
<title>OpenClaw Office</title>
|
|
173
|
+
<style>
|
|
174
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
175
|
+
body {
|
|
176
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
177
|
+
background: #0a0a14;
|
|
178
|
+
color: #fff;
|
|
179
|
+
min-height: 100vh;
|
|
180
|
+
display: flex;
|
|
181
|
+
align-items: center;
|
|
182
|
+
justify-content: center;
|
|
183
|
+
}
|
|
184
|
+
.container {
|
|
185
|
+
text-align: center;
|
|
186
|
+
padding: 40px;
|
|
187
|
+
}
|
|
188
|
+
h1 { font-size: 48px; margin-bottom: 20px; }
|
|
189
|
+
p { color: #888; margin-bottom: 30px; }
|
|
190
|
+
.agents { display: flex; gap: 20px; justify-content: center; flex-wrap: wrap; }
|
|
191
|
+
.agent {
|
|
192
|
+
background: #1a1a2e;
|
|
193
|
+
border: 1px solid #3a3a5a;
|
|
194
|
+
border-radius: 12px;
|
|
195
|
+
padding: 20px;
|
|
196
|
+
min-width: 150px;
|
|
197
|
+
}
|
|
198
|
+
.agent-icon { font-size: 40px; margin-bottom: 10px; }
|
|
199
|
+
.agent-name { font-weight: 600; margin-bottom: 5px; }
|
|
200
|
+
.agent-status { font-size: 12px; color: #888; }
|
|
201
|
+
.status-active { color: #00ff88; }
|
|
202
|
+
.status-idle { color: #ffaa00; }
|
|
203
|
+
.status-offline { color: #666; }
|
|
204
|
+
.build-notice {
|
|
205
|
+
margin-top: 40px;
|
|
206
|
+
padding: 20px;
|
|
207
|
+
background: #1a1a2e;
|
|
208
|
+
border-radius: 8px;
|
|
209
|
+
font-size: 14px;
|
|
210
|
+
color: #888;
|
|
211
|
+
}
|
|
212
|
+
code {
|
|
213
|
+
background: #2a2a3e;
|
|
214
|
+
padding: 2px 8px;
|
|
215
|
+
border-radius: 4px;
|
|
216
|
+
font-family: monospace;
|
|
217
|
+
}
|
|
218
|
+
</style>
|
|
219
|
+
</head>
|
|
220
|
+
<body>
|
|
221
|
+
<div class="container">
|
|
222
|
+
<h1>🏢</h1>
|
|
223
|
+
<p>OpenClaw Office Visualization</p>
|
|
224
|
+
<div class="agents" id="agents">
|
|
225
|
+
<div class="agent">
|
|
226
|
+
<div class="agent-icon">👤</div>
|
|
227
|
+
<div class="agent-name">Loading...</div>
|
|
228
|
+
<div class="agent-status">Fetching agents</div>
|
|
229
|
+
</div>
|
|
230
|
+
</div>
|
|
231
|
+
<div class="build-notice">
|
|
232
|
+
<p>For the full pixel art experience, build the UI:</p>
|
|
233
|
+
<p style="margin-top: 10px;"><code>cd office-viz && pnpm build:ui</code></p>
|
|
234
|
+
</div>
|
|
235
|
+
</div>
|
|
236
|
+
<script>
|
|
237
|
+
async function loadAgents() {
|
|
238
|
+
try {
|
|
239
|
+
const res = await fetch('/office/api/agents');
|
|
240
|
+
const data = await res.json();
|
|
241
|
+
const container = document.getElementById('agents');
|
|
242
|
+
|
|
243
|
+
if (data.agents.length === 0) {
|
|
244
|
+
container.innerHTML = '<p style="color: #666;">No agents currently running</p>';
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
container.innerHTML = data.agents.map(agent => \`
|
|
249
|
+
<div class="agent">
|
|
250
|
+
<div class="agent-icon">\${agent.status === 'active' ? '🏃' : agent.status === 'idle' ? '🧍' : '💤'}</div>
|
|
251
|
+
<div class="agent-name">\${agent.name}</div>
|
|
252
|
+
<div class="agent-status status-\${agent.status}">\${agent.status}</div>
|
|
253
|
+
\${agent.currentTool ? \`<div class="agent-status">🔧 \${agent.currentTool}</div>\` : ''}
|
|
254
|
+
</div>
|
|
255
|
+
\`).join('');
|
|
256
|
+
} catch (e) {
|
|
257
|
+
console.error('Failed to load agents:', e);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
loadAgents();
|
|
262
|
+
setInterval(loadAgents, 5000);
|
|
263
|
+
</script>
|
|
264
|
+
</body>
|
|
265
|
+
</html>`;
|
|
266
|
+
return new Response(html, {
|
|
267
|
+
headers: { 'Content-Type': 'text/html' }
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Subscribe to OpenClaw events for real-time updates
|
|
272
|
+
*/
|
|
273
|
+
subscribeToEvents() {
|
|
274
|
+
const { events } = this.ctx;
|
|
275
|
+
// Session started
|
|
276
|
+
events.on('session.start', (data) => {
|
|
277
|
+
const session = data;
|
|
278
|
+
this.addOrUpdateAgent({
|
|
279
|
+
id: session.id,
|
|
280
|
+
name: this.generateAgentName(session.channel, session.id),
|
|
281
|
+
status: 'active',
|
|
282
|
+
sessionId: session.id,
|
|
283
|
+
lastActivity: Date.now(),
|
|
284
|
+
model: session.model,
|
|
285
|
+
channel: session.channel
|
|
286
|
+
});
|
|
287
|
+
});
|
|
288
|
+
// Session ended
|
|
289
|
+
events.on('session.end', (data) => {
|
|
290
|
+
const session = data;
|
|
291
|
+
const agent = this.agents.get(session.id);
|
|
292
|
+
if (agent) {
|
|
293
|
+
agent.status = 'offline';
|
|
294
|
+
agent.lastActivity = Date.now();
|
|
295
|
+
}
|
|
296
|
+
});
|
|
297
|
+
// Tool execution started
|
|
298
|
+
events.on('tool.start', (data) => {
|
|
299
|
+
const tool = data;
|
|
300
|
+
const agent = this.agents.get(tool.sessionId);
|
|
301
|
+
if (agent) {
|
|
302
|
+
agent.status = 'active';
|
|
303
|
+
agent.currentTool = tool.toolName;
|
|
304
|
+
agent.lastActivity = Date.now();
|
|
305
|
+
}
|
|
306
|
+
});
|
|
307
|
+
// Tool execution ended
|
|
308
|
+
events.on('tool.end', (data) => {
|
|
309
|
+
const tool = data;
|
|
310
|
+
const agent = this.agents.get(tool.sessionId);
|
|
311
|
+
if (agent) {
|
|
312
|
+
agent.currentTool = undefined;
|
|
313
|
+
agent.status = 'idle';
|
|
314
|
+
agent.lastActivity = Date.now();
|
|
315
|
+
}
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
/**
|
|
319
|
+
* Generate a friendly name for an agent
|
|
320
|
+
*/
|
|
321
|
+
generateAgentName(channel, sessionId) {
|
|
322
|
+
const channelName = channel ? channel.charAt(0).toUpperCase() + channel.slice(1) : 'Agent';
|
|
323
|
+
const shortId = sessionId ? sessionId.slice(-6) : Math.random().toString(36).slice(-6);
|
|
324
|
+
return `${channelName} ${shortId}`;
|
|
325
|
+
}
|
|
326
|
+
/**
|
|
327
|
+
* Sync agents from current sessions
|
|
328
|
+
*/
|
|
329
|
+
async syncAgents() {
|
|
330
|
+
try {
|
|
331
|
+
const sessions = await this.ctx.sessions.list();
|
|
332
|
+
for (const session of sessions) {
|
|
333
|
+
this.addOrUpdateAgent({
|
|
334
|
+
id: session.id,
|
|
335
|
+
name: this.generateAgentName(session.channel, session.id),
|
|
336
|
+
status: session.status === 'active' ? 'active' :
|
|
337
|
+
session.status === 'waiting' ? 'waiting' : 'idle',
|
|
338
|
+
sessionId: session.id,
|
|
339
|
+
lastActivity: session.lastActivity || Date.now(),
|
|
340
|
+
model: session.model,
|
|
341
|
+
channel: session.channel
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
catch (error) {
|
|
346
|
+
this.ctx.logger.warn(`Failed to sync agents: ${error}`);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
/**
|
|
350
|
+
* Add or update an agent
|
|
351
|
+
*/
|
|
352
|
+
addOrUpdateAgent(agent) {
|
|
353
|
+
this.agents.set(agent.id, agent);
|
|
354
|
+
}
|
|
355
|
+
/**
|
|
356
|
+
* Cleanup on shutdown
|
|
357
|
+
*/
|
|
358
|
+
async destroy() {
|
|
359
|
+
this.agents.clear();
|
|
360
|
+
this.ctx.logger.info('Office Visualization plugin destroyed');
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
/**
|
|
364
|
+
* Plugin entry point
|
|
365
|
+
* Called by OpenClaw when loading the plugin
|
|
366
|
+
*/
|
|
367
|
+
export default async function (ctx) {
|
|
368
|
+
const plugin = new OfficeVizPlugin(ctx);
|
|
369
|
+
await plugin.init();
|
|
370
|
+
return plugin;
|
|
371
|
+
}
|
|
372
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "office-viz",
|
|
3
|
+
"name": "Office Visualization",
|
|
4
|
+
"version": "1.0.0",
|
|
5
|
+
"description": "Pixel art office visualization showing running agents as animated characters",
|
|
6
|
+
"author": "OpenClaw Community",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"homepage": "https://github.com/openclaw-community/office-viz",
|
|
9
|
+
"repository": "https://github.com/openclaw-community/office-viz",
|
|
10
|
+
"keywords": ["visualization", "dashboard", "pixel-art", "agents", "office"],
|
|
11
|
+
"kind": "service",
|
|
12
|
+
"configSchema": {
|
|
13
|
+
"type": "object",
|
|
14
|
+
"properties": {
|
|
15
|
+
"enabled": {
|
|
16
|
+
"type": "boolean",
|
|
17
|
+
"default": true,
|
|
18
|
+
"description": "Enable or disable the office visualization"
|
|
19
|
+
},
|
|
20
|
+
"basePath": {
|
|
21
|
+
"type": "string",
|
|
22
|
+
"default": "/office",
|
|
23
|
+
"description": "URL path for the visualization UI"
|
|
24
|
+
},
|
|
25
|
+
"theme": {
|
|
26
|
+
"type": "string",
|
|
27
|
+
"enum": ["default", "dark", "light"],
|
|
28
|
+
"default": "default",
|
|
29
|
+
"description": "Color theme for the visualization"
|
|
30
|
+
},
|
|
31
|
+
"showInactiveAgents": {
|
|
32
|
+
"type": "boolean",
|
|
33
|
+
"default": true,
|
|
34
|
+
"description": "Show agents that are offline in the visualization"
|
|
35
|
+
},
|
|
36
|
+
"animationSpeed": {
|
|
37
|
+
"type": "number",
|
|
38
|
+
"minimum": 0.5,
|
|
39
|
+
"maximum": 2.0,
|
|
40
|
+
"default": 1.0,
|
|
41
|
+
"description": "Animation speed multiplier"
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
"additionalProperties": false
|
|
45
|
+
},
|
|
46
|
+
"ui": {
|
|
47
|
+
"dashboard": {
|
|
48
|
+
"label": "Office",
|
|
49
|
+
"icon": "building",
|
|
50
|
+
"path": "/office",
|
|
51
|
+
"description": "View agents as pixel art characters in a virtual office"
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
"permissions": [
|
|
55
|
+
"sessions:read",
|
|
56
|
+
"presence:read",
|
|
57
|
+
"http:register"
|
|
58
|
+
]
|
|
59
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@patrax/office-viz",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Pixel art office visualization plugin for OpenClaw - see your agents as animated characters",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": "./dist/index.js",
|
|
11
|
+
"types": "./dist/index.d.ts"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist",
|
|
16
|
+
"ui/dist",
|
|
17
|
+
"openclaw.plugin.json",
|
|
18
|
+
"README.md",
|
|
19
|
+
"LICENSE"
|
|
20
|
+
],
|
|
21
|
+
"scripts": {
|
|
22
|
+
"build": "npm run build:plugin && npm run build:ui",
|
|
23
|
+
"build:plugin": "tsc",
|
|
24
|
+
"build:ui": "cd ui && npm install && npm run build",
|
|
25
|
+
"dev": "tsc --watch",
|
|
26
|
+
"dev:ui": "cd ui && npm run dev",
|
|
27
|
+
"clean": "rm -rf dist ui/dist",
|
|
28
|
+
"prepublishOnly": "npm run clean && npm run build"
|
|
29
|
+
},
|
|
30
|
+
"keywords": [
|
|
31
|
+
"openclaw",
|
|
32
|
+
"plugin",
|
|
33
|
+
"visualization",
|
|
34
|
+
"pixel-art",
|
|
35
|
+
"agents",
|
|
36
|
+
"office",
|
|
37
|
+
"dashboard"
|
|
38
|
+
],
|
|
39
|
+
"author": "OpenClaw Community",
|
|
40
|
+
"license": "MIT",
|
|
41
|
+
"repository": {
|
|
42
|
+
"type": "git",
|
|
43
|
+
"url": "https://github.com/openclaw-community/office-viz.git"
|
|
44
|
+
},
|
|
45
|
+
"homepage": "https://github.com/openclaw-community/office-viz#readme",
|
|
46
|
+
"bugs": {
|
|
47
|
+
"url": "https://github.com/openclaw-community/office-viz/issues"
|
|
48
|
+
},
|
|
49
|
+
"engines": {
|
|
50
|
+
"node": ">=18.0.0"
|
|
51
|
+
},
|
|
52
|
+
"devDependencies": {
|
|
53
|
+
"@types/node": "^25.3.3",
|
|
54
|
+
"typescript": "^5.3.0"
|
|
55
|
+
},
|
|
56
|
+
"peerDependencies": {
|
|
57
|
+
"openclaw": ">=0.1.0"
|
|
58
|
+
},
|
|
59
|
+
"peerDependenciesMeta": {
|
|
60
|
+
"openclaw": {
|
|
61
|
+
"optional": true
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
var I=Object.defineProperty;var A=(p,i,s)=>i in p?I(p,i,{enumerable:!0,configurable:!0,writable:!0,value:s}):p[i]=s;var o=(p,i,s)=>A(p,typeof i!="symbol"?i+"":i,s);(function(){const i=document.createElement("link").relList;if(i&&i.supports&&i.supports("modulepreload"))return;for(const t of document.querySelectorAll('link[rel="modulepreload"]'))e(t);new MutationObserver(t=>{for(const l of t)if(l.type==="childList")for(const a of l.addedNodes)a.tagName==="LINK"&&a.rel==="modulepreload"&&e(a)}).observe(document,{childList:!0,subtree:!0});function s(t){const l={};return t.integrity&&(l.integrity=t.integrity),t.referrerPolicy&&(l.referrerPolicy=t.referrerPolicy),t.crossOrigin==="use-credentials"?l.credentials="include":t.crossOrigin==="anonymous"?l.credentials="omit":l.credentials="same-origin",l}function e(t){if(t.ep)return;t.ep=!0;const l=s(t);fetch(t.href,l)}})();class z{constructor(i,s,e){o(this,"ctx");o(this,"sprites");o(this,"tileSize");o(this,"floorColor","#2a2a3e");o(this,"floorAccent","#252538");o(this,"wallColor","#3a3a5a");o(this,"wallTop","#4a4a6a");this.ctx=i,this.sprites=s,this.tileSize=e}renderFloor(i){const s=this.ctx,e=this.tileSize;for(let t=0;t<i.height;t++)for(let l=0;l<i.width;l++){const a=(l+t)%2===0;s.fillStyle=a?this.floorColor:this.floorAccent,s.fillRect(l*e,t*e,e,e)}s.strokeStyle="rgba(255, 255, 255, 0.03)",s.lineWidth=1;for(let t=0;t<=i.height;t++)s.beginPath(),s.moveTo(0,t*e),s.lineTo(i.width*e,t*e),s.stroke();for(let t=0;t<=i.width;t++)s.beginPath(),s.moveTo(t*e,0),s.lineTo(t*e,i.height*e),s.stroke()}renderWalls(i){const s=this.ctx,e=this.tileSize,t=e*.6;s.fillStyle=this.wallColor,s.fillRect(0,0,i.width*e,t),s.fillStyle=this.wallTop,s.fillRect(0,0,i.width*e,4);const l=s.createLinearGradient(0,t,0,t+20);l.addColorStop(0,"rgba(0, 0, 0, 0.3)"),l.addColorStop(1,"rgba(0, 0, 0, 0)"),s.fillStyle=l,s.fillRect(0,t,i.width*e,20)}renderFurniture(i){for(const s of i.furniture){const e=s.x*this.tileSize,t=s.y*this.tileSize;switch(s.type){case"plant":this.renderPlant(e,t);break;case"coffee-machine":this.renderCoffeeMachine(e,t);break;case"water-cooler":this.renderWaterCooler(e,t);break;case"whiteboard":this.renderWhiteboard(e,t);break;case"server-rack":this.renderServerRack(e,t);break;case"couch":this.renderCouch(e,t);break}}}renderPlant(i,s){const e=this.ctx,t=this.tileSize;e.fillStyle="#8b4513",e.fillRect(i+t*.25,s+t*.6,t*.5,t*.35),e.fillStyle="#a0522d",e.fillRect(i+t*.2,s+t*.55,t*.6,t*.1),e.fillStyle="#228b22",e.beginPath(),e.arc(i+t*.5,s+t*.35,t*.25,0,Math.PI*2),e.fill(),e.fillStyle="#32cd32",e.beginPath(),e.arc(i+t*.35,s+t*.4,t*.15,0,Math.PI*2),e.fill(),e.beginPath(),e.arc(i+t*.65,s+t*.4,t*.15,0,Math.PI*2),e.fill()}renderCoffeeMachine(i,s){const e=this.ctx,t=this.tileSize;e.fillStyle="#333",e.fillRect(i+t*.15,s+t*.2,t*.7,t*.7),e.fillStyle="#444",e.fillRect(i+t*.2,s+t*.3,t*.6,t*.4),e.fillStyle="#ff6600",e.beginPath(),e.arc(i+t*.5,s+t*.35,4,0,Math.PI*2),e.fill(),e.fillStyle="#222",e.fillRect(i+t*.3,s+t*.55,t*.4,t*.2)}renderWaterCooler(i,s){const e=this.ctx,t=this.tileSize;e.fillStyle="#ddd",e.fillRect(i+t*.25,s+t*.5,t*.5,t*.4),e.fillStyle="#87ceeb",e.globalAlpha=.7,e.fillRect(i+t*.3,s+t*.1,t*.4,t*.45),e.globalAlpha=1,e.fillStyle="#4169e1",e.fillRect(i+t*.35,s+t*.05,t*.3,t*.08)}renderWhiteboard(i,s){const e=this.ctx,t=this.tileSize;e.fillStyle="#555",e.fillRect(i,s,t*.15,t*1.8),e.fillStyle="#f5f5f5",e.fillRect(i+t*.02,s+t*.1,t*.11,t*1.6),e.fillStyle="#333",e.fillRect(i+t*.04,s+t*.3,t*.07,2),e.fillRect(i+t*.04,s+t*.5,t*.05,2),e.fillRect(i+t*.04,s+t*.7,t*.07,2)}renderServerRack(i,s){const e=this.ctx,t=this.tileSize;e.fillStyle="#222",e.fillRect(i,s,t*.2,t*1.8);const l=Date.now()/1e3;for(let a=0;a<5;a++){e.fillStyle="#333",e.fillRect(i+t*.02,s+t*.2+a*t*.3,t*.16,t*.25);const h=.5+a*.3,d=Math.sin(l*h+a*2.1)>-.4;e.fillStyle=d?"#00ff00":"#ff0000",e.fillRect(i+t*.14,s+t*.25+a*t*.3,3,3)}}renderCouch(i,s){const e=this.ctx,t=this.tileSize;e.fillStyle="#4a4a6a",e.fillRect(i,s+t*.3,t*3,t*.5),e.fillStyle="#5a5a7a",e.fillRect(i+t*.1,s+t*.15,t*.8,t*.4),e.fillRect(i+t*1.1,s+t*.15,t*.8,t*.4),e.fillRect(i+t*2.1,s+t*.15,t*.8,t*.4),e.fillStyle="#3a3a5a",e.fillRect(i,s,t*3,t*.2)}renderDesks(i){for(const s of i.desks)this.renderDesk(s.x*this.tileSize,s.y*this.tileSize,s.occupied)}renderDesk(i,s,e){const t=this.ctx,l=this.tileSize;t.fillStyle="rgba(0, 0, 0, 0.2)",t.fillRect(i+4,s+l*.3+4,l*1.2,l*.6),t.fillStyle="#8b7355",t.fillRect(i,s+l*.3,l*1.2,l*.6),t.fillStyle="#a08060",t.fillRect(i,s+l*.3,l*1.2,4),t.fillStyle="#222",t.fillRect(i+l*.3,s+l*.1,l*.6,l*.35);const a=e?"#1a1a2e":"#111";t.fillStyle=a,t.fillRect(i+l*.33,s+l*.13,l*.54,l*.28),e&&(t.fillStyle="rgba(0, 212, 255, 0.1)",t.fillRect(i+l*.33,s+l*.13,l*.54,l*.28)),t.fillStyle="#333",t.fillRect(i+l*.5,s+l*.45,l*.2,l*.08),this.renderChair(i+l*.35,s+l*1)}renderChair(i,s){const e=this.ctx,t=this.tileSize;e.fillStyle="#333",e.fillRect(i,s-t*.3,t*.5,t*.4),e.fillStyle="#444",e.fillRect(i-t*.05,s+t*.05,t*.6,t*.25),e.fillStyle="#222",e.fillRect(i+t*.15,s+t*.3,t*.2,t*.15)}renderCharacter(i,s){const e=this.ctx,t=this.tileSize,l=i.x*t,a=i.y*t,h=this.sprites.getCharacterSprite(i.agent.status,i.direction,i.frame);if(h){const d=h.width,f=h.height;e.drawImage(h,l-d/2,a-f+t*.3,d,f)}else this.renderCharacterProcedural(i,l,a);s&&(e.strokeStyle="#00d4ff",e.lineWidth=2,e.beginPath(),e.ellipse(l,a+t*.1,t*.35,t*.15,0,0,Math.PI*2),e.stroke()),this.renderStatusIndicator(i.agent.status,l,a-t*.6),this.renderNameTag(i.agent.name,l,a+t*.35)}renderCharacterProcedural(i,s,e){const t=this.ctx,l=this.tileSize,a=i.agent.status,h={active:"#4a90d9",idle:"#d9a84a",waiting:"#4ad9d9",offline:"#666666"},d="#ffdbac",f=h[a],g="#3a2a1a",u=i.isMoving?Math.sin(i.animationTime*8)*2:0,S=i.isMoving?Math.sin(i.animationTime*8)*3:0;t.save(),t.fillStyle="rgba(0, 0, 0, 0.3)",t.beginPath(),t.ellipse(s,e+l*.15,l*.25,l*.08,0,0,Math.PI*2),t.fill();const w=e-l*.3+u*.5;t.fillStyle=f,t.fillRect(s-l*.15,w,l*.3,l*.35),t.fillRect(s-l*.25,w+2+S,l*.08,l*.25),t.fillRect(s+l*.17,w+2-S,l*.08,l*.25),t.fillStyle="#2a2a4a";const M=i.isMoving?u:0;t.fillRect(s-l*.12,w+l*.3,l*.1,l*.25+M),t.fillRect(s+l*.02,w+l*.3,l*.1,l*.25-M);const y=e-l*.55;t.fillStyle=d,t.beginPath(),t.arc(s,y,l*.15,0,Math.PI*2),t.fill(),t.fillStyle=g,t.beginPath(),t.arc(s,y-l*.05,l*.13,Math.PI,Math.PI*2),t.fill(),t.fillStyle="#333",a==="offline"?(t.fillRect(s-l*.08,y,4,1),t.fillRect(s+l*.04,y,4,1)):i.direction!=="up"&&(t.fillRect(s-l*.07,y-1,3,4),t.fillRect(s+l*.04,y-1,3,4)),t.restore()}renderStatusIndicator(i,s,e){const t=this.ctx,l={active:"#00ff88",idle:"#ffaa00",waiting:"#00d4ff",offline:"#666666"};if(i==="active"&&(t.fillStyle="rgba(0, 255, 136, 0.3)",t.beginPath(),t.arc(s,e,8,0,Math.PI*2),t.fill()),t.fillStyle=l[i],t.beginPath(),t.arc(s,e,4,0,Math.PI*2),t.fill(),i==="active"){const a=(Math.sin(Date.now()/200)+1)/2;t.strokeStyle=l[i],t.globalAlpha=1-a,t.lineWidth=2,t.beginPath(),t.arc(s,e,4+a*6,0,Math.PI*2),t.stroke(),t.globalAlpha=1}}renderNameTag(i,s,e){const t=this.ctx;t.font="10px -apple-system, BlinkMacSystemFont, sans-serif",t.textAlign="center",t.textBaseline="top";const l=i.length>12?i.slice(0,10)+"...":i,d=t.measureText(l).width+4*2,f=14;t.fillStyle="rgba(0, 0, 0, 0.6)",t.fillRect(s-d/2,e,d,f),t.fillStyle="#fff",t.fillText(l,s,e+2)}renderBubble(i,s){const e=this.ctx,t=this.tileSize,l=i.x*t,a=i.y*t-t*.8;e.font="11px -apple-system, BlinkMacSystemFont, sans-serif";const h=e.measureText(s),f=Math.min(h.width+8*2,t*2.5),g=24;e.fillStyle="#fff",e.beginPath(),e.roundRect(l-f/2,a-g,f,g,6),e.fill(),e.beginPath(),e.moveTo(l-5,a),e.lineTo(l+5,a),e.lineTo(l,a+8),e.closePath(),e.fill(),e.fillStyle="#333",e.textAlign="center",e.textBaseline="middle";const u=s.length>20?s.slice(0,18)+"...":s;e.fillText(u,l,a-g/2)}}class L{constructor(){o(this,"sprites",new Map);o(this,"spriteSize",32)}async loadAll(){const i=["active","idle","waiting","offline"],s=["up","down","left","right"],e=[0,1,2,3];for(const t of i)for(const l of s)for(const a of e){const h=this.getSpriteKey(t,l,a),d=this.generateCharacterSprite(t,l,a);this.sprites.set(h,d)}console.log(`Generated ${this.sprites.size} character sprites`)}getSpriteKey(i,s,e){return`char_${i}_${s}_${e}`}getCharacterSprite(i,s,e){const t=this.getSpriteKey(i,s,e);return this.sprites.get(t)||null}generateCharacterSprite(i,s,e){const t=document.createElement("canvas"),l=this.spriteSize;t.width=l,t.height=l*1.5;const a=t.getContext("2d");a.imageSmoothingEnabled=!1;const h={active:"#4a90d9",idle:"#d9a84a",waiting:"#4ad9d9",offline:"#666666"},d="#ffdbac",f=h[i],g="#3a2a1a",u="#2a2a4a",S="#1a1a2a",w=[0,1,0,-1],M=[0,2,0,-2],y=[0,1,0,-1],c=w[e],k=M[e],x=y[e],n=l/2,r=l*1.2;a.fillStyle="rgba(0, 0, 0, 0.3)",this.drawEllipse(a,n,r+4,10,4);const v=e!==0;if(s==="down"||s==="up"){a.fillStyle=u;const m=r-8+(v?k:0),b=r-8+(v?-k:0);this.drawRect(a,n-5,m,4,10),this.drawRect(a,n+1,b,4,10),a.fillStyle=S,this.drawRect(a,n-6,m+8,5,3),this.drawRect(a,n+1,b+8,5,3),a.fillStyle=f,this.drawRect(a,n-7,r-18+c,14,12);const R=r-16+(v?x:0),C=r-16+(v?-x:0);this.drawRect(a,n-10,R+c,4,10),this.drawRect(a,n+6,C+c,4,10),a.fillStyle=d,this.drawRect(a,n-10,R+8+c,4,3),this.drawRect(a,n+6,C+8+c,4,3),a.fillStyle=d,this.drawCircle(a,n,r-28+c,8),a.fillStyle=g,s==="down"?(this.drawArc(a,n,r-30+c,7,Math.PI,Math.PI*2),this.drawRect(a,n-7,r-34+c,14,4)):this.drawCircle(a,n,r-30+c,7),s==="down"&&(a.fillStyle="#333",i==="offline"?(this.drawRect(a,n-5,r-28+c,3,1),this.drawRect(a,n+2,r-28+c,3,1)):(this.drawRect(a,n-5,r-29+c,2,3),this.drawRect(a,n+3,r-29+c,2,3)),i==="active"&&(a.fillStyle="#333",this.drawRect(a,n-2,r-24+c,4,1)))}else{a.save(),s==="left"&&(a.scale(-1,1),a.translate(-l,0)),a.fillStyle=u;const m=r-8+(v?-k:0);this.drawRect(a,n-2,m,4,10),a.fillStyle=S,this.drawRect(a,n-3,m+8,5,3),a.fillStyle=f,this.drawRect(a,n-5,r-18+c,10,12);const b=r-16+(v?-x:0);this.drawRect(a,n-6,b+c,4,10),a.fillStyle=d,this.drawRect(a,n-6,b+8+c,4,3),a.fillStyle=u;const R=r-8+(v?k:0);this.drawRect(a,n+0,R,4,10),a.fillStyle=S,this.drawRect(a,n-1,R+8,5,3),a.fillStyle=f;const C=r-16+(v?x:0);this.drawRect(a,n+2,C+c,4,10),a.fillStyle=d,this.drawRect(a,n+2,C+8+c,4,3),a.fillStyle=d,this.drawCircle(a,n,r-28+c,8),a.fillStyle=g,this.drawArc(a,n-1,r-30+c,7,Math.PI*.7,Math.PI*1.8),this.drawRect(a,n-7,r-34+c,10,5),a.fillStyle="#333",i==="offline"?this.drawRect(a,n+2,r-28+c,3,1):this.drawRect(a,n+2,r-29+c,2,3),a.restore()}return t}drawRect(i,s,e,t,l){i.fillRect(Math.floor(s),Math.floor(e),t,l)}drawCircle(i,s,e,t){i.beginPath(),i.arc(Math.floor(s),Math.floor(e),t,0,Math.PI*2),i.fill()}drawEllipse(i,s,e,t,l){i.beginPath(),i.ellipse(Math.floor(s),Math.floor(e),t,l,0,0,Math.PI*2),i.fill()}drawArc(i,s,e,t,l,a){i.beginPath(),i.arc(Math.floor(s),Math.floor(e),t,l,a),i.fill()}}class E{constructor(i="/office/api"){o(this,"baseUrl");this.baseUrl=i}async getAgents(){try{const i=await fetch(`${this.baseUrl}/agents`);if(!i.ok)throw new Error(`HTTP ${i.status}`);return await i.json()}catch(i){return console.warn("Failed to fetch agents, using mock data:",i),this.getMockAgents()}}async getPresence(){try{const i=await fetch(`${this.baseUrl}/presence`);if(!i.ok)throw new Error(`HTTP ${i.status}`);return await i.json()}catch(i){return console.warn("Failed to fetch presence:",i),null}}async getConfig(){try{const i=await fetch(`${this.baseUrl}/config`);if(!i.ok)throw new Error(`HTTP ${i.status}`);return await i.json()}catch(i){return console.warn("Failed to fetch config:",i),null}}getMockAgents(){return{agents:[{id:"agent-001",name:"CLI abc123",status:"active",sessionId:"session-001",lastActivity:Date.now(),currentTool:"developer__shell",model:"claude-sonnet-4-20250514",channel:"cli"},{id:"agent-002",name:"Slack def456",status:"idle",sessionId:"session-002",lastActivity:Date.now()-6e4,model:"claude-sonnet-4-20250514",channel:"slack"},{id:"agent-003",name:"API ghi789",status:"active",sessionId:"session-003",lastActivity:Date.now(),currentTool:"googledrive__read",model:"gpt-4o",channel:"api"},{id:"agent-004",name:"Discord jkl012",status:"waiting",sessionId:"session-004",lastActivity:Date.now()-3e4,model:"claude-sonnet-4-20250514",channel:"discord"},{id:"agent-005",name:"Web mno345",status:"offline",sessionId:"session-005",lastActivity:Date.now()-36e5,model:"claude-sonnet-4-20250514",channel:"web"}],timestamp:Date.now()}}}const P={width:12,height:10,desks:[{id:1,x:2,y:2,occupied:!1},{id:2,x:5,y:2,occupied:!1},{id:3,x:8,y:2,occupied:!1},{id:4,x:2,y:5,occupied:!1},{id:5,x:5,y:5,occupied:!1},{id:6,x:8,y:5,occupied:!1},{id:7,x:2,y:8,occupied:!1},{id:8,x:5,y:8,occupied:!1},{id:9,x:8,y:8,occupied:!1}],furniture:[{type:"plant",x:.5,y:1,width:1,height:1},{type:"plant",x:10.5,y:1,width:1,height:1},{type:"coffee-machine",x:10.5,y:4,width:1,height:1},{type:"water-cooler",x:10.5,y:6,width:1,height:1},{type:"whiteboard",x:.3,y:3,width:1,height:2},{type:"server-rack",x:.3,y:6,width:1,height:2},{type:"couch",x:4,y:9.2,width:3,height:1}]};class ${constructor(i){o(this,"container");o(this,"canvas");o(this,"ctx");o(this,"renderer");o(this,"sprites");o(this,"api");o(this,"layout");o(this,"config");o(this,"characters",new Map);o(this,"tileSize",48);o(this,"scale",1);o(this,"animationFrame",null);o(this,"lastTime",0);o(this,"pollInterval",null);o(this,"hoveredCharacter",null);o(this,"tooltip",null);o(this,"loadingOverlay",null);this.container=i,this.layout={...P,desks:P.desks.map(s=>({...s}))},this.config={theme:"default",showInactiveAgents:!0,animationSpeed:1},this.sprites=new L,this.api=new E}async start(){this.createDOM(),await this.sprites.loadAll(),await this.loadConfig(),await this.syncAgents(),this.startPolling(),this.startRenderLoop(),this.hideLoading()}createDOM(){this.container.innerHTML=`
|
|
2
|
+
<div class="office-container">
|
|
3
|
+
<header class="office-header">
|
|
4
|
+
<div class="office-title">
|
|
5
|
+
<span class="office-title-icon">🏢</span>
|
|
6
|
+
<span>OpenClaw Office</span>
|
|
7
|
+
</div>
|
|
8
|
+
<div class="office-stats">
|
|
9
|
+
<div class="stat">
|
|
10
|
+
<span class="stat-dot active"></span>
|
|
11
|
+
<span class="stat-count" id="active-count">0</span>
|
|
12
|
+
<span>active</span>
|
|
13
|
+
</div>
|
|
14
|
+
<div class="stat">
|
|
15
|
+
<span class="stat-dot idle"></span>
|
|
16
|
+
<span class="stat-count" id="idle-count">0</span>
|
|
17
|
+
<span>idle</span>
|
|
18
|
+
</div>
|
|
19
|
+
<div class="stat">
|
|
20
|
+
<span class="stat-dot offline"></span>
|
|
21
|
+
<span class="stat-count" id="offline-count">0</span>
|
|
22
|
+
<span>offline</span>
|
|
23
|
+
</div>
|
|
24
|
+
</div>
|
|
25
|
+
</header>
|
|
26
|
+
<div class="canvas-wrapper">
|
|
27
|
+
<canvas id="office-canvas"></canvas>
|
|
28
|
+
<div class="vignette"></div>
|
|
29
|
+
<div class="loading-overlay">
|
|
30
|
+
<div class="loading-spinner"></div>
|
|
31
|
+
<div class="loading-text">Loading office...</div>
|
|
32
|
+
</div>
|
|
33
|
+
</div>
|
|
34
|
+
</div>
|
|
35
|
+
`,this.canvas=document.getElementById("office-canvas"),this.ctx=this.canvas.getContext("2d"),this.loadingOverlay=this.container.querySelector(".loading-overlay"),this.tooltip=document.createElement("div"),this.tooltip.className="agent-tooltip",this.tooltip.style.display="none",document.body.appendChild(this.tooltip),this.resize(),this.setupEventListeners(),this.renderer=new z(this.ctx,this.sprites,this.tileSize)}setupEventListeners(){this.canvas.addEventListener("mousemove",this.handleMouseMove.bind(this)),this.canvas.addEventListener("mouseleave",this.handleMouseLeave.bind(this)),this.canvas.addEventListener("click",this.handleClick.bind(this))}handleMouseMove(i){const s=this.canvas.getBoundingClientRect(),e=(i.clientX-s.left)/this.scale/this.tileSize,t=(i.clientY-s.top)/this.scale/this.tileSize;let l=null;for(const a of this.characters.values()){const h=e-a.x,d=t-a.y;if(Math.abs(h)<.5&&Math.abs(d)<.8){l=a;break}}l!==this.hoveredCharacter?(this.hoveredCharacter=l,l&&this.tooltip?this.showTooltip(l,i.clientX,i.clientY):this.hideTooltip()):l&&this.tooltip&&(this.tooltip.style.left=`${i.clientX}px`,this.tooltip.style.top=`${i.clientY}px`),this.canvas.style.cursor=l?"pointer":"default"}handleMouseLeave(){this.hoveredCharacter=null,this.hideTooltip(),this.canvas.style.cursor="default"}handleClick(i){this.hoveredCharacter&&(this.hoveredCharacter.bubbleText=this.hoveredCharacter.agent.currentTool||"Hello!",this.hoveredCharacter.bubbleTime=3)}showTooltip(i,s,e){if(!this.tooltip)return;const t={active:"#00ff88",idle:"#ffaa00",waiting:"#00d4ff",offline:"#666666"};this.tooltip.innerHTML=`
|
|
36
|
+
<div class="tooltip-name">${i.agent.name}</div>
|
|
37
|
+
<div class="tooltip-status">
|
|
38
|
+
<span class="tooltip-status-dot" style="background: ${t[i.agent.status]}"></span>
|
|
39
|
+
<span>${i.agent.status}</span>
|
|
40
|
+
${i.agent.channel?`<span>• ${i.agent.channel}</span>`:""}
|
|
41
|
+
</div>
|
|
42
|
+
${i.agent.currentTool?`<div class="tooltip-tool">🔧 ${i.agent.currentTool}</div>`:""}
|
|
43
|
+
`,this.tooltip.style.display="block",this.tooltip.style.left=`${s}px`,this.tooltip.style.top=`${e}px`}hideTooltip(){this.tooltip&&(this.tooltip.style.display="none")}resize(){const i=this.container.querySelector(".canvas-wrapper");if(!i)return;const s=i.clientWidth,e=i.clientHeight,t=this.layout.width*this.tileSize,l=this.layout.height*this.tileSize,a=s/t,h=e/l;this.scale=Math.min(a,h,2),this.canvas.width=t,this.canvas.height=l,this.canvas.style.width=`${t*this.scale}px`,this.canvas.style.height=`${l*this.scale}px`;const d=(s-t*this.scale)/2,f=(e-l*this.scale)/2;this.canvas.style.left=`${d}px`,this.canvas.style.top=`${f}px`}async loadConfig(){const i=await this.api.getConfig();i&&(this.config={...this.config,...i})}async syncAgents(){const i=await this.api.getAgents();if(!i)return;const s=new Set;for(const e of i.agents)if(s.add(e.id),!(!this.config.showInactiveAgents&&e.status==="offline"))if(this.characters.has(e.id)){const t=this.characters.get(e.id);t.agent=e}else{const t=this.assignDesk(e.id);if(t){const l=this.createCharacter(e,t);this.characters.set(e.id,l)}}for(const[e,t]of this.characters)if(!s.has(e)){const l=this.layout.desks.find(a=>a.id===t.deskId);l&&(l.occupied=!1,l.agentId=void 0),this.characters.delete(e)}this.updateStats()}assignDesk(i){const s=this.layout.desks.find(t=>t.agentId===i);if(s)return s;const e=this.layout.desks.find(t=>!t.occupied);return e?(e.occupied=!0,e.agentId=i,e):null}createCharacter(i,s){return{agent:i,x:s.x+.5,y:s.y+1.5,targetX:s.x+.5,targetY:s.y+1.5,direction:"up",frame:0,animationTime:0,deskId:s.id,isMoving:!1,isSitting:!0}}updateStats(){const i={active:0,idle:0,waiting:0,offline:0};for(const l of this.characters.values())i[l.agent.status]++;const s=document.getElementById("active-count"),e=document.getElementById("idle-count"),t=document.getElementById("offline-count");s&&(s.textContent=String(i.active)),e&&(e.textContent=String(i.idle+i.waiting)),t&&(t.textContent=String(i.offline))}startPolling(){this.pollInterval=window.setInterval(()=>{this.syncAgents()},5e3)}startRenderLoop(){const i=s=>{const e=(s-this.lastTime)/1e3;this.lastTime=s,this.update(e),this.render(),this.animationFrame=requestAnimationFrame(i)};this.animationFrame=requestAnimationFrame(i)}update(i){const s=this.config.animationSpeed;for(const e of this.characters.values()){if(e.animationTime+=i*s,e.isMoving&&(e.frame=Math.floor(e.animationTime*8)%4),e.isMoving){const t=e.targetX-e.x,l=e.targetY-e.y,a=Math.sqrt(t*t+l*l);if(a<.1)e.x=e.targetX,e.y=e.targetY,e.isMoving=!1,e.frame=0;else{const h=2*s;e.x+=t/a*h*i,e.y+=l/a*h*i,Math.abs(t)>Math.abs(l)?e.direction=t>0?"right":"left":e.direction=l>0?"down":"up"}}e.bubbleTime!==void 0&&e.bubbleTime>0&&(e.bubbleTime-=i,e.bubbleTime<=0&&(e.bubbleText=void 0,e.bubbleTime=void 0))}}render(){this.ctx.clearRect(0,0,this.canvas.width,this.canvas.height),this.renderer.renderFloor(this.layout),this.renderer.renderWalls(this.layout),this.renderer.renderFurniture(this.layout),this.renderer.renderDesks(this.layout);const i=Array.from(this.characters.values()).sort((s,e)=>s.y-e.y);for(const s of i){const e=s===this.hoveredCharacter;this.renderer.renderCharacter(s,e),s.bubbleText&&this.renderer.renderBubble(s,s.bubbleText)}}hideLoading(){this.loadingOverlay&&this.loadingOverlay.classList.add("hidden")}destroy(){this.animationFrame&&cancelAnimationFrame(this.animationFrame),this.pollInterval&&clearInterval(this.pollInterval),this.tooltip&&this.tooltip.remove()}}async function T(){const p=document.getElementById("app");if(!p){console.error("App container not found");return}const i=new $(p);window.addEventListener("resize",()=>{i.resize()});try{await i.start(),console.log("OpenClaw Office Visualization started")}catch(s){console.error("Failed to start Office Visualization:",s)}window.addEventListener("beforeunload",()=>{i.destroy()})}document.readyState==="loading"?document.addEventListener("DOMContentLoaded",T):T();
|
|
44
|
+
//# sourceMappingURL=index-DglNlTuL.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index-DglNlTuL.js","sources":["../../src/office/Renderer.ts","../../src/office/SpriteManager.ts","../../src/office/ApiClient.ts","../../src/office/OfficeApp.ts","../../src/main.ts"],"sourcesContent":["/**\n * Canvas Renderer\n * Handles all drawing operations for the office visualization\n */\n\nimport { Character, OfficeLayout, AgentStatus } from './types'\nimport { SpriteManager } from './SpriteManager'\n\nexport class Renderer {\n private ctx: CanvasRenderingContext2D\n private sprites: SpriteManager\n private tileSize: number\n\n // Colors\n private floorColor = '#2a2a3e'\n private floorAccent = '#252538'\n private wallColor = '#3a3a5a'\n private wallTop = '#4a4a6a'\n\n constructor(ctx: CanvasRenderingContext2D, sprites: SpriteManager, tileSize: number) {\n this.ctx = ctx\n this.sprites = sprites\n this.tileSize = tileSize\n }\n\n renderFloor(layout: OfficeLayout): void {\n const ctx = this.ctx\n const ts = this.tileSize\n\n // Draw checkered floor\n for (let y = 0; y < layout.height; y++) {\n for (let x = 0; x < layout.width; x++) {\n const isLight = (x + y) % 2 === 0\n ctx.fillStyle = isLight ? this.floorColor : this.floorAccent\n ctx.fillRect(x * ts, y * ts, ts, ts)\n }\n }\n\n // Add subtle grid lines\n ctx.strokeStyle = 'rgba(255, 255, 255, 0.03)'\n ctx.lineWidth = 1\n for (let y = 0; y <= layout.height; y++) {\n ctx.beginPath()\n ctx.moveTo(0, y * ts)\n ctx.lineTo(layout.width * ts, y * ts)\n ctx.stroke()\n }\n for (let x = 0; x <= layout.width; x++) {\n ctx.beginPath()\n ctx.moveTo(x * ts, 0)\n ctx.lineTo(x * ts, layout.height * ts)\n ctx.stroke()\n }\n }\n\n renderWalls(layout: OfficeLayout): void {\n const ctx = this.ctx\n const ts = this.tileSize\n const wallHeight = ts * 0.6\n\n // Top wall\n ctx.fillStyle = this.wallColor\n ctx.fillRect(0, 0, layout.width * ts, wallHeight)\n \n // Wall top edge (lighter)\n ctx.fillStyle = this.wallTop\n ctx.fillRect(0, 0, layout.width * ts, 4)\n\n // Wall shadow\n const gradient = ctx.createLinearGradient(0, wallHeight, 0, wallHeight + 20)\n gradient.addColorStop(0, 'rgba(0, 0, 0, 0.3)')\n gradient.addColorStop(1, 'rgba(0, 0, 0, 0)')\n ctx.fillStyle = gradient\n ctx.fillRect(0, wallHeight, layout.width * ts, 20)\n }\n\n renderFurniture(layout: OfficeLayout): void {\n for (const item of layout.furniture) {\n const x = item.x * this.tileSize\n const y = item.y * this.tileSize\n\n switch (item.type) {\n case 'plant':\n this.renderPlant(x, y)\n break\n case 'coffee-machine':\n this.renderCoffeeMachine(x, y)\n break\n case 'water-cooler':\n this.renderWaterCooler(x, y)\n break\n case 'whiteboard':\n this.renderWhiteboard(x, y)\n break\n case 'server-rack':\n this.renderServerRack(x, y)\n break\n case 'couch':\n this.renderCouch(x, y)\n break\n }\n }\n }\n\n private renderPlant(x: number, y: number): void {\n const ctx = this.ctx\n const ts = this.tileSize\n\n // Pot\n ctx.fillStyle = '#8b4513'\n ctx.fillRect(x + ts * 0.25, y + ts * 0.6, ts * 0.5, ts * 0.35)\n \n // Pot rim\n ctx.fillStyle = '#a0522d'\n ctx.fillRect(x + ts * 0.2, y + ts * 0.55, ts * 0.6, ts * 0.1)\n\n // Leaves\n ctx.fillStyle = '#228b22'\n ctx.beginPath()\n ctx.arc(x + ts * 0.5, y + ts * 0.35, ts * 0.25, 0, Math.PI * 2)\n ctx.fill()\n \n ctx.fillStyle = '#32cd32'\n ctx.beginPath()\n ctx.arc(x + ts * 0.35, y + ts * 0.4, ts * 0.15, 0, Math.PI * 2)\n ctx.fill()\n ctx.beginPath()\n ctx.arc(x + ts * 0.65, y + ts * 0.4, ts * 0.15, 0, Math.PI * 2)\n ctx.fill()\n }\n\n private renderCoffeeMachine(x: number, y: number): void {\n const ctx = this.ctx\n const ts = this.tileSize\n\n // Machine body\n ctx.fillStyle = '#333'\n ctx.fillRect(x + ts * 0.15, y + ts * 0.2, ts * 0.7, ts * 0.7)\n \n // Front panel\n ctx.fillStyle = '#444'\n ctx.fillRect(x + ts * 0.2, y + ts * 0.3, ts * 0.6, ts * 0.4)\n \n // Coffee indicator light\n ctx.fillStyle = '#ff6600'\n ctx.beginPath()\n ctx.arc(x + ts * 0.5, y + ts * 0.35, 4, 0, Math.PI * 2)\n ctx.fill()\n \n // Cup area\n ctx.fillStyle = '#222'\n ctx.fillRect(x + ts * 0.3, y + ts * 0.55, ts * 0.4, ts * 0.2)\n }\n\n private renderWaterCooler(x: number, y: number): void {\n const ctx = this.ctx\n const ts = this.tileSize\n\n // Base\n ctx.fillStyle = '#ddd'\n ctx.fillRect(x + ts * 0.25, y + ts * 0.5, ts * 0.5, ts * 0.4)\n \n // Water bottle\n ctx.fillStyle = '#87ceeb'\n ctx.globalAlpha = 0.7\n ctx.fillRect(x + ts * 0.3, y + ts * 0.1, ts * 0.4, ts * 0.45)\n ctx.globalAlpha = 1\n \n // Bottle cap\n ctx.fillStyle = '#4169e1'\n ctx.fillRect(x + ts * 0.35, y + ts * 0.05, ts * 0.3, ts * 0.08)\n }\n\n private renderWhiteboard(x: number, y: number): void {\n const ctx = this.ctx\n const ts = this.tileSize\n\n // Board frame\n ctx.fillStyle = '#555'\n ctx.fillRect(x, y, ts * 0.15, ts * 1.8)\n \n // Board surface\n ctx.fillStyle = '#f5f5f5'\n ctx.fillRect(x + ts * 0.02, y + ts * 0.1, ts * 0.11, ts * 1.6)\n \n // Some \"writing\"\n ctx.fillStyle = '#333'\n ctx.fillRect(x + ts * 0.04, y + ts * 0.3, ts * 0.07, 2)\n ctx.fillRect(x + ts * 0.04, y + ts * 0.5, ts * 0.05, 2)\n ctx.fillRect(x + ts * 0.04, y + ts * 0.7, ts * 0.07, 2)\n }\n\n private renderServerRack(x: number, y: number): void {\n const ctx = this.ctx\n const ts = this.tileSize\n\n // Rack frame\n ctx.fillStyle = '#222'\n ctx.fillRect(x, y, ts * 0.2, ts * 1.8)\n\n // Server units\n const time = Date.now() / 1000\n for (let i = 0; i < 5; i++) {\n ctx.fillStyle = '#333'\n ctx.fillRect(x + ts * 0.02, y + ts * 0.2 + i * ts * 0.3, ts * 0.16, ts * 0.25)\n\n // LED lights - use time-based blinking at different rates per unit\n const blinkRate = 0.5 + i * 0.3\n const isGreen = Math.sin(time * blinkRate + i * 2.1) > -0.4\n ctx.fillStyle = isGreen ? '#00ff00' : '#ff0000'\n ctx.fillRect(x + ts * 0.14, y + ts * 0.25 + i * ts * 0.3, 3, 3)\n }\n }\n\n private renderCouch(x: number, y: number): void {\n const ctx = this.ctx\n const ts = this.tileSize\n\n // Couch base\n ctx.fillStyle = '#4a4a6a'\n ctx.fillRect(x, y + ts * 0.3, ts * 3, ts * 0.5)\n \n // Cushions\n ctx.fillStyle = '#5a5a7a'\n ctx.fillRect(x + ts * 0.1, y + ts * 0.15, ts * 0.8, ts * 0.4)\n ctx.fillRect(x + ts * 1.1, y + ts * 0.15, ts * 0.8, ts * 0.4)\n ctx.fillRect(x + ts * 2.1, y + ts * 0.15, ts * 0.8, ts * 0.4)\n \n // Back rest\n ctx.fillStyle = '#3a3a5a'\n ctx.fillRect(x, y, ts * 3, ts * 0.2)\n }\n\n renderDesks(layout: OfficeLayout): void {\n for (const desk of layout.desks) {\n this.renderDesk(desk.x * this.tileSize, desk.y * this.tileSize, desk.occupied)\n }\n }\n\n private renderDesk(x: number, y: number, occupied: boolean): void {\n const ctx = this.ctx\n const ts = this.tileSize\n\n // Desk shadow\n ctx.fillStyle = 'rgba(0, 0, 0, 0.2)'\n ctx.fillRect(x + 4, y + ts * 0.3 + 4, ts * 1.2, ts * 0.6)\n\n // Desk surface\n ctx.fillStyle = '#8b7355'\n ctx.fillRect(x, y + ts * 0.3, ts * 1.2, ts * 0.6)\n \n // Desk edge (lighter)\n ctx.fillStyle = '#a08060'\n ctx.fillRect(x, y + ts * 0.3, ts * 1.2, 4)\n\n // Monitor\n ctx.fillStyle = '#222'\n ctx.fillRect(x + ts * 0.3, y + ts * 0.1, ts * 0.6, ts * 0.35)\n \n // Screen\n const screenColor = occupied ? '#1a1a2e' : '#111'\n ctx.fillStyle = screenColor\n ctx.fillRect(x + ts * 0.33, y + ts * 0.13, ts * 0.54, ts * 0.28)\n \n // Screen glow when occupied\n if (occupied) {\n ctx.fillStyle = 'rgba(0, 212, 255, 0.1)'\n ctx.fillRect(x + ts * 0.33, y + ts * 0.13, ts * 0.54, ts * 0.28)\n }\n \n // Monitor stand\n ctx.fillStyle = '#333'\n ctx.fillRect(x + ts * 0.5, y + ts * 0.45, ts * 0.2, ts * 0.08)\n\n // Chair\n this.renderChair(x + ts * 0.35, y + ts * 1.0)\n }\n\n private renderChair(x: number, y: number): void {\n const ctx = this.ctx\n const ts = this.tileSize\n\n // Chair back\n ctx.fillStyle = '#333'\n ctx.fillRect(x, y - ts * 0.3, ts * 0.5, ts * 0.4)\n \n // Chair seat\n ctx.fillStyle = '#444'\n ctx.fillRect(x - ts * 0.05, y + ts * 0.05, ts * 0.6, ts * 0.25)\n \n // Chair base\n ctx.fillStyle = '#222'\n ctx.fillRect(x + ts * 0.15, y + ts * 0.3, ts * 0.2, ts * 0.15)\n }\n\n renderCharacter(char: Character, isHovered: boolean): void {\n const ctx = this.ctx\n const ts = this.tileSize\n const x = char.x * ts\n const y = char.y * ts\n\n // Get sprite or render procedurally\n const sprite = this.sprites.getCharacterSprite(char.agent.status, char.direction, char.frame)\n \n if (sprite) {\n // Draw sprite\n const spriteWidth = sprite.width\n const spriteHeight = sprite.height\n ctx.drawImage(\n sprite,\n x - spriteWidth / 2,\n y - spriteHeight + ts * 0.3,\n spriteWidth,\n spriteHeight\n )\n } else {\n // Fallback procedural rendering\n this.renderCharacterProcedural(char, x, y)\n }\n\n // Hover highlight\n if (isHovered) {\n ctx.strokeStyle = '#00d4ff'\n ctx.lineWidth = 2\n ctx.beginPath()\n ctx.ellipse(x, y + ts * 0.1, ts * 0.35, ts * 0.15, 0, 0, Math.PI * 2)\n ctx.stroke()\n }\n\n // Status indicator\n this.renderStatusIndicator(char.agent.status, x, y - ts * 0.6)\n\n // Name tag\n this.renderNameTag(char.agent.name, x, y + ts * 0.35)\n }\n\n private renderCharacterProcedural(char: Character, x: number, y: number): void {\n const ctx = this.ctx\n const ts = this.tileSize\n const status = char.agent.status\n\n // Colors based on status\n const bodyColors: Record<AgentStatus, string> = {\n active: '#4a90d9',\n idle: '#d9a84a',\n waiting: '#4ad9d9',\n offline: '#666666'\n }\n\n const skinColor = '#ffdbac'\n const bodyColor = bodyColors[status]\n const hairColor = '#3a2a1a'\n\n // Animation offset\n const walkOffset = char.isMoving ? Math.sin(char.animationTime * 8) * 2 : 0\n const armSwing = char.isMoving ? Math.sin(char.animationTime * 8) * 3 : 0\n\n ctx.save()\n\n // Shadow\n ctx.fillStyle = 'rgba(0, 0, 0, 0.3)'\n ctx.beginPath()\n ctx.ellipse(x, y + ts * 0.15, ts * 0.25, ts * 0.08, 0, 0, Math.PI * 2)\n ctx.fill()\n\n // Body\n const bodyY = y - ts * 0.3 + walkOffset * 0.5\n ctx.fillStyle = bodyColor\n \n // Torso\n ctx.fillRect(x - ts * 0.15, bodyY, ts * 0.3, ts * 0.35)\n\n // Arms\n ctx.fillRect(x - ts * 0.25, bodyY + 2 + armSwing, ts * 0.08, ts * 0.25)\n ctx.fillRect(x + ts * 0.17, bodyY + 2 - armSwing, ts * 0.08, ts * 0.25)\n\n // Legs\n ctx.fillStyle = '#2a2a4a'\n const legOffset = char.isMoving ? walkOffset : 0\n ctx.fillRect(x - ts * 0.12, bodyY + ts * 0.3, ts * 0.1, ts * 0.25 + legOffset)\n ctx.fillRect(x + ts * 0.02, bodyY + ts * 0.3, ts * 0.1, ts * 0.25 - legOffset)\n\n // Head\n const headY = y - ts * 0.55\n ctx.fillStyle = skinColor\n ctx.beginPath()\n ctx.arc(x, headY, ts * 0.15, 0, Math.PI * 2)\n ctx.fill()\n\n // Hair\n ctx.fillStyle = hairColor\n ctx.beginPath()\n ctx.arc(x, headY - ts * 0.05, ts * 0.13, Math.PI, Math.PI * 2)\n ctx.fill()\n\n // Eyes\n ctx.fillStyle = '#333'\n if (status === 'offline') {\n // Closed eyes\n ctx.fillRect(x - ts * 0.08, headY, 4, 1)\n ctx.fillRect(x + ts * 0.04, headY, 4, 1)\n } else if (char.direction !== 'up') {\n // Open eyes\n ctx.fillRect(x - ts * 0.07, headY - 1, 3, 4)\n ctx.fillRect(x + ts * 0.04, headY - 1, 3, 4)\n }\n\n ctx.restore()\n }\n\n private renderStatusIndicator(status: AgentStatus, x: number, y: number): void {\n const ctx = this.ctx\n\n const colors: Record<AgentStatus, string> = {\n active: '#00ff88',\n idle: '#ffaa00',\n waiting: '#00d4ff',\n offline: '#666666'\n }\n\n // Glow\n if (status === 'active') {\n ctx.fillStyle = 'rgba(0, 255, 136, 0.3)'\n ctx.beginPath()\n ctx.arc(x, y, 8, 0, Math.PI * 2)\n ctx.fill()\n }\n\n // Indicator dot\n ctx.fillStyle = colors[status]\n ctx.beginPath()\n ctx.arc(x, y, 4, 0, Math.PI * 2)\n ctx.fill()\n\n // Pulse animation for active\n if (status === 'active') {\n const pulse = (Math.sin(Date.now() / 200) + 1) / 2\n ctx.strokeStyle = colors[status]\n ctx.globalAlpha = 1 - pulse\n ctx.lineWidth = 2\n ctx.beginPath()\n ctx.arc(x, y, 4 + pulse * 6, 0, Math.PI * 2)\n ctx.stroke()\n ctx.globalAlpha = 1\n }\n }\n\n private renderNameTag(name: string, x: number, y: number): void {\n const ctx = this.ctx\n\n ctx.font = '10px -apple-system, BlinkMacSystemFont, sans-serif'\n ctx.textAlign = 'center'\n ctx.textBaseline = 'top'\n\n // Truncate name if too long\n const displayName = name.length > 12 ? name.slice(0, 10) + '...' : name\n\n // Background\n const metrics = ctx.measureText(displayName)\n const padding = 4\n const bgWidth = metrics.width + padding * 2\n const bgHeight = 14\n\n ctx.fillStyle = 'rgba(0, 0, 0, 0.6)'\n ctx.fillRect(x - bgWidth / 2, y, bgWidth, bgHeight)\n\n // Text\n ctx.fillStyle = '#fff'\n ctx.fillText(displayName, x, y + 2)\n }\n\n renderBubble(char: Character, text: string): void {\n const ctx = this.ctx\n const ts = this.tileSize\n const x = char.x * ts\n const y = char.y * ts - ts * 0.8\n\n ctx.font = '11px -apple-system, BlinkMacSystemFont, sans-serif'\n const metrics = ctx.measureText(text)\n const padding = 8\n const bubbleWidth = Math.min(metrics.width + padding * 2, ts * 2.5)\n const bubbleHeight = 24\n\n // Bubble background\n ctx.fillStyle = '#fff'\n ctx.beginPath()\n ctx.roundRect(x - bubbleWidth / 2, y - bubbleHeight, bubbleWidth, bubbleHeight, 6)\n ctx.fill()\n\n // Bubble tail\n ctx.beginPath()\n ctx.moveTo(x - 5, y)\n ctx.lineTo(x + 5, y)\n ctx.lineTo(x, y + 8)\n ctx.closePath()\n ctx.fill()\n\n // Text\n ctx.fillStyle = '#333'\n ctx.textAlign = 'center'\n ctx.textBaseline = 'middle'\n \n // Truncate if needed\n const displayText = text.length > 20 ? text.slice(0, 18) + '...' : text\n ctx.fillText(displayText, x, y - bubbleHeight / 2)\n }\n}\n","/**\n * Sprite Manager\n * Handles loading and caching of character sprites\n * Uses procedural generation for pixel art characters\n */\n\nimport { AgentStatus, Direction } from './types'\n\nexport class SpriteManager {\n private sprites: Map<string, HTMLCanvasElement> = new Map()\n private spriteSize = 32\n\n async loadAll(): Promise<void> {\n // Generate procedural sprites for all combinations\n const statuses: AgentStatus[] = ['active', 'idle', 'waiting', 'offline']\n const directions: Direction[] = ['up', 'down', 'left', 'right']\n const frames = [0, 1, 2, 3]\n\n for (const status of statuses) {\n for (const direction of directions) {\n for (const frame of frames) {\n const key = this.getSpriteKey(status, direction, frame)\n const sprite = this.generateCharacterSprite(status, direction, frame)\n this.sprites.set(key, sprite)\n }\n }\n }\n\n console.log(`Generated ${this.sprites.size} character sprites`)\n }\n\n private getSpriteKey(status: AgentStatus, direction: Direction, frame: number): string {\n return `char_${status}_${direction}_${frame}`\n }\n\n getCharacterSprite(status: AgentStatus, direction: Direction, frame: number): HTMLCanvasElement | null {\n const key = this.getSpriteKey(status, direction, frame)\n return this.sprites.get(key) || null\n }\n\n private generateCharacterSprite(status: AgentStatus, direction: Direction, frame: number): HTMLCanvasElement {\n const canvas = document.createElement('canvas')\n const size = this.spriteSize\n canvas.width = size\n canvas.height = size * 1.5\n const ctx = canvas.getContext('2d')!\n\n // Enable pixel-perfect rendering\n ctx.imageSmoothingEnabled = false\n\n // Colors based on status\n const bodyColors: Record<AgentStatus, string> = {\n active: '#4a90d9',\n idle: '#d9a84a',\n waiting: '#4ad9d9',\n offline: '#666666'\n }\n\n const skinColor = '#ffdbac'\n const bodyColor = bodyColors[status]\n const hairColor = '#3a2a1a'\n const pantsColor = '#2a2a4a'\n const shoeColor = '#1a1a2a'\n\n // Animation offsets based on frame\n const walkCycle = [0, 1, 0, -1]\n const legCycle = [0, 2, 0, -2]\n const armCycle = [0, 1, 0, -1]\n \n const bounce = walkCycle[frame]\n const legOffset = legCycle[frame]\n const armOffset = armCycle[frame]\n\n const cx = size / 2\n const baseY = size * 1.2\n\n // Shadow\n ctx.fillStyle = 'rgba(0, 0, 0, 0.3)'\n this.drawEllipse(ctx, cx, baseY + 4, 10, 4)\n\n // Determine if walking\n const isWalking = frame !== 0\n\n // Draw based on direction\n if (direction === 'down' || direction === 'up') {\n // Front/back view\n \n // Legs\n ctx.fillStyle = pantsColor\n const leftLegY = baseY - 8 + (isWalking ? legOffset : 0)\n const rightLegY = baseY - 8 + (isWalking ? -legOffset : 0)\n this.drawRect(ctx, cx - 5, leftLegY, 4, 10)\n this.drawRect(ctx, cx + 1, rightLegY, 4, 10)\n \n // Shoes\n ctx.fillStyle = shoeColor\n this.drawRect(ctx, cx - 6, leftLegY + 8, 5, 3)\n this.drawRect(ctx, cx + 1, rightLegY + 8, 5, 3)\n\n // Body\n ctx.fillStyle = bodyColor\n this.drawRect(ctx, cx - 7, baseY - 18 + bounce, 14, 12)\n\n // Arms\n const leftArmY = baseY - 16 + (isWalking ? armOffset : 0)\n const rightArmY = baseY - 16 + (isWalking ? -armOffset : 0)\n this.drawRect(ctx, cx - 10, leftArmY + bounce, 4, 10)\n this.drawRect(ctx, cx + 6, rightArmY + bounce, 4, 10)\n \n // Hands\n ctx.fillStyle = skinColor\n this.drawRect(ctx, cx - 10, leftArmY + 8 + bounce, 4, 3)\n this.drawRect(ctx, cx + 6, rightArmY + 8 + bounce, 4, 3)\n\n // Head\n ctx.fillStyle = skinColor\n this.drawCircle(ctx, cx, baseY - 28 + bounce, 8)\n\n // Hair\n ctx.fillStyle = hairColor\n if (direction === 'down') {\n // Front view - show bangs\n this.drawArc(ctx, cx, baseY - 30 + bounce, 7, Math.PI, Math.PI * 2)\n this.drawRect(ctx, cx - 7, baseY - 34 + bounce, 14, 4)\n } else {\n // Back view - full hair\n this.drawCircle(ctx, cx, baseY - 30 + bounce, 7)\n }\n\n // Face (only for front view)\n if (direction === 'down') {\n ctx.fillStyle = '#333'\n if (status === 'offline') {\n // Closed eyes (lines)\n this.drawRect(ctx, cx - 5, baseY - 28 + bounce, 3, 1)\n this.drawRect(ctx, cx + 2, baseY - 28 + bounce, 3, 1)\n } else {\n // Open eyes\n this.drawRect(ctx, cx - 5, baseY - 29 + bounce, 2, 3)\n this.drawRect(ctx, cx + 3, baseY - 29 + bounce, 2, 3)\n }\n \n // Mouth\n if (status === 'active') {\n // Smile\n ctx.fillStyle = '#333'\n this.drawRect(ctx, cx - 2, baseY - 24 + bounce, 4, 1)\n }\n }\n } else {\n // Side view (left/right)\n ctx.save()\n if (direction === 'left') {\n ctx.scale(-1, 1)\n ctx.translate(-size, 0)\n }\n\n // Back leg\n ctx.fillStyle = pantsColor\n const backLegY = baseY - 8 + (isWalking ? -legOffset : 0)\n this.drawRect(ctx, cx - 2, backLegY, 4, 10)\n ctx.fillStyle = shoeColor\n this.drawRect(ctx, cx - 3, backLegY + 8, 5, 3)\n\n // Body\n ctx.fillStyle = bodyColor\n this.drawRect(ctx, cx - 5, baseY - 18 + bounce, 10, 12)\n\n // Back arm\n const backArmY = baseY - 16 + (isWalking ? -armOffset : 0)\n this.drawRect(ctx, cx - 6, backArmY + bounce, 4, 10)\n ctx.fillStyle = skinColor\n this.drawRect(ctx, cx - 6, backArmY + 8 + bounce, 4, 3)\n\n // Front leg\n ctx.fillStyle = pantsColor\n const frontLegY = baseY - 8 + (isWalking ? legOffset : 0)\n this.drawRect(ctx, cx + 0, frontLegY, 4, 10)\n ctx.fillStyle = shoeColor\n this.drawRect(ctx, cx - 1, frontLegY + 8, 5, 3)\n\n // Front arm\n ctx.fillStyle = bodyColor\n const frontArmY = baseY - 16 + (isWalking ? armOffset : 0)\n this.drawRect(ctx, cx + 2, frontArmY + bounce, 4, 10)\n ctx.fillStyle = skinColor\n this.drawRect(ctx, cx + 2, frontArmY + 8 + bounce, 4, 3)\n\n // Head\n ctx.fillStyle = skinColor\n this.drawCircle(ctx, cx, baseY - 28 + bounce, 8)\n\n // Hair (side view)\n ctx.fillStyle = hairColor\n this.drawArc(ctx, cx - 1, baseY - 30 + bounce, 7, Math.PI * 0.7, Math.PI * 1.8)\n this.drawRect(ctx, cx - 7, baseY - 34 + bounce, 10, 5)\n\n // Eye (side view)\n ctx.fillStyle = '#333'\n if (status === 'offline') {\n this.drawRect(ctx, cx + 2, baseY - 28 + bounce, 3, 1)\n } else {\n this.drawRect(ctx, cx + 2, baseY - 29 + bounce, 2, 3)\n }\n\n ctx.restore()\n }\n\n return canvas\n }\n\n // Helper drawing methods for pixel art\n private drawRect(ctx: CanvasRenderingContext2D, x: number, y: number, w: number, h: number): void {\n ctx.fillRect(Math.floor(x), Math.floor(y), w, h)\n }\n\n private drawCircle(ctx: CanvasRenderingContext2D, x: number, y: number, r: number): void {\n ctx.beginPath()\n ctx.arc(Math.floor(x), Math.floor(y), r, 0, Math.PI * 2)\n ctx.fill()\n }\n\n private drawEllipse(ctx: CanvasRenderingContext2D, x: number, y: number, rx: number, ry: number): void {\n ctx.beginPath()\n ctx.ellipse(Math.floor(x), Math.floor(y), rx, ry, 0, 0, Math.PI * 2)\n ctx.fill()\n }\n\n private drawArc(ctx: CanvasRenderingContext2D, x: number, y: number, r: number, start: number, end: number): void {\n ctx.beginPath()\n ctx.arc(Math.floor(x), Math.floor(y), r, start, end)\n ctx.fill()\n }\n}\n","/**\n * API Client\n * Handles communication with the OpenClaw Gateway\n */\n\nimport { AgentsResponse, PresenceResponse, OfficeConfig, Agent } from './types'\n\nexport class ApiClient {\n private baseUrl: string\n\n constructor(baseUrl: string = '/office/api') {\n this.baseUrl = baseUrl\n }\n\n async getAgents(): Promise<AgentsResponse | null> {\n try {\n const response = await fetch(`${this.baseUrl}/agents`)\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}`)\n }\n return await response.json()\n } catch (error) {\n console.warn('Failed to fetch agents, using mock data:', error)\n return this.getMockAgents()\n }\n }\n\n async getPresence(): Promise<PresenceResponse | null> {\n try {\n const response = await fetch(`${this.baseUrl}/presence`)\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}`)\n }\n return await response.json()\n } catch (error) {\n console.warn('Failed to fetch presence:', error)\n return null\n }\n }\n\n async getConfig(): Promise<Partial<OfficeConfig> | null> {\n try {\n const response = await fetch(`${this.baseUrl}/config`)\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}`)\n }\n return await response.json()\n } catch (error) {\n console.warn('Failed to fetch config:', error)\n return null\n }\n }\n\n /**\n * Mock data for development/demo purposes\n */\n private getMockAgents(): AgentsResponse {\n const mockAgents: Agent[] = [\n {\n id: 'agent-001',\n name: 'CLI abc123',\n status: 'active',\n sessionId: 'session-001',\n lastActivity: Date.now(),\n currentTool: 'developer__shell',\n model: 'claude-sonnet-4-20250514',\n channel: 'cli'\n },\n {\n id: 'agent-002',\n name: 'Slack def456',\n status: 'idle',\n sessionId: 'session-002',\n lastActivity: Date.now() - 60000,\n model: 'claude-sonnet-4-20250514',\n channel: 'slack'\n },\n {\n id: 'agent-003',\n name: 'API ghi789',\n status: 'active',\n sessionId: 'session-003',\n lastActivity: Date.now(),\n currentTool: 'googledrive__read',\n model: 'gpt-4o',\n channel: 'api'\n },\n {\n id: 'agent-004',\n name: 'Discord jkl012',\n status: 'waiting',\n sessionId: 'session-004',\n lastActivity: Date.now() - 30000,\n model: 'claude-sonnet-4-20250514',\n channel: 'discord'\n },\n {\n id: 'agent-005',\n name: 'Web mno345',\n status: 'offline',\n sessionId: 'session-005',\n lastActivity: Date.now() - 3600000,\n model: 'claude-sonnet-4-20250514',\n channel: 'web'\n }\n ]\n\n return {\n agents: mockAgents,\n timestamp: Date.now()\n }\n }\n}\n\n/**\n * Realtime Client for WebSocket updates\n * Connects to OpenClaw Gateway WebSocket for live updates\n */\nexport class RealtimeClient {\n private ws: WebSocket | null = null\n private url: string\n private reconnectAttempts = 0\n private maxReconnectAttempts = 5\n private reconnectDelay = 1000\n private listeners: Map<string, Set<(data: unknown) => void>> = new Map()\n\n constructor(url?: string) {\n // Default to Gateway WebSocket URL\n const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'\n this.url = url || `${protocol}//${window.location.host}/ws`\n }\n\n connect(): void {\n if (this.ws?.readyState === WebSocket.OPEN) {\n return\n }\n\n try {\n this.ws = new WebSocket(this.url)\n\n this.ws.onopen = () => {\n console.log('WebSocket connected')\n this.reconnectAttempts = 0\n this.emit('connected', null)\n }\n\n this.ws.onmessage = (event) => {\n try {\n const message = JSON.parse(event.data)\n this.handleMessage(message)\n } catch (error) {\n console.warn('Failed to parse WebSocket message:', error)\n }\n }\n\n this.ws.onclose = () => {\n console.log('WebSocket disconnected')\n this.emit('disconnected', null)\n this.scheduleReconnect()\n }\n\n this.ws.onerror = (error) => {\n console.error('WebSocket error:', error)\n this.emit('error', error)\n }\n } catch (error) {\n console.error('Failed to create WebSocket:', error)\n this.scheduleReconnect()\n }\n }\n\n disconnect(): void {\n if (this.ws) {\n this.ws.close()\n this.ws = null\n }\n }\n\n private scheduleReconnect(): void {\n if (this.reconnectAttempts >= this.maxReconnectAttempts) {\n console.warn('Max reconnect attempts reached')\n return\n }\n\n this.reconnectAttempts++\n const delay = this.reconnectDelay * Math.pow(2, this.reconnectAttempts - 1)\n \n console.log(`Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts})`)\n setTimeout(() => this.connect(), delay)\n }\n\n private handleMessage(message: { type: string; data?: unknown }): void {\n const { type, data } = message\n\n switch (type) {\n case 'session.start':\n this.emit('session.start', data)\n break\n case 'session.end':\n this.emit('session.end', data)\n break\n case 'tool.start':\n this.emit('tool.start', data)\n break\n case 'tool.end':\n this.emit('tool.end', data)\n break\n case 'presence.update':\n this.emit('presence.update', data)\n break\n default:\n // Unknown message type\n break\n }\n }\n\n on(event: string, callback: (data: unknown) => void): void {\n if (!this.listeners.has(event)) {\n this.listeners.set(event, new Set())\n }\n this.listeners.get(event)!.add(callback)\n }\n\n off(event: string, callback: (data: unknown) => void): void {\n this.listeners.get(event)?.delete(callback)\n }\n\n private emit(event: string, data: unknown): void {\n this.listeners.get(event)?.forEach(callback => {\n try {\n callback(data)\n } catch (error) {\n console.error(`Error in event listener for ${event}:`, error)\n }\n })\n }\n}\n","/**\n * Main Office Application\n * Manages the canvas, game loop, and visualization state\n */\n\nimport { Agent, Character, Desk, OfficeLayout, OfficeConfig, AgentStatus } from './types'\nimport { Renderer } from './Renderer'\nimport { SpriteManager } from './SpriteManager'\nimport { ApiClient } from './ApiClient'\n\n// Default office layout\nconst DEFAULT_LAYOUT: OfficeLayout = {\n width: 12,\n height: 10,\n desks: [\n { id: 1, x: 2, y: 2, occupied: false },\n { id: 2, x: 5, y: 2, occupied: false },\n { id: 3, x: 8, y: 2, occupied: false },\n { id: 4, x: 2, y: 5, occupied: false },\n { id: 5, x: 5, y: 5, occupied: false },\n { id: 6, x: 8, y: 5, occupied: false },\n { id: 7, x: 2, y: 8, occupied: false },\n { id: 8, x: 5, y: 8, occupied: false },\n { id: 9, x: 8, y: 8, occupied: false }\n ],\n furniture: [\n { type: 'plant', x: 0.5, y: 1, width: 1, height: 1 },\n { type: 'plant', x: 10.5, y: 1, width: 1, height: 1 },\n { type: 'coffee-machine', x: 10.5, y: 4, width: 1, height: 1 },\n { type: 'water-cooler', x: 10.5, y: 6, width: 1, height: 1 },\n { type: 'whiteboard', x: 0.3, y: 3, width: 1, height: 2 },\n { type: 'server-rack', x: 0.3, y: 6, width: 1, height: 2 },\n { type: 'couch', x: 4, y: 9.2, width: 3, height: 1 }\n ]\n}\n\nexport class OfficeApp {\n private container: HTMLElement\n private canvas!: HTMLCanvasElement\n private ctx!: CanvasRenderingContext2D\n private renderer!: Renderer\n private sprites: SpriteManager\n private api: ApiClient\n\n private layout: OfficeLayout\n private config: OfficeConfig\n private characters: Map<string, Character> = new Map()\n private tileSize = 48\n private scale = 1\n\n private animationFrame: number | null = null\n private lastTime = 0\n private pollInterval: number | null = null\n\n private hoveredCharacter: Character | null = null\n private tooltip: HTMLElement | null = null\n private loadingOverlay: HTMLElement | null = null\n\n constructor(container: HTMLElement) {\n this.container = container\n this.layout = { ...DEFAULT_LAYOUT, desks: DEFAULT_LAYOUT.desks.map(d => ({ ...d })) }\n this.config = {\n theme: 'default',\n showInactiveAgents: true,\n animationSpeed: 1.0\n }\n this.sprites = new SpriteManager()\n this.api = new ApiClient()\n }\n\n async start(): Promise<void> {\n // Create DOM structure\n this.createDOM()\n \n // Load sprites\n await this.sprites.loadAll()\n \n // Load config from server\n await this.loadConfig()\n \n // Initial agent sync\n await this.syncAgents()\n \n // Start polling for updates\n this.startPolling()\n \n // Start render loop\n this.startRenderLoop()\n \n // Hide loading overlay\n this.hideLoading()\n }\n\n private createDOM(): void {\n this.container.innerHTML = `\n <div class=\"office-container\">\n <header class=\"office-header\">\n <div class=\"office-title\">\n <span class=\"office-title-icon\">🏢</span>\n <span>OpenClaw Office</span>\n </div>\n <div class=\"office-stats\">\n <div class=\"stat\">\n <span class=\"stat-dot active\"></span>\n <span class=\"stat-count\" id=\"active-count\">0</span>\n <span>active</span>\n </div>\n <div class=\"stat\">\n <span class=\"stat-dot idle\"></span>\n <span class=\"stat-count\" id=\"idle-count\">0</span>\n <span>idle</span>\n </div>\n <div class=\"stat\">\n <span class=\"stat-dot offline\"></span>\n <span class=\"stat-count\" id=\"offline-count\">0</span>\n <span>offline</span>\n </div>\n </div>\n </header>\n <div class=\"canvas-wrapper\">\n <canvas id=\"office-canvas\"></canvas>\n <div class=\"vignette\"></div>\n <div class=\"loading-overlay\">\n <div class=\"loading-spinner\"></div>\n <div class=\"loading-text\">Loading office...</div>\n </div>\n </div>\n </div>\n `\n\n this.canvas = document.getElementById('office-canvas') as HTMLCanvasElement\n this.ctx = this.canvas.getContext('2d')!\n this.loadingOverlay = this.container.querySelector('.loading-overlay')\n\n // Create tooltip element\n this.tooltip = document.createElement('div')\n this.tooltip.className = 'agent-tooltip'\n this.tooltip.style.display = 'none'\n document.body.appendChild(this.tooltip)\n\n // Setup canvas size\n this.resize()\n\n // Setup event listeners\n this.setupEventListeners()\n\n // Initialize renderer\n this.renderer = new Renderer(this.ctx, this.sprites, this.tileSize)\n }\n\n private setupEventListeners(): void {\n this.canvas.addEventListener('mousemove', this.handleMouseMove.bind(this))\n this.canvas.addEventListener('mouseleave', this.handleMouseLeave.bind(this))\n this.canvas.addEventListener('click', this.handleClick.bind(this))\n }\n\n private handleMouseMove(e: MouseEvent): void {\n const rect = this.canvas.getBoundingClientRect()\n const x = (e.clientX - rect.left) / this.scale / this.tileSize\n const y = (e.clientY - rect.top) / this.scale / this.tileSize\n\n // Find character under cursor\n let found: Character | null = null\n for (const char of this.characters.values()) {\n const dx = x - char.x\n const dy = y - char.y\n if (Math.abs(dx) < 0.5 && Math.abs(dy) < 0.8) {\n found = char\n break\n }\n }\n\n if (found !== this.hoveredCharacter) {\n this.hoveredCharacter = found\n if (found && this.tooltip) {\n this.showTooltip(found, e.clientX, e.clientY)\n } else {\n this.hideTooltip()\n }\n } else if (found && this.tooltip) {\n // Update tooltip position\n this.tooltip.style.left = `${e.clientX}px`\n this.tooltip.style.top = `${e.clientY}px`\n }\n\n this.canvas.style.cursor = found ? 'pointer' : 'default'\n }\n\n private handleMouseLeave(): void {\n this.hoveredCharacter = null\n this.hideTooltip()\n this.canvas.style.cursor = 'default'\n }\n\n private handleClick(_e: MouseEvent): void {\n if (this.hoveredCharacter) {\n // Show bubble with agent info\n this.hoveredCharacter.bubbleText = this.hoveredCharacter.agent.currentTool || 'Hello!'\n this.hoveredCharacter.bubbleTime = 3\n }\n }\n\n private showTooltip(char: Character, x: number, y: number): void {\n if (!this.tooltip) return\n\n const statusColors: Record<AgentStatus, string> = {\n active: '#00ff88',\n idle: '#ffaa00',\n waiting: '#00d4ff',\n offline: '#666666'\n }\n\n this.tooltip.innerHTML = `\n <div class=\"tooltip-name\">${char.agent.name}</div>\n <div class=\"tooltip-status\">\n <span class=\"tooltip-status-dot\" style=\"background: ${statusColors[char.agent.status]}\"></span>\n <span>${char.agent.status}</span>\n ${char.agent.channel ? `<span>• ${char.agent.channel}</span>` : ''}\n </div>\n ${char.agent.currentTool ? `<div class=\"tooltip-tool\">🔧 ${char.agent.currentTool}</div>` : ''}\n `\n\n this.tooltip.style.display = 'block'\n this.tooltip.style.left = `${x}px`\n this.tooltip.style.top = `${y}px`\n }\n\n private hideTooltip(): void {\n if (this.tooltip) {\n this.tooltip.style.display = 'none'\n }\n }\n\n resize(): void {\n const wrapper = this.container.querySelector('.canvas-wrapper') as HTMLElement\n if (!wrapper) return\n\n const wrapperWidth = wrapper.clientWidth\n const wrapperHeight = wrapper.clientHeight\n\n const officeWidth = this.layout.width * this.tileSize\n const officeHeight = this.layout.height * this.tileSize\n\n // Calculate scale to fit\n const scaleX = wrapperWidth / officeWidth\n const scaleY = wrapperHeight / officeHeight\n this.scale = Math.min(scaleX, scaleY, 2) // Cap at 2x\n\n // Set canvas size\n this.canvas.width = officeWidth\n this.canvas.height = officeHeight\n\n // Apply scale via CSS transform\n this.canvas.style.width = `${officeWidth * this.scale}px`\n this.canvas.style.height = `${officeHeight * this.scale}px`\n\n // Center the canvas\n const left = (wrapperWidth - officeWidth * this.scale) / 2\n const top = (wrapperHeight - officeHeight * this.scale) / 2\n this.canvas.style.left = `${left}px`\n this.canvas.style.top = `${top}px`\n }\n\n private async loadConfig(): Promise<void> {\n const config = await this.api.getConfig()\n if (config) {\n this.config = { ...this.config, ...config }\n }\n }\n\n private async syncAgents(): Promise<void> {\n const response = await this.api.getAgents()\n if (!response) return\n\n const currentIds = new Set<string>()\n\n for (const agent of response.agents) {\n currentIds.add(agent.id)\n\n // Skip offline agents if configured\n if (!this.config.showInactiveAgents && agent.status === 'offline') {\n continue\n }\n\n if (this.characters.has(agent.id)) {\n // Update existing character\n const char = this.characters.get(agent.id)!\n char.agent = agent\n } else {\n // Create new character\n const desk = this.assignDesk(agent.id)\n if (desk) {\n const char = this.createCharacter(agent, desk)\n this.characters.set(agent.id, char)\n }\n }\n }\n\n // Remove characters for agents that no longer exist\n for (const [id, char] of this.characters) {\n if (!currentIds.has(id)) {\n // Free up the desk\n const desk = this.layout.desks.find(d => d.id === char.deskId)\n if (desk) {\n desk.occupied = false\n desk.agentId = undefined\n }\n this.characters.delete(id)\n }\n }\n\n // Update stats display\n this.updateStats()\n }\n\n private assignDesk(agentId: string): Desk | null {\n // Check if agent already has a desk\n const existingDesk = this.layout.desks.find(d => d.agentId === agentId)\n if (existingDesk) return existingDesk\n\n // Find first available desk\n const availableDesk = this.layout.desks.find(d => !d.occupied)\n if (availableDesk) {\n availableDesk.occupied = true\n availableDesk.agentId = agentId\n return availableDesk\n }\n\n return null\n }\n\n private createCharacter(agent: Agent, desk: Desk): Character {\n return {\n agent,\n x: desk.x + 0.5,\n y: desk.y + 1.5, // Position at chair\n targetX: desk.x + 0.5,\n targetY: desk.y + 1.5,\n direction: 'up',\n frame: 0,\n animationTime: 0,\n deskId: desk.id,\n isMoving: false,\n isSitting: true\n }\n }\n\n private updateStats(): void {\n const counts = { active: 0, idle: 0, waiting: 0, offline: 0 }\n \n for (const char of this.characters.values()) {\n counts[char.agent.status]++\n }\n\n const activeEl = document.getElementById('active-count')\n const idleEl = document.getElementById('idle-count')\n const offlineEl = document.getElementById('offline-count')\n\n if (activeEl) activeEl.textContent = String(counts.active)\n if (idleEl) idleEl.textContent = String(counts.idle + counts.waiting)\n if (offlineEl) offlineEl.textContent = String(counts.offline)\n }\n\n private startPolling(): void {\n // Poll for agent updates every 5 seconds\n this.pollInterval = window.setInterval(() => {\n this.syncAgents()\n }, 5000)\n }\n\n private startRenderLoop(): void {\n const loop = (time: number) => {\n const deltaTime = (time - this.lastTime) / 1000\n this.lastTime = time\n\n this.update(deltaTime)\n this.render()\n\n this.animationFrame = requestAnimationFrame(loop)\n }\n\n this.animationFrame = requestAnimationFrame(loop)\n }\n\n private update(deltaTime: number): void {\n const speed = this.config.animationSpeed\n\n for (const char of this.characters.values()) {\n // Update animation time\n char.animationTime += deltaTime * speed\n\n // Update frame for walking animation\n if (char.isMoving) {\n char.frame = Math.floor(char.animationTime * 8) % 4\n }\n\n // Move towards target\n if (char.isMoving) {\n const dx = char.targetX - char.x\n const dy = char.targetY - char.y\n const dist = Math.sqrt(dx * dx + dy * dy)\n\n if (dist < 0.1) {\n char.x = char.targetX\n char.y = char.targetY\n char.isMoving = false\n char.frame = 0\n } else {\n const moveSpeed = 2 * speed\n char.x += (dx / dist) * moveSpeed * deltaTime\n char.y += (dy / dist) * moveSpeed * deltaTime\n\n // Update direction\n if (Math.abs(dx) > Math.abs(dy)) {\n char.direction = dx > 0 ? 'right' : 'left'\n } else {\n char.direction = dy > 0 ? 'down' : 'up'\n }\n }\n }\n\n // Update bubble timer\n if (char.bubbleTime !== undefined && char.bubbleTime > 0) {\n char.bubbleTime -= deltaTime\n if (char.bubbleTime <= 0) {\n char.bubbleText = undefined\n char.bubbleTime = undefined\n }\n }\n }\n }\n\n private render(): void {\n // Clear canvas\n this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height)\n\n // Render layers in order\n this.renderer.renderFloor(this.layout)\n this.renderer.renderWalls(this.layout)\n this.renderer.renderFurniture(this.layout)\n this.renderer.renderDesks(this.layout)\n\n // Sort characters by Y position for proper z-ordering\n const sortedChars = Array.from(this.characters.values())\n .sort((a, b) => a.y - b.y)\n\n // Render characters\n for (const char of sortedChars) {\n const isHovered = char === this.hoveredCharacter\n this.renderer.renderCharacter(char, isHovered)\n\n // Render speech bubble if active\n if (char.bubbleText) {\n this.renderer.renderBubble(char, char.bubbleText)\n }\n }\n }\n\n private hideLoading(): void {\n if (this.loadingOverlay) {\n this.loadingOverlay.classList.add('hidden')\n }\n }\n\n destroy(): void {\n if (this.animationFrame) {\n cancelAnimationFrame(this.animationFrame)\n }\n if (this.pollInterval) {\n clearInterval(this.pollInterval)\n }\n if (this.tooltip) {\n this.tooltip.remove()\n }\n }\n}\n","/**\n * OpenClaw Office Visualization - Main Entry Point\n */\n\nimport './styles.css'\nimport { OfficeApp } from './office/OfficeApp'\n\n// Initialize the application\nasync function main() {\n const container = document.getElementById('app')\n \n if (!container) {\n console.error('App container not found')\n return\n }\n\n const app = new OfficeApp(container)\n \n // Handle window resize\n window.addEventListener('resize', () => {\n app.resize()\n })\n\n // Start the application\n try {\n await app.start()\n console.log('OpenClaw Office Visualization started')\n } catch (error) {\n console.error('Failed to start Office Visualization:', error)\n }\n\n // Cleanup on page unload\n window.addEventListener('beforeunload', () => {\n app.destroy()\n })\n}\n\n// Run when DOM is ready\nif (document.readyState === 'loading') {\n document.addEventListener('DOMContentLoaded', main)\n} else {\n main()\n}\n"],"names":["Renderer","ctx","sprites","tileSize","__publicField","layout","ts","y","x","isLight","wallHeight","gradient","item","time","i","blinkRate","isGreen","desk","occupied","screenColor","char","isHovered","sprite","spriteWidth","spriteHeight","status","bodyColors","skinColor","bodyColor","hairColor","walkOffset","armSwing","bodyY","legOffset","headY","colors","pulse","name","displayName","bgWidth","bgHeight","text","metrics","bubbleWidth","bubbleHeight","displayText","SpriteManager","statuses","directions","frames","direction","frame","key","canvas","size","pantsColor","shoeColor","walkCycle","legCycle","armCycle","bounce","armOffset","cx","baseY","isWalking","leftLegY","rightLegY","leftArmY","rightArmY","backLegY","backArmY","frontLegY","frontArmY","w","h","r","rx","ry","start","end","ApiClient","baseUrl","response","error","DEFAULT_LAYOUT","OfficeApp","container","d","e","rect","found","dx","dy","_e","statusColors","wrapper","wrapperWidth","wrapperHeight","officeWidth","officeHeight","scaleX","scaleY","left","top","config","currentIds","agent","id","agentId","existingDesk","availableDesk","counts","activeEl","idleEl","offlineEl","loop","deltaTime","speed","dist","moveSpeed","sortedChars","a","b","main","app"],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
*{margin:0;padding:0;box-sizing:border-box}:root{--bg-primary: #0a0a14;--bg-secondary: #1a1a2e;--bg-tertiary: #2d2d44;--text-primary: #ffffff;--text-secondary: #888888;--text-muted: #666666;--accent-primary: #00d4ff;--accent-success: #00ff88;--accent-warning: #ffaa00;--accent-error: #ff4444;--border-color: #3a3a5a}html,body,#app{width:100%;height:100%;overflow:hidden;background:var(--bg-primary);color:var(--text-primary);font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,sans-serif}.office-container{display:flex;flex-direction:column;width:100%;height:100%}.office-header{display:flex;justify-content:space-between;align-items:center;padding:12px 20px;background:var(--bg-secondary);border-bottom:1px solid var(--border-color);z-index:10}.office-title{display:flex;align-items:center;gap:10px;font-size:18px;font-weight:600}.office-title-icon{font-size:24px}.office-stats{display:flex;gap:20px}.stat{display:flex;align-items:center;gap:6px;font-size:14px;color:var(--text-secondary)}.stat-dot{width:8px;height:8px;border-radius:50%}.stat-dot.active{background:var(--accent-success);box-shadow:0 0 8px var(--accent-success)}.stat-dot.idle{background:var(--accent-warning);box-shadow:0 0 8px var(--accent-warning)}.stat-dot.offline{background:var(--text-muted)}.stat-count{font-weight:600;color:var(--text-primary)}.canvas-wrapper{flex:1;position:relative;overflow:hidden;background:var(--bg-primary)}#office-canvas{position:absolute;image-rendering:pixelated;image-rendering:crisp-edges;cursor:default}.vignette{position:absolute;top:0;left:0;right:0;bottom:0;pointer-events:none;background:radial-gradient(ellipse at center,transparent 0%,transparent 60%,rgba(0,0,0,.4) 100%)}.agent-tooltip{position:fixed;z-index:1000;background:var(--bg-secondary);border:1px solid var(--border-color);border-radius:8px;padding:10px 14px;pointer-events:none;transform:translate(10px,-50%);box-shadow:0 4px 20px #00000080;min-width:150px}.tooltip-name{font-weight:600;font-size:14px;margin-bottom:4px}.tooltip-status{display:flex;align-items:center;gap:6px;font-size:12px;color:var(--text-secondary)}.tooltip-status-dot{width:6px;height:6px;border-radius:50%}.tooltip-tool{margin-top:6px;font-size:11px;color:var(--accent-primary);font-family:Monaco,Menlo,monospace}.loading-overlay{position:absolute;top:0;left:0;right:0;bottom:0;display:flex;flex-direction:column;align-items:center;justify-content:center;background:var(--bg-primary);z-index:100;transition:opacity .3s ease}.loading-overlay.hidden{opacity:0;pointer-events:none}.loading-spinner{width:40px;height:40px;border:3px solid var(--bg-tertiary);border-top-color:var(--accent-primary);border-radius:50%;animation:spin 1s linear infinite}@keyframes spin{to{transform:rotate(360deg)}}.loading-text{margin-top:16px;color:var(--text-secondary);font-size:14px}::-webkit-scrollbar{width:8px;height:8px}::-webkit-scrollbar-track{background:var(--bg-secondary)}::-webkit-scrollbar-thumb{background:var(--bg-tertiary);border-radius:4px}::-webkit-scrollbar-thumb:hover{background:var(--border-color)}@media (max-width: 600px){.office-header{flex-direction:column;gap:10px;padding:10px}.office-stats{gap:12px}.stat{font-size:12px}}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>OpenClaw Office</title>
|
|
7
|
+
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>🏢</text></svg>">
|
|
8
|
+
<style>
|
|
9
|
+
html, body {
|
|
10
|
+
margin: 0;
|
|
11
|
+
padding: 0;
|
|
12
|
+
width: 100%;
|
|
13
|
+
height: 100%;
|
|
14
|
+
overflow: hidden;
|
|
15
|
+
background: #0a0a14;
|
|
16
|
+
}
|
|
17
|
+
</style>
|
|
18
|
+
<script type="module" crossorigin src="/office/assets/index-DglNlTuL.js"></script>
|
|
19
|
+
<link rel="stylesheet" crossorigin href="/office/assets/index-Dm9OFqqZ.css">
|
|
20
|
+
</head>
|
|
21
|
+
<body>
|
|
22
|
+
<div id="app"></div>
|
|
23
|
+
</body>
|
|
24
|
+
</html>
|