feat(chat): add typed change-set summary message in timeline
This commit is contained in:
@@ -303,6 +303,7 @@ type PilotMessage = {
|
||||
id: string;
|
||||
role: "user" | "assistant" | "system";
|
||||
text: string;
|
||||
messageKind?: string | null;
|
||||
requestId?: string | null;
|
||||
eventType?: string | null;
|
||||
phase?: string | null;
|
||||
@@ -434,6 +435,27 @@ function pilotRoleBadge(role: PilotMessage["role"]) {
|
||||
return "AI";
|
||||
}
|
||||
|
||||
function summarizeChangeActions(items: PilotMessage["changeItems"] | null | undefined) {
|
||||
const totals = { created: 0, updated: 0, deleted: 0 };
|
||||
for (const item of items ?? []) {
|
||||
if (item.action === "created") totals.created += 1;
|
||||
else if (item.action === "updated") totals.updated += 1;
|
||||
else if (item.action === "deleted") totals.deleted += 1;
|
||||
}
|
||||
return totals;
|
||||
}
|
||||
|
||||
function summarizeChangeEntities(items: PilotMessage["changeItems"] | null | undefined) {
|
||||
const map = new Map<string, number>();
|
||||
for (const item of items ?? []) {
|
||||
const key = item.entity || "unknown";
|
||||
map.set(key, (map.get(key) ?? 0) + 1);
|
||||
}
|
||||
return [...map.entries()]
|
||||
.map(([entity, count]) => ({ entity, count }))
|
||||
.sort((a, b) => b.count - a.count);
|
||||
}
|
||||
|
||||
function formatPilotStamp(iso?: string) {
|
||||
if (!iso) return "";
|
||||
return new Intl.DateTimeFormat("en-GB", {
|
||||
@@ -2699,7 +2721,50 @@ async function decideFeedCard(card: FeedCard, decision: "accepted" | "rejected")
|
||||
<span class="pilot-time">{{ formatPilotStamp(message.createdAt) }}</span>
|
||||
</div>
|
||||
|
||||
<div class="pilot-message-text">
|
||||
<div v-if="message.messageKind === 'change_set_summary'" class="rounded-xl border border-amber-300/35 bg-amber-500/10 p-3">
|
||||
<p class="text-xs font-semibold text-amber-100">
|
||||
{{ message.changeSummary || "Technical change summary" }}
|
||||
</p>
|
||||
<div class="mt-2 overflow-x-auto">
|
||||
<table class="w-full min-w-[340px] text-left text-[11px] text-white/85">
|
||||
<thead>
|
||||
<tr class="text-white/60">
|
||||
<th class="py-1 pr-2 font-medium">Metric</th>
|
||||
<th class="py-1 pr-2 font-medium">Value</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="py-1 pr-2">Total changes</td>
|
||||
<td class="py-1 pr-2">{{ message.changeItems?.length || 0 }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="py-1 pr-2">Created</td>
|
||||
<td class="py-1 pr-2">{{ summarizeChangeActions(message.changeItems).created }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="py-1 pr-2">Updated</td>
|
||||
<td class="py-1 pr-2">{{ summarizeChangeActions(message.changeItems).updated }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="py-1 pr-2">Archived</td>
|
||||
<td class="py-1 pr-2">{{ summarizeChangeActions(message.changeItems).deleted }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div v-if="summarizeChangeEntities(message.changeItems).length" class="mt-2 flex flex-wrap gap-1.5">
|
||||
<span
|
||||
v-for="row in summarizeChangeEntities(message.changeItems)"
|
||||
:key="`entity-summary-${message.id}-${row.entity}`"
|
||||
class="rounded border border-white/20 px-2 py-0.5 text-[10px] text-white/75"
|
||||
>
|
||||
{{ row.entity }}: {{ row.count }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-else class="pilot-message-text">
|
||||
{{ message.text }}
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user