agent-relay-server 0.4.22 → 0.4.24
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/package.json +1 -1
- package/public/dashboard.js +1661 -17
- package/public/index.html +1026 -32
- package/src/db.ts +252 -1
- package/src/routes.ts +336 -3
- package/src/security.ts +3 -1
- package/src/types.ts +61 -0
package/public/index.html
CHANGED
|
@@ -35,6 +35,10 @@
|
|
|
35
35
|
.status-dot.idle { background: var(--tblr-success); }
|
|
36
36
|
.status-dot.busy { background: var(--tblr-warning); }
|
|
37
37
|
.status-dot.offline { background: var(--tblr-secondary); opacity: 0.5; }
|
|
38
|
+
.status-dot.stale { background: var(--tblr-danger); box-shadow: 0 0 6px var(--tblr-danger); }
|
|
39
|
+
.status-dot.reconnecting { animation: pulse-dot 1s ease-in-out infinite; }
|
|
40
|
+
.status-dot.paired { box-shadow: 0 0 0 3px rgba(var(--tblr-success-rgb), 0.18); }
|
|
41
|
+
.status-dot.attention { box-shadow: 0 0 0 3px rgba(var(--tblr-danger-rgb), 0.18); }
|
|
38
42
|
|
|
39
43
|
@keyframes pulse-dot {
|
|
40
44
|
0%, 100% { opacity: 1; box-shadow: 0 0 6px var(--tblr-success); }
|
|
@@ -81,6 +85,75 @@
|
|
|
81
85
|
.msg-card.thread-child { margin-left: 24px; border-left-color: var(--tblr-border-color); }
|
|
82
86
|
|
|
83
87
|
.msg-body { white-space: pre-wrap; word-break: break-word; font-size: 13px; }
|
|
88
|
+
.inbox-fit { height: calc(100vh - 280px); min-height: 380px; }
|
|
89
|
+
.inbox-thread { cursor: pointer; min-height: 72px; }
|
|
90
|
+
.inbox-thread.active { background: var(--tblr-bg-surface-secondary); border-left: 3px solid var(--tblr-primary); }
|
|
91
|
+
.inbox-thread.attention { border-left: 3px solid var(--tblr-warning); }
|
|
92
|
+
.inbox-thread-snippet { max-width: 100%; }
|
|
93
|
+
.attention-card { border-left: 3px solid var(--tblr-warning); color: inherit; }
|
|
94
|
+
button.attention-card { background: var(--tblr-bg-surface); border-top: 1px solid var(--tblr-border-color); border-right: 1px solid var(--tblr-border-color); border-bottom: 1px solid var(--tblr-border-color); }
|
|
95
|
+
button.attention-card:hover { background: var(--tblr-bg-surface-secondary); }
|
|
96
|
+
.attention-empty { border-left-color: var(--tblr-border-color); }
|
|
97
|
+
.attention-badges .badge { font-weight: 500; }
|
|
98
|
+
.presence-badges .badge { font-weight: 600; }
|
|
99
|
+
.activity-item { cursor: pointer; }
|
|
100
|
+
.activity-item:hover { background: var(--tblr-bg-surface-secondary); }
|
|
101
|
+
.activity-icon {
|
|
102
|
+
width: 32px;
|
|
103
|
+
height: 32px;
|
|
104
|
+
display: inline-flex;
|
|
105
|
+
align-items: center;
|
|
106
|
+
justify-content: center;
|
|
107
|
+
border-radius: 6px;
|
|
108
|
+
flex: 0 0 32px;
|
|
109
|
+
}
|
|
110
|
+
.pair-badge .ti { font-size: 13px; vertical-align: -2px; }
|
|
111
|
+
.pair-card { border-left: 3px solid var(--tblr-border-color); }
|
|
112
|
+
.pair-card.active { border-left-color: var(--tblr-success); }
|
|
113
|
+
.pair-card.pending { border-left-color: var(--tblr-warning); }
|
|
114
|
+
.agent-drawer-backdrop {
|
|
115
|
+
position: fixed;
|
|
116
|
+
inset: 0;
|
|
117
|
+
background: rgba(0, 0, 0, 0.45);
|
|
118
|
+
z-index: 1030;
|
|
119
|
+
}
|
|
120
|
+
.agent-drawer {
|
|
121
|
+
position: fixed;
|
|
122
|
+
top: 0;
|
|
123
|
+
right: 0;
|
|
124
|
+
width: min(480px, 100vw);
|
|
125
|
+
height: 100vh;
|
|
126
|
+
overflow-y: auto;
|
|
127
|
+
background: var(--tblr-bg-surface);
|
|
128
|
+
border-left: 1px solid var(--tblr-border-color);
|
|
129
|
+
z-index: 1040;
|
|
130
|
+
box-shadow: -16px 0 48px rgba(0, 0, 0, 0.35);
|
|
131
|
+
}
|
|
132
|
+
.detail-row {
|
|
133
|
+
display: grid;
|
|
134
|
+
grid-template-columns: 96px minmax(0, 1fr);
|
|
135
|
+
gap: 12px;
|
|
136
|
+
align-items: start;
|
|
137
|
+
}
|
|
138
|
+
.command-palette {
|
|
139
|
+
position: fixed;
|
|
140
|
+
inset: 0;
|
|
141
|
+
z-index: 1060;
|
|
142
|
+
background: rgba(0, 0, 0, 0.55);
|
|
143
|
+
display: grid;
|
|
144
|
+
place-items: start center;
|
|
145
|
+
padding: 10vh 16px 16px;
|
|
146
|
+
}
|
|
147
|
+
.command-palette-panel {
|
|
148
|
+
width: min(720px, 100%);
|
|
149
|
+
background: var(--tblr-bg-surface);
|
|
150
|
+
border: 1px solid var(--tblr-border-color);
|
|
151
|
+
border-radius: 8px;
|
|
152
|
+
box-shadow: 0 24px 80px rgba(0, 0, 0, 0.45);
|
|
153
|
+
overflow: hidden;
|
|
154
|
+
}
|
|
155
|
+
.command-palette-item { cursor: pointer; }
|
|
156
|
+
.command-palette-item:hover { background: var(--tblr-bg-surface-secondary); }
|
|
84
157
|
|
|
85
158
|
.fade-in { animation: fadeIn 0.2s ease-in; }
|
|
86
159
|
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
|
|
@@ -102,7 +175,7 @@
|
|
|
102
175
|
</style>
|
|
103
176
|
</head>
|
|
104
177
|
<body>
|
|
105
|
-
<div x-data="relay" x-init="init()" class="d-flex">
|
|
178
|
+
<div x-data="relay" x-init="init()" class="d-flex" @keydown.window.escape="commandPaletteOpen && closeCommandPalette()">
|
|
106
179
|
|
|
107
180
|
<!-- Sidebar -->
|
|
108
181
|
<aside class="ar-sidebar d-none d-md-flex">
|
|
@@ -110,17 +183,37 @@
|
|
|
110
183
|
<nav class="nav flex-column py-2">
|
|
111
184
|
<a href="#" class="nav-link" :class="{ active: view === 'overview' }" @click.prevent="switchView('overview')">
|
|
112
185
|
<i class="ti ti-dashboard"></i>Overview
|
|
186
|
+
<span class="badge bg-warning text-white ms-auto" x-show="attentionSummary.total > 0" x-text="attentionSummary.total"></span>
|
|
113
187
|
</a>
|
|
114
188
|
<a href="#" class="nav-link" :class="{ active: view === 'agents' }" @click.prevent="switchView('agents')">
|
|
115
189
|
<i class="ti ti-robot"></i>Agents
|
|
116
|
-
<span class="badge bg-
|
|
190
|
+
<span class="badge bg-warning text-white ms-auto" x-show="attentionAgentCount > 0" x-text="attentionAgentCount"></span>
|
|
191
|
+
<span class="badge bg-success text-white ms-1" x-text="onlineCount"></span>
|
|
192
|
+
</a>
|
|
193
|
+
<a href="#" class="nav-link" :class="{ active: view === 'inbox' }" @click.prevent="switchView('inbox')">
|
|
194
|
+
<i class="ti ti-inbox"></i>Inbox
|
|
195
|
+
<span class="badge bg-danger text-white ms-auto" x-show="attentionSummary.unreadInbox > 0" x-text="attentionSummary.unreadInbox"></span>
|
|
196
|
+
</a>
|
|
197
|
+
<a href="#" class="nav-link" :class="{ active: view === 'activity' }" @click.prevent="switchView('activity')">
|
|
198
|
+
<i class="ti ti-activity"></i>Activity
|
|
199
|
+
<span class="badge bg-secondary text-white ms-auto" x-show="activityItems.length > 0" x-text="activityItems.length"></span>
|
|
200
|
+
</a>
|
|
201
|
+
<a href="#" class="nav-link" :class="{ active: view === 'pairs' }" @click.prevent="switchView('pairs')">
|
|
202
|
+
<i class="ti ti-link"></i>Pairs
|
|
203
|
+
<span class="badge bg-warning text-white ms-auto" x-show="attentionSummary.pendingPairInvites > 0" x-text="attentionSummary.pendingPairInvites"></span>
|
|
204
|
+
<span class="badge bg-primary text-white ms-1" x-text="pairs.length"></span>
|
|
117
205
|
</a>
|
|
118
206
|
<a href="#" class="nav-link" :class="{ active: view === 'messages' }" @click.prevent="switchView('messages')">
|
|
119
207
|
<i class="ti ti-messages"></i>Messages
|
|
120
208
|
</a>
|
|
209
|
+
<a href="#" class="nav-link" :class="{ active: view === 'work' }" @click.prevent="switchView('work')">
|
|
210
|
+
<i class="ti ti-list-check"></i>Work Queue
|
|
211
|
+
<span class="badge bg-warning text-white ms-auto" x-show="attentionSummary.claimableTasks > 0" x-text="attentionSummary.claimableTasks"></span>
|
|
212
|
+
</a>
|
|
121
213
|
<a href="#" class="nav-link" :class="{ active: view === 'tasks' }" @click.prevent="switchView('tasks')">
|
|
122
214
|
<i class="ti ti-checkup-list"></i>Tasks
|
|
123
|
-
<span class="badge bg-warning text-white ms-auto" x-
|
|
215
|
+
<span class="badge bg-warning text-white ms-auto" x-show="attentionSummary.claimableTasks > 0" x-text="attentionSummary.claimableTasks"></span>
|
|
216
|
+
<span class="badge bg-secondary text-white ms-1" x-text="stats.openTasks ?? 0"></span>
|
|
124
217
|
</a>
|
|
125
218
|
<a href="#" class="nav-link" :class="{ active: view === 'analytics' }" @click.prevent="switchView('analytics')">
|
|
126
219
|
<i class="ti ti-chart-area-line"></i>Analytics
|
|
@@ -131,12 +224,21 @@
|
|
|
131
224
|
<span class="status-dot" :class="connected ? 'online' : 'offline'"></span>
|
|
132
225
|
<span class="small" x-text="authNeeded ? 'Auth required' : connected ? 'Live' : 'Reconnecting…'"></span>
|
|
133
226
|
</div>
|
|
227
|
+
<button class="btn btn-sm btn-ghost-secondary w-100 mb-2 justify-content-start" @click="openCommandPalette()">
|
|
228
|
+
<i class="ti ti-command me-1"></i>Command palette
|
|
229
|
+
</button>
|
|
134
230
|
<div class="d-flex align-items-center gap-2 mb-2">
|
|
135
231
|
<label class="form-check form-switch mb-0">
|
|
136
232
|
<input type="checkbox" class="form-check-input" x-model="showOffline">
|
|
137
233
|
<span class="form-check-label small">Show offline</span>
|
|
138
234
|
</label>
|
|
139
235
|
</div>
|
|
236
|
+
<div class="d-flex align-items-center gap-2 mb-2">
|
|
237
|
+
<label class="form-check form-switch mb-0">
|
|
238
|
+
<input type="checkbox" class="form-check-input" x-model="showBuiltIns">
|
|
239
|
+
<span class="form-check-label small">System agents</span>
|
|
240
|
+
</label>
|
|
241
|
+
</div>
|
|
140
242
|
<div class="d-flex align-items-center gap-2 mb-2">
|
|
141
243
|
<label class="form-check form-switch mb-0">
|
|
142
244
|
<input type="checkbox" class="form-check-input" x-model="autoRefresh">
|
|
@@ -149,7 +251,7 @@
|
|
|
149
251
|
|
|
150
252
|
<!-- Mobile nav -->
|
|
151
253
|
<div class="mobile-nav d-none border-bottom p-2 gap-1 position-fixed top-0 w-100 bg-dark" style="z-index:50">
|
|
152
|
-
<template x-for="v in ['overview','agents','messages','tasks','analytics']">
|
|
254
|
+
<template x-for="v in ['overview','agents','inbox','activity','pairs','messages','work','tasks','analytics']">
|
|
153
255
|
<button class="btn btn-sm" :class="view === v ? 'btn-primary' : 'btn-ghost-secondary'" @click="switchView(v)" x-text="v.charAt(0).toUpperCase() + v.slice(1)"></button>
|
|
154
256
|
</template>
|
|
155
257
|
</div>
|
|
@@ -243,9 +345,24 @@
|
|
|
243
345
|
<div class="small">All checks passing</div>
|
|
244
346
|
</template>
|
|
245
347
|
<template x-if="healthIssues.length > 0">
|
|
246
|
-
<div class="
|
|
247
|
-
<template x-for="
|
|
248
|
-
<
|
|
348
|
+
<div class="mt-2">
|
|
349
|
+
<template x-for="diagnostic in healthDiagnostics" :key="diagnostic.name">
|
|
350
|
+
<div class="border rounded p-2 mb-2">
|
|
351
|
+
<div class="d-flex align-items-start gap-2">
|
|
352
|
+
<span class="badge" :class="diagnostic.status === 'error' ? 'bg-danger-lt' : 'bg-warning-lt'" x-text="diagnostic.name"></span>
|
|
353
|
+
<div class="flex-grow-1 min-width-0">
|
|
354
|
+
<div class="small fw-bold" x-text="diagnostic.detail"></div>
|
|
355
|
+
<div class="small text-secondary" x-text="diagnostic.impact"></div>
|
|
356
|
+
</div>
|
|
357
|
+
</div>
|
|
358
|
+
<div class="d-flex gap-1 mt-2 flex-wrap">
|
|
359
|
+
<template x-for="action in diagnostic.actions" :key="diagnostic.name + action.label">
|
|
360
|
+
<button class="btn btn-sm btn-ghost-secondary py-0 px-1" @click="runHealthAction(action)">
|
|
361
|
+
<i class="ti me-1" :class="action.icon"></i><span x-text="action.label"></span>
|
|
362
|
+
</button>
|
|
363
|
+
</template>
|
|
364
|
+
</div>
|
|
365
|
+
</div>
|
|
249
366
|
</template>
|
|
250
367
|
</div>
|
|
251
368
|
</template>
|
|
@@ -253,6 +370,49 @@
|
|
|
253
370
|
</div>
|
|
254
371
|
</template>
|
|
255
372
|
|
|
373
|
+
<div class="row g-3 mb-4">
|
|
374
|
+
<div class="col-md-6 col-xl">
|
|
375
|
+
<button class="card attention-card w-100 text-start" :class="{ 'attention-empty': attentionSummary.unreadInbox === 0 }" @click="switchView('inbox')">
|
|
376
|
+
<div class="card-body py-3">
|
|
377
|
+
<div class="text-secondary small">Unread</div>
|
|
378
|
+
<div class="h2 mb-0" :class="attentionSummary.unreadInbox ? 'text-danger' : ''" x-text="attentionSummary.unreadInbox"></div>
|
|
379
|
+
</div>
|
|
380
|
+
</button>
|
|
381
|
+
</div>
|
|
382
|
+
<div class="col-md-6 col-xl">
|
|
383
|
+
<button class="card attention-card w-100 text-start" :class="{ 'attention-empty': attentionSummary.needsHumanResponse === 0 }" @click="switchView('inbox')">
|
|
384
|
+
<div class="card-body py-3">
|
|
385
|
+
<div class="text-secondary small">Needs response</div>
|
|
386
|
+
<div class="h2 mb-0" :class="attentionSummary.needsHumanResponse ? 'text-warning' : ''" x-text="attentionSummary.needsHumanResponse"></div>
|
|
387
|
+
</div>
|
|
388
|
+
</button>
|
|
389
|
+
</div>
|
|
390
|
+
<div class="col-md-6 col-xl">
|
|
391
|
+
<button class="card attention-card w-100 text-start" :class="{ 'attention-empty': attentionSummary.agentQuestions === 0 }" @click="switchView('inbox')">
|
|
392
|
+
<div class="card-body py-3">
|
|
393
|
+
<div class="text-secondary small">Agent questions</div>
|
|
394
|
+
<div class="h2 mb-0" :class="attentionSummary.agentQuestions ? 'text-info' : ''" x-text="attentionSummary.agentQuestions"></div>
|
|
395
|
+
</div>
|
|
396
|
+
</button>
|
|
397
|
+
</div>
|
|
398
|
+
<div class="col-md-6 col-xl">
|
|
399
|
+
<button class="card attention-card w-100 text-start" :class="{ 'attention-empty': attentionSummary.pendingPairInvites === 0 }" @click="switchView('pairs')">
|
|
400
|
+
<div class="card-body py-3">
|
|
401
|
+
<div class="text-secondary small">Pair invites</div>
|
|
402
|
+
<div class="h2 mb-0" :class="attentionSummary.pendingPairInvites ? 'text-warning' : ''" x-text="attentionSummary.pendingPairInvites"></div>
|
|
403
|
+
</div>
|
|
404
|
+
</button>
|
|
405
|
+
</div>
|
|
406
|
+
<div class="col-md-6 col-xl">
|
|
407
|
+
<button class="card attention-card w-100 text-start" :class="{ 'attention-empty': attentionSummary.claimableTasks === 0 }" @click="switchView('work')">
|
|
408
|
+
<div class="card-body py-3">
|
|
409
|
+
<div class="text-secondary small">Claimable waiting</div>
|
|
410
|
+
<div class="h2 mb-0" :class="attentionSummary.claimableTasks ? 'text-warning' : ''" x-text="attentionSummary.claimableTasks"></div>
|
|
411
|
+
</div>
|
|
412
|
+
</button>
|
|
413
|
+
</div>
|
|
414
|
+
</div>
|
|
415
|
+
|
|
256
416
|
<!-- Two-column: Agents + Recent messages -->
|
|
257
417
|
<div class="row g-3">
|
|
258
418
|
<div class="col-lg-5">
|
|
@@ -263,8 +423,8 @@
|
|
|
263
423
|
</div>
|
|
264
424
|
<div class="list-group list-group-flush" style="max-height: 60vh; overflow-y: auto">
|
|
265
425
|
<template x-for="a in sortedAgents.slice(0, 20)" :key="a.id">
|
|
266
|
-
<div class="list-group-item d-flex align-items-center gap-2" style="cursor:pointer" @click="
|
|
267
|
-
<span class="status-dot" :class="
|
|
426
|
+
<div class="list-group-item d-flex align-items-center gap-2" style="cursor:pointer" @click="openAgentDetail(a)">
|
|
427
|
+
<span class="status-dot" :class="agentStatusClass(a)"></span>
|
|
268
428
|
<span class="agent-type-icon" :class="agentType(a)" :title="agentTypeTitle(a)" :aria-label="agentTypeTitle(a)">
|
|
269
429
|
<i class="ti" :class="agentTypeIcon(a)"></i>
|
|
270
430
|
</span>
|
|
@@ -278,6 +438,23 @@
|
|
|
278
438
|
</template>
|
|
279
439
|
</div>
|
|
280
440
|
<div class="text-secondary small text-truncate" x-text="a.id"></div>
|
|
441
|
+
<div class="presence-badges d-flex gap-1 mt-1 flex-wrap">
|
|
442
|
+
<template x-for="badge in agentPresenceBadges(a)" :key="badge.label">
|
|
443
|
+
<span class="badge" :class="badge.className" x-text="badge.label"></span>
|
|
444
|
+
</template>
|
|
445
|
+
</div>
|
|
446
|
+
<template x-if="agentPair(a)">
|
|
447
|
+
<span class="badge pair-badge mt-1" :class="pairBadgeClass(agentPair(a))" :title="pairTitle(agentPair(a), a.id)">
|
|
448
|
+
<i class="ti ti-link me-1"></i><span x-text="pairBadgeLabel(agentPair(a), a.id)"></span>
|
|
449
|
+
</span>
|
|
450
|
+
</template>
|
|
451
|
+
<div class="attention-badges d-flex gap-1 mt-1 flex-wrap" x-show="agentAttention(a).total > 0" :title="agentAttentionTitle(a)">
|
|
452
|
+
<span class="badge bg-danger-lt" x-show="agentAttention(a).unread" x-text="agentAttention(a).unread + ' unread'"></span>
|
|
453
|
+
<span class="badge bg-warning-lt" x-show="agentAttention(a).needsHumanResponse">needs response</span>
|
|
454
|
+
<span class="badge bg-info-lt" x-show="agentAttention(a).agentQuestion">question</span>
|
|
455
|
+
<span class="badge bg-warning-lt" x-show="agentAttention(a).pendingPairInvite">pair invite</span>
|
|
456
|
+
<span class="badge bg-orange-lt" x-show="agentAttention(a).claimableTasks" x-text="agentAttention(a).claimableTasks + ' claimable'"></span>
|
|
457
|
+
</div>
|
|
281
458
|
</div>
|
|
282
459
|
<span class="text-secondary small" x-text="timeAgo(a.lastSeen)"></span>
|
|
283
460
|
</div>
|
|
@@ -292,29 +469,32 @@
|
|
|
292
469
|
<div class="col-lg-7">
|
|
293
470
|
<div class="card">
|
|
294
471
|
<div class="card-header d-flex align-items-center">
|
|
295
|
-
<h3 class="card-title">Recent
|
|
296
|
-
<
|
|
472
|
+
<h3 class="card-title">Recent Activity</h3>
|
|
473
|
+
<button class="btn btn-sm btn-ghost-secondary ms-auto" @click="switchView('activity')">
|
|
474
|
+
<i class="ti ti-arrow-up-right"></i>
|
|
475
|
+
</button>
|
|
297
476
|
</div>
|
|
298
477
|
<div class="card-body p-0" style="max-height: 60vh; overflow-y: auto">
|
|
299
|
-
<template x-for="
|
|
300
|
-
<
|
|
301
|
-
<div class="d-flex align-items-
|
|
302
|
-
<span class="
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
<
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
<
|
|
312
|
-
|
|
478
|
+
<template x-for="item in activityItems.slice(0, 15)" :key="item.id">
|
|
479
|
+
<button type="button" class="list-group-item list-group-item-action activity-item text-start w-100 border-0 border-bottom rounded-0" @click="openActivityItem(item)">
|
|
480
|
+
<div class="d-flex align-items-start gap-3">
|
|
481
|
+
<span class="activity-icon" :class="activityKindClass(item.kind)">
|
|
482
|
+
<i class="ti" :class="item.icon"></i>
|
|
483
|
+
</span>
|
|
484
|
+
<div class="flex-grow-1 min-width-0">
|
|
485
|
+
<div class="d-flex align-items-center gap-2">
|
|
486
|
+
<span class="fw-bold small text-truncate" x-text="item.title"></span>
|
|
487
|
+
<span class="badge" :class="activityKindClass(item.kind)" x-text="item.kind"></span>
|
|
488
|
+
<span class="text-secondary small ms-auto" x-text="timeAgo(item.ts)"></span>
|
|
489
|
+
</div>
|
|
490
|
+
<div class="msg-body text-secondary text-truncate" x-show="item.body" x-text="item.body"></div>
|
|
491
|
+
<div class="text-secondary small text-truncate" x-show="item.meta" x-text="item.meta"></div>
|
|
492
|
+
</div>
|
|
313
493
|
</div>
|
|
314
|
-
</
|
|
494
|
+
</button>
|
|
315
495
|
</template>
|
|
316
|
-
<template x-if="
|
|
317
|
-
<div class="p-3 text-secondary text-center">No
|
|
496
|
+
<template x-if="activityItems.length === 0">
|
|
497
|
+
<div class="p-3 text-secondary text-center">No activity</div>
|
|
318
498
|
</template>
|
|
319
499
|
</div>
|
|
320
500
|
</div>
|
|
@@ -328,6 +508,18 @@
|
|
|
328
508
|
<div class="d-flex align-items-center mb-3 gap-3 flex-wrap">
|
|
329
509
|
<h2 class="page-title mb-0">Agents</h2>
|
|
330
510
|
<div class="ms-auto d-flex gap-2 align-items-center">
|
|
511
|
+
<select class="form-select form-select-sm" style="width:auto; min-width: 150px" x-model="agentPresetFilter">
|
|
512
|
+
<option value="">View: All</option>
|
|
513
|
+
<option value="active">View: Active agents</option>
|
|
514
|
+
<option value="offline_stale">View: Offline stale</option>
|
|
515
|
+
<option value="claude">View: Claude</option>
|
|
516
|
+
<option value="codex">View: Codex</option>
|
|
517
|
+
<option value="paired">View: Paired</option>
|
|
518
|
+
<option value="unpaired">View: Unpaired</option>
|
|
519
|
+
<option value="waiting">View: Waiting for me</option>
|
|
520
|
+
<option value="claimable">View: Claimable</option>
|
|
521
|
+
<option value="errors">View: Errors</option>
|
|
522
|
+
</select>
|
|
331
523
|
<select class="form-select form-select-sm" style="width:auto; min-width: 140px" x-model="agentStatusFilter">
|
|
332
524
|
<option value="">Status: All</option>
|
|
333
525
|
<option value="starting">Status: Starting</option>
|
|
@@ -348,6 +540,10 @@
|
|
|
348
540
|
<option value="lastSeen">Sort: Last seen</option>
|
|
349
541
|
<option value="created">Sort: Created</option>
|
|
350
542
|
</select>
|
|
543
|
+
<label class="form-check form-switch mb-0">
|
|
544
|
+
<input type="checkbox" class="form-check-input" x-model="showBuiltIns">
|
|
545
|
+
<span class="form-check-label small">System agents</span>
|
|
546
|
+
</label>
|
|
351
547
|
<button class="btn btn-sm btn-ghost-secondary" @click="agentSortDir = agentSortDir === 'asc' ? 'desc' : 'asc'">
|
|
352
548
|
<i class="ti" :class="agentSortDir === 'asc' ? 'ti-sort-ascending' : 'ti-sort-descending'"></i>
|
|
353
549
|
</button>
|
|
@@ -365,10 +561,10 @@
|
|
|
365
561
|
<div class="row g-3">
|
|
366
562
|
<template x-for="a in sortedAgents" :key="a.id">
|
|
367
563
|
<div class="col-md-6 col-xl-4">
|
|
368
|
-
<div class="card agent-card" :class="{ selected: selectedAgent === a.id }">
|
|
564
|
+
<div class="card agent-card" :class="{ selected: selectedAgent === a.id }" @click="openAgentDetail(a)">
|
|
369
565
|
<div class="card-body">
|
|
370
566
|
<div class="d-flex align-items-start gap-2">
|
|
371
|
-
<span class="status-dot mt-1" :class="
|
|
567
|
+
<span class="status-dot mt-1" :class="agentStatusClass(a)" :title="agentStatusTitle(a)"></span>
|
|
372
568
|
<span class="agent-type-icon mt-0" :class="agentType(a)" :title="agentTypeTitle(a)" :aria-label="agentTypeTitle(a)">
|
|
373
569
|
<i class="ti" :class="agentTypeIcon(a)"></i>
|
|
374
570
|
</span>
|
|
@@ -380,7 +576,29 @@
|
|
|
380
576
|
<span class="text-truncate" :class="a.label ? 'text-secondary small' : 'fw-bold'" x-text="a.name || a.id.slice(-12)"></span>
|
|
381
577
|
</div>
|
|
382
578
|
<div class="text-secondary small text-truncate mt-1" x-text="a.id"></div>
|
|
579
|
+
<div class="presence-badges d-flex gap-1 mt-1 flex-wrap">
|
|
580
|
+
<span class="badge" :class="'bg-' + agentPresence(a).tone + '-lt'">
|
|
581
|
+
<i class="ti me-1" :class="agentPresence(a).icon"></i><span x-text="agentPresence(a).label"></span>
|
|
582
|
+
</span>
|
|
583
|
+
<template x-for="badge in agentPresenceBadges(a)" :key="badge.label">
|
|
584
|
+
<span class="badge" :class="badge.className" x-text="badge.label"></span>
|
|
585
|
+
</template>
|
|
586
|
+
</div>
|
|
383
587
|
<div class="d-flex gap-1 mt-1 flex-wrap">
|
|
588
|
+
<template x-if="agentAttention(a).total > 0">
|
|
589
|
+
<span class="badge bg-warning text-white" :title="agentAttentionTitle(a)">
|
|
590
|
+
<i class="ti ti-bell me-1"></i><span x-text="agentAttention(a).total"></span>
|
|
591
|
+
</span>
|
|
592
|
+
</template>
|
|
593
|
+
<template x-if="agentPair(a)">
|
|
594
|
+
<span class="badge pair-badge" :class="pairBadgeClass(agentPair(a))" :title="pairTitle(agentPair(a), a.id)">
|
|
595
|
+
<i class="ti ti-link me-1"></i><span x-text="pairBadgeLabel(agentPair(a), a.id)"></span>
|
|
596
|
+
</span>
|
|
597
|
+
</template>
|
|
598
|
+
<span class="badge bg-danger-lt" x-show="agentAttention(a).unread" x-text="agentAttention(a).unread + ' unread'"></span>
|
|
599
|
+
<span class="badge bg-warning-lt" x-show="agentAttention(a).needsHumanResponse">needs response</span>
|
|
600
|
+
<span class="badge bg-info-lt" x-show="agentAttention(a).agentQuestion">question</span>
|
|
601
|
+
<span class="badge bg-orange-lt" x-show="agentAttention(a).claimableTasks" x-text="agentAttention(a).claimableTasks + ' claimable'"></span>
|
|
384
602
|
<template x-if="a.machine">
|
|
385
603
|
<span class="badge bg-secondary-lt" x-text="a.machine"></span>
|
|
386
604
|
</template>
|
|
@@ -407,6 +625,9 @@
|
|
|
407
625
|
<button class="btn btn-sm btn-ghost-secondary p-1" title="Send message" @click.stop="openComposeToAgent(a)">
|
|
408
626
|
<i class="ti ti-send"></i>
|
|
409
627
|
</button>
|
|
628
|
+
<button class="btn btn-sm btn-ghost-secondary p-1" title="Pair with..." @click.stop="openPairInvite(a.id)">
|
|
629
|
+
<i class="ti ti-link-plus"></i>
|
|
630
|
+
</button>
|
|
410
631
|
<button class="btn btn-sm btn-ghost-secondary p-1" title="Rename" @click.stop="openRename(a)">
|
|
411
632
|
<i class="ti ti-pencil"></i>
|
|
412
633
|
</button>
|
|
@@ -432,13 +653,373 @@
|
|
|
432
653
|
<i class="ti ti-robot-off" style="font-size:48px; opacity:0.3"></i>
|
|
433
654
|
<p
|
|
434
655
|
class="mt-2"
|
|
435
|
-
x-text="(agentStatusFilter || agentTagFilter) ? 'No agents match the current filters' : (showOffline ? 'No agents registered' : 'No active agents — enable Show Offline')"
|
|
656
|
+
x-text="(agentPresetFilter || agentStatusFilter || agentTagFilter) ? 'No agents match the current filters' : (hiddenBuiltInAgentCount ? 'Only system agents hidden — enable System agents' : (showOffline ? 'No agents registered' : 'No active agents — enable Show Offline'))"
|
|
436
657
|
></p>
|
|
437
658
|
</div>
|
|
438
659
|
</div>
|
|
439
660
|
</template>
|
|
440
661
|
</div>
|
|
441
662
|
|
|
663
|
+
<!-- ==================== INBOX ==================== -->
|
|
664
|
+
<div x-show="view === 'inbox'" x-cloak class="fade-in">
|
|
665
|
+
<div class="d-flex align-items-center mb-3 gap-2 flex-wrap">
|
|
666
|
+
<h2 class="page-title mb-0">Inbox</h2>
|
|
667
|
+
<span class="badge bg-danger-lt" x-show="attentionSummary.unreadInbox > 0" x-text="attentionSummary.unreadInbox + ' unread'"></span>
|
|
668
|
+
<span class="badge bg-warning-lt" x-show="attentionSummary.needsHumanResponse > 0" x-text="attentionSummary.needsHumanResponse + ' need response'"></span>
|
|
669
|
+
<span class="badge bg-info-lt" x-show="attentionSummary.agentQuestions > 0" x-text="attentionSummary.agentQuestions + ' questions'"></span>
|
|
670
|
+
<div class="ms-auto d-flex gap-2 align-items-center flex-wrap">
|
|
671
|
+
<input type="search" class="form-control form-control-sm" style="width: 220px" placeholder="Search inbox" x-model.debounce.200ms="inboxSearch">
|
|
672
|
+
<label class="form-check form-switch mb-0">
|
|
673
|
+
<input type="checkbox" class="form-check-input" x-model="inboxShowArchived">
|
|
674
|
+
<span class="form-check-label small">Archived</span>
|
|
675
|
+
</label>
|
|
676
|
+
<button class="btn btn-sm btn-ghost-secondary" @click="fetchMessages()">
|
|
677
|
+
<i class="ti ti-refresh"></i>
|
|
678
|
+
</button>
|
|
679
|
+
</div>
|
|
680
|
+
</div>
|
|
681
|
+
|
|
682
|
+
<div class="card mb-3">
|
|
683
|
+
<div class="card-body">
|
|
684
|
+
<div class="row g-2 align-items-start">
|
|
685
|
+
<div class="col-md-2">
|
|
686
|
+
<select class="form-select form-select-sm" x-model="inboxCompose.toMode" @change="resetInboxComposeTarget()">
|
|
687
|
+
<option value="agent">Agent</option>
|
|
688
|
+
<option value="tag">Tag</option>
|
|
689
|
+
<option value="cap">Capability</option>
|
|
690
|
+
</select>
|
|
691
|
+
</div>
|
|
692
|
+
<div class="col-md-3">
|
|
693
|
+
<select class="form-select form-select-sm" x-model="inboxCompose.to">
|
|
694
|
+
<option value="">Target</option>
|
|
695
|
+
<template x-for="option in inboxComposeTargetOptions" :key="option.value">
|
|
696
|
+
<option :value="option.value" x-text="option.label"></option>
|
|
697
|
+
</template>
|
|
698
|
+
</select>
|
|
699
|
+
</div>
|
|
700
|
+
<div class="col-md-3">
|
|
701
|
+
<input type="text" class="form-control form-control-sm" placeholder="Subject" x-model="inboxCompose.subject">
|
|
702
|
+
</div>
|
|
703
|
+
<div class="col-md-2">
|
|
704
|
+
<input type="text" class="form-control form-control-sm" placeholder="Channel" x-model="inboxCompose.channel">
|
|
705
|
+
</div>
|
|
706
|
+
<div class="col-md-2 d-flex gap-2 align-items-center">
|
|
707
|
+
<label class="form-check mb-0">
|
|
708
|
+
<input type="checkbox" class="form-check-input" x-model="inboxCompose.claimable">
|
|
709
|
+
<span class="form-check-label small">Claimable</span>
|
|
710
|
+
</label>
|
|
711
|
+
</div>
|
|
712
|
+
<div class="col-12">
|
|
713
|
+
<div class="d-flex gap-2">
|
|
714
|
+
<textarea class="form-control" rows="2" placeholder="Message" x-model="inboxCompose.body" @keydown.ctrl.enter.prevent="doSendInboxCompose()" @keydown.meta.enter.prevent="doSendInboxCompose()"></textarea>
|
|
715
|
+
<button class="btn btn-primary align-self-stretch" @click="doSendInboxCompose()" :disabled="!inboxCompose.to || !inboxCompose.body">
|
|
716
|
+
<i class="ti ti-send"></i>
|
|
717
|
+
</button>
|
|
718
|
+
</div>
|
|
719
|
+
</div>
|
|
720
|
+
</div>
|
|
721
|
+
</div>
|
|
722
|
+
</div>
|
|
723
|
+
|
|
724
|
+
<div class="row g-3 inbox-fit">
|
|
725
|
+
<div class="col-lg-4 h-100">
|
|
726
|
+
<div class="card h-100">
|
|
727
|
+
<div class="list-group list-group-flush h-100 overflow-auto">
|
|
728
|
+
<template x-for="thread in inboxThreads" :key="thread.id">
|
|
729
|
+
<div
|
|
730
|
+
class="list-group-item list-group-item-action inbox-thread"
|
|
731
|
+
:class="{ active: selectedInboxThreadData?.id === thread.id, attention: thread.attention?.score > 0, 'text-secondary': thread.archived }"
|
|
732
|
+
@click="openInboxThread(thread)"
|
|
733
|
+
>
|
|
734
|
+
<div class="d-flex align-items-center gap-2">
|
|
735
|
+
<span class="agent-type-icon" :class="agentType(agentsById[thread.peer])" :title="agentTypeTitle(agentsById[thread.peer])">
|
|
736
|
+
<i class="ti" :class="agentTypeIcon(agentsById[thread.peer])"></i>
|
|
737
|
+
</span>
|
|
738
|
+
<span class="fw-bold text-truncate" x-text="conversationTitle(thread)"></span>
|
|
739
|
+
<span class="badge bg-danger text-white" x-show="thread.attention?.unread" x-text="thread.attention.unread"></span>
|
|
740
|
+
<span class="text-secondary small ms-auto" x-text="timeAgo(thread.lastMessage?.createdAt)"></span>
|
|
741
|
+
</div>
|
|
742
|
+
<div class="text-secondary small text-truncate mt-1 inbox-thread-snippet" x-text="messagePreview(thread.lastMessage)"></div>
|
|
743
|
+
<div class="attention-badges d-flex gap-1 mt-1 flex-wrap" x-show="thread.attention?.score > 0">
|
|
744
|
+
<span class="badge bg-warning-lt" x-show="thread.attention?.needsHumanResponse">needs response</span>
|
|
745
|
+
<span class="badge bg-info-lt" x-show="thread.attention?.agentQuestion">agent asked a question</span>
|
|
746
|
+
</div>
|
|
747
|
+
<div class="d-flex align-items-center gap-1 mt-2">
|
|
748
|
+
<span class="badge bg-secondary-lt" x-show="thread.archived">archived</span>
|
|
749
|
+
<span class="badge bg-primary-lt" x-show="thread.draft">draft</span>
|
|
750
|
+
<button class="btn btn-sm btn-ghost-secondary py-0 px-1 ms-auto" title="Mark unread" @click.stop="markInboxThreadUnread(thread)">
|
|
751
|
+
<i class="ti ti-mail"></i>
|
|
752
|
+
</button>
|
|
753
|
+
<button class="btn btn-sm btn-ghost-secondary py-0 px-1" :title="thread.archived ? 'Unarchive' : 'Archive'" @click.stop="thread.archived ? unarchiveInboxThread(thread) : archiveInboxThread(thread)">
|
|
754
|
+
<i class="ti" :class="thread.archived ? 'ti-archive-off' : 'ti-archive'"></i>
|
|
755
|
+
</button>
|
|
756
|
+
<button class="btn btn-sm btn-ghost-danger py-0 px-1" title="Delete thread" @click.stop="confirmDeleteInboxThread(thread)">
|
|
757
|
+
<i class="ti ti-trash"></i>
|
|
758
|
+
</button>
|
|
759
|
+
</div>
|
|
760
|
+
</div>
|
|
761
|
+
</template>
|
|
762
|
+
<template x-if="inboxThreads.length === 0">
|
|
763
|
+
<div class="list-group-item text-secondary text-center py-5">
|
|
764
|
+
<i class="ti ti-inbox-off" style="font-size:48px; opacity:0.3"></i>
|
|
765
|
+
<p class="mt-2 mb-0" x-text="inboxSearch ? 'No matching threads' : 'No correspondence'"></p>
|
|
766
|
+
</div>
|
|
767
|
+
</template>
|
|
768
|
+
</div>
|
|
769
|
+
</div>
|
|
770
|
+
</div>
|
|
771
|
+
|
|
772
|
+
<div class="col-lg-8 h-100">
|
|
773
|
+
<div class="card h-100">
|
|
774
|
+
<template x-if="selectedInboxThreadData">
|
|
775
|
+
<div class="card-header d-flex align-items-center gap-2">
|
|
776
|
+
<h3 class="card-title mb-0 text-truncate" x-text="conversationTitle(selectedInboxThreadData)"></h3>
|
|
777
|
+
<div class="attention-badges d-flex gap-1 flex-wrap">
|
|
778
|
+
<span class="badge bg-danger-lt" x-show="selectedInboxThreadData.attention?.unread" x-text="selectedInboxThreadData.attention.unread + ' unread'"></span>
|
|
779
|
+
<span class="badge bg-warning-lt" x-show="selectedInboxThreadData.attention?.needsHumanResponse">needs response</span>
|
|
780
|
+
<span class="badge bg-info-lt" x-show="selectedInboxThreadData.attention?.agentQuestion">agent asked a question</span>
|
|
781
|
+
<span class="badge bg-secondary-lt" x-show="selectedInboxThreadData.archived">archived</span>
|
|
782
|
+
</div>
|
|
783
|
+
<button class="btn btn-sm btn-ghost-secondary ms-auto" title="Mark unread" @click="markInboxThreadUnread(selectedInboxThreadData)">
|
|
784
|
+
<i class="ti ti-mail"></i>
|
|
785
|
+
</button>
|
|
786
|
+
<button class="btn btn-sm btn-ghost-secondary" :title="selectedInboxThreadData.archived ? 'Unarchive' : 'Archive'" @click="selectedInboxThreadData.archived ? unarchiveInboxThread(selectedInboxThreadData) : archiveInboxThread(selectedInboxThreadData)">
|
|
787
|
+
<i class="ti" :class="selectedInboxThreadData.archived ? 'ti-archive-off' : 'ti-archive'"></i>
|
|
788
|
+
</button>
|
|
789
|
+
<button class="btn btn-sm btn-ghost-secondary" title="Export thread Markdown" @click="exportThread(selectedInboxThreadData, 'markdown')">
|
|
790
|
+
<i class="ti ti-file-export"></i>
|
|
791
|
+
</button>
|
|
792
|
+
<button class="btn btn-sm btn-ghost-secondary" title="Export thread JSON" @click="exportThread(selectedInboxThreadData, 'json')">
|
|
793
|
+
<i class="ti ti-braces"></i>
|
|
794
|
+
</button>
|
|
795
|
+
<button class="btn btn-sm btn-ghost-danger" title="Delete thread" @click="confirmDeleteInboxThread(selectedInboxThreadData)">
|
|
796
|
+
<i class="ti ti-trash"></i>
|
|
797
|
+
</button>
|
|
798
|
+
</div>
|
|
799
|
+
</template>
|
|
800
|
+
<div class="card-body p-0 overflow-auto">
|
|
801
|
+
<template x-if="!selectedInboxThreadData">
|
|
802
|
+
<div class="p-4 text-center text-secondary">
|
|
803
|
+
<i class="ti ti-inbox" style="font-size:48px; opacity:0.3"></i>
|
|
804
|
+
<p class="mt-2">Select a conversation</p>
|
|
805
|
+
</div>
|
|
806
|
+
</template>
|
|
807
|
+
<template x-for="m in selectedInboxMessages" :key="m.id">
|
|
808
|
+
<div class="msg-card p-3 border-bottom" :class="{ claimed: m.claimedBy }">
|
|
809
|
+
<div class="d-flex align-items-center gap-2 mb-1">
|
|
810
|
+
<span class="fw-bold small" x-text="displayTarget(m.from)"></span>
|
|
811
|
+
<i class="ti ti-arrow-right text-secondary" style="font-size:12px"></i>
|
|
812
|
+
<span class="small" x-text="displayTarget(m.to)"></span>
|
|
813
|
+
<span class="text-secondary small ms-auto" x-text="'#' + m.id + ' · ' + fmtTime(m.createdAt)"></span>
|
|
814
|
+
</div>
|
|
815
|
+
<template x-if="m.subject">
|
|
816
|
+
<div class="fw-bold small mb-1" x-text="m.subject"></div>
|
|
817
|
+
</template>
|
|
818
|
+
<div class="msg-body" x-text="m.body"></div>
|
|
819
|
+
<div class="d-flex align-items-center gap-2 mt-2 flex-wrap">
|
|
820
|
+
<button class="btn btn-sm btn-ghost-primary py-0 px-1" @click="startReply(m)">
|
|
821
|
+
<i class="ti ti-corner-up-left" style="font-size:14px"></i> Reply
|
|
822
|
+
</button>
|
|
823
|
+
<template x-if="m.channel">
|
|
824
|
+
<span class="badge bg-warning-lt" x-text="'#' + m.channel"></span>
|
|
825
|
+
</template>
|
|
826
|
+
<template x-if="m.claimedBy">
|
|
827
|
+
<span class="badge bg-success-lt" x-text="'claimed by ' + displayTarget(m.claimedBy)"></span>
|
|
828
|
+
</template>
|
|
829
|
+
<template x-if="m.replyTo">
|
|
830
|
+
<span class="text-secondary small ms-auto" x-text="'reply to #' + m.replyTo"></span>
|
|
831
|
+
</template>
|
|
832
|
+
</div>
|
|
833
|
+
</div>
|
|
834
|
+
</template>
|
|
835
|
+
</div>
|
|
836
|
+
<template x-if="selectedInboxThreadData">
|
|
837
|
+
<div class="card-footer">
|
|
838
|
+
<div class="d-flex gap-2 align-items-start">
|
|
839
|
+
<textarea
|
|
840
|
+
class="form-control"
|
|
841
|
+
rows="3"
|
|
842
|
+
placeholder="Reply"
|
|
843
|
+
:value="replyDraftForThread(selectedInboxThreadData)"
|
|
844
|
+
@input="setReplyDraft(selectedInboxThreadData, $event.target.value)"
|
|
845
|
+
@keydown.ctrl.enter.prevent="sendInboxReply(selectedInboxThreadData)"
|
|
846
|
+
@keydown.meta.enter.prevent="sendInboxReply(selectedInboxThreadData)"
|
|
847
|
+
></textarea>
|
|
848
|
+
<div class="d-flex flex-column gap-2">
|
|
849
|
+
<button class="btn btn-primary" @click="sendInboxReply(selectedInboxThreadData)" :disabled="!replyDraftForThread(selectedInboxThreadData)">
|
|
850
|
+
<i class="ti ti-corner-up-left"></i>
|
|
851
|
+
</button>
|
|
852
|
+
<button class="btn btn-ghost-secondary" @click="clearReplyDraft(selectedInboxThreadData)" :disabled="!replyDraftForThread(selectedInboxThreadData)">
|
|
853
|
+
<i class="ti ti-eraser"></i>
|
|
854
|
+
</button>
|
|
855
|
+
</div>
|
|
856
|
+
</div>
|
|
857
|
+
</div>
|
|
858
|
+
</template>
|
|
859
|
+
</div>
|
|
860
|
+
</div>
|
|
861
|
+
</div>
|
|
862
|
+
</div>
|
|
863
|
+
|
|
864
|
+
<!-- ==================== ACTIVITY ==================== -->
|
|
865
|
+
<div x-show="view === 'activity'" x-cloak class="fade-in">
|
|
866
|
+
<div class="d-flex align-items-center mb-3 gap-2 flex-wrap">
|
|
867
|
+
<h2 class="page-title mb-0">Activity</h2>
|
|
868
|
+
<span class="badge bg-secondary-lt" x-text="activityItems.length + ' events'"></span>
|
|
869
|
+
<div class="ms-auto d-flex gap-2 align-items-center flex-wrap">
|
|
870
|
+
<select class="form-select form-select-sm" style="width:auto; min-width: 150px" x-model="activityFilter">
|
|
871
|
+
<option value="">Type: All</option>
|
|
872
|
+
<option value="question">Questions</option>
|
|
873
|
+
<option value="reply">Replies</option>
|
|
874
|
+
<option value="message">Messages</option>
|
|
875
|
+
<option value="operator">Operator</option>
|
|
876
|
+
<option value="pair">Pairs</option>
|
|
877
|
+
<option value="task">Tasks</option>
|
|
878
|
+
<option value="state">State</option>
|
|
879
|
+
</select>
|
|
880
|
+
<button class="btn btn-sm btn-ghost-secondary" @click="refreshLiveData()">
|
|
881
|
+
<i class="ti ti-refresh"></i>
|
|
882
|
+
</button>
|
|
883
|
+
<button class="btn btn-sm btn-ghost-secondary" @click="exportActivity('markdown')" title="Export timeline Markdown">
|
|
884
|
+
<i class="ti ti-file-export"></i>
|
|
885
|
+
</button>
|
|
886
|
+
<button class="btn btn-sm btn-ghost-secondary" @click="exportActivity('json')" title="Export timeline JSON">
|
|
887
|
+
<i class="ti ti-braces"></i>
|
|
888
|
+
</button>
|
|
889
|
+
</div>
|
|
890
|
+
</div>
|
|
891
|
+
|
|
892
|
+
<div class="card">
|
|
893
|
+
<div class="list-group list-group-flush">
|
|
894
|
+
<template x-for="item in activityItems" :key="item.id">
|
|
895
|
+
<button type="button" class="list-group-item list-group-item-action activity-item text-start" @click="openActivityItem(item)">
|
|
896
|
+
<div class="d-flex align-items-start gap-3">
|
|
897
|
+
<span class="activity-icon" :class="activityKindClass(item.kind)">
|
|
898
|
+
<i class="ti" :class="item.icon"></i>
|
|
899
|
+
</span>
|
|
900
|
+
<div class="flex-grow-1 min-width-0">
|
|
901
|
+
<div class="d-flex align-items-center gap-2 flex-wrap">
|
|
902
|
+
<span class="fw-bold text-truncate" x-text="item.title"></span>
|
|
903
|
+
<span class="badge" :class="activityKindClass(item.kind)" x-text="item.kind"></span>
|
|
904
|
+
<span class="text-secondary small ms-auto" x-text="timeAgo(item.ts)"></span>
|
|
905
|
+
</div>
|
|
906
|
+
<div class="msg-body mt-1" x-show="item.body" x-text="item.body"></div>
|
|
907
|
+
<div class="text-secondary small mt-1 text-truncate" x-show="item.meta" x-text="item.meta"></div>
|
|
908
|
+
</div>
|
|
909
|
+
</div>
|
|
910
|
+
</button>
|
|
911
|
+
</template>
|
|
912
|
+
<template x-if="activityItems.length === 0">
|
|
913
|
+
<div class="list-group-item text-secondary text-center py-5">
|
|
914
|
+
<i class="ti ti-activity" style="font-size:48px; opacity:0.3"></i>
|
|
915
|
+
<p class="mt-2 mb-0">No activity</p>
|
|
916
|
+
</div>
|
|
917
|
+
</template>
|
|
918
|
+
</div>
|
|
919
|
+
</div>
|
|
920
|
+
</div>
|
|
921
|
+
|
|
922
|
+
<!-- ==================== PAIRS ==================== -->
|
|
923
|
+
<div x-show="view === 'pairs'" x-cloak class="fade-in">
|
|
924
|
+
<div class="d-flex align-items-center mb-3 gap-2 flex-wrap">
|
|
925
|
+
<h2 class="page-title mb-0">Pairs</h2>
|
|
926
|
+
<span class="badge bg-warning-lt" x-show="attentionSummary.pendingPairInvites > 0" x-text="attentionSummary.pendingPairInvites + ' invites pending'"></span>
|
|
927
|
+
<div class="ms-auto d-flex gap-2 align-items-center flex-wrap">
|
|
928
|
+
<select class="form-select form-select-sm" style="width:auto; min-width: 150px" x-model="pairStatusFilter" @change="fetchPairs()">
|
|
929
|
+
<option value="open">Status: Open</option>
|
|
930
|
+
<option value="">Status: All</option>
|
|
931
|
+
<option value="pending">Status: Pending</option>
|
|
932
|
+
<option value="active">Status: Active</option>
|
|
933
|
+
<option value="ended">Status: Ended</option>
|
|
934
|
+
<option value="rejected">Status: Rejected</option>
|
|
935
|
+
<option value="expired">Status: Expired</option>
|
|
936
|
+
</select>
|
|
937
|
+
<button class="btn btn-sm btn-ghost-secondary" @click="fetchPairs()">
|
|
938
|
+
<i class="ti ti-refresh"></i>
|
|
939
|
+
</button>
|
|
940
|
+
<button class="btn btn-sm btn-primary" @click="openPairInvite()">
|
|
941
|
+
<i class="ti ti-link-plus me-1"></i>Pair agents
|
|
942
|
+
</button>
|
|
943
|
+
</div>
|
|
944
|
+
</div>
|
|
945
|
+
|
|
946
|
+
<div class="row g-3">
|
|
947
|
+
<template x-for="pair in pairs" :key="pair.id">
|
|
948
|
+
<div class="col-lg-6">
|
|
949
|
+
<div class="card pair-card" :class="pair.status">
|
|
950
|
+
<div class="card-body">
|
|
951
|
+
<div class="d-flex align-items-start gap-3">
|
|
952
|
+
<span class="badge text-white mt-1" :class="pairStatusClass(pair)" x-text="pair.status"></span>
|
|
953
|
+
<div class="flex-grow-1 min-width-0">
|
|
954
|
+
<template x-if="pair.status === 'pending'">
|
|
955
|
+
<span class="badge bg-warning-lt mb-2">
|
|
956
|
+
<i class="ti ti-bell me-1"></i>pair invite pending
|
|
957
|
+
</span>
|
|
958
|
+
</template>
|
|
959
|
+
<div class="d-flex align-items-center gap-2 flex-wrap">
|
|
960
|
+
<button class="btn btn-sm btn-ghost-secondary py-0 px-1" @click="openAgentDetail(agentsById[pair.requesterId])">
|
|
961
|
+
<i class="ti ti-arrow-up-right me-1"></i><span x-text="displayTarget(pair.requesterId)"></span>
|
|
962
|
+
</button>
|
|
963
|
+
<i class="ti ti-arrows-exchange text-secondary"></i>
|
|
964
|
+
<button class="btn btn-sm btn-ghost-secondary py-0 px-1" @click="openAgentDetail(agentsById[pair.targetId])">
|
|
965
|
+
<span x-text="displayTarget(pair.targetId)"></span>
|
|
966
|
+
</button>
|
|
967
|
+
</div>
|
|
968
|
+
<div class="text-secondary small mt-1 text-truncate">
|
|
969
|
+
<span x-text="pair.id"></span>
|
|
970
|
+
<span class="mx-1">·</span>
|
|
971
|
+
<span x-text="'Updated ' + timeAgo(pair.updatedAt || pair.createdAt)"></span>
|
|
972
|
+
</div>
|
|
973
|
+
<template x-if="pair.objective">
|
|
974
|
+
<div class="msg-body mt-2" x-text="pair.objective"></div>
|
|
975
|
+
</template>
|
|
976
|
+
<div class="d-flex align-items-center gap-2 mt-3 flex-wrap">
|
|
977
|
+
<template x-if="pair.status === 'pending'">
|
|
978
|
+
<button class="btn btn-sm btn-success" @click="doAcceptPair(pair)">
|
|
979
|
+
<i class="ti ti-check me-1"></i>Accept
|
|
980
|
+
</button>
|
|
981
|
+
</template>
|
|
982
|
+
<template x-if="pair.status === 'pending'">
|
|
983
|
+
<button class="btn btn-sm btn-ghost-danger" @click="doRejectPair(pair)">
|
|
984
|
+
<i class="ti ti-x me-1"></i>Reject
|
|
985
|
+
</button>
|
|
986
|
+
</template>
|
|
987
|
+
<template x-if="pair.status === 'active'">
|
|
988
|
+
<button class="btn btn-sm btn-primary" @click="openPairMessage(pair)">
|
|
989
|
+
<i class="ti ti-send me-1"></i>Message
|
|
990
|
+
</button>
|
|
991
|
+
</template>
|
|
992
|
+
<template x-if="pair.status === 'active'">
|
|
993
|
+
<button class="btn btn-sm btn-ghost-danger" @click="doHangupPair(pair)">
|
|
994
|
+
<i class="ti ti-phone-off me-1"></i>Hang up
|
|
995
|
+
</button>
|
|
996
|
+
</template>
|
|
997
|
+
<button class="btn btn-sm btn-ghost-secondary py-0 px-1" @click="exportPair(pair, 'markdown')" title="Export pair Markdown">
|
|
998
|
+
<i class="ti ti-file-export"></i>
|
|
999
|
+
</button>
|
|
1000
|
+
<button class="btn btn-sm btn-ghost-secondary py-0 px-1" @click="exportPair(pair, 'json')" title="Export pair JSON">
|
|
1001
|
+
<i class="ti ti-braces"></i>
|
|
1002
|
+
</button>
|
|
1003
|
+
<span class="text-secondary small ms-auto" x-text="'Created ' + fmtTime(pair.createdAt)"></span>
|
|
1004
|
+
</div>
|
|
1005
|
+
</div>
|
|
1006
|
+
</div>
|
|
1007
|
+
</div>
|
|
1008
|
+
</div>
|
|
1009
|
+
</div>
|
|
1010
|
+
</template>
|
|
1011
|
+
</div>
|
|
1012
|
+
|
|
1013
|
+
<template x-if="pairs.length === 0">
|
|
1014
|
+
<div class="card">
|
|
1015
|
+
<div class="card-body text-center text-secondary py-5">
|
|
1016
|
+
<i class="ti ti-link-off" style="font-size:48px; opacity:0.3"></i>
|
|
1017
|
+
<p class="mt-2">No pair sessions</p>
|
|
1018
|
+
</div>
|
|
1019
|
+
</div>
|
|
1020
|
+
</template>
|
|
1021
|
+
</div>
|
|
1022
|
+
|
|
442
1023
|
<!-- ==================== MESSAGES ==================== -->
|
|
443
1024
|
<div x-show="view === 'messages'" x-cloak class="fade-in">
|
|
444
1025
|
|
|
@@ -535,10 +1116,112 @@
|
|
|
535
1116
|
</div>
|
|
536
1117
|
</div>
|
|
537
1118
|
|
|
1119
|
+
<!-- ==================== WORK QUEUE ==================== -->
|
|
1120
|
+
<div x-show="view === 'work'" x-cloak class="fade-in">
|
|
1121
|
+
<div class="d-flex align-items-center mb-3 gap-2 flex-wrap">
|
|
1122
|
+
<h2 class="page-title mb-0">Work Queue</h2>
|
|
1123
|
+
<span class="badge bg-warning-lt" x-show="attentionSummary.claimableTasks > 0" x-text="attentionSummary.claimableTasks + ' claimable waiting'"></span>
|
|
1124
|
+
<div class="ms-auto d-flex gap-2 align-items-center flex-wrap">
|
|
1125
|
+
<select class="form-select form-select-sm" style="width: auto; min-width: 160px" x-model="selectedAgent">
|
|
1126
|
+
<option value="">Claim as...</option>
|
|
1127
|
+
<template x-for="a in composeAgents" :key="a.id">
|
|
1128
|
+
<option :value="a.id" x-text="displayName(a) + ' [' + a.id.slice(-6) + ']'"></option>
|
|
1129
|
+
</template>
|
|
1130
|
+
</select>
|
|
1131
|
+
<button class="btn btn-sm btn-ghost-secondary" @click="fetchMessages(); fetchTasks()">
|
|
1132
|
+
<i class="ti ti-refresh"></i>
|
|
1133
|
+
</button>
|
|
1134
|
+
</div>
|
|
1135
|
+
</div>
|
|
1136
|
+
|
|
1137
|
+
<div class="card">
|
|
1138
|
+
<div class="table-responsive">
|
|
1139
|
+
<table class="table table-vcenter card-table">
|
|
1140
|
+
<thead>
|
|
1141
|
+
<tr>
|
|
1142
|
+
<th>Work</th>
|
|
1143
|
+
<th>Severity</th>
|
|
1144
|
+
<th>Status</th>
|
|
1145
|
+
<th>Owner</th>
|
|
1146
|
+
<th>Age</th>
|
|
1147
|
+
<th class="w-1"></th>
|
|
1148
|
+
</tr>
|
|
1149
|
+
</thead>
|
|
1150
|
+
<tbody>
|
|
1151
|
+
<template x-for="item in workQueueItems" :key="item.id">
|
|
1152
|
+
<tr>
|
|
1153
|
+
<td class="min-width-0">
|
|
1154
|
+
<div class="d-flex align-items-center gap-2">
|
|
1155
|
+
<span class="badge bg-secondary-lt" x-text="item.sourceType"></span>
|
|
1156
|
+
<span class="fw-bold text-truncate" x-text="item.title"></span>
|
|
1157
|
+
</div>
|
|
1158
|
+
<div class="text-secondary small text-truncate mt-1" x-text="item.body"></div>
|
|
1159
|
+
<div class="d-flex gap-1 mt-1 flex-wrap">
|
|
1160
|
+
<span class="badge bg-secondary-lt" x-text="item.source"></span>
|
|
1161
|
+
<span class="badge bg-primary-lt" x-text="displayTarget(item.target)"></span>
|
|
1162
|
+
<span class="badge bg-warning-lt" x-show="item.channel" x-text="'#' + item.channel"></span>
|
|
1163
|
+
</div>
|
|
1164
|
+
</td>
|
|
1165
|
+
<td><span class="badge" :class="severityClass(item.severity)" x-text="item.severity"></span></td>
|
|
1166
|
+
<td>
|
|
1167
|
+
<template x-if="item.sourceType === 'task'">
|
|
1168
|
+
<select class="form-select form-select-sm" style="min-width: 140px" :value="item.status" @change="doUpdateTaskStatus(item.task, $event.target.value)">
|
|
1169
|
+
<option value="open">Open</option>
|
|
1170
|
+
<option value="claimed">Claimed</option>
|
|
1171
|
+
<option value="in_progress">In Progress</option>
|
|
1172
|
+
<option value="blocked">Blocked</option>
|
|
1173
|
+
<option value="done">Done</option>
|
|
1174
|
+
<option value="failed">Failed</option>
|
|
1175
|
+
<option value="canceled">Canceled</option>
|
|
1176
|
+
</select>
|
|
1177
|
+
</template>
|
|
1178
|
+
<template x-if="item.sourceType === 'message'">
|
|
1179
|
+
<span class="badge bg-secondary-lt" x-text="item.status"></span>
|
|
1180
|
+
</template>
|
|
1181
|
+
</td>
|
|
1182
|
+
<td>
|
|
1183
|
+
<span x-show="item.owner" x-text="displayTarget(item.owner)"></span>
|
|
1184
|
+
<span class="text-secondary" x-show="!item.owner">Unassigned</span>
|
|
1185
|
+
</td>
|
|
1186
|
+
<td class="text-secondary small" x-text="timeAgo(item.updatedAt)"></td>
|
|
1187
|
+
<td>
|
|
1188
|
+
<div class="d-flex gap-1 justify-content-end">
|
|
1189
|
+
<template x-if="item.sourceType === 'message' && item.claimable">
|
|
1190
|
+
<button class="btn btn-sm btn-ghost-warning py-0 px-1" @click="doClaim(item.message.id)">
|
|
1191
|
+
<i class="ti ti-hand-grab me-1"></i>Claim
|
|
1192
|
+
</button>
|
|
1193
|
+
</template>
|
|
1194
|
+
<template x-if="item.sourceType === 'task' && item.claimable">
|
|
1195
|
+
<button class="btn btn-sm btn-ghost-warning py-0 px-1" @click="doClaimTask(item.task.id)">
|
|
1196
|
+
<i class="ti ti-hand-grab me-1"></i>Claim
|
|
1197
|
+
</button>
|
|
1198
|
+
</template>
|
|
1199
|
+
<template x-if="item.sourceType === 'task'">
|
|
1200
|
+
<button class="btn btn-sm btn-ghost-secondary py-0 px-1" @click="openTaskEvents(item.task)">
|
|
1201
|
+
<i class="ti ti-history"></i>
|
|
1202
|
+
</button>
|
|
1203
|
+
</template>
|
|
1204
|
+
</div>
|
|
1205
|
+
</td>
|
|
1206
|
+
</tr>
|
|
1207
|
+
</template>
|
|
1208
|
+
</tbody>
|
|
1209
|
+
</table>
|
|
1210
|
+
</div>
|
|
1211
|
+
<template x-if="workQueueItems.length === 0">
|
|
1212
|
+
<div class="card-body text-center text-secondary py-5">
|
|
1213
|
+
<i class="ti ti-list-check" style="font-size:48px; opacity:0.3"></i>
|
|
1214
|
+
<p class="mt-2">No queued work</p>
|
|
1215
|
+
</div>
|
|
1216
|
+
</template>
|
|
1217
|
+
</div>
|
|
1218
|
+
</div>
|
|
1219
|
+
|
|
538
1220
|
<!-- ==================== TASKS ==================== -->
|
|
539
1221
|
<div x-show="view === 'tasks'" x-cloak class="fade-in">
|
|
540
1222
|
<div class="d-flex align-items-center mb-3 gap-2 flex-wrap">
|
|
541
1223
|
<h2 class="page-title mb-0">Tasks</h2>
|
|
1224
|
+
<span class="badge bg-warning-lt" x-show="attentionSummary.claimableTasks > 0" x-text="attentionSummary.claimableTasks + ' claimable waiting'"></span>
|
|
542
1225
|
<div class="ms-auto d-flex gap-2 align-items-center flex-wrap">
|
|
543
1226
|
<select class="form-select form-select-sm" style="width:auto; min-width: 150px" x-model="taskStatusFilter" @change="fetchTasks()">
|
|
544
1227
|
<option value="">Status: Active</option>
|
|
@@ -568,6 +1251,7 @@
|
|
|
568
1251
|
<div class="d-flex align-items-center gap-2">
|
|
569
1252
|
<span class="fw-bold text-truncate" x-text="task.title"></span>
|
|
570
1253
|
<span class="badge bg-secondary-lt" x-text="task.status"></span>
|
|
1254
|
+
<span class="badge bg-warning-lt" x-show="['open','blocked'].includes(task.status) && !task.claimedBy">claimable waiting</span>
|
|
571
1255
|
</div>
|
|
572
1256
|
<div class="text-secondary small mt-1">
|
|
573
1257
|
<span x-text="'#' + task.id"></span>
|
|
@@ -599,6 +1283,12 @@
|
|
|
599
1283
|
<i class="ti ti-history" style="font-size:14px"></i>
|
|
600
1284
|
Events
|
|
601
1285
|
</button>
|
|
1286
|
+
<button class="btn btn-sm btn-ghost-secondary py-0 px-1" @click="exportTask(task, 'markdown')" title="Export task Markdown">
|
|
1287
|
+
<i class="ti ti-file-export" style="font-size:14px"></i>
|
|
1288
|
+
</button>
|
|
1289
|
+
<button class="btn btn-sm btn-ghost-secondary py-0 px-1" @click="exportTask(task, 'json')" title="Export task JSON">
|
|
1290
|
+
<i class="ti ti-braces" style="font-size:14px"></i>
|
|
1291
|
+
</button>
|
|
602
1292
|
</div>
|
|
603
1293
|
</div>
|
|
604
1294
|
<span class="text-secondary small" x-text="timeAgo(task.updatedAt)"></span>
|
|
@@ -690,7 +1380,216 @@
|
|
|
690
1380
|
</div>
|
|
691
1381
|
|
|
692
1382
|
</div>
|
|
693
|
-
|
|
1383
|
+
</main>
|
|
1384
|
+
|
|
1385
|
+
<!-- ==================== COMMAND PALETTE ==================== -->
|
|
1386
|
+
<div class="command-palette" x-show="commandPaletteOpen" x-cloak @click.self="closeCommandPalette()">
|
|
1387
|
+
<div class="command-palette-panel" @keydown.escape.stop="closeCommandPalette()">
|
|
1388
|
+
<div class="p-3 border-bottom">
|
|
1389
|
+
<div class="input-icon">
|
|
1390
|
+
<span class="input-icon-addon"><i class="ti ti-search"></i></span>
|
|
1391
|
+
<input
|
|
1392
|
+
type="search"
|
|
1393
|
+
class="form-control form-control-lg"
|
|
1394
|
+
placeholder="Command"
|
|
1395
|
+
x-model.debounce.100ms="commandQuery"
|
|
1396
|
+
x-ref="commandSearch"
|
|
1397
|
+
@keydown.enter.prevent="runCommand(commandPaletteItems[0])"
|
|
1398
|
+
>
|
|
1399
|
+
</div>
|
|
1400
|
+
</div>
|
|
1401
|
+
<div class="list-group list-group-flush" style="max-height: 60vh; overflow-y: auto">
|
|
1402
|
+
<template x-for="command in commandPaletteItems" :key="command.id">
|
|
1403
|
+
<button type="button" class="list-group-item list-group-item-action command-palette-item text-start" @click="runCommand(command)">
|
|
1404
|
+
<div class="d-flex align-items-center gap-3">
|
|
1405
|
+
<span class="activity-icon bg-secondary-lt">
|
|
1406
|
+
<i class="ti" :class="command.icon"></i>
|
|
1407
|
+
</span>
|
|
1408
|
+
<div class="min-width-0">
|
|
1409
|
+
<div class="fw-bold text-truncate" x-text="command.title"></div>
|
|
1410
|
+
<div class="text-secondary small text-truncate" x-text="command.subtitle"></div>
|
|
1411
|
+
</div>
|
|
1412
|
+
</div>
|
|
1413
|
+
</button>
|
|
1414
|
+
</template>
|
|
1415
|
+
<template x-if="commandPaletteItems.length === 0">
|
|
1416
|
+
<div class="list-group-item text-secondary text-center py-4">No commands</div>
|
|
1417
|
+
</template>
|
|
1418
|
+
</div>
|
|
1419
|
+
</div>
|
|
1420
|
+
</div>
|
|
1421
|
+
|
|
1422
|
+
<!-- ==================== AGENT DETAIL DRAWER ==================== -->
|
|
1423
|
+
<template x-if="selectedAgentDetail">
|
|
1424
|
+
<div>
|
|
1425
|
+
<div class="agent-drawer-backdrop" x-show="agentDetailOpen" x-cloak @click="closeAgentDetail()"></div>
|
|
1426
|
+
<aside class="agent-drawer" x-show="agentDetailOpen" x-cloak>
|
|
1427
|
+
<div class="p-3 border-bottom d-flex align-items-start gap-2">
|
|
1428
|
+
<span class="status-dot mt-2" :class="agentStatusClass(selectedAgentDetail)"></span>
|
|
1429
|
+
<span class="agent-type-icon mt-1" :class="agentType(selectedAgentDetail)" :title="agentTypeTitle(selectedAgentDetail)">
|
|
1430
|
+
<i class="ti" :class="agentTypeIcon(selectedAgentDetail)"></i>
|
|
1431
|
+
</span>
|
|
1432
|
+
<div class="flex-grow-1 min-width-0">
|
|
1433
|
+
<div class="d-flex align-items-center gap-2">
|
|
1434
|
+
<template x-if="selectedAgentDetail.label">
|
|
1435
|
+
<span class="agent-label text-truncate" x-text="selectedAgentDetail.label"></span>
|
|
1436
|
+
</template>
|
|
1437
|
+
<span class="fw-bold text-truncate" x-text="selectedAgentDetail.name || selectedAgentDetail.id.slice(-12)"></span>
|
|
1438
|
+
</div>
|
|
1439
|
+
<div class="text-secondary small text-truncate" x-text="selectedAgentDetail.id"></div>
|
|
1440
|
+
</div>
|
|
1441
|
+
<button class="btn btn-sm btn-ghost-secondary p-1" @click="closeAgentDetail()" title="Close">
|
|
1442
|
+
<i class="ti ti-x"></i>
|
|
1443
|
+
</button>
|
|
1444
|
+
</div>
|
|
1445
|
+
|
|
1446
|
+
<div class="p-3 border-bottom d-flex gap-2 flex-wrap">
|
|
1447
|
+
<button class="btn btn-sm btn-primary" @click="openComposeToAgent(selectedAgentDetail)">
|
|
1448
|
+
<i class="ti ti-send me-1"></i>Message
|
|
1449
|
+
</button>
|
|
1450
|
+
<button class="btn btn-sm btn-ghost-secondary" @click="selectedAgent = selectedAgentDetail.id; switchView('messages'); closeAgentDetail()">
|
|
1451
|
+
<i class="ti ti-filter me-1"></i>Messages
|
|
1452
|
+
</button>
|
|
1453
|
+
<button class="btn btn-sm btn-ghost-secondary" @click="switchView('pairs'); closeAgentDetail()">
|
|
1454
|
+
<i class="ti ti-link me-1"></i>Pairs
|
|
1455
|
+
</button>
|
|
1456
|
+
<button class="btn btn-sm btn-ghost-secondary" @click="openPairInvite(selectedAgentDetail.id); closeAgentDetail()">
|
|
1457
|
+
<i class="ti ti-link-plus me-1"></i>Pair with...
|
|
1458
|
+
</button>
|
|
1459
|
+
<button class="btn btn-sm btn-ghost-secondary" @click="openRename(selectedAgentDetail)">
|
|
1460
|
+
<i class="ti ti-pencil me-1"></i>Rename
|
|
1461
|
+
</button>
|
|
1462
|
+
</div>
|
|
1463
|
+
|
|
1464
|
+
<div class="p-3 border-bottom">
|
|
1465
|
+
<h3 class="card-title mb-3">Status</h3>
|
|
1466
|
+
<template x-if="agentAttention(selectedAgentDetail).total > 0">
|
|
1467
|
+
<div class="alert alert-warning py-2 mb-3">
|
|
1468
|
+
<div class="fw-bold small mb-1">Needs attention</div>
|
|
1469
|
+
<div class="attention-badges d-flex gap-1 flex-wrap">
|
|
1470
|
+
<span class="badge bg-danger-lt" x-show="agentAttention(selectedAgentDetail).unread" x-text="agentAttention(selectedAgentDetail).unread + ' unread'"></span>
|
|
1471
|
+
<span class="badge bg-warning-lt" x-show="agentAttention(selectedAgentDetail).needsHumanResponse">needs human response</span>
|
|
1472
|
+
<span class="badge bg-info-lt" x-show="agentAttention(selectedAgentDetail).agentQuestion">agent asked a question</span>
|
|
1473
|
+
<span class="badge bg-warning-lt" x-show="agentAttention(selectedAgentDetail).pendingPairInvite">pair invite pending</span>
|
|
1474
|
+
<span class="badge bg-orange-lt" x-show="agentAttention(selectedAgentDetail).claimableTasks" x-text="agentAttention(selectedAgentDetail).claimableTasks + ' claimable waiting'"></span>
|
|
1475
|
+
</div>
|
|
1476
|
+
</div>
|
|
1477
|
+
</template>
|
|
1478
|
+
<div class="detail-row mb-2">
|
|
1479
|
+
<div class="text-secondary small">State</div>
|
|
1480
|
+
<div>
|
|
1481
|
+
<span class="badge" :class="'bg-' + agentPresence(selectedAgentDetail).tone + '-lt'">
|
|
1482
|
+
<i class="ti me-1" :class="agentPresence(selectedAgentDetail).icon"></i><span x-text="agentPresence(selectedAgentDetail).label"></span>
|
|
1483
|
+
</span>
|
|
1484
|
+
<template x-for="badge in agentPresenceBadges(selectedAgentDetail)" :key="badge.label">
|
|
1485
|
+
<span class="badge ms-1" :class="badge.className" x-text="badge.label"></span>
|
|
1486
|
+
</template>
|
|
1487
|
+
<template x-if="selectedAgentDetail.ready">
|
|
1488
|
+
<span class="badge bg-success-lt ms-1">ready</span>
|
|
1489
|
+
</template>
|
|
1490
|
+
</div>
|
|
1491
|
+
</div>
|
|
1492
|
+
<div class="detail-row mb-2">
|
|
1493
|
+
<div class="text-secondary small">Last seen</div>
|
|
1494
|
+
<div class="small" x-text="timeAgo(selectedAgentDetail.lastSeen)"></div>
|
|
1495
|
+
</div>
|
|
1496
|
+
<div class="detail-row mb-2">
|
|
1497
|
+
<div class="text-secondary small">Created</div>
|
|
1498
|
+
<div class="small" x-text="fmtTime(selectedAgentDetail.createdAt)"></div>
|
|
1499
|
+
</div>
|
|
1500
|
+
<template x-if="selectedAgentDetail.machine || selectedAgentDetail.rig">
|
|
1501
|
+
<div class="detail-row mb-2">
|
|
1502
|
+
<div class="text-secondary small">Host</div>
|
|
1503
|
+
<div class="d-flex gap-1 flex-wrap">
|
|
1504
|
+
<template x-if="selectedAgentDetail.machine">
|
|
1505
|
+
<span class="badge bg-secondary-lt" x-text="selectedAgentDetail.machine"></span>
|
|
1506
|
+
</template>
|
|
1507
|
+
<template x-if="selectedAgentDetail.rig">
|
|
1508
|
+
<span class="badge bg-primary-lt" x-text="selectedAgentDetail.rig"></span>
|
|
1509
|
+
</template>
|
|
1510
|
+
</div>
|
|
1511
|
+
</div>
|
|
1512
|
+
</template>
|
|
1513
|
+
</div>
|
|
1514
|
+
|
|
1515
|
+
<template x-if="agentPair(selectedAgentDetail)">
|
|
1516
|
+
<div class="p-3 border-bottom">
|
|
1517
|
+
<h3 class="card-title mb-3">Pair</h3>
|
|
1518
|
+
<div class="d-flex align-items-center gap-2 flex-wrap">
|
|
1519
|
+
<span class="badge pair-badge" :class="pairBadgeClass(agentPair(selectedAgentDetail))" :title="pairTitle(agentPair(selectedAgentDetail), selectedAgentDetail.id)">
|
|
1520
|
+
<i class="ti ti-link me-1"></i><span x-text="pairBadgeLabel(agentPair(selectedAgentDetail), selectedAgentDetail.id)"></span>
|
|
1521
|
+
</span>
|
|
1522
|
+
<span class="text-secondary small" x-text="agentPair(selectedAgentDetail).id"></span>
|
|
1523
|
+
</div>
|
|
1524
|
+
<template x-if="agentPair(selectedAgentDetail).objective">
|
|
1525
|
+
<div class="msg-body mt-2" x-text="agentPair(selectedAgentDetail).objective"></div>
|
|
1526
|
+
</template>
|
|
1527
|
+
<div class="d-flex gap-2 mt-3 flex-wrap">
|
|
1528
|
+
<template x-if="agentPair(selectedAgentDetail).status === 'active'">
|
|
1529
|
+
<button class="btn btn-sm btn-primary" @click="openPairMessage(agentPair(selectedAgentDetail), selectedAgentDetail.id)">
|
|
1530
|
+
<i class="ti ti-send me-1"></i>Pair message
|
|
1531
|
+
</button>
|
|
1532
|
+
</template>
|
|
1533
|
+
<template x-if="agentPair(selectedAgentDetail).status === 'pending' && agentPair(selectedAgentDetail).targetId === selectedAgentDetail.id">
|
|
1534
|
+
<button class="btn btn-sm btn-success" @click="doAcceptPair(agentPair(selectedAgentDetail))">
|
|
1535
|
+
<i class="ti ti-check me-1"></i>Accept
|
|
1536
|
+
</button>
|
|
1537
|
+
</template>
|
|
1538
|
+
<template x-if="agentPair(selectedAgentDetail).status === 'pending' && agentPair(selectedAgentDetail).targetId === selectedAgentDetail.id">
|
|
1539
|
+
<button class="btn btn-sm btn-ghost-danger" @click="doRejectPair(agentPair(selectedAgentDetail))">
|
|
1540
|
+
<i class="ti ti-x me-1"></i>Reject
|
|
1541
|
+
</button>
|
|
1542
|
+
</template>
|
|
1543
|
+
<template x-if="agentPair(selectedAgentDetail).status === 'active'">
|
|
1544
|
+
<button class="btn btn-sm btn-ghost-danger" @click="doHangupPair(agentPair(selectedAgentDetail), selectedAgentDetail.id)">
|
|
1545
|
+
<i class="ti ti-phone-off me-1"></i>Hang up
|
|
1546
|
+
</button>
|
|
1547
|
+
</template>
|
|
1548
|
+
</div>
|
|
1549
|
+
</div>
|
|
1550
|
+
</template>
|
|
1551
|
+
|
|
1552
|
+
<div class="p-3 border-bottom">
|
|
1553
|
+
<h3 class="card-title mb-3">Tags</h3>
|
|
1554
|
+
<div class="d-flex gap-1 flex-wrap">
|
|
1555
|
+
<template x-for="tag in (selectedAgentDetail.tags || [])" :key="tag">
|
|
1556
|
+
<span class="badge bg-cyan-lt" x-text="tag"></span>
|
|
1557
|
+
</template>
|
|
1558
|
+
<template x-if="!(selectedAgentDetail.tags || []).length">
|
|
1559
|
+
<span class="text-secondary small">No tags</span>
|
|
1560
|
+
</template>
|
|
1561
|
+
</div>
|
|
1562
|
+
<template x-if="selectedAgentDetail.capabilities && selectedAgentDetail.capabilities.length">
|
|
1563
|
+
<div class="d-flex gap-1 flex-wrap mt-2">
|
|
1564
|
+
<template x-for="cap in selectedAgentDetail.capabilities" :key="cap">
|
|
1565
|
+
<span class="badge bg-purple-lt" x-text="cap"></span>
|
|
1566
|
+
</template>
|
|
1567
|
+
</div>
|
|
1568
|
+
</template>
|
|
1569
|
+
</div>
|
|
1570
|
+
|
|
1571
|
+
<div class="p-3">
|
|
1572
|
+
<h3 class="card-title mb-3">Recent Messages</h3>
|
|
1573
|
+
<div class="list-group list-group-flush">
|
|
1574
|
+
<template x-for="m in agentDetailMessages" :key="m.id">
|
|
1575
|
+
<div class="list-group-item px-0">
|
|
1576
|
+
<div class="d-flex align-items-center gap-2 mb-1">
|
|
1577
|
+
<span class="fw-bold small" x-text="displayTarget(m.from)"></span>
|
|
1578
|
+
<i class="ti ti-arrow-right text-secondary" style="font-size:12px"></i>
|
|
1579
|
+
<span class="small" x-text="displayTarget(m.to)"></span>
|
|
1580
|
+
<span class="text-secondary small ms-auto" x-text="'#' + m.id"></span>
|
|
1581
|
+
</div>
|
|
1582
|
+
<div class="text-secondary small text-truncate" x-text="messagePreview(m)"></div>
|
|
1583
|
+
</div>
|
|
1584
|
+
</template>
|
|
1585
|
+
<template x-if="agentDetailMessages.length === 0">
|
|
1586
|
+
<div class="text-secondary small">No recent messages loaded</div>
|
|
1587
|
+
</template>
|
|
1588
|
+
</div>
|
|
1589
|
+
</div>
|
|
1590
|
+
</aside>
|
|
1591
|
+
</div>
|
|
1592
|
+
</template>
|
|
694
1593
|
|
|
695
1594
|
<!-- ==================== COMPOSE MODAL ==================== -->
|
|
696
1595
|
<div class="modal modal-blur" :class="{ show: composeOpen }" :style="composeOpen ? 'display:block' : 'display:none'" tabindex="-1" @click.self="composeOpen = false">
|
|
@@ -776,6 +1675,101 @@
|
|
|
776
1675
|
</div>
|
|
777
1676
|
<div class="modal-backdrop fade show" x-show="composeOpen" x-cloak></div>
|
|
778
1677
|
|
|
1678
|
+
<!-- ==================== PAIR INVITE MODAL ==================== -->
|
|
1679
|
+
<div class="modal modal-blur" :class="{ show: pairInviteOpen }" :style="pairInviteOpen ? 'display:block' : 'display:none'" tabindex="-1" @click.self="closePairInvite()">
|
|
1680
|
+
<div class="modal-dialog modal-lg modal-dialog-centered">
|
|
1681
|
+
<div class="modal-content">
|
|
1682
|
+
<div class="modal-header">
|
|
1683
|
+
<h5 class="modal-title">Pair Agents</h5>
|
|
1684
|
+
<button class="btn-close" @click="closePairInvite()"></button>
|
|
1685
|
+
</div>
|
|
1686
|
+
<div class="modal-body">
|
|
1687
|
+
<div class="row g-3">
|
|
1688
|
+
<div class="col-md-6">
|
|
1689
|
+
<label class="form-label">Requester</label>
|
|
1690
|
+
<select class="form-select" x-model="pairInvite.requesterId">
|
|
1691
|
+
<option value="">Select requester…</option>
|
|
1692
|
+
<template x-for="a in composeAgents" :key="a.id">
|
|
1693
|
+
<option :value="a.id" x-text="displayName(a) + ' [' + a.id.slice(-6) + ']'"></option>
|
|
1694
|
+
</template>
|
|
1695
|
+
</select>
|
|
1696
|
+
</div>
|
|
1697
|
+
<div class="col-md-6">
|
|
1698
|
+
<label class="form-label">Target</label>
|
|
1699
|
+
<select class="form-select" x-model="pairInvite.targetId">
|
|
1700
|
+
<option value="">Select target…</option>
|
|
1701
|
+
<template x-for="a in composeAgents" :key="a.id">
|
|
1702
|
+
<option :value="a.id" x-text="displayName(a) + ' [' + a.id.slice(-6) + ']'"></option>
|
|
1703
|
+
</template>
|
|
1704
|
+
</select>
|
|
1705
|
+
</div>
|
|
1706
|
+
<div class="col-12">
|
|
1707
|
+
<label class="form-label">Objective <span class="text-secondary">(optional)</span></label>
|
|
1708
|
+
<textarea class="form-control" rows="4" x-model="pairInvite.objective" x-ref="pairInviteObjective"></textarea>
|
|
1709
|
+
</div>
|
|
1710
|
+
</div>
|
|
1711
|
+
</div>
|
|
1712
|
+
<div class="modal-footer">
|
|
1713
|
+
<button class="btn btn-ghost-secondary" @click="closePairInvite()">Cancel</button>
|
|
1714
|
+
<button class="btn btn-primary" @click="doCreatePair()">
|
|
1715
|
+
<i class="ti ti-link-plus me-1"></i>Create invite
|
|
1716
|
+
</button>
|
|
1717
|
+
</div>
|
|
1718
|
+
</div>
|
|
1719
|
+
</div>
|
|
1720
|
+
</div>
|
|
1721
|
+
<div class="modal-backdrop fade show" x-show="pairInviteOpen" x-cloak></div>
|
|
1722
|
+
|
|
1723
|
+
<!-- ==================== PAIR MESSAGE MODAL ==================== -->
|
|
1724
|
+
<div class="modal modal-blur" :class="{ show: pairMessageOpen }" :style="pairMessageOpen ? 'display:block' : 'display:none'" tabindex="-1" @click.self="closePairMessage()">
|
|
1725
|
+
<div class="modal-dialog modal-lg modal-dialog-centered">
|
|
1726
|
+
<div class="modal-content">
|
|
1727
|
+
<div class="modal-header">
|
|
1728
|
+
<h5 class="modal-title">Pair Message</h5>
|
|
1729
|
+
<button class="btn-close" @click="closePairMessage()"></button>
|
|
1730
|
+
</div>
|
|
1731
|
+
<div class="modal-body">
|
|
1732
|
+
<template x-if="pairMessagePair">
|
|
1733
|
+
<div class="alert alert-info py-2">
|
|
1734
|
+
<i class="ti ti-link me-1"></i>
|
|
1735
|
+
<span x-text="displayTarget(pairMessagePair.requesterId)"></span>
|
|
1736
|
+
<span class="mx-1">↔</span>
|
|
1737
|
+
<span x-text="displayTarget(pairMessagePair.targetId)"></span>
|
|
1738
|
+
</div>
|
|
1739
|
+
</template>
|
|
1740
|
+
<div class="row g-3">
|
|
1741
|
+
<div class="col-md-6">
|
|
1742
|
+
<label class="form-label">From</label>
|
|
1743
|
+
<select class="form-select" x-model="pairMessage.from">
|
|
1744
|
+
<template x-if="pairMessagePair">
|
|
1745
|
+
<option :value="pairMessagePair.requesterId" x-text="displayTarget(pairMessagePair.requesterId)"></option>
|
|
1746
|
+
</template>
|
|
1747
|
+
<template x-if="pairMessagePair">
|
|
1748
|
+
<option :value="pairMessagePair.targetId" x-text="displayTarget(pairMessagePair.targetId)"></option>
|
|
1749
|
+
</template>
|
|
1750
|
+
</select>
|
|
1751
|
+
</div>
|
|
1752
|
+
<div class="col-md-6">
|
|
1753
|
+
<label class="form-label">Subject <span class="text-secondary">(optional)</span></label>
|
|
1754
|
+
<input type="text" class="form-control" x-model="pairMessage.subject">
|
|
1755
|
+
</div>
|
|
1756
|
+
<div class="col-12">
|
|
1757
|
+
<label class="form-label">Message</label>
|
|
1758
|
+
<textarea class="form-control" rows="5" x-model="pairMessage.body" x-ref="pairMessageBody"></textarea>
|
|
1759
|
+
</div>
|
|
1760
|
+
</div>
|
|
1761
|
+
</div>
|
|
1762
|
+
<div class="modal-footer">
|
|
1763
|
+
<button class="btn btn-ghost-secondary" @click="closePairMessage()">Cancel</button>
|
|
1764
|
+
<button class="btn btn-primary" @click="doSendPairMessage()">
|
|
1765
|
+
<i class="ti ti-send me-1"></i>Send
|
|
1766
|
+
</button>
|
|
1767
|
+
</div>
|
|
1768
|
+
</div>
|
|
1769
|
+
</div>
|
|
1770
|
+
</div>
|
|
1771
|
+
<div class="modal-backdrop fade show" x-show="pairMessageOpen" x-cloak></div>
|
|
1772
|
+
|
|
779
1773
|
<!-- ==================== THREAD MODAL ==================== -->
|
|
780
1774
|
<div class="modal modal-blur" :class="{ show: threadOpen }" :style="threadOpen ? 'display:block' : 'display:none'" tabindex="-1" @click.self="threadOpen = false">
|
|
781
1775
|
<div class="modal-dialog modal-lg modal-dialog-centered modal-dialog-scrollable">
|