opc-agent 0.9.0 → 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/README.md +145 -144
- package/dist/channels/web.js +118 -39
- package/dist/cli.js +109 -1
- package/dist/core/errors.d.ts +68 -0
- package/dist/core/errors.js +149 -0
- package/dist/core/security.d.ts +48 -0
- package/dist/core/security.js +146 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +25 -1
- package/dist/plugins/index.d.ts +24 -3
- package/dist/plugins/index.js +109 -4
- package/dist/schema/oad.d.ts +54 -0
- package/dist/schema/oad.js +6 -1
- package/package.json +1 -1
- package/src/channels/web.ts +118 -39
- package/src/cli.ts +108 -1
- package/src/core/errors.ts +148 -0
- package/src/core/security.ts +171 -0
- package/src/index.ts +7 -0
- package/src/plugins/index.ts +128 -7
- package/src/schema/oad.ts +6 -0
- package/tests/errors.test.ts +83 -0
- package/tests/security.test.ts +60 -0
package/README.md
CHANGED
|
@@ -1,188 +1,189 @@
|
|
|
1
|
-
|
|
1
|
+
<p align="center">
|
|
2
|
+
<h1 align="center">🤖 OPC Agent</h1>
|
|
3
|
+
<p align="center"><strong>Open Agent Framework — Build, test, and run AI Agents for business workstations</strong></p>
|
|
4
|
+
<p align="center">
|
|
5
|
+
<a href="https://www.npmjs.com/package/opc-agent"><img src="https://img.shields.io/npm/v/opc-agent?color=blue" alt="npm"></a>
|
|
6
|
+
<a href="https://github.com/anthropic-lab/opc-agent/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-Apache--2.0-green" alt="license"></a>
|
|
7
|
+
<a href="https://github.com/anthropic-lab/opc-agent/actions"><img src="https://img.shields.io/badge/tests-passing-brightgreen" alt="tests"></a>
|
|
8
|
+
<a href="https://www.npmjs.com/package/opc-agent"><img src="https://img.shields.io/npm/dm/opc-agent?color=orange" alt="downloads"></a>
|
|
9
|
+
</p>
|
|
10
|
+
</p>
|
|
2
11
|
|
|
3
|
-
|
|
12
|
+
---
|
|
4
13
|
|
|
5
|
-
|
|
6
|
-
[](LICENSE)
|
|
14
|
+
OPC Agent is a **TypeScript-first framework** for building production AI agents. Define your agent in a single YAML file (OAD — Open Agent Definition), connect any LLM provider, deploy to any channel.
|
|
7
15
|
|
|
8
|
-
##
|
|
9
|
-
|
|
10
|
-
- 🤖 **Agent Framework** — BaseAgent with lifecycle management, skills, and LLM integration
|
|
11
|
-
- 📋 **OAD Schema** — Declarative agent definition (YAML/JSON) with validation
|
|
12
|
-
- 🧠 **Memory System** — Short-term + long-term memory with DeepBrain integration
|
|
13
|
-
- 🔌 **Multi-Channel** — Web, WebSocket, and Telegram channels
|
|
14
|
-
- 🛡️ **DTV Framework** — Data, Trust, and Value tracking for agents
|
|
15
|
-
- 🎯 **Skill System** — Pluggable skills with registry and priority execution
|
|
16
|
-
- 📦 **Templates** — Customer service, sales assistant, knowledge base, code reviewer
|
|
17
|
-
- 🚀 **CLI** — Interactive project creation, dev mode, build, test, run
|
|
18
|
-
|
|
19
|
-
## Quick Start
|
|
16
|
+
## ⚡ Quick Start (30 seconds)
|
|
20
17
|
|
|
21
18
|
```bash
|
|
22
|
-
# Install
|
|
19
|
+
# Install
|
|
23
20
|
npm install -g opc-agent
|
|
24
21
|
|
|
25
|
-
# Create
|
|
22
|
+
# Create your first agent
|
|
26
23
|
opc init my-agent
|
|
27
|
-
|
|
28
|
-
# Or with a specific template
|
|
29
|
-
opc init my-bot --template sales-assistant
|
|
30
|
-
|
|
31
|
-
# Run the agent
|
|
32
24
|
cd my-agent
|
|
25
|
+
|
|
26
|
+
# Run it
|
|
33
27
|
opc run
|
|
34
28
|
```
|
|
35
29
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
| Template | Description |
|
|
39
|
-
|----------|-------------|
|
|
40
|
-
| `customer-service` | FAQ lookup + human handoff |
|
|
41
|
-
| `sales-assistant` | Product Q&A + lead capture + appointment booking |
|
|
42
|
-
| `knowledge-base` | RAG with DeepBrain semantic search |
|
|
43
|
-
| `code-reviewer` | Bug detection + style checking |
|
|
30
|
+
Your agent is now live at `http://localhost:3000` with a beautiful web chat UI.
|
|
44
31
|
|
|
45
|
-
##
|
|
32
|
+
## ✨ Features
|
|
46
33
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
34
|
+
### 🔌 Multi-Provider LLM Support
|
|
35
|
+
```yaml
|
|
36
|
+
# oad.yaml
|
|
37
|
+
spec:
|
|
38
|
+
provider:
|
|
39
|
+
default: deepseek
|
|
40
|
+
allowed: [openai, deepseek, qwen, anthropic, ollama]
|
|
41
|
+
model: deepseek-chat
|
|
42
|
+
```
|
|
52
43
|
|
|
53
|
-
|
|
54
|
-
opc deploy --target openclaw --install
|
|
44
|
+
Supports **OpenAI**, **DeepSeek**, **Anthropic**, **Qwen**, **Ollama** (local), and any OpenAI-compatible API.
|
|
55
45
|
|
|
56
|
-
|
|
57
|
-
|
|
46
|
+
### 📡 Multi-Channel Deployment
|
|
47
|
+
```yaml
|
|
48
|
+
spec:
|
|
49
|
+
channels:
|
|
50
|
+
- type: web # Beautiful chat UI
|
|
51
|
+
port: 3000
|
|
52
|
+
- type: telegram # Telegram bot
|
|
53
|
+
- type: websocket # Real-time WebSocket
|
|
54
|
+
- type: slack # Slack integration
|
|
55
|
+
- type: email # Email channel
|
|
56
|
+
- type: wechat # WeChat Official Account
|
|
57
|
+
- type: voice # Voice (STT/TTS)
|
|
58
|
+
- type: webhook # Incoming webhooks
|
|
58
59
|
```
|
|
59
60
|
|
|
60
|
-
|
|
61
|
+
### 🧠 Knowledge Base (RAG)
|
|
62
|
+
```typescript
|
|
63
|
+
import { KnowledgeBase } from 'opc-agent';
|
|
61
64
|
|
|
62
|
-
|
|
65
|
+
const kb = new KnowledgeBase('./docs');
|
|
66
|
+
await kb.addFile('product-manual.pdf');
|
|
67
|
+
// Agent automatically uses KB for context
|
|
68
|
+
```
|
|
63
69
|
|
|
64
|
-
|
|
70
|
+
### 🔧 Plugin System
|
|
71
|
+
```yaml
|
|
72
|
+
spec:
|
|
73
|
+
plugins:
|
|
74
|
+
- name: logging
|
|
75
|
+
- name: analytics
|
|
76
|
+
- name: rate-limit
|
|
77
|
+
config: { maxPerMinute: 60 }
|
|
78
|
+
```
|
|
65
79
|
|
|
66
|
-
|
|
67
|
-
|---------|-------------|
|
|
68
|
-
| `opc init [name]` | Create new project (interactive) |
|
|
69
|
-
| `opc create <name>` | Create agent from template |
|
|
70
|
-
| `opc info` | Show agent info from OAD |
|
|
71
|
-
| `opc build` | Validate OAD |
|
|
72
|
-
| `opc test` | Run in sandbox mode |
|
|
73
|
-
| `opc run` | Start agent with channels |
|
|
74
|
-
| `opc dev` | Hot-reload development mode |
|
|
75
|
-
| `opc deploy` | **Deploy to OpenClaw runtime** |
|
|
76
|
-
| `opc publish` | Validate and generate manifest |
|
|
77
|
-
| `opc search <query>` | Search OPC Registry (coming soon) |
|
|
80
|
+
Built-in plugins: `logging`, `analytics`, `rate-limit`. Custom plugins support lifecycle hooks: `onInit`, `onMessage`, `onResponse`, `onError`, `onShutdown`.
|
|
78
81
|
|
|
79
|
-
|
|
82
|
+
### 🔒 Security
|
|
83
|
+
- Input sanitization (XSS, injection prevention)
|
|
84
|
+
- API key rotation & management
|
|
85
|
+
- CORS configuration
|
|
86
|
+
- Helmet-style security headers
|
|
87
|
+
- Content Security Policy
|
|
88
|
+
- Auth middleware with session isolation
|
|
80
89
|
|
|
81
|
-
|
|
90
|
+
### 🧪 Agent Testing
|
|
91
|
+
```bash
|
|
92
|
+
opc test # Run test cases
|
|
93
|
+
opc test --watch # Watch mode
|
|
94
|
+
```
|
|
82
95
|
|
|
83
96
|
```yaml
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
description: "My AI agent"
|
|
90
|
-
marketplace:
|
|
91
|
-
category: support
|
|
92
|
-
pricing: free
|
|
93
|
-
tags: [ai, support]
|
|
94
|
-
spec:
|
|
95
|
-
provider:
|
|
96
|
-
default: deepseek
|
|
97
|
-
allowed: [openai, deepseek, qwen]
|
|
98
|
-
model: deepseek-chat
|
|
99
|
-
systemPrompt: "You are a helpful assistant."
|
|
100
|
-
skills:
|
|
101
|
-
- name: faq-lookup
|
|
102
|
-
description: "Answer FAQs"
|
|
103
|
-
channels:
|
|
104
|
-
- type: web
|
|
105
|
-
port: 3000
|
|
106
|
-
- type: telegram
|
|
107
|
-
config:
|
|
108
|
-
token: "BOT_TOKEN"
|
|
109
|
-
- type: websocket
|
|
110
|
-
port: 3002
|
|
111
|
-
memory:
|
|
112
|
-
shortTerm: true
|
|
113
|
-
longTerm:
|
|
114
|
-
provider: deepbrain
|
|
115
|
-
collection: my-knowledge
|
|
116
|
-
dtv:
|
|
117
|
-
trust:
|
|
118
|
-
level: sandbox
|
|
119
|
-
value:
|
|
120
|
-
metrics: [response_time]
|
|
97
|
+
# tests/greeting.yaml
|
|
98
|
+
- input: "Hello"
|
|
99
|
+
expect:
|
|
100
|
+
contains: ["hello", "hi"]
|
|
101
|
+
maxLatencyMs: 5000
|
|
121
102
|
```
|
|
122
103
|
|
|
123
|
-
|
|
104
|
+
### 🎭 Multi-Agent Orchestration
|
|
105
|
+
```typescript
|
|
106
|
+
import { Orchestrator } from 'opc-agent';
|
|
124
107
|
|
|
125
|
-
|
|
126
|
-
|
|
108
|
+
const orchestrator = new Orchestrator({
|
|
109
|
+
agents: [triageAgent, salesAgent, supportAgent],
|
|
110
|
+
strategy: 'route-by-intent',
|
|
111
|
+
});
|
|
112
|
+
```
|
|
127
113
|
|
|
128
|
-
###
|
|
129
|
-
|
|
114
|
+
### 📊 Built-in Analytics & Monitoring
|
|
115
|
+
- `/api/health` — Health check
|
|
116
|
+
- `/api/metrics` — Prometheus-compatible metrics
|
|
117
|
+
- `/api/dashboard` — Real-time dashboard UI
|
|
118
|
+
- Conversation export (JSON, Markdown, CSV)
|
|
130
119
|
|
|
131
|
-
|
|
132
|
-
npm install deepbrain
|
|
133
|
-
```
|
|
120
|
+
## 🏗️ Architecture
|
|
134
121
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
122
|
+
```
|
|
123
|
+
┌─────────────────────────────────────────────────┐
|
|
124
|
+
│ OAD (YAML) │
|
|
125
|
+
│ Agent Definition & Config │
|
|
126
|
+
├─────────────────────────────────────────────────┤
|
|
127
|
+
│ │
|
|
128
|
+
│ ┌──────────┐ ┌──────────┐ ┌──────────────┐ │
|
|
129
|
+
│ │ Channels │ │ Plugins │ │ Security │ │
|
|
130
|
+
│ │ web,tg, │ │ logging, │ │ sanitize, │ │
|
|
131
|
+
│ │ ws,slack │ │ analytics│ │ CORS, auth │ │
|
|
132
|
+
│ └────┬─────┘ └────┬─────┘ └──────┬───────┘ │
|
|
133
|
+
│ │ │ │ │
|
|
134
|
+
│ ┌────▼──────────────▼───────────────▼────────┐ │
|
|
135
|
+
│ │ Agent Runtime │ │
|
|
136
|
+
│ │ ┌─────────┐ ┌────────┐ ┌─────────────┐ │ │
|
|
137
|
+
│ │ │ Memory │ │ Skills │ │ Knowledge │ │ │
|
|
138
|
+
│ │ └─────────┘ └────────┘ └─────────────┘ │ │
|
|
139
|
+
│ └────────────────────┬───────────────────────┘ │
|
|
140
|
+
│ │ │
|
|
141
|
+
│ ┌────────────────────▼───────────────────────┐ │
|
|
142
|
+
│ │ LLM Providers │ │
|
|
143
|
+
│ │ OpenAI · DeepSeek · Anthropic · Ollama │ │
|
|
144
|
+
│ └─────────────────────────────────────────────┘│
|
|
145
|
+
└─────────────────────────────────────────────────┘
|
|
141
146
|
```
|
|
142
147
|
|
|
143
|
-
|
|
148
|
+
## 📖 CLI Reference
|
|
144
149
|
|
|
145
|
-
|
|
150
|
+
| Command | Description |
|
|
151
|
+
|---------|-------------|
|
|
152
|
+
| `opc init [name]` | Create a new agent project |
|
|
153
|
+
| `opc run` | Start the agent |
|
|
154
|
+
| `opc dev` | Start in development mode (auto-reload) |
|
|
155
|
+
| `opc test` | Run agent test cases |
|
|
156
|
+
| `opc validate` | Validate OAD configuration |
|
|
157
|
+
| `opc deploy hermes` | Deploy to Hermes cloud |
|
|
158
|
+
| `opc plugin list` | List available plugins |
|
|
159
|
+
| `opc plugin add <name>` | Add a plugin to config |
|
|
160
|
+
| `opc migrate` | Migrate OAD to latest schema |
|
|
161
|
+
| `opc marketplace publish` | Publish to marketplace |
|
|
146
162
|
|
|
147
|
-
|
|
148
|
-
- **WebSocket** — Real-time bidirectional communication with broadcast
|
|
149
|
-
- **Telegram** — Webhook handler for Telegram Bot API
|
|
163
|
+
## 🤝 Contributing
|
|
150
164
|
|
|
151
|
-
|
|
165
|
+
We welcome contributions! Here's how:
|
|
152
166
|
|
|
153
|
-
|
|
154
|
-
|
|
167
|
+
1. Fork the repository
|
|
168
|
+
2. Create a feature branch: `git checkout -b feat/my-feature`
|
|
169
|
+
3. Make your changes with tests
|
|
170
|
+
4. Run tests: `npm test`
|
|
171
|
+
5. Submit a pull request
|
|
155
172
|
|
|
156
|
-
|
|
157
|
-
const agent = new BaseAgent({
|
|
158
|
-
name: 'my-agent',
|
|
159
|
-
systemPrompt: 'You are helpful.',
|
|
160
|
-
});
|
|
161
|
-
await agent.init();
|
|
162
|
-
|
|
163
|
-
// With skills
|
|
164
|
-
agent.registerSkill({
|
|
165
|
-
name: 'greeter',
|
|
166
|
-
description: 'Greet users',
|
|
167
|
-
execute: async (ctx, msg) => {
|
|
168
|
-
if (msg.content.includes('hello')) {
|
|
169
|
-
return { handled: true, response: 'Hi!', confidence: 1.0 };
|
|
170
|
-
}
|
|
171
|
-
return { handled: false, confidence: 0 };
|
|
172
|
-
},
|
|
173
|
-
});
|
|
173
|
+
### Development Setup
|
|
174
174
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
175
|
+
```bash
|
|
176
|
+
git clone https://github.com/anthropic-lab/opc-agent.git
|
|
177
|
+
cd opc-agent
|
|
178
|
+
npm install
|
|
179
|
+
npm run build
|
|
180
|
+
npm test
|
|
180
181
|
```
|
|
181
182
|
|
|
182
|
-
##
|
|
183
|
+
## 📄 License
|
|
183
184
|
|
|
184
|
-
|
|
185
|
+
[Apache License 2.0](LICENSE) — Use it freely in commercial and open source projects.
|
|
185
186
|
|
|
186
|
-
|
|
187
|
+
---
|
|
187
188
|
|
|
188
|
-
|
|
189
|
+
<p align="center">Built with ❤️ by the OPC team</p>
|
package/dist/channels/web.js
CHANGED
|
@@ -62,52 +62,127 @@ const CHAT_HTML = `<!DOCTYPE html>
|
|
|
62
62
|
<html lang="en">
|
|
63
63
|
<head>
|
|
64
64
|
<meta charset="UTF-8">
|
|
65
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
65
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
|
66
66
|
<title>OPC Agent</title>
|
|
67
67
|
<style>
|
|
68
|
+
:root{--bg:#0a0a0f;--surface:#12121a;--border:#1e1e2e;--text:#e0e0e0;--text-dim:#888;--accent:#818cf8;--accent-hover:#6366f1;--user-bg:#2563eb;--user-hover:#1d4ed8;--error-bg:#7f1d1d;--error-text:#fca5a5;--success:#22c55e;--radius:12px}
|
|
68
69
|
*{margin:0;padding:0;box-sizing:border-box}
|
|
69
|
-
body{background
|
|
70
|
-
header{background
|
|
71
|
-
header
|
|
72
|
-
header .
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
.
|
|
76
|
-
.
|
|
77
|
-
.
|
|
78
|
-
.
|
|
70
|
+
body{background:var(--bg);color:var(--text);font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,'Helvetica Neue',sans-serif;height:100vh;height:100dvh;display:flex;flex-direction:column;overflow:hidden}
|
|
71
|
+
header{background:var(--surface);padding:14px 20px;border-bottom:1px solid var(--border);display:flex;align-items:center;gap:12px;flex-shrink:0;backdrop-filter:blur(12px)}
|
|
72
|
+
header .avatar{width:36px;height:36px;border-radius:50%;background:linear-gradient(135deg,var(--accent),#6366f1);display:flex;align-items:center;justify-content:center;font-size:16px;flex-shrink:0}
|
|
73
|
+
header .info{flex:1;min-width:0}
|
|
74
|
+
header h1{font-size:16px;font-weight:600;color:#fff;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
|
75
|
+
header .status{font-size:12px;color:var(--success);display:flex;align-items:center;gap:4px}
|
|
76
|
+
header .status .dot{width:6px;height:6px;border-radius:50%;background:var(--success);animation:pulse 2s infinite}
|
|
77
|
+
nav.header-nav{display:flex;gap:4px}
|
|
78
|
+
nav.header-nav a{color:var(--text-dim);text-decoration:none;font-size:12px;padding:4px 10px;border-radius:6px;transition:all .2s}
|
|
79
|
+
nav.header-nav a:hover{color:#fff;background:rgba(255,255,255,.06)}
|
|
80
|
+
@keyframes pulse{0%,100%{opacity:1}50%{opacity:.3}}
|
|
81
|
+
@keyframes fadeIn{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}}
|
|
82
|
+
@keyframes slideIn{from{opacity:0;transform:scale(.96)}to{opacity:1;transform:scale(1)}}
|
|
83
|
+
#messages{flex:1;overflow-y:auto;padding:20px;display:flex;flex-direction:column;gap:12px;scroll-behavior:smooth}
|
|
84
|
+
#messages::-webkit-scrollbar{width:4px}
|
|
85
|
+
#messages::-webkit-scrollbar-track{background:transparent}
|
|
86
|
+
#messages::-webkit-scrollbar-thumb{background:var(--border);border-radius:4px}
|
|
87
|
+
.msg-wrap{display:flex;flex-direction:column;animation:fadeIn .3s ease-out}
|
|
88
|
+
.msg-wrap.user{align-items:flex-end}
|
|
89
|
+
.msg-wrap.assistant{align-items:flex-start}
|
|
90
|
+
.msg{max-width:min(720px,85%);padding:10px 14px;border-radius:var(--radius);line-height:1.7;font-size:14px;word-break:break-word;position:relative;transition:all .2s}
|
|
91
|
+
.msg.user{background:var(--user-bg);color:#fff;border-bottom-right-radius:4px}
|
|
92
|
+
.msg.assistant{background:var(--surface);color:var(--text);border:1px solid var(--border);border-bottom-left-radius:4px}
|
|
93
|
+
.msg.error{background:var(--error-bg);color:var(--error-text);border:1px solid rgba(239,68,68,.3)}
|
|
94
|
+
.msg pre{background:rgba(0,0,0,.4);padding:12px;border-radius:8px;overflow-x:auto;margin:8px 0;font-size:13px;font-family:'JetBrains Mono','Fira Code','Cascadia Code',monospace;line-height:1.5}
|
|
95
|
+
.msg code{font-family:'JetBrains Mono','Fira Code','Cascadia Code',monospace;font-size:13px;background:rgba(0,0,0,.3);padding:1px 5px;border-radius:4px}
|
|
96
|
+
.msg pre code{background:none;padding:0}
|
|
97
|
+
.msg .cursor{display:inline-block;width:2px;height:14px;background:var(--accent);animation:blink .6s infinite;vertical-align:text-bottom;margin-left:2px}
|
|
79
98
|
@keyframes blink{0%,100%{opacity:1}50%{opacity:0}}
|
|
80
|
-
.
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
99
|
+
.typing{display:flex;gap:4px;padding:12px 16px;align-items:center}
|
|
100
|
+
.typing span{width:6px;height:6px;border-radius:50%;background:var(--text-dim);animation:typingDot 1.4s infinite}
|
|
101
|
+
.typing span:nth-child(2){animation-delay:.2s}
|
|
102
|
+
.typing span:nth-child(3){animation-delay:.4s}
|
|
103
|
+
@keyframes typingDot{0%,60%,100%{opacity:.3;transform:scale(.8)}30%{opacity:1;transform:scale(1)}}
|
|
104
|
+
.reactions{display:flex;gap:4px;margin-top:4px}
|
|
105
|
+
.reactions button{background:rgba(255,255,255,.06);border:1px solid transparent;border-radius:16px;padding:2px 8px;font-size:13px;cursor:pointer;transition:all .15s;color:var(--text-dim)}
|
|
106
|
+
.reactions button:hover{background:rgba(255,255,255,.12);border-color:var(--border)}
|
|
107
|
+
.reactions button.active{background:rgba(99,102,241,.2);border-color:var(--accent);color:var(--accent)}
|
|
108
|
+
.msg-time{font-size:11px;color:var(--text-dim);margin-top:2px;opacity:0;transition:opacity .2s}
|
|
109
|
+
.msg-wrap:hover .msg-time{opacity:1}
|
|
110
|
+
.attachment{display:flex;align-items:center;gap:8px;background:rgba(0,0,0,.3);padding:8px 12px;border-radius:8px;margin-top:6px;font-size:13px}
|
|
111
|
+
.attachment .icon{font-size:18px}
|
|
112
|
+
#input-area{background:var(--surface);padding:12px 20px 16px;border-top:1px solid var(--border);display:flex;gap:10px;align-items:flex-end;flex-shrink:0}
|
|
113
|
+
#input{flex:1;background:var(--bg);border:1px solid var(--border);border-radius:var(--radius);padding:10px 14px;color:#fff;font-size:14px;outline:none;resize:none;max-height:150px;min-height:42px;font-family:inherit;line-height:1.5;transition:border-color .2s}
|
|
114
|
+
#input:focus{border-color:var(--accent)}
|
|
115
|
+
#input::placeholder{color:var(--text-dim)}
|
|
116
|
+
#send{background:var(--user-bg);color:#fff;border:none;border-radius:var(--radius);width:42px;height:42px;font-size:18px;cursor:pointer;transition:all .2s;display:flex;align-items:center;justify-content:center;flex-shrink:0}
|
|
117
|
+
#send:hover{background:var(--user-hover);transform:scale(1.05)}
|
|
118
|
+
#send:disabled{background:#334155;cursor:not-allowed;transform:none}
|
|
119
|
+
.empty-state{flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;color:var(--text-dim);gap:12px;padding:40px;text-align:center}
|
|
120
|
+
.empty-state .logo{font-size:48px;opacity:.6}
|
|
121
|
+
.empty-state h2{color:var(--text);font-size:20px;font-weight:500}
|
|
122
|
+
.empty-state p{font-size:14px;max-width:400px;line-height:1.6}
|
|
123
|
+
@media(max-width:640px){
|
|
124
|
+
header{padding:10px 14px}
|
|
125
|
+
#messages{padding:12px}
|
|
126
|
+
#input-area{padding:10px 14px 14px}
|
|
127
|
+
.msg{max-width:90%;font-size:14px}
|
|
128
|
+
nav.header-nav{display:none}
|
|
129
|
+
}
|
|
87
130
|
</style>
|
|
88
131
|
</head>
|
|
89
132
|
<body>
|
|
90
|
-
<header
|
|
91
|
-
<div id="
|
|
133
|
+
<header>
|
|
134
|
+
<div class="avatar" id="avatar">🤖</div>
|
|
135
|
+
<div class="info"><h1 id="title">OPC Agent</h1><div class="status"><span class="dot"></span>Online</div></div>
|
|
136
|
+
<nav class="header-nav"><a href="/dashboard">Dashboard</a><a href="/templates">Templates</a></nav>
|
|
137
|
+
</header>
|
|
138
|
+
<div id="messages">
|
|
139
|
+
<div class="empty-state" id="empty"><div class="logo">💬</div><h2>Start a conversation</h2><p>Type a message below to chat with your AI agent.</p></div>
|
|
140
|
+
</div>
|
|
92
141
|
<div id="input-area">
|
|
93
|
-
<textarea id="input" rows="1" placeholder="Type a message
|
|
94
|
-
<button id="send"
|
|
142
|
+
<textarea id="input" rows="1" placeholder="Type a message…" autocomplete="off"></textarea>
|
|
143
|
+
<button id="send" aria-label="Send">↑</button>
|
|
95
144
|
</div>
|
|
96
145
|
<script>
|
|
97
|
-
const msgs=document.getElementById('messages'),input=document.getElementById('input'),btn=document.getElementById('send');
|
|
146
|
+
const msgs=document.getElementById('messages'),input=document.getElementById('input'),btn=document.getElementById('send'),empty=document.getElementById('empty');
|
|
98
147
|
let sessionId=crypto.randomUUID(),sending=false;
|
|
99
148
|
|
|
100
|
-
function
|
|
149
|
+
function esc(s){const d=document.createElement('div');d.textContent=s;return d.innerHTML}
|
|
150
|
+
function fmtTime(){return new Date().toLocaleTimeString([],{hour:'2-digit',minute:'2-digit'})}
|
|
151
|
+
function renderMd(text){
|
|
152
|
+
let h=esc(text);
|
|
153
|
+
h=h.replace(/\`\`\`(\\w*)\\n([\\s\\S]*?)\`\`\`/g,'<pre><code>$2</code></pre>');
|
|
154
|
+
h=h.replace(/\`([^\`]+)\`/g,'<code>$1</code>');
|
|
155
|
+
h=h.replace(/\\*\\*(.+?)\\*\\*/g,'<strong>$1</strong>');
|
|
156
|
+
h=h.replace(/\\n/g,'<br>');
|
|
157
|
+
return h;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function addMsg(role,text,opts){
|
|
161
|
+
if(empty)empty.remove();
|
|
162
|
+
const wrap=document.createElement('div');
|
|
163
|
+
wrap.className='msg-wrap '+role;
|
|
101
164
|
const d=document.createElement('div');
|
|
102
165
|
d.className='msg '+role;
|
|
103
|
-
d.textContent=text;
|
|
104
|
-
|
|
166
|
+
if(opts?.html)d.innerHTML=text;else if(role==='assistant'&&text)d.innerHTML=renderMd(text);else d.textContent=text;
|
|
167
|
+
wrap.appendChild(d);
|
|
168
|
+
const time=document.createElement('div');
|
|
169
|
+
time.className='msg-time';
|
|
170
|
+
time.textContent=fmtTime();
|
|
171
|
+
wrap.appendChild(time);
|
|
172
|
+
if(role==='assistant'&&text){
|
|
173
|
+
const rx=document.createElement('div');rx.className='reactions';
|
|
174
|
+
rx.innerHTML='<button data-r="👍" onclick="react(this)">👍</button><button data-r="👎" onclick="react(this)">👎</button>';
|
|
175
|
+
wrap.appendChild(rx);
|
|
176
|
+
}
|
|
177
|
+
msgs.appendChild(wrap);
|
|
105
178
|
msgs.scrollTop=msgs.scrollHeight;
|
|
106
179
|
return d;
|
|
107
180
|
}
|
|
108
181
|
|
|
182
|
+
window.react=function(el){el.classList.toggle('active')};
|
|
183
|
+
|
|
109
184
|
input.addEventListener('keydown',e=>{if(e.key==='Enter'&&!e.shiftKey){e.preventDefault();send()}});
|
|
110
|
-
input.addEventListener('input',()=>{input.style.height='auto';input.style.height=Math.min(input.scrollHeight,
|
|
185
|
+
input.addEventListener('input',()=>{input.style.height='auto';input.style.height=Math.min(input.scrollHeight,150)+'px'});
|
|
111
186
|
btn.addEventListener('click',send);
|
|
112
187
|
|
|
113
188
|
async function send(){
|
|
@@ -116,8 +191,13 @@ async function send(){
|
|
|
116
191
|
sending=true;btn.disabled=true;
|
|
117
192
|
input.value='';input.style.height='auto';
|
|
118
193
|
addMsg('user',text);
|
|
119
|
-
const
|
|
120
|
-
|
|
194
|
+
const wrap=document.createElement('div');wrap.className='msg-wrap assistant';
|
|
195
|
+
const d=document.createElement('div');d.className='msg assistant';
|
|
196
|
+
d.innerHTML='<div class="typing"><span></span><span></span><span></span></div>';
|
|
197
|
+
wrap.appendChild(d);
|
|
198
|
+
const time=document.createElement('div');time.className='msg-time';time.textContent=fmtTime();
|
|
199
|
+
wrap.appendChild(time);
|
|
200
|
+
msgs.appendChild(wrap);msgs.scrollTop=msgs.scrollHeight;
|
|
121
201
|
try{
|
|
122
202
|
const res=await fetch('/api/chat',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({message:text,sessionId})});
|
|
123
203
|
if(!res.ok)throw new Error('HTTP '+res.status);
|
|
@@ -130,21 +210,20 @@ async function send(){
|
|
|
130
210
|
const lines=chunk.split('\\n');
|
|
131
211
|
for(const line of lines){
|
|
132
212
|
if(!line.startsWith('data: '))continue;
|
|
133
|
-
const
|
|
134
|
-
if(
|
|
135
|
-
try{const j=JSON.parse(d);if(j.content)full+=j.content;if(j.error)full='Error: '+j.error;}catch{}
|
|
213
|
+
const dd=line.slice(6);if(dd==='[DONE]')continue;
|
|
214
|
+
try{const j=JSON.parse(dd);if(j.content)full+=j.content;if(j.error)full='Error: '+j.error;}catch{}
|
|
136
215
|
}
|
|
137
|
-
|
|
216
|
+
d.innerHTML=renderMd(full)+'<span class="cursor"></span>';
|
|
138
217
|
msgs.scrollTop=msgs.scrollHeight;
|
|
139
218
|
}
|
|
140
|
-
if(!full)
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
219
|
+
if(!full){d.textContent='(empty response)';}else{d.innerHTML=renderMd(full);}
|
|
220
|
+
const rx=document.createElement('div');rx.className='reactions';
|
|
221
|
+
rx.innerHTML='<button data-r="👍" onclick="react(this)">👍</button><button data-r="👎" onclick="react(this)">👎</button>';
|
|
222
|
+
wrap.appendChild(rx);
|
|
223
|
+
}catch(e){d.className='msg error';d.textContent='Error: '+e.message;}
|
|
144
224
|
sending=false;btn.disabled=false;input.focus();
|
|
145
225
|
}
|
|
146
226
|
|
|
147
|
-
// Fetch agent info
|
|
148
227
|
fetch('/api/info').then(r=>r.json()).then(d=>{if(d.name)document.getElementById('title').textContent=d.name}).catch(()=>{});
|
|
149
228
|
</script>
|
|
150
229
|
</body>
|
|
@@ -365,7 +444,7 @@ class WebChannel extends index_1.BaseChannel {
|
|
|
365
444
|
timestamp: Date.now(),
|
|
366
445
|
uptime: uptimeMs,
|
|
367
446
|
uptimeHuman: `${Math.floor(uptimeMs / 3600000)}h ${Math.floor((uptimeMs % 3600000) / 60000)}m`,
|
|
368
|
-
version: '0.
|
|
447
|
+
version: '1.0.0',
|
|
369
448
|
agent: this.agentName,
|
|
370
449
|
stats: {
|
|
371
450
|
sessions: this.stats.sessions,
|