zozul-cli 0.3.4 → 0.3.5
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/dashboard/index.html +15 -60
- package/package.json +1 -1
- package/src/dashboard/index.html +15 -60
|
@@ -1043,64 +1043,10 @@ function renderSummaryStats(s) {
|
|
|
1043
1043
|
|
|
1044
1044
|
async function loadTasks() {
|
|
1045
1045
|
try {
|
|
1046
|
-
const
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
if (!tasks.length) { allTaskGroups = []; renderTaskTable([]); return; }
|
|
1051
|
-
|
|
1052
|
-
// Discover tag combinations by sampling one turn per tag
|
|
1053
|
-
const samples = await Promise.all(
|
|
1054
|
-
tasks.map(t => fetchJson('/api/tasks/turns?tags=' + encodeURIComponent(t.task) + '&mode=any&limit=1')
|
|
1055
|
-
.then(turns => turns[0]?.tags || t.task)
|
|
1056
|
-
.catch(() => t.task)
|
|
1057
|
-
)
|
|
1058
|
-
);
|
|
1059
|
-
// Deduplicate: normalize each combo to sorted pipe-separated
|
|
1060
|
-
const combos = [...new Set(samples.map(s => s.split(', ').sort().join('|')))];
|
|
1061
|
-
|
|
1062
|
-
// Fetch stats for each unique combo
|
|
1063
|
-
const comboStats = await Promise.all(
|
|
1064
|
-
combos.map(combo => {
|
|
1065
|
-
const tags = combo.split('|');
|
|
1066
|
-
const qs = 'tags=' + tags.map(encodeURIComponent).join(',') + '&mode=all';
|
|
1067
|
-
return fetchJson('/api/tasks/stats?' + qs)
|
|
1068
|
-
.then(s => ({ ...s, tags: combo }))
|
|
1069
|
-
.catch(() => ({ tags: combo, total_turns: 0, user_turns: 0, total_duration_ms: 0, total_cost_usd: 0 }));
|
|
1070
|
-
})
|
|
1071
|
-
);
|
|
1072
|
-
|
|
1073
|
-
let taskGroups = comboStats.map(s => ({
|
|
1074
|
-
tags: s.tags,
|
|
1075
|
-
turn_count: s.total_turns ?? 0,
|
|
1076
|
-
human_interventions: s.user_turns ?? 0,
|
|
1077
|
-
total_duration_ms: s.total_duration_ms ?? 0,
|
|
1078
|
-
total_cost_usd: s.total_cost_usd ?? 0,
|
|
1079
|
-
last_seen: s.last_seen ?? null,
|
|
1080
|
-
}));
|
|
1081
|
-
|
|
1082
|
-
// Add cost gap to the Untagged row so task costs sum to total
|
|
1083
|
-
const attributedCost = taskGroups.reduce((s, g) => s + (g.total_cost_usd || 0), 0);
|
|
1084
|
-
const totalCost = stats.total_cost_usd || 0;
|
|
1085
|
-
const gap = totalCost - attributedCost;
|
|
1086
|
-
if (gap > 0.01) {
|
|
1087
|
-
const untagged = taskGroups.find(g => g.tags === 'Untagged');
|
|
1088
|
-
if (untagged) {
|
|
1089
|
-
untagged.total_cost_usd = (untagged.total_cost_usd || 0) + gap;
|
|
1090
|
-
} else {
|
|
1091
|
-
taskGroups.push({
|
|
1092
|
-
tags: 'Untagged',
|
|
1093
|
-
turn_count: 0,
|
|
1094
|
-
human_interventions: 0,
|
|
1095
|
-
total_duration_ms: 0,
|
|
1096
|
-
total_cost_usd: gap,
|
|
1097
|
-
last_seen: null,
|
|
1098
|
-
});
|
|
1099
|
-
}
|
|
1100
|
-
}
|
|
1101
|
-
|
|
1102
|
-
allTaskGroups = taskGroups;
|
|
1103
|
-
renderTaskTable(sortData(taskGroups, sortState.tasks));
|
|
1046
|
+
const timeQs = timeQueryString();
|
|
1047
|
+
const groups = await fetchJson('/api/task-groups' + (timeQs ? '?' + timeQs : ''));
|
|
1048
|
+
allTaskGroups = groups;
|
|
1049
|
+
renderTaskTable(sortData(groups, sortState.tasks));
|
|
1104
1050
|
} catch (e) {
|
|
1105
1051
|
console.error('tasks load failed', e);
|
|
1106
1052
|
}
|
|
@@ -1152,8 +1098,17 @@ async function showTaskDetail(tagSet) {
|
|
|
1152
1098
|
statsEl.innerHTML = '<div class="stat-card" style="grid-column:1/-1"><div class="label">Loading...</div></div>';
|
|
1153
1099
|
|
|
1154
1100
|
if (tags.length === 1 && tags[0] === 'Untagged') {
|
|
1155
|
-
|
|
1156
|
-
|
|
1101
|
+
const group = allTaskGroups.find(g => g.tags === 'Untagged') || {};
|
|
1102
|
+
statsEl.innerHTML = [
|
|
1103
|
+
{ label: 'Task', value: '<span style="font-size:14px">' + pills + '</span>' },
|
|
1104
|
+
{ label: 'Total Turns', value: fmtNum(group.turn_count) },
|
|
1105
|
+
{ label: 'Human Interventions', value: fmtNum(group.human_interventions) },
|
|
1106
|
+
{ label: 'Duration', value: fmtDuration(group.total_duration_ms) },
|
|
1107
|
+
{ label: 'Cost', value: fmtCost(group.total_cost_usd) },
|
|
1108
|
+
].map(c =>
|
|
1109
|
+
'<div class="stat-card"><div class="label">' + c.label + '</div><div class="value" style="font-size:16px">' + c.value + '</div></div>'
|
|
1110
|
+
).join('');
|
|
1111
|
+
document.getElementById('task-turns-table').innerHTML = '';
|
|
1157
1112
|
document.getElementById('task-turns-pagination').innerHTML = '';
|
|
1158
1113
|
return;
|
|
1159
1114
|
}
|
package/package.json
CHANGED
package/src/dashboard/index.html
CHANGED
|
@@ -1043,64 +1043,10 @@ function renderSummaryStats(s) {
|
|
|
1043
1043
|
|
|
1044
1044
|
async function loadTasks() {
|
|
1045
1045
|
try {
|
|
1046
|
-
const
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
if (!tasks.length) { allTaskGroups = []; renderTaskTable([]); return; }
|
|
1051
|
-
|
|
1052
|
-
// Discover tag combinations by sampling one turn per tag
|
|
1053
|
-
const samples = await Promise.all(
|
|
1054
|
-
tasks.map(t => fetchJson('/api/tasks/turns?tags=' + encodeURIComponent(t.task) + '&mode=any&limit=1')
|
|
1055
|
-
.then(turns => turns[0]?.tags || t.task)
|
|
1056
|
-
.catch(() => t.task)
|
|
1057
|
-
)
|
|
1058
|
-
);
|
|
1059
|
-
// Deduplicate: normalize each combo to sorted pipe-separated
|
|
1060
|
-
const combos = [...new Set(samples.map(s => s.split(', ').sort().join('|')))];
|
|
1061
|
-
|
|
1062
|
-
// Fetch stats for each unique combo
|
|
1063
|
-
const comboStats = await Promise.all(
|
|
1064
|
-
combos.map(combo => {
|
|
1065
|
-
const tags = combo.split('|');
|
|
1066
|
-
const qs = 'tags=' + tags.map(encodeURIComponent).join(',') + '&mode=all';
|
|
1067
|
-
return fetchJson('/api/tasks/stats?' + qs)
|
|
1068
|
-
.then(s => ({ ...s, tags: combo }))
|
|
1069
|
-
.catch(() => ({ tags: combo, total_turns: 0, user_turns: 0, total_duration_ms: 0, total_cost_usd: 0 }));
|
|
1070
|
-
})
|
|
1071
|
-
);
|
|
1072
|
-
|
|
1073
|
-
let taskGroups = comboStats.map(s => ({
|
|
1074
|
-
tags: s.tags,
|
|
1075
|
-
turn_count: s.total_turns ?? 0,
|
|
1076
|
-
human_interventions: s.user_turns ?? 0,
|
|
1077
|
-
total_duration_ms: s.total_duration_ms ?? 0,
|
|
1078
|
-
total_cost_usd: s.total_cost_usd ?? 0,
|
|
1079
|
-
last_seen: s.last_seen ?? null,
|
|
1080
|
-
}));
|
|
1081
|
-
|
|
1082
|
-
// Add cost gap to the Untagged row so task costs sum to total
|
|
1083
|
-
const attributedCost = taskGroups.reduce((s, g) => s + (g.total_cost_usd || 0), 0);
|
|
1084
|
-
const totalCost = stats.total_cost_usd || 0;
|
|
1085
|
-
const gap = totalCost - attributedCost;
|
|
1086
|
-
if (gap > 0.01) {
|
|
1087
|
-
const untagged = taskGroups.find(g => g.tags === 'Untagged');
|
|
1088
|
-
if (untagged) {
|
|
1089
|
-
untagged.total_cost_usd = (untagged.total_cost_usd || 0) + gap;
|
|
1090
|
-
} else {
|
|
1091
|
-
taskGroups.push({
|
|
1092
|
-
tags: 'Untagged',
|
|
1093
|
-
turn_count: 0,
|
|
1094
|
-
human_interventions: 0,
|
|
1095
|
-
total_duration_ms: 0,
|
|
1096
|
-
total_cost_usd: gap,
|
|
1097
|
-
last_seen: null,
|
|
1098
|
-
});
|
|
1099
|
-
}
|
|
1100
|
-
}
|
|
1101
|
-
|
|
1102
|
-
allTaskGroups = taskGroups;
|
|
1103
|
-
renderTaskTable(sortData(taskGroups, sortState.tasks));
|
|
1046
|
+
const timeQs = timeQueryString();
|
|
1047
|
+
const groups = await fetchJson('/api/task-groups' + (timeQs ? '?' + timeQs : ''));
|
|
1048
|
+
allTaskGroups = groups;
|
|
1049
|
+
renderTaskTable(sortData(groups, sortState.tasks));
|
|
1104
1050
|
} catch (e) {
|
|
1105
1051
|
console.error('tasks load failed', e);
|
|
1106
1052
|
}
|
|
@@ -1152,8 +1098,17 @@ async function showTaskDetail(tagSet) {
|
|
|
1152
1098
|
statsEl.innerHTML = '<div class="stat-card" style="grid-column:1/-1"><div class="label">Loading...</div></div>';
|
|
1153
1099
|
|
|
1154
1100
|
if (tags.length === 1 && tags[0] === 'Untagged') {
|
|
1155
|
-
|
|
1156
|
-
|
|
1101
|
+
const group = allTaskGroups.find(g => g.tags === 'Untagged') || {};
|
|
1102
|
+
statsEl.innerHTML = [
|
|
1103
|
+
{ label: 'Task', value: '<span style="font-size:14px">' + pills + '</span>' },
|
|
1104
|
+
{ label: 'Total Turns', value: fmtNum(group.turn_count) },
|
|
1105
|
+
{ label: 'Human Interventions', value: fmtNum(group.human_interventions) },
|
|
1106
|
+
{ label: 'Duration', value: fmtDuration(group.total_duration_ms) },
|
|
1107
|
+
{ label: 'Cost', value: fmtCost(group.total_cost_usd) },
|
|
1108
|
+
].map(c =>
|
|
1109
|
+
'<div class="stat-card"><div class="label">' + c.label + '</div><div class="value" style="font-size:16px">' + c.value + '</div></div>'
|
|
1110
|
+
).join('');
|
|
1111
|
+
document.getElementById('task-turns-table').innerHTML = '';
|
|
1157
1112
|
document.getElementById('task-turns-pagination').innerHTML = '';
|
|
1158
1113
|
return;
|
|
1159
1114
|
}
|