diff --git a/server/chat_flow_task_main.py b/server/chat_flow_task_main.py index d17cb41..db12867 100644 --- a/server/chat_flow_task_main.py +++ b/server/chat_flow_task_main.py @@ -152,6 +152,7 @@ _VALID_USER_MESSAGE_SOURCES = { "goal_prompt", "goal_review", "compression", + "compression_handoff", "permission", "sandbox", "skill", @@ -164,7 +165,7 @@ def _user_message_ui_defaults(source: str, *, auto_user_message_event: bool = Fa return {"visibility": "hidden", "starts_work": False} if normalized in {"guidance", "notify"}: return {"visibility": "compact", "starts_work": False} - if normalized in {"goal_review", "compression", "sub_agent", "background_command"}: + if normalized in {"goal_review", "compression", "compression_handoff", "sub_agent", "background_command"}: return {"visibility": "compact", "starts_work": True} if normalized == "presend": return {"visibility": "chat", "starts_work": True} @@ -1085,16 +1086,22 @@ async def handle_task_with_sender( if guide_message: finalize_user_work_timer() finalize_run_versioning_checkpoint("auto_deep_compress_handoff") - await handle_task_with_sender( - web_terminal, - workspace, - guide_message, - [], - sender, - client_sid, - username, - [] - ) + # 压缩后的续接引导语单独标记来源,使其以 compact 形式展示, + # 而不是混入普通用户消息流。 + setattr(web_terminal, "_current_user_message_source", "compression_handoff") + try: + await handle_task_with_sender( + web_terminal, + workspace, + guide_message, + [], + sender, + client_sid, + username, + [] + ) + finally: + setattr(web_terminal, "_current_user_message_source", "user") return finalize_user_work_timer() finalize_run_versioning_checkpoint("auto_deep_compress_end") @@ -1570,16 +1577,22 @@ async def handle_task_with_sender( if deep_result.get("success") and guide_message: finalize_user_work_timer() finalize_run_versioning_checkpoint("deep_compress_handoff") - await handle_task_with_sender( - web_terminal, - workspace, - guide_message, - [], - sender, - client_sid, - username, - [] - ) + # 压缩后的续接引导语单独标记来源,使其以 compact 形式展示, + # 而不是混入普通用户消息流。 + setattr(web_terminal, "_current_user_message_source", "compression_handoff") + try: + await handle_task_with_sender( + web_terminal, + workspace, + guide_message, + [], + sender, + client_sid, + username, + [] + ) + finally: + setattr(web_terminal, "_current_user_message_source", "user") return finalize_user_work_timer() finalize_run_versioning_checkpoint("deep_compress_end") diff --git a/server/chat_flow_task_support.py b/server/chat_flow_task_support.py index ab16d07..ddd9fb6 100644 --- a/server/chat_flow_task_support.py +++ b/server/chat_flow_task_support.py @@ -19,6 +19,7 @@ _VALID_SOURCES = { "goal_prompt", "goal_review", "compression", + "compression_handoff", "permission", "sandbox", "skill", @@ -32,7 +33,7 @@ def _runtime_message_ui_defaults(src: str, *, inline: bool = False) -> Dict[str, return {"visibility": "hidden", "starts_work": False} if normalized in {"guidance", "notify"}: return {"visibility": "compact", "starts_work": False} - if normalized in {"goal_review", "compression", "sub_agent", "background_command", "presend"}: + if normalized in {"goal_review", "compression", "compression_handoff", "sub_agent", "background_command", "presend"}: return {"visibility": "compact", "starts_work": True} if normalized == "goal": # Backward-compatible fallback for old callers. Inline goal injections are diff --git a/server/conversation.py b/server/conversation.py index 56a5969..803c483 100644 --- a/server/conversation.py +++ b/server/conversation.py @@ -1207,6 +1207,7 @@ def compress_conversation(conversation_id, terminal: WebTerminal, workspace: Use images=[], conversation_id=new_conversation_id, videos=[], + message_source="compression_handoff", ) response_payload["auto_task_started"] = True response_payload["auto_task_id"] = task_rec.task_id diff --git a/static/src/components/chat/ChatArea.vue b/static/src/components/chat/ChatArea.vue index 35c393e..734b2ec 100644 --- a/static/src/components/chat/ChatArea.vue +++ b/static/src/components/chat/ChatArea.vue @@ -11,44 +11,49 @@ 'message-block--before-sub-agent-notice': isFollowedBySubAgentNotice(index) }" > -
-
- - {{ userHeaderLabel(msg) }} +
+ -
-
-
+
@@ -627,6 +632,9 @@ const personalization = usePersonalizationStore(); const blockDisplayMode = computed(() => { return personalization.experiments.blockDisplayMode || 'stacked'; }); +const compactMessageDisplay = computed(() => { + return personalization.experiments.compactMessageDisplay || 'full'; +}); const stackedBlocksEnabled = computed(() => { // 堆叠模式和极简模式都使用堆叠布局 return blockDisplayMode.value === 'stacked' || blockDisplayMode.value === 'minimal'; @@ -1380,6 +1388,12 @@ function userHeaderLabel(msg: any): string { if (source === 'goal') { return '目标'; } + if (source === 'goal_review') { + return '审核'; + } + if (source === 'compression' || source === 'compression_handoff') { + return '压缩'; + } if (source === 'notify' || source === 'sub_agent' || source === 'background_command') { return '通知'; } @@ -1394,12 +1408,54 @@ function userHeaderIconKey(msg: any): string { if (source === 'goal') { return 'flag'; } + if (source === 'goal_review') { + return 'circleAlert'; + } + if (source === 'compression' || source === 'compression_handoff') { + return 'layers'; + } if (source === 'notify' || source === 'sub_agent' || source === 'background_command') { return 'bell'; } return 'user'; } +// ---- 简略信息渲染(compact 消息可选显示形式)---- +// 命中条件:当前消息为 compact 可见性,且用户在个人空间选择了“简略信息”。 +function isBriefCompactMessage(msg: any): boolean { + if (!msg || msg.role !== 'user') { + return false; + } + if (compactMessageDisplay.value !== 'brief') { + return false; + } + return getMessageVisibility(msg) === 'compact'; +} + +// 简略信息只展示一行概要标签(不显示原始内容),按来源给出固定文案。 +function compactBriefLabel(msg: any): string { + const source = userHeaderSource(msg); + switch (source) { + case 'goal_review': + return '目标审核完成'; + case 'sub_agent': + return '后台子智能体完成'; + case 'background_command': + return '后台指令完成'; + case 'compression': + case 'compression_handoff': + return '对话压缩完成'; + case 'goal': + return '目标进行中'; + case 'guidance': + return '运行中引导'; + case 'notify': + return '系统通知'; + default: + return '系统消息'; + } +} + onMounted(() => { attachBounceListener(); console.warn('[SCROLL_BOUNCE_TRACE]', 'mounted', { diff --git a/static/src/components/personalization/PersonalizationDrawer.vue b/static/src/components/personalization/PersonalizationDrawer.vue index ac22ece..df8dcbc 100644 --- a/static/src/components/personalization/PersonalizationDrawer.vue +++ b/static/src/components/personalization/PersonalizationDrawer.vue @@ -615,6 +615,45 @@
+
+ 简略消息显示审核、子智能体等紧凑系统消息的显示形式。 +
+ +
+ +
+
+
{ ); }); +const currentCompactMessageDisplay = computed(() => experiments.value.compactMessageDisplay); + +const compactMessageDisplayLabel = computed(() => { + return ( + compactMessageDisplayOptions.find( + (option) => option.value === currentCompactMessageDisplay.value + )?.label || '完整信息' + ); +}); + const usageSummary = ref({ total_input_tokens: 0, total_output_tokens: 0, @@ -2084,6 +2133,26 @@ const handleBlockDisplayModeChange = (mode: 'traditional' | 'stacked' | 'minimal personalization.setBlockDisplayMode(mode); }; +const compactMessageDisplayOptions = [ + { + id: 'full', + label: '完整信息', + desc: '审核、子智能体等系统消息显示完整原始内容', + value: 'full' as const + }, + { + id: 'brief', + label: '简略信息', + desc: '仅以一行横线概要替代(如「目标审核完成」)', + value: 'brief' as const + } +]; + +const selectCompactMessageDisplay = (mode: 'full' | 'brief') => { + personalization.setCompactMessageDisplay(mode); + closeDropdown(); +}; + const openAdminPanel = () => { window.open('/admin/monitor', '_blank', 'noopener'); personalization.closeDrawer(); diff --git a/static/src/stores/personalization.ts b/static/src/stores/personalization.ts index 32c673e..ac1bb04 100644 --- a/static/src/stores/personalization.ts +++ b/static/src/stores/personalization.ts @@ -2,6 +2,7 @@ import { defineStore } from 'pinia'; import { useModelStore } from './model'; export type BlockDisplayMode = 'traditional' | 'stacked' | 'minimal'; +export type CompactMessageDisplay = 'full' | 'brief'; type RunMode = 'fast' | 'thinking' | 'deep'; type PermissionMode = 'readonly' | 'approval' | 'auto_approval' | 'unrestricted'; @@ -53,6 +54,7 @@ interface PersonalForm { interface ExperimentState { blockDisplayMode: BlockDisplayMode; + compactMessageDisplay: CompactMessageDisplay; } interface PersonalizationState { @@ -142,7 +144,8 @@ const defaultForm = (): PersonalForm => ({ }); const defaultExperimentState = (): ExperimentState => ({ - blockDisplayMode: 'stacked' + blockDisplayMode: 'stacked', + compactMessageDisplay: 'full' }); const loadExperimentState = (): ExperimentState => { @@ -168,8 +171,18 @@ const loadExperimentState = (): ExperimentState => { blockDisplayMode = parsed.stackedBlocksEnabled ? 'stacked' : 'traditional'; } + let compactMessageDisplay: CompactMessageDisplay = + defaultExperimentState().compactMessageDisplay; + if ( + typeof parsed?.compactMessageDisplay === 'string' && + ['full', 'brief'].includes(parsed.compactMessageDisplay) + ) { + compactMessageDisplay = parsed.compactMessageDisplay; + } + return { - blockDisplayMode + blockDisplayMode, + compactMessageDisplay }; } catch (error) { console.warn('无法读取实验功能设置:', error); @@ -760,6 +773,13 @@ export const usePersonalizationStore = defineStore('personalization', { }; this.persistExperiments(); }, + setCompactMessageDisplay(mode: CompactMessageDisplay) { + this.experiments = { + ...this.experiments, + compactMessageDisplay: mode + }; + this.persistExperiments(); + }, setCommunicationStyle(style: 'default' | 'human_like') { this.form = { ...this.form, diff --git a/static/src/styles/components/chat/_chat-area.scss b/static/src/styles/components/chat/_chat-area.scss index 282b00e..228dc07 100644 --- a/static/src/styles/components/chat/_chat-area.scss +++ b/static/src/styles/components/chat/_chat-area.scss @@ -511,6 +511,38 @@ border: 1px solid var(--theme-control-border); } +/* 简略信息:以横跨显示区域的横线包裹一行概要文字(类似 markdown 的 --- 分隔线)。 */ +.user-message.user-message--brief { + align-items: stretch; + width: 100%; +} + +.user-message.user-message--brief .compact-brief-line { + display: flex; + align-items: center; + gap: 14px; + width: min(760px, 92%); + margin: 2px auto; + color: var(--claude-text-secondary); + user-select: none; +} + +.user-message.user-message--brief .compact-brief-line::before, +.user-message.user-message--brief .compact-brief-line::after { + content: ''; + flex: 1 1 auto; + height: 0; + border-top: 1px solid var(--theme-control-border, var(--claude-border)); +} + +.user-message.user-message--brief .compact-brief-text { + flex: 0 0 auto; + font-size: 12.5px; + letter-spacing: 0.04em; + white-space: nowrap; + opacity: 0.9; +} + .user-message .message-text .user-skill-link { color: #2563eb; font-weight: 600; diff --git a/static/src/utils/messageVisibility.ts b/static/src/utils/messageVisibility.ts index 69bfd7d..b1f207c 100644 --- a/static/src/utils/messageVisibility.ts +++ b/static/src/utils/messageVisibility.ts @@ -6,6 +6,7 @@ const COMPACT_FALLBACK_SOURCES = new Set([ 'guidance', 'goal_review', 'compression', + 'compression_handoff', 'sub_agent', 'background_command' ]); @@ -72,7 +73,7 @@ export function messageStartsWork(message: any): boolean { if (source === 'guidance' || source === 'goal_prompt' || source === 'skill') { return false; } - if (source === 'goal_review' || source === 'compression' || isLegacyGoalReview(message)) { + if (source === 'goal_review' || source === 'compression' || source === 'compression_handoff' || isLegacyGoalReview(message)) { return true; } return LEGACY_STARTS_WORK_SOURCES.has(source || 'user');