diff --git a/src-tauri/src/parsers/claude.rs b/src-tauri/src/parsers/claude.rs index f0df591..34df1ab 100644 --- a/src-tauri/src/parsers/claude.rs +++ b/src-tauri/src/parsers/claude.rs @@ -24,6 +24,8 @@ fn system_tag_regex() -> &'static Regex { r"|.*?", r"|.*?", r"|.*?", + r"|.*?", + r"|.*?", )) .unwrap() }) @@ -62,6 +64,21 @@ fn is_meta_message(value: &serde_json::Value) -> bool { /// Claude Code for local commands like `/context` or `/model`). /// These carry `model: ""` and all-zero usage, so they should be /// excluded from conversation turns and stats. +const CONTEXT_CONTINUATION_PREFIX: &str = + "This session is being continued from a previous conversation"; + +/// Detect Claude Code context continuation summary messages. +/// These are injected as "user" type but are actually system context. +fn is_context_continuation(content: &[ContentBlock]) -> bool { + content.iter().any(|block| { + if let ContentBlock::Text { text } = block { + text.starts_with(CONTEXT_CONTINUATION_PREFIX) + } else { + false + } + }) +} + fn is_synthetic_assistant(value: &serde_json::Value) -> bool { value .get("message") @@ -486,18 +503,24 @@ impl ClaudeParser { .unwrap_or("") .to_string(); - if title.is_none() { - if let Some(first_text) = content.iter().find_map(|c| match c { - ContentBlock::Text { text } => Some(text.clone()), - _ => None, - }) { - title = Some(truncate_str(&first_text, 100)); + // Detect context continuation summary and treat as system message + let role = if is_context_continuation(&content) { + MessageRole::System + } else { + if title.is_none() { + if let Some(first_text) = content.iter().find_map(|c| match c { + ContentBlock::Text { text } => Some(text.clone()), + _ => None, + }) { + title = Some(truncate_str(&first_text, 100)); + } } - } + MessageRole::User + }; messages.push(UnifiedMessage { id: uuid, - role: MessageRole::User, + role, content, timestamp, usage: None, diff --git a/src/components/message/message-list-view.tsx b/src/components/message/message-list-view.tsx index 727b85d..72375f9 100644 --- a/src/components/message/message-list-view.tsx +++ b/src/components/message/message-list-view.tsx @@ -1,6 +1,6 @@ "use client" -import { memo, useCallback, useEffect, useMemo, useRef } from "react" +import { memo, useCallback, useEffect, useMemo, useRef, useState } from "react" import { useConversationRuntime } from "@/contexts/conversation-runtime-context" import { ContentPartsRenderer } from "./content-parts-renderer" import { @@ -20,7 +20,7 @@ import { MessageThreadScrollButton, } from "@/components/ai-elements/message-thread" import { Message, MessageContent } from "@/components/ai-elements/message" -import { Loader2 } from "lucide-react" +import { ChevronDown, ChevronRight, Info, Loader2 } from "lucide-react" import { useTranslations } from "next-intl" import { buildPlanKey, @@ -68,6 +68,41 @@ type ThreadRenderItem = kind: "typing" } +const CollapsibleSystemMessage = memo(function CollapsibleSystemMessage({ + group, +}: { + group: ResolvedMessageGroup +}) { + const [expanded, setExpanded] = useState(false) + const t = useTranslations("Folder.chat.messageList") + + return ( +
+ + {expanded && ( +
+
+ +
+
+ )} +
+ ) +}) + const HistoricalMessageGroup = memo(function HistoricalMessageGroup({ group, dimmed = false, @@ -77,6 +112,10 @@ const HistoricalMessageGroup = memo(function HistoricalMessageGroup({ dimmed?: boolean showStats?: boolean }) { + if (group.role === "system") { + return + } + return (
diff --git a/src/i18n/messages/ar.json b/src/i18n/messages/ar.json index 34d5b14..46684e8 100644 --- a/src/i18n/messages/ar.json +++ b/src/i18n/messages/ar.json @@ -1445,7 +1445,8 @@ "attachedResources": "الموارد المرفقة", "loading": "جارٍ التحميل...", "error": "خطأ: {message}", - "emptyConversation": "لا توجد رسائل في هذه المحادثة." + "emptyConversation": "لا توجد رسائل في هذه المحادثة.", + "systemMessage": "رسالة النظام" }, "liveTurnStats": { "thinking": "جارٍ التفكير...", diff --git a/src/i18n/messages/de.json b/src/i18n/messages/de.json index 547eba8..dc5f956 100644 --- a/src/i18n/messages/de.json +++ b/src/i18n/messages/de.json @@ -1445,7 +1445,8 @@ "attachedResources": "Angehängte Ressourcen", "loading": "Lädt...", "error": "Fehler: {message}", - "emptyConversation": "Keine Nachrichten in dieser Unterhaltung." + "emptyConversation": "Keine Nachrichten in dieser Unterhaltung.", + "systemMessage": "Systemnachricht" }, "liveTurnStats": { "thinking": "Denkt nach...", diff --git a/src/i18n/messages/en.json b/src/i18n/messages/en.json index 1642387..7addaa5 100644 --- a/src/i18n/messages/en.json +++ b/src/i18n/messages/en.json @@ -1445,7 +1445,8 @@ "attachedResources": "Attached resources", "loading": "Loading...", "error": "Error: {message}", - "emptyConversation": "No messages in this conversation." + "emptyConversation": "No messages in this conversation.", + "systemMessage": "System message" }, "liveTurnStats": { "thinking": "Thinking...", diff --git a/src/i18n/messages/es.json b/src/i18n/messages/es.json index 0ae228c..bf20e5f 100644 --- a/src/i18n/messages/es.json +++ b/src/i18n/messages/es.json @@ -1445,7 +1445,8 @@ "attachedResources": "Recursos adjuntos", "loading": "Cargando...", "error": "Error del chat: {message}", - "emptyConversation": "No hay mensajes en esta conversación." + "emptyConversation": "No hay mensajes en esta conversación.", + "systemMessage": "Mensaje del sistema" }, "liveTurnStats": { "thinking": "Pensando...", diff --git a/src/i18n/messages/fr.json b/src/i18n/messages/fr.json index 359d7da..a02261d 100644 --- a/src/i18n/messages/fr.json +++ b/src/i18n/messages/fr.json @@ -1445,7 +1445,8 @@ "attachedResources": "Ressources jointes", "loading": "Chargement...", "error": "Erreur : {message}", - "emptyConversation": "Aucun message dans cette conversation." + "emptyConversation": "Aucun message dans cette conversation.", + "systemMessage": "Message système" }, "liveTurnStats": { "thinking": "Réflexion...", diff --git a/src/i18n/messages/ja.json b/src/i18n/messages/ja.json index 419d9a3..6379498 100644 --- a/src/i18n/messages/ja.json +++ b/src/i18n/messages/ja.json @@ -1445,7 +1445,8 @@ "attachedResources": "添付リソース", "loading": "読み込み中...", "error": "エラー: {message}", - "emptyConversation": "この会話にはメッセージがありません。" + "emptyConversation": "この会話にはメッセージがありません。", + "systemMessage": "システムメッセージ" }, "liveTurnStats": { "thinking": "考え中...", diff --git a/src/i18n/messages/ko.json b/src/i18n/messages/ko.json index 1759be9..08a4ad5 100644 --- a/src/i18n/messages/ko.json +++ b/src/i18n/messages/ko.json @@ -1445,7 +1445,8 @@ "attachedResources": "첨부된 리소스", "loading": "불러오는 중...", "error": "오류: {message}", - "emptyConversation": "이 대화에는 메시지가 없습니다." + "emptyConversation": "이 대화에는 메시지가 없습니다.", + "systemMessage": "시스템 메시지" }, "liveTurnStats": { "thinking": "생각 중...", diff --git a/src/i18n/messages/pt.json b/src/i18n/messages/pt.json index 3426b27..a7a6a9f 100644 --- a/src/i18n/messages/pt.json +++ b/src/i18n/messages/pt.json @@ -1445,7 +1445,8 @@ "attachedResources": "Recursos anexados", "loading": "Carregando...", "error": "Erro: {message}", - "emptyConversation": "Nenhuma mensagem nesta conversa." + "emptyConversation": "Nenhuma mensagem nesta conversa.", + "systemMessage": "Mensagem do sistema" }, "liveTurnStats": { "thinking": "Pensando...", diff --git a/src/i18n/messages/zh-CN.json b/src/i18n/messages/zh-CN.json index 3a2ff75..3cb4f23 100644 --- a/src/i18n/messages/zh-CN.json +++ b/src/i18n/messages/zh-CN.json @@ -1445,7 +1445,8 @@ "attachedResources": "附加资源", "loading": "加载中...", "error": "错误:{message}", - "emptyConversation": "当前会话暂无消息。" + "emptyConversation": "当前会话暂无消息。", + "systemMessage": "系统消息" }, "liveTurnStats": { "thinking": "思考中...", diff --git a/src/i18n/messages/zh-TW.json b/src/i18n/messages/zh-TW.json index e2620f4..af8514b 100644 --- a/src/i18n/messages/zh-TW.json +++ b/src/i18n/messages/zh-TW.json @@ -1445,7 +1445,8 @@ "attachedResources": "附加資源", "loading": "載入中...", "error": "錯誤:{message}", - "emptyConversation": "目前會話暫無訊息。" + "emptyConversation": "目前會話暫無訊息。", + "systemMessage": "系統訊息" }, "liveTurnStats": { "thinking": "思考中...",