From 593d737e26ea4fab198c62c8f44107a629dca6bf Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Fri, 10 Apr 2026 22:10:31 -0400 Subject: [PATCH] feat(briefing): UI polish for agentic briefing messages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three visual improvements for the briefing conversation: 1. Intermediate tool-call messages (empty content, briefing_intermediate: true) now render as a compact dashed status row with per-tool pills showing result counts, instead of an empty assistant bubble followed by a stack of ToolCallCards. Click to expand the full cards. 2. Slot badge on non-intermediate briefing messages — "Full Briefing" for compilation, "Morning/Midday/Afternoon Update" for slot injections. Slot updates get a softer bubble treatment (transparent background, muted border, smaller text) so the compilation stays visually dominant. 3. Slot separator now triggers on briefing_slot metadata (not the compilation-only rss_item_ids), and uses a look-behind so it only fires when there's a prior slot message — no separator above the first briefing of the day. New component: BriefingToolStatusRow.vue handles the intermediate pill row and delegates to ToolCallCard when expanded. Co-Authored-By: Claude Opus 4.6 --- .../src/components/BriefingToolStatusRow.vue | 168 ++++++++++++++++++ frontend/src/components/ChatMessage.vue | 76 +++++++- frontend/src/components/ChatPanel.vue | 43 ++--- 3 files changed, 264 insertions(+), 23 deletions(-) create mode 100644 frontend/src/components/BriefingToolStatusRow.vue diff --git a/frontend/src/components/BriefingToolStatusRow.vue b/frontend/src/components/BriefingToolStatusRow.vue new file mode 100644 index 0000000..7ba64c4 --- /dev/null +++ b/frontend/src/components/BriefingToolStatusRow.vue @@ -0,0 +1,168 @@ + + + + + diff --git a/frontend/src/components/ChatMessage.vue b/frontend/src/components/ChatMessage.vue index 14dc4d1..ebb6f67 100644 --- a/frontend/src/components/ChatMessage.vue +++ b/frontend/src/components/ChatMessage.vue @@ -3,8 +3,16 @@ import { computed } from "vue"; import { renderMarkdown } from "@/utils/markdown"; import { useSettingsStore } from "@/stores/settings"; import ToolCallCard from "@/components/ToolCallCard.vue"; +import BriefingToolStatusRow from "@/components/BriefingToolStatusRow.vue"; import type { Message } from "@/types/chat"; +const SLOT_LABELS: Record = { + compilation: "Full Briefing", + morning: "Morning Update", + midday: "Midday Update", + afternoon: "Afternoon Update", +}; + const settingsStore = useSettingsStore(); const props = defineProps<{ @@ -41,6 +49,30 @@ function formatMs(ms: number | null | undefined): string { return `${(ms / 1000).toFixed(1)}s`; } +const metadata = computed(() => (props.message.metadata ?? {}) as Record); + +const isBriefingIntermediate = computed( + () => props.message.role === "assistant" && metadata.value.briefing_intermediate === true, +); + +const briefingSlot = computed(() => { + const slot = metadata.value.briefing_slot; + return typeof slot === "string" ? slot : null; +}); + +const slotLabel = computed(() => { + const slot = briefingSlot.value; + if (!slot) return null; + return SLOT_LABELS[slot] ?? slot; +}); + +const isSlotUpdate = computed( + () => + !isBriefingIntermediate.value && + briefingSlot.value != null && + briefingSlot.value !== "compilation", +); + const timingParts = computed((): string[] => { const t = props.message.timing; if (!t) return []; @@ -57,11 +89,16 @@ const timingParts = computed((): string[] => {