aimeat 1.15.1 → 1.16.2
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/dist/locales/en.json +60 -1
- package/dist/locales/fi.json +60 -1
- package/dist/public/css/views/agents-detail.css +279 -0
- package/dist/public/js/services/agent-tasks.js +11 -0
- package/dist/public/js/services/agents.js +8 -0
- package/dist/public/views/profile/agents/agent-card.js +5 -1
- package/dist/public/views/profile/agents/rate-modal.js +87 -0
- package/dist/public/views/profile/agents/tab-quality.js +213 -0
- package/dist/public/views/profile/agents/tab-tasks.js +4 -2
- package/dist/public/views/profile/agents-tasks-subtab.js +134 -3
- package/dist/src/generated/api-types.d.ts +238 -3
- package/dist/src/generated/api-types.d.ts.map +1 -1
- package/dist/src/models/agent-task-schemas.d.ts +28 -0
- package/dist/src/models/agent-task-schemas.d.ts.map +1 -1
- package/dist/src/models/agent-task-schemas.js +21 -0
- package/dist/src/models/agent-task-schemas.js.map +1 -1
- package/dist/src/routes/agent-activity.d.ts +3 -0
- package/dist/src/routes/agent-activity.d.ts.map +1 -1
- package/dist/src/routes/agent-activity.js +44 -0
- package/dist/src/routes/agent-activity.js.map +1 -1
- package/dist/src/routes/agent-integration.d.ts.map +1 -1
- package/dist/src/routes/agent-integration.js +4 -0
- package/dist/src/routes/agent-integration.js.map +1 -1
- package/dist/src/routes/agent-tasks.d.ts +3 -0
- package/dist/src/routes/agent-tasks.d.ts.map +1 -1
- package/dist/src/routes/agent-tasks.js +91 -1
- package/dist/src/routes/agent-tasks.js.map +1 -1
- package/dist/src/routes/agents.d.ts.map +1 -1
- package/dist/src/routes/agents.js +35 -0
- package/dist/src/routes/agents.js.map +1 -1
- package/dist/src/services/agent-statistics.d.ts +80 -0
- package/dist/src/services/agent-statistics.d.ts.map +1 -0
- package/dist/src/services/agent-statistics.js +180 -0
- package/dist/src/services/agent-statistics.js.map +1 -0
- package/dist/src/storage/interface.d.ts +43 -1
- package/dist/src/storage/interface.d.ts.map +1 -1
- package/dist/src/storage/interface.js +2 -1
- package/dist/src/storage/interface.js.map +1 -1
- package/dist/src/storage/providers/mongodb/index.d.ts.map +1 -1
- package/dist/src/storage/providers/mongodb/index.js +5 -1
- package/dist/src/storage/providers/mongodb/index.js.map +1 -1
- package/dist/src/storage/providers/sqlite/index.d.ts.map +1 -1
- package/dist/src/storage/providers/sqlite/index.js +6 -4
- package/dist/src/storage/providers/sqlite/index.js.map +1 -1
- package/dist/src/storage/providers/sqlite/repos/agent-task.d.ts.map +1 -1
- package/dist/src/storage/providers/sqlite/repos/agent-task.js +6 -4
- package/dist/src/storage/providers/sqlite/repos/agent-task.js.map +1 -1
- package/dist/src/storage/providers/sqlite/schema.d.ts.map +1 -1
- package/dist/src/storage/providers/sqlite/schema.js +9 -1
- package/dist/src/storage/providers/sqlite/schema.js.map +1 -1
- package/package.json +1 -1
- package/prisma/schema.prisma +2 -0
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file tab-quality.js
|
|
3
|
+
* @description Quality tab for the expanded agent view. Reads the recomputed
|
|
4
|
+
* GET /v1/agents/:name/statistics rollups and renders three sections:
|
|
5
|
+
* Performance (task counts, success rate, make-time, durations by context),
|
|
6
|
+
* Reviews by context (per-context star ratings + sample size + low-confidence
|
|
7
|
+
* flag), and Custom metrics (whatever the agent published under
|
|
8
|
+
* agents.<name>.statistics.custom.*). Quality READS statistics + reviews — it
|
|
9
|
+
* is the evaluative lens over the objective data.
|
|
10
|
+
* @structure default export TabQuality({ agentName })
|
|
11
|
+
* @usage rendered by agent-card.js renderTabContent() for the 'quality' tab
|
|
12
|
+
* @version-history
|
|
13
|
+
* v1.1.0 -- 2026-05-31 -- Add "Rate deliverables" list: completed tasks the owner
|
|
14
|
+
* can rate inline via the shared RateModal (unrated first); refreshes the
|
|
15
|
+
* rollups on submit.
|
|
16
|
+
* v1.0.0 -- 2026-05-31 -- Initial Quality tab (per-context reviews + performance + custom)
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { h } from 'preact';
|
|
20
|
+
import { useState, useEffect, useRef } from 'preact/hooks';
|
|
21
|
+
import htm from 'htm';
|
|
22
|
+
import { t } from '/js/i18n.js';
|
|
23
|
+
import { getAgentStatistics, listTasks, rateTask } from '/js/services/agent-tasks.js';
|
|
24
|
+
import RateModal from './rate-modal.js';
|
|
25
|
+
|
|
26
|
+
const html = htm.bind(h);
|
|
27
|
+
|
|
28
|
+
/** Glyph row for an average star value (rounded to nearest whole star). */
|
|
29
|
+
function starGlyphs(value) {
|
|
30
|
+
const full = Math.max(0, Math.min(5, Math.round(value)));
|
|
31
|
+
return '★★★★★'.slice(0, full) + '☆☆☆☆☆'.slice(0, 5 - full);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Localised context label, falling back to the raw enum value. */
|
|
35
|
+
function ctxLabel(ctx) {
|
|
36
|
+
const key = `profile.agents.detail.quality.contexts.${ctx}`;
|
|
37
|
+
const label = t(key);
|
|
38
|
+
return label !== key ? label : ctx;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function fmtSeconds(secs) {
|
|
42
|
+
return `${Number(secs || 0).toLocaleString()}${t('profile.agents.detail.quality.seconds')}`;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export default function TabQuality({ agentName, showToast }) {
|
|
46
|
+
const [data, setData] = useState(null);
|
|
47
|
+
const [loading, setLoading] = useState(true);
|
|
48
|
+
const [doneTasks, setDoneTasks] = useState([]);
|
|
49
|
+
const [rateTarget, setRateTarget] = useState(null); // task being rated, or null
|
|
50
|
+
const [sendingRate, setSendingRate] = useState(false);
|
|
51
|
+
|
|
52
|
+
async function loadData() {
|
|
53
|
+
setLoading(true);
|
|
54
|
+
try {
|
|
55
|
+
const [statsResp, tasksResp] = await Promise.all([
|
|
56
|
+
getAgentStatistics(agentName),
|
|
57
|
+
listTasks(agentName, { status: 'done', per_page: 100 }).catch(() => null),
|
|
58
|
+
]);
|
|
59
|
+
setData(statsResp?.data || null);
|
|
60
|
+
setDoneTasks(tasksResp?.data?.tasks || []);
|
|
61
|
+
} catch { setData(null); }
|
|
62
|
+
setLoading(false);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async function handleSubmitRate(body) {
|
|
66
|
+
if (!rateTarget) return;
|
|
67
|
+
setSendingRate(true);
|
|
68
|
+
try {
|
|
69
|
+
await rateTask(agentName, rateTarget.id, body);
|
|
70
|
+
showToast?.(t('profile.agents.tasks.rate.success'));
|
|
71
|
+
setRateTarget(null);
|
|
72
|
+
await loadData();
|
|
73
|
+
} catch (err) {
|
|
74
|
+
showToast?.(err.message || t('profile.agents.tasks.rate.error'), true);
|
|
75
|
+
}
|
|
76
|
+
setSendingRate(false);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
useEffect(() => { loadData(); }, [agentName]);
|
|
80
|
+
|
|
81
|
+
// Live updates: refresh when a rating lands or tasks change.
|
|
82
|
+
const loadRef = useRef(loadData);
|
|
83
|
+
loadRef.current = loadData;
|
|
84
|
+
useEffect(() => {
|
|
85
|
+
const handler = () => loadRef.current();
|
|
86
|
+
window.addEventListener('aimeat-live-update', handler);
|
|
87
|
+
return () => window.removeEventListener('aimeat-live-update', handler);
|
|
88
|
+
}, []);
|
|
89
|
+
|
|
90
|
+
if (loading) {
|
|
91
|
+
return html`<div class="pf-agd-empty">${t('profile.loading')}</div>`;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const perf = data?.performance || {};
|
|
95
|
+
const reviews = data?.reviews || {};
|
|
96
|
+
const custom = data?.custom || [];
|
|
97
|
+
const byContext = reviews.byContext || {};
|
|
98
|
+
const contextKeys = Object.keys(byContext);
|
|
99
|
+
const durByContext = perf.duration?.byContext || {};
|
|
100
|
+
const durKeys = Object.keys(durByContext);
|
|
101
|
+
|
|
102
|
+
return html`
|
|
103
|
+
<div class="pf-agd-quality">
|
|
104
|
+
<!-- Performance -->
|
|
105
|
+
<div class="pf-agd-section-title">${t('profile.agents.detail.quality.performanceTitle')}</div>
|
|
106
|
+
<div class="pf-agd-quality-desc">${t('profile.agents.detail.quality.performanceDesc')}</div>
|
|
107
|
+
<div class="pf-agd-stats-grid">
|
|
108
|
+
<div class="pf-agd-stat-card">
|
|
109
|
+
<div class="pf-agd-stat-value">${perf.tasks?.total ?? 0}</div>
|
|
110
|
+
<div class="pf-agd-stat-label">${t('profile.agents.detail.quality.tasksTotal')}</div>
|
|
111
|
+
</div>
|
|
112
|
+
<div class="pf-agd-stat-card">
|
|
113
|
+
<div class="pf-agd-stat-value">${perf.tasks?.completed ?? 0}</div>
|
|
114
|
+
<div class="pf-agd-stat-label">${t('profile.agents.detail.quality.tasksCompleted')}</div>
|
|
115
|
+
</div>
|
|
116
|
+
<div class="pf-agd-stat-card">
|
|
117
|
+
<div class="pf-agd-stat-value">${perf.tasks?.successRate != null ? `${Math.round(perf.tasks.successRate * 100)}%` : '-'}</div>
|
|
118
|
+
<div class="pf-agd-stat-label">${t('profile.agents.detail.quality.successRate')}</div>
|
|
119
|
+
</div>
|
|
120
|
+
<div class="pf-agd-stat-card">
|
|
121
|
+
<div class="pf-agd-stat-value">${fmtSeconds(perf.duration?.avgCompletionSeconds)}</div>
|
|
122
|
+
<div class="pf-agd-stat-label">${t('profile.agents.detail.quality.avgTime')}</div>
|
|
123
|
+
</div>
|
|
124
|
+
<div class="pf-agd-stat-card">
|
|
125
|
+
<div class="pf-agd-stat-value">${perf.events?.total ?? 0}</div>
|
|
126
|
+
<div class="pf-agd-stat-label">${t('profile.agents.detail.quality.events')}</div>
|
|
127
|
+
</div>
|
|
128
|
+
</div>
|
|
129
|
+
${durKeys.length > 0 && html`
|
|
130
|
+
<div class="pf-agd-quality-durations">
|
|
131
|
+
${durKeys.map(ctx => html`
|
|
132
|
+
<span key=${ctx} class="pf-agd-quality-dur-chip">
|
|
133
|
+
${ctxLabel(ctx)}: ${fmtSeconds(durByContext[ctx].avgSeconds)} (${durByContext[ctx].count})
|
|
134
|
+
</span>
|
|
135
|
+
`)}
|
|
136
|
+
</div>
|
|
137
|
+
`}
|
|
138
|
+
|
|
139
|
+
<!-- Reviews by context -->
|
|
140
|
+
<div class="pf-agd-section-title">${t('profile.agents.detail.quality.reviewsTitle')}</div>
|
|
141
|
+
<div class="pf-agd-quality-desc">${t('profile.agents.detail.quality.reviewsDesc')}</div>
|
|
142
|
+
${contextKeys.length === 0
|
|
143
|
+
? html`<div class="pf-agd-empty">${t('profile.agents.detail.quality.noReviews')}</div>`
|
|
144
|
+
: html`
|
|
145
|
+
<div class="pf-agd-quality-reviews">
|
|
146
|
+
${contextKeys.map(ctx => {
|
|
147
|
+
const s = byContext[ctx];
|
|
148
|
+
return html`
|
|
149
|
+
<div key=${ctx} class="pf-agd-quality-review-row">
|
|
150
|
+
<div class="pf-agd-quality-review-head">
|
|
151
|
+
<span class="pf-agd-quality-ctx">${ctxLabel(ctx)}</span>
|
|
152
|
+
<span class="pf-agd-quality-stars">${starGlyphs(s.avgStars)}</span>
|
|
153
|
+
<span class="pf-agd-quality-avg">${Number(s.avgStars).toFixed(1)}</span>
|
|
154
|
+
<span class="pf-agd-quality-n">${t('profile.agents.detail.quality.ratings', { count: s.n })}</span>
|
|
155
|
+
${s.lowConfidence && html`<span class="pf-agd-quality-lowconf">${t('profile.agents.detail.quality.lowConfidence')}</span>`}
|
|
156
|
+
</div>
|
|
157
|
+
<div class="pf-agd-quality-review-meta">${t('profile.agents.detail.quality.grounded', { count: s.sourceGroundedN })}</div>
|
|
158
|
+
</div>
|
|
159
|
+
`;
|
|
160
|
+
})}
|
|
161
|
+
<div class="pf-agd-quality-overall">
|
|
162
|
+
${t('profile.agents.detail.quality.overall')}: ${starGlyphs(reviews.overall?.avgStars || 0)} ${Number(reviews.overall?.avgStars || 0).toFixed(1)} (${t('profile.agents.detail.quality.ratings', { count: reviews.overall?.n || 0 })})
|
|
163
|
+
</div>
|
|
164
|
+
</div>
|
|
165
|
+
`}
|
|
166
|
+
|
|
167
|
+
<!-- Rate deliverables (completed tasks the owner can rate) -->
|
|
168
|
+
<div class="pf-agd-section-title">${t('profile.agents.detail.quality.pendingTitle')}</div>
|
|
169
|
+
<div class="pf-agd-quality-desc">${t('profile.agents.detail.quality.pendingDesc')}</div>
|
|
170
|
+
${doneTasks.length === 0
|
|
171
|
+
? html`<div class="pf-agd-empty">${t('profile.agents.detail.quality.allRated')}</div>`
|
|
172
|
+
: html`
|
|
173
|
+
<div class="pf-agd-quality-pending">
|
|
174
|
+
${[...doneTasks].sort((a, b) => (a.rating ? 1 : 0) - (b.rating ? 1 : 0)).map(task => html`
|
|
175
|
+
<div key=${task.id} class="pf-agd-quality-pending-row">
|
|
176
|
+
<span class="pf-agd-quality-pending-title">${task.title || task.id}</span>
|
|
177
|
+
${task.rating
|
|
178
|
+
? html`<span class="pf-agd-quality-pending-rated">${starGlyphs(task.rating.stars)} ${ctxLabel(task.rating.context)}</span>`
|
|
179
|
+
: html`<span class="pf-agd-quality-pending-unrated">${t('profile.agents.detail.quality.unrated')}</span>`}
|
|
180
|
+
<button class="btn-outline btn-sm" onClick=${() => setRateTarget(task)}>
|
|
181
|
+
${task.rating ? t('profile.agents.tasks.rate.rerate') : t('profile.agents.tasks.rate.button')}
|
|
182
|
+
</button>
|
|
183
|
+
</div>
|
|
184
|
+
`)}
|
|
185
|
+
</div>
|
|
186
|
+
`}
|
|
187
|
+
|
|
188
|
+
<!-- Custom metrics -->
|
|
189
|
+
<div class="pf-agd-section-title">${t('profile.agents.detail.quality.customTitle')}</div>
|
|
190
|
+
<div class="pf-agd-quality-desc">${t('profile.agents.detail.quality.customDesc')}</div>
|
|
191
|
+
${custom.length === 0
|
|
192
|
+
? html`<div class="pf-agd-empty">${t('profile.agents.detail.quality.noCustom')}</div>`
|
|
193
|
+
: html`
|
|
194
|
+
<div class="pf-agd-quality-custom">
|
|
195
|
+
${custom.map(c => html`
|
|
196
|
+
<div key=${c.key} class="pf-agd-quality-custom-row">
|
|
197
|
+
<span class="pf-agd-quality-custom-key">${c.key}</span>
|
|
198
|
+
<span class="pf-agd-quality-custom-val">${typeof c.value === 'object' ? JSON.stringify(c.value) : String(c.value)}</span>
|
|
199
|
+
</div>
|
|
200
|
+
`)}
|
|
201
|
+
</div>
|
|
202
|
+
`}
|
|
203
|
+
|
|
204
|
+
<${RateModal}
|
|
205
|
+
open=${!!rateTarget}
|
|
206
|
+
onClose=${() => setRateTarget(null)}
|
|
207
|
+
onSubmit=${handleSubmitRate}
|
|
208
|
+
submitting=${sendingRate}
|
|
209
|
+
existing=${rateTarget?.rating}
|
|
210
|
+
/>
|
|
211
|
+
</div>
|
|
212
|
+
`;
|
|
213
|
+
}
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
* @description Tasks tab wrapper for agent detail tab-view.
|
|
4
4
|
* Delegates to the existing AgentTasksSubtab component.
|
|
5
5
|
* @version-history
|
|
6
|
+
* v1.1.0 -- 2026-06-01 -- Forward the `agent` object so the subtab can show the
|
|
7
|
+
* max_concurrent_tasks runner config.
|
|
6
8
|
* v1.0.0 -- 2026-05-24 -- Initial creation for Agent Detail Tab-View
|
|
7
9
|
*/
|
|
8
10
|
|
|
@@ -12,6 +14,6 @@ import AgentTasksSubtab from '../agents-tasks-subtab.js';
|
|
|
12
14
|
|
|
13
15
|
const html = htm.bind(h);
|
|
14
16
|
|
|
15
|
-
export default function TabTasks({ agentName, session, showToast }) {
|
|
16
|
-
return html`<${AgentTasksSubtab} agentName=${agentName} session=${session} showToast=${showToast} />`;
|
|
17
|
+
export default function TabTasks({ agent, agentName, session, showToast }) {
|
|
18
|
+
return html`<${AgentTasksSubtab} agent=${agent} agentName=${agentName} session=${session} showToast=${showToast} />`;
|
|
17
19
|
}
|
|
@@ -9,6 +9,17 @@
|
|
|
9
9
|
* - TaskItem -- task row with expand/collapse, todo list, start button
|
|
10
10
|
* - RequestChangesModal -- inline modal for owner to send a free-text change request
|
|
11
11
|
* @version-history
|
|
12
|
+
* v4.6.0 -- 2026-06-01 -- Add "max concurrent tasks" runner config (number input,
|
|
13
|
+
* default 1) that PATCHes /v1/agents/:name/max-concurrent-tasks. Consumed by
|
|
14
|
+
* the agent's runner (e.g. a CrewAI daemon) via the integration kit.
|
|
15
|
+
* v4.5.0 -- 2026-05-31 -- Extract RateModal to shared ./agents/rate-modal.js
|
|
16
|
+
* so the Quality tab can reuse it; no behaviour change here.
|
|
17
|
+
* v4.4.0 -- 2026-05-31 -- Add owner "Rate deliverable" control on done tasks
|
|
18
|
+
* (RateModal: stars + context + source-grounded + comment) wired to
|
|
19
|
+
* POST /tasks/:id/rate; shows the current rating with a re-rate affordance.
|
|
20
|
+
* v4.3.0 -- 2026-05-31 -- Add per-task "blur title" toggle in the collapsed
|
|
21
|
+
* task row (eye icon). Hides a task title behind a CSS blur for screen
|
|
22
|
+
* recordings; preference persists per task ID in localStorage (browser-only).
|
|
12
23
|
* v3.2.0 -- 2026-05-24 -- Add interactive filter pills, scope/rules display, migrate to pf-agd- CSS prefix
|
|
13
24
|
* v3.1.0 -- 2026-05-24 -- Fix: use correct locale key for empty state
|
|
14
25
|
* v3.0.0 -- 2026-05-22 -- Replace TaskCreateForm with TaskCreationBuilder (design spec split-panel)
|
|
@@ -38,11 +49,36 @@ const html = htm.bind(h);
|
|
|
38
49
|
import { t } from '/js/i18n.js';
|
|
39
50
|
import { timeAgo } from '/js/utils.js';
|
|
40
51
|
import { apiGet, apiPost } from '/js/api.js';
|
|
41
|
-
import { listTasks, deleteTask, startTask, listEvents, requestChanges, createTask } from '/js/services/agent-tasks.js';
|
|
52
|
+
import { listTasks, deleteTask, startTask, listEvents, requestChanges, createTask, rateTask } from '/js/services/agent-tasks.js';
|
|
53
|
+
import { setMaxConcurrentTasks } from '/js/services/agents.js';
|
|
42
54
|
import { useConfirm, Modal } from '/components/Modal.js';
|
|
55
|
+
import RateModal from './agents/rate-modal.js';
|
|
43
56
|
|
|
44
57
|
const TASK_FILTERS = ['all', 'active', 'queued', 'completed', 'failed'];
|
|
45
58
|
|
|
59
|
+
// Per-browser "blur the title" preference. Used when screen-recording the tab
|
|
60
|
+
// so sensitive task titles can be hidden without affecting other viewers or
|
|
61
|
+
// the server. Stored as an array of task IDs in localStorage; survives reloads
|
|
62
|
+
// but never leaves this browser.
|
|
63
|
+
const BLUR_STORAGE_KEY = 'aimeat.blurredTaskTitles';
|
|
64
|
+
|
|
65
|
+
function readBlurredSet() {
|
|
66
|
+
try {
|
|
67
|
+
const parsed = JSON.parse(localStorage.getItem(BLUR_STORAGE_KEY));
|
|
68
|
+
return new Set(Array.isArray(parsed) ? parsed : []);
|
|
69
|
+
} catch { return new Set(); }
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function isTaskBlurred(taskId) {
|
|
73
|
+
return readBlurredSet().has(taskId);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function setTaskBlurred(taskId, blurred) {
|
|
77
|
+
const set = readBlurredSet();
|
|
78
|
+
if (blurred) set.add(taskId); else set.delete(taskId);
|
|
79
|
+
try { localStorage.setItem(BLUR_STORAGE_KEY, JSON.stringify([...set])); } catch { /* storage full/blocked -- preference just won't persist */ }
|
|
80
|
+
}
|
|
81
|
+
|
|
46
82
|
// Character limits -- keep in sync with AgentTaskCreateSchema in
|
|
47
83
|
// src/models/agent-task-schemas.ts (title max 256, description max 10000).
|
|
48
84
|
// The agent crew reads task.description as its prompt, so that is the field
|
|
@@ -183,11 +219,23 @@ function TaskItem({ task, agentName, showToast, onRefresh }) {
|
|
|
183
219
|
const [starting, setStarting] = useState(false);
|
|
184
220
|
const [showRevisionModal, setShowRevisionModal] = useState(false);
|
|
185
221
|
const [sendingRevision, setSendingRevision] = useState(false);
|
|
222
|
+
const [showRateModal, setShowRateModal] = useState(false);
|
|
223
|
+
const [sendingRate, setSendingRate] = useState(false);
|
|
186
224
|
const [showOutdated, setShowOutdated] = useState(false);
|
|
187
225
|
// Deliverable preview: null = not requested, {loading}|{notFound}|{value}.
|
|
188
226
|
const [deliverable, setDeliverable] = useState(null);
|
|
227
|
+
// Local-only "hide the title" toggle (for screen recordings). Persisted per
|
|
228
|
+
// task ID in localStorage; see helpers at top of file.
|
|
229
|
+
const [blurred, setBlurred] = useState(() => isTaskBlurred(task.id));
|
|
189
230
|
const { confirm, ConfirmUI } = useConfirm();
|
|
190
231
|
|
|
232
|
+
function handleToggleBlur(e) {
|
|
233
|
+
e.stopPropagation();
|
|
234
|
+
const next = !blurred;
|
|
235
|
+
setBlurred(next);
|
|
236
|
+
setTaskBlurred(task.id, next);
|
|
237
|
+
}
|
|
238
|
+
|
|
191
239
|
// Fetch the task's published deliverable from the agent's memory namespace.
|
|
192
240
|
// Uses the owner list endpoint (?agent=<gaii>&prefix=<key>) which returns the
|
|
193
241
|
// value inline; if the exact key is gone, show that it no longer exists.
|
|
@@ -299,6 +347,24 @@ function TaskItem({ task, agentName, showToast, onRefresh }) {
|
|
|
299
347
|
setShowRevisionModal(true);
|
|
300
348
|
}
|
|
301
349
|
|
|
350
|
+
function handleOpenRate(e) {
|
|
351
|
+
e.stopPropagation();
|
|
352
|
+
setShowRateModal(true);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
async function handleSubmitRate(body) {
|
|
356
|
+
setSendingRate(true);
|
|
357
|
+
try {
|
|
358
|
+
await rateTask(agentName, task.id, body);
|
|
359
|
+
showToast(t('profile.agents.tasks.rate.success'));
|
|
360
|
+
setShowRateModal(false);
|
|
361
|
+
onRefresh();
|
|
362
|
+
} catch (err) {
|
|
363
|
+
showToast(err.message || t('profile.agents.tasks.rate.error'), true);
|
|
364
|
+
}
|
|
365
|
+
setSendingRate(false);
|
|
366
|
+
}
|
|
367
|
+
|
|
302
368
|
async function handleSubmitRevision(message) {
|
|
303
369
|
setSendingRevision(true);
|
|
304
370
|
try {
|
|
@@ -321,6 +387,8 @@ function TaskItem({ task, agentName, showToast, onRefresh }) {
|
|
|
321
387
|
const isQueued = task.status === 'queued' || task.status === 'draft';
|
|
322
388
|
const isRevisionRequested = task.status === 'revision_requested';
|
|
323
389
|
const isActive = task.status === 'active';
|
|
390
|
+
const isDone = task.status === 'done';
|
|
391
|
+
const rating = task.rating;
|
|
324
392
|
const canStart = isQueued && hasTodos;
|
|
325
393
|
const canRequestChanges = task.status === 'queued' && hasTodos;
|
|
326
394
|
const totalMinutes = todos.reduce((sum, td) => sum + (td.estimateMinutes || 0), 0);
|
|
@@ -338,7 +406,13 @@ function TaskItem({ task, agentName, showToast, onRefresh }) {
|
|
|
338
406
|
<div>
|
|
339
407
|
<div class="pf-agd-task-item" onClick=${handleExpand}>
|
|
340
408
|
<div class="pf-agd-task-title-row">
|
|
341
|
-
<
|
|
409
|
+
<button
|
|
410
|
+
class=${`pf-agd-blur-toggle ${blurred ? 'pf-agd-blur-toggle--on' : ''}`}
|
|
411
|
+
onClick=${handleToggleBlur}
|
|
412
|
+
title=${blurred ? t('profile.agents.tasks.unblurTitle') : t('profile.agents.tasks.blurTitle')}
|
|
413
|
+
aria-pressed=${blurred}
|
|
414
|
+
>${blurred ? '🙈' : '👁'}</button>
|
|
415
|
+
<span class=${`pf-agd-task-title ${blurred ? 'pf-agd-task-title--blurred' : ''}`}>${task.title || task.id}</span>
|
|
342
416
|
${progress && html`<span class="pf-agd-todo-progress">${progress}</span>`}
|
|
343
417
|
</div>
|
|
344
418
|
<div class="pf-agd-task-meta">
|
|
@@ -367,6 +441,21 @@ function TaskItem({ task, agentName, showToast, onRefresh }) {
|
|
|
367
441
|
`}
|
|
368
442
|
`}
|
|
369
443
|
|
|
444
|
+
${isDone && html`
|
|
445
|
+
<div class="pf-agd-rate-row">
|
|
446
|
+
${rating
|
|
447
|
+
? html`
|
|
448
|
+
<span class="pf-agd-rate-current">
|
|
449
|
+
<span class="pf-agd-rate-current-stars">${'★★★★★'.slice(0, rating.stars)}${'☆☆☆☆☆'.slice(0, 5 - rating.stars)}</span>
|
|
450
|
+
<span class="pf-agd-rate-current-ctx">${t(`profile.agents.detail.quality.contexts.${rating.context}`)}</span>
|
|
451
|
+
${rating.comment && html`<span class="pf-agd-rate-current-comment">${rating.comment}</span>`}
|
|
452
|
+
</span>
|
|
453
|
+
<button class="btn-ghost btn-sm" onClick=${handleOpenRate}>${t('profile.agents.tasks.rate.rerate')}</button>
|
|
454
|
+
`
|
|
455
|
+
: html`<button class="btn-outline btn-sm" onClick=${handleOpenRate}>${t('profile.agents.tasks.rate.button')}</button>`}
|
|
456
|
+
</div>
|
|
457
|
+
`}
|
|
458
|
+
|
|
370
459
|
${task.scope && html`
|
|
371
460
|
<div class="pf-agd-info-row">
|
|
372
461
|
<span class="pf-agd-info-label">${t('profile.agents.detail.tasks.scope')}</span>
|
|
@@ -497,17 +586,42 @@ function TaskItem({ task, agentName, showToast, onRefresh }) {
|
|
|
497
586
|
onSubmit=${handleSubmitRevision}
|
|
498
587
|
submitting=${sendingRevision}
|
|
499
588
|
/>
|
|
589
|
+
<${RateModal}
|
|
590
|
+
open=${showRateModal}
|
|
591
|
+
onClose=${() => setShowRateModal(false)}
|
|
592
|
+
onSubmit=${handleSubmitRate}
|
|
593
|
+
submitting=${sendingRate}
|
|
594
|
+
existing=${rating}
|
|
595
|
+
/>
|
|
500
596
|
</div>
|
|
501
597
|
`}
|
|
502
598
|
</div>
|
|
503
599
|
`;
|
|
504
600
|
}
|
|
505
601
|
|
|
506
|
-
export default function AgentTasksSubtab({ agentName, showToast }) {
|
|
602
|
+
export default function AgentTasksSubtab({ agent, agentName, showToast }) {
|
|
507
603
|
const [tasks, setTasks] = useState(null);
|
|
508
604
|
const [showCreate, setShowCreate] = useState(false);
|
|
509
605
|
const [error, setError] = useState(null);
|
|
510
606
|
const [filter, setFilter] = useState('all');
|
|
607
|
+
// Runner concurrency config (default 1 = serial). Saved to the agent via PATCH.
|
|
608
|
+
const [maxConcurrent, setMaxConcurrent] = useState(agent?.max_concurrent_tasks ?? 1);
|
|
609
|
+
const [savingConcurrency, setSavingConcurrency] = useState(false);
|
|
610
|
+
|
|
611
|
+
async function handleSaveConcurrency(next) {
|
|
612
|
+
const n = Math.max(1, Math.min(20, parseInt(next, 10) || 1));
|
|
613
|
+
setMaxConcurrent(n);
|
|
614
|
+
if (n === (agent?.max_concurrent_tasks ?? 1)) return;
|
|
615
|
+
setSavingConcurrency(true);
|
|
616
|
+
try {
|
|
617
|
+
await setMaxConcurrentTasks(agentName, n);
|
|
618
|
+
if (agent) agent.max_concurrent_tasks = n;
|
|
619
|
+
showToast?.(t('profile.agents.tasks.concurrency.saved'));
|
|
620
|
+
} catch (err) {
|
|
621
|
+
showToast?.(err.message || t('profile.agents.tasks.concurrency.error'), true);
|
|
622
|
+
}
|
|
623
|
+
setSavingConcurrency(false);
|
|
624
|
+
}
|
|
511
625
|
|
|
512
626
|
async function loadTasks() {
|
|
513
627
|
try {
|
|
@@ -563,6 +677,23 @@ export default function AgentTasksSubtab({ agentName, showToast }) {
|
|
|
563
677
|
</button>
|
|
564
678
|
</div>
|
|
565
679
|
|
|
680
|
+
<div class="pf-agd-concurrency">
|
|
681
|
+
<label class="pf-agd-concurrency-label" for="pf-agd-mct-${agentName}">
|
|
682
|
+
${t('profile.agents.tasks.concurrency.label')}
|
|
683
|
+
</label>
|
|
684
|
+
<input
|
|
685
|
+
id="pf-agd-mct-${agentName}"
|
|
686
|
+
class="pf-agd-concurrency-input"
|
|
687
|
+
type="number"
|
|
688
|
+
min="1"
|
|
689
|
+
max="20"
|
|
690
|
+
value=${maxConcurrent}
|
|
691
|
+
disabled=${savingConcurrency}
|
|
692
|
+
onChange=${e => handleSaveConcurrency(e.target.value)}
|
|
693
|
+
/>
|
|
694
|
+
<span class="pf-agd-concurrency-hint">${t('profile.agents.tasks.concurrency.hint')}</span>
|
|
695
|
+
</div>
|
|
696
|
+
|
|
566
697
|
${showCreate && html`
|
|
567
698
|
<${TaskCreateForm} agentName=${agentName} showToast=${showToast} onCreated=${handleCreated} onCancel=${() => setShowCreate(false)} />
|
|
568
699
|
`}
|