feat: 添加 reasoning_content 字段到多个消息结构,支持思考过程展示
This commit is contained in:
parent
0ce89a0e4e
commit
6ff5907616
@ -571,7 +571,8 @@ impl OutboundMessage {
|
|||||||
"assistant" => {
|
"assistant" => {
|
||||||
if let Some(tool_calls) = &message.tool_calls {
|
if let Some(tool_calls) = &message.tool_calls {
|
||||||
let mut outbound = Vec::new();
|
let mut outbound = Vec::new();
|
||||||
if !message.content.trim().is_empty() {
|
let has_content_or_reasoning = !message.content.trim().is_empty() || message.reasoning_content.is_some();
|
||||||
|
if has_content_or_reasoning {
|
||||||
let mut resp = Self::assistant(
|
let mut resp = Self::assistant(
|
||||||
channel.to_string(),
|
channel.to_string(),
|
||||||
chat_id.to_string(),
|
chat_id.to_string(),
|
||||||
@ -585,7 +586,7 @@ impl OutboundMessage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
outbound.extend(tool_calls.iter().map(|tool_call| {
|
outbound.extend(tool_calls.iter().map(|tool_call| {
|
||||||
Self::tool_call(
|
let mut tc = Self::tool_call(
|
||||||
channel.to_string(),
|
channel.to_string(),
|
||||||
chat_id.to_string(),
|
chat_id.to_string(),
|
||||||
session_id.clone(),
|
session_id.clone(),
|
||||||
@ -594,7 +595,9 @@ impl OutboundMessage {
|
|||||||
tool_call.arguments.clone(),
|
tool_call.arguments.clone(),
|
||||||
reply_to.clone(),
|
reply_to.clone(),
|
||||||
metadata.clone(),
|
metadata.clone(),
|
||||||
)
|
);
|
||||||
|
tc.reasoning_content = message.reasoning_content.clone();
|
||||||
|
tc
|
||||||
}));
|
}));
|
||||||
outbound
|
outbound
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@ -762,6 +762,7 @@ fn chat_message_to_ws_outbound(msg: &crate::bus::ChatMessage) -> Option<WsOutbou
|
|||||||
subagent_task_id: None,
|
subagent_task_id: None,
|
||||||
topic_id: None,
|
topic_id: None,
|
||||||
timestamp: Some(msg.timestamp / 1000),
|
timestamp: Some(msg.timestamp / 1000),
|
||||||
|
reasoning_content: msg.reasoning_content.clone(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -156,6 +156,8 @@ pub enum WsOutbound {
|
|||||||
topic_id: Option<String>,
|
topic_id: Option<String>,
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
timestamp: Option<i64>,
|
timestamp: Option<i64>,
|
||||||
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
|
reasoning_content: Option<String>,
|
||||||
},
|
},
|
||||||
#[serde(rename = "tool_result")]
|
#[serde(rename = "tool_result")]
|
||||||
ToolResult {
|
ToolResult {
|
||||||
|
|||||||
@ -15,7 +15,8 @@ pub(crate) fn ws_outbound_from_chat_message(message: &ChatMessage) -> Vec<WsOutb
|
|||||||
"assistant" => {
|
"assistant" => {
|
||||||
if let Some(tool_calls) = &message.tool_calls {
|
if let Some(tool_calls) = &message.tool_calls {
|
||||||
let mut outbound = Vec::new();
|
let mut outbound = Vec::new();
|
||||||
if !message.content.trim().is_empty() {
|
let has_content_or_reasoning = !message.content.trim().is_empty() || message.reasoning_content.is_some();
|
||||||
|
if has_content_or_reasoning {
|
||||||
outbound.push(WsOutbound::AssistantResponse {
|
outbound.push(WsOutbound::AssistantResponse {
|
||||||
id: message.id.clone(),
|
id: message.id.clone(),
|
||||||
content: message.content.clone(),
|
content: message.content.clone(),
|
||||||
@ -38,6 +39,7 @@ pub(crate) fn ws_outbound_from_chat_message(message: &ChatMessage) -> Vec<WsOutb
|
|||||||
subagent_task_id: None,
|
subagent_task_id: None,
|
||||||
topic_id: None,
|
topic_id: None,
|
||||||
timestamp: None,
|
timestamp: None,
|
||||||
|
reasoning_content: message.reasoning_content.clone(),
|
||||||
}));
|
}));
|
||||||
outbound
|
outbound
|
||||||
} else {
|
} else {
|
||||||
@ -126,6 +128,7 @@ pub(crate) fn ws_outbound_from_outbound_message(message: &OutboundMessage) -> Ve
|
|||||||
subagent_task_id: message.metadata.get("subagent_task_id").cloned(),
|
subagent_task_id: message.metadata.get("subagent_task_id").cloned(),
|
||||||
topic_id: message.metadata.get("topic_id").cloned(),
|
topic_id: message.metadata.get("topic_id").cloned(),
|
||||||
timestamp: Some(crate::protocol::now_timestamp()),
|
timestamp: Some(crate::protocol::now_timestamp()),
|
||||||
|
reasoning_content: message.reasoning_content.clone(),
|
||||||
}],
|
}],
|
||||||
OutboundEventKind::ToolResult => vec![WsOutbound::ToolResult {
|
OutboundEventKind::ToolResult => vec![WsOutbound::ToolResult {
|
||||||
id: message
|
id: message
|
||||||
|
|||||||
@ -125,6 +125,7 @@ fn test_tool_call_outbound_serialization() {
|
|||||||
subagent_task_id: None,
|
subagent_task_id: None,
|
||||||
topic_id: None,
|
topic_id: None,
|
||||||
timestamp: None,
|
timestamp: None,
|
||||||
|
reasoning_content: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let json = serde_json::to_string(&msg).unwrap();
|
let json = serde_json::to_string(&msg).unwrap();
|
||||||
|
|||||||
@ -242,11 +242,11 @@ function ThinkingSection({ content }: { content: string }) {
|
|||||||
return (
|
return (
|
||||||
<div className="mb-3 rounded-lg border border-purple-500/20 bg-purple-500/5 overflow-hidden">
|
<div className="mb-3 rounded-lg border border-purple-500/20 bg-purple-500/5 overflow-hidden">
|
||||||
<button
|
<button
|
||||||
onClick={() => setExpanded(!expanded)}
|
onClick={(e) => { e.stopPropagation(); setExpanded(!expanded) }}
|
||||||
className="flex items-center gap-2 w-full px-3 py-2 text-xs text-[var(--text-muted)] hover:text-[var(--text-secondary)] hover:bg-purple-500/5 transition-colors cursor-pointer select-none"
|
className="flex items-center gap-2 w-full px-3 py-2 text-xs text-[var(--text-muted)] hover:text-[var(--text-secondary)] hover:bg-purple-500/5 transition-colors cursor-pointer select-none"
|
||||||
>
|
>
|
||||||
<Brain className="h-3.5 w-3.5 text-purple-400 flex-shrink-0" />
|
<Brain className="h-3.5 w-3.5 text-purple-400 flex-shrink-0" />
|
||||||
<span className="font-medium text-purple-300">思考过程</span>
|
<span className="font-medium text-purple-300">Thinking</span>
|
||||||
{expanded ? (
|
{expanded ? (
|
||||||
<ChevronDown className="h-3 w-3 ml-auto flex-shrink-0" />
|
<ChevronDown className="h-3 w-3 ml-auto flex-shrink-0" />
|
||||||
) : (
|
) : (
|
||||||
@ -260,7 +260,7 @@ function ThinkingSection({ content }: { content: string }) {
|
|||||||
</button>
|
</button>
|
||||||
{expanded && (
|
{expanded && (
|
||||||
<div className="px-3 py-2 border-t border-purple-500/10 animate-thinking-reveal">
|
<div className="px-3 py-2 border-t border-purple-500/10 animate-thinking-reveal">
|
||||||
<div className="text-xs text-purple-200/80 italic whitespace-pre-wrap leading-relaxed max-h-64 overflow-y-auto scrollbar-thin">
|
<div className="text-xs text-purple-200/80 whitespace-pre-wrap leading-relaxed max-h-64 overflow-y-auto scrollbar-thin">
|
||||||
{content}
|
{content}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -437,6 +437,12 @@ export function MessageBubble({ message, onNavigateToSubAgent }: MessageBubblePr
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* 模型思考内容(工具调用时展示) */}
|
||||||
|
{message.reasoningContent && !toolExpanded && (
|
||||||
|
<div className="px-3 pb-1">
|
||||||
|
<ThinkingSection content={message.reasoningContent} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
{/* Collapsed preview */}
|
{/* Collapsed preview */}
|
||||||
{!toolExpanded && (
|
{!toolExpanded && (
|
||||||
<>
|
<>
|
||||||
@ -495,6 +501,10 @@ export function MessageBubble({ message, onNavigateToSubAgent }: MessageBubblePr
|
|||||||
{/* Expanded */}
|
{/* Expanded */}
|
||||||
{toolExpanded && (
|
{toolExpanded && (
|
||||||
<div className="border-t border-[var(--border-color)] px-3 py-2 space-y-2">
|
<div className="border-t border-[var(--border-color)] px-3 py-2 space-y-2">
|
||||||
|
{/* 模型思考内容(展开时也展示) */}
|
||||||
|
{message.reasoningContent && (
|
||||||
|
<ThinkingSection content={message.reasoningContent} />
|
||||||
|
)}
|
||||||
{taskResult ? (
|
{taskResult ? (
|
||||||
<>
|
<>
|
||||||
{taskPrompt && (
|
{taskPrompt && (
|
||||||
@ -646,7 +656,7 @@ export function MessageBubble({ message, onNavigateToSubAgent }: MessageBubblePr
|
|||||||
{!isTool && message.reasoningContent && (
|
{!isTool && message.reasoningContent && (
|
||||||
<ThinkingSection content={message.reasoningContent} />
|
<ThinkingSection content={message.reasoningContent} />
|
||||||
)}
|
)}
|
||||||
// AI 和工具消息使用 Markdown 渲染
|
{/* AI 和工具消息使用 Markdown 渲染 */}
|
||||||
<div className="markdown-content text-sm leading-relaxed">
|
<div className="markdown-content text-sm leading-relaxed">
|
||||||
<ReactMarkdown
|
<ReactMarkdown
|
||||||
remarkPlugins={[remarkGfm]}
|
remarkPlugins={[remarkGfm]}
|
||||||
|
|||||||
@ -210,6 +210,7 @@ export function useChat(): UseChatReturn {
|
|||||||
toolCallId: msg.tool_call_id,
|
toolCallId: msg.tool_call_id,
|
||||||
arguments: msg.arguments,
|
arguments: msg.arguments,
|
||||||
subagentTaskId: msg.subagent_task_id,
|
subagentTaskId: msg.subagent_task_id,
|
||||||
|
reasoningContent: msg.reasoning_content,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case 'tool_result': {
|
case 'tool_result': {
|
||||||
@ -460,6 +461,7 @@ export function useChat(): UseChatReturn {
|
|||||||
toolCallId: msg.tool_call_id,
|
toolCallId: msg.tool_call_id,
|
||||||
arguments: msg.arguments,
|
arguments: msg.arguments,
|
||||||
subagentTaskId: msg.subagent_task_id,
|
subagentTaskId: msg.subagent_task_id,
|
||||||
|
reasoningContent: msg.reasoning_content,
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
break
|
break
|
||||||
|
|||||||
@ -59,6 +59,7 @@ export interface ToolCall {
|
|||||||
subagent_task_id?: string
|
subagent_task_id?: string
|
||||||
topic_id?: string
|
topic_id?: string
|
||||||
timestamp?: number
|
timestamp?: number
|
||||||
|
reasoning_content?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ToolResult {
|
export interface ToolResult {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user