fix(briefing): use briefing context for follow-ups; add slot separator

- build_context: when conversation_type is 'briefing', inject a system
  prompt instruction telling the model to answer from conversation history
  and article context instead of searching the web
- Consolidate briefing conversation type detection to one DB query (was
  being checked twice — once for the system prompt addition, once for
  article context injection)
- ChatPanel: render a visual 'New Briefing Update' separator line before
  2nd+ briefing slot messages (identified by metadata.rss_item_ids)
- types/chat.ts: add metadata field to Message interface

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-06 06:15:18 -04:00
parent a171210224
commit 9af8ab8f70
3 changed files with 61 additions and 15 deletions
+37 -5
View File
@@ -299,12 +299,22 @@ defineExpose({ focus, prefill, send })
<!-- Message list -->
<div ref="messagesEl" class="messages-container">
<div class="messages-inner">
<ChatMessage
v-for="msg in store.currentConversation?.messages ?? []"
<template
v-for="(msg, index) in store.currentConversation?.messages ?? []"
:key="msg.id"
:message="msg"
@save-as-note="handleSaveAsNote"
/>
>
<!-- Briefing slot separator: shown before a 2nd+ briefing slot message -->
<div
v-if="briefingMode && index > 0 && msg.role === 'assistant' && msg.metadata?.rss_item_ids"
class="briefing-slot-separator"
>
<span>New Briefing Update</span>
</div>
<ChatMessage
:message="msg"
@save-as-note="handleSaveAsNote"
/>
</template>
<!-- Streaming bubble -->
<ChatStreamingBubble v-if="store.streaming" />
<!-- Queued messages -->
@@ -523,6 +533,28 @@ defineExpose({ focus, prefill, send })
justify-content: flex-end;
}
/* Briefing slot separator */
.briefing-slot-separator {
display: flex;
align-items: center;
gap: 0.75rem;
margin: 1.25rem 0 0.5rem;
color: var(--color-text-muted, #888);
font-size: 0.7rem;
font-weight: 600;
letter-spacing: 0.08em;
text-transform: uppercase;
opacity: 0.7;
}
.briefing-slot-separator::before,
.briefing-slot-separator::after {
content: '';
flex: 1;
height: 1px;
background: var(--color-border, #333);
opacity: 0.5;
}
/* Context sidebar */
.context-sidebar {
width: 200px;
+1
View File
@@ -28,6 +28,7 @@ export interface Message {
context_note_id: number | null;
context_note_title?: string | null;
tool_calls?: ToolCallRecord[] | null;
metadata?: Record<string, unknown> | null;
created_at: string;
timing?: GenerationTiming;
thinking?: string;
+23 -10
View File
@@ -607,6 +607,25 @@ async def build_context(
f"\n\n--- Earlier Conversation ---\n{history_summary}\n--- End Earlier Conversation ---"
)
# Detect briefing conversation — used for both system prompt instruction and article injection
_is_briefing_conv = False
if conv_id is not None:
from fabledassistant.models import async_session as _async_session
from fabledassistant.models.conversation import Conversation as _Conversation
async with _async_session() as _sess:
_conv = await _sess.get(_Conversation, conv_id)
if _conv and getattr(_conv, "conversation_type", None) == "briefing":
_is_briefing_conv = True
if _is_briefing_conv:
system_content += (
"\n\nYou are in a briefing conversation. "
"The conversation history contains today's briefing — news stories, weather, and tasks. "
"When the user asks about a topic, person, or event from the briefing, answer directly "
"from the conversation history and the article context that follows. "
"Do NOT search the web for information that is already present in the briefing."
)
context_meta: dict = {
"context_note_id": None,
"context_note_title": None,
@@ -769,16 +788,10 @@ async def build_context(
)
# Briefing article context for follow-up Q&A
if conv_id is not None:
from fabledassistant.models import async_session as _async_session
from fabledassistant.models.conversation import Conversation
async with _async_session() as _sess:
_conv = await _sess.get(Conversation, conv_id)
if _conv and getattr(_conv, "conversation_type", None) == "briefing":
article_context = await _build_briefing_article_context(conv_id)
if article_context:
user_context_parts.append(article_context.strip())
if _is_briefing_conv:
article_context = await _build_briefing_article_context(conv_id) # type: ignore[arg-type]
if article_context:
user_context_parts.append(article_context.strip())
# Build final user message — context prefix (if any) followed by the actual message
if user_context_parts: