Compare commits
No commits in common. "4487f1a490f4cabe30871d8f854a548c2ed60540" and "694b3ce0e01fb0d349851cf9e5ce5aa132d317aa" have entirely different histories.
4487f1a490
...
694b3ce0e0
@ -400,7 +400,6 @@ pub struct OutboundMessage {
|
|||||||
pub tool_call_id: Option<String>,
|
pub tool_call_id: Option<String>,
|
||||||
pub tool_name: Option<String>,
|
pub tool_name: Option<String>,
|
||||||
pub tool_arguments: Option<serde_json::Value>,
|
pub tool_arguments: Option<serde_json::Value>,
|
||||||
pub reasoning_content: Option<String>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
@ -440,7 +439,6 @@ impl OutboundMessage {
|
|||||||
tool_call_id: None,
|
tool_call_id: None,
|
||||||
tool_name: None,
|
tool_name: None,
|
||||||
tool_arguments: None,
|
tool_arguments: None,
|
||||||
reasoning_content: None,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -495,7 +493,6 @@ impl OutboundMessage {
|
|||||||
tool_call_id: Some(message_id.into()),
|
tool_call_id: Some(message_id.into()),
|
||||||
tool_name: Some(tool_name),
|
tool_name: Some(tool_name),
|
||||||
tool_arguments: Some(tool_arguments),
|
tool_arguments: Some(tool_arguments),
|
||||||
reasoning_content: None,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -525,7 +522,6 @@ impl OutboundMessage {
|
|||||||
tool_call_id: Some(tool_call_id.into()),
|
tool_call_id: Some(tool_call_id.into()),
|
||||||
tool_name: Some(tool_name),
|
tool_name: Some(tool_name),
|
||||||
tool_arguments: None,
|
tool_arguments: None,
|
||||||
reasoning_content: None,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -555,7 +551,6 @@ impl OutboundMessage {
|
|||||||
tool_call_id: Some(tool_call_id.into()),
|
tool_call_id: Some(tool_call_id.into()),
|
||||||
tool_name: Some(tool_name),
|
tool_name: Some(tool_name),
|
||||||
tool_arguments: None,
|
tool_arguments: None,
|
||||||
reasoning_content: None,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -571,25 +566,19 @@ 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();
|
||||||
let has_content_or_reasoning = !message.content.trim().is_empty() || message.reasoning_content.is_some();
|
if !message.content.trim().is_empty() {
|
||||||
if has_content_or_reasoning {
|
outbound.push(Self::assistant(
|
||||||
let mut resp = Self::assistant(
|
|
||||||
channel.to_string(),
|
channel.to_string(),
|
||||||
chat_id.to_string(),
|
chat_id.to_string(),
|
||||||
session_id.clone(),
|
session_id.clone(),
|
||||||
message.content.clone(),
|
message.content.clone(),
|
||||||
reply_to.clone(),
|
reply_to.clone(),
|
||||||
metadata.clone(),
|
metadata.clone(),
|
||||||
);
|
));
|
||||||
resp.reasoning_content = message.reasoning_content.clone();
|
|
||||||
outbound.push(resp);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// AssistantResponse 已携带 reasoning 时,ToolCall 不再重复;
|
|
||||||
// 只有 AssistantResponse 没发时,ToolCall 才带 reasoning
|
|
||||||
let tc_reasoning = if has_content_or_reasoning { None } else { message.reasoning_content.clone() };
|
|
||||||
outbound.extend(tool_calls.iter().map(|tool_call| {
|
outbound.extend(tool_calls.iter().map(|tool_call| {
|
||||||
let mut tc = Self::tool_call(
|
Self::tool_call(
|
||||||
channel.to_string(),
|
channel.to_string(),
|
||||||
chat_id.to_string(),
|
chat_id.to_string(),
|
||||||
session_id.clone(),
|
session_id.clone(),
|
||||||
@ -598,22 +587,18 @@ impl OutboundMessage {
|
|||||||
tool_call.arguments.clone(),
|
tool_call.arguments.clone(),
|
||||||
reply_to.clone(),
|
reply_to.clone(),
|
||||||
metadata.clone(),
|
metadata.clone(),
|
||||||
);
|
)
|
||||||
tc.reasoning_content = tc_reasoning.clone();
|
|
||||||
tc
|
|
||||||
}));
|
}));
|
||||||
outbound
|
outbound
|
||||||
} else {
|
} else {
|
||||||
let mut resp = Self::assistant(
|
vec![Self::assistant(
|
||||||
channel.to_string(),
|
channel.to_string(),
|
||||||
chat_id.to_string(),
|
chat_id.to_string(),
|
||||||
session_id,
|
session_id,
|
||||||
message.content.clone(),
|
message.content.clone(),
|
||||||
reply_to,
|
reply_to,
|
||||||
metadata.clone(),
|
metadata.clone(),
|
||||||
);
|
)]
|
||||||
resp.reasoning_content = message.reasoning_content.clone();
|
|
||||||
vec![resp]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"tool" => match message
|
"tool" => match message
|
||||||
|
|||||||
@ -79,7 +79,6 @@ impl OutputAdapter for WebSocketOutputAdapter {
|
|||||||
content: msg.content.clone(),
|
content: msg.content.clone(),
|
||||||
role: "assistant".to_string(),
|
role: "assistant".to_string(),
|
||||||
attachments: Vec::new(), subagent_task_id: None, topic_id: None, timestamp: Some(crate::protocol::now_timestamp()),
|
attachments: Vec::new(), subagent_task_id: None, topic_id: None, timestamp: Some(crate::protocol::now_timestamp()),
|
||||||
reasoning_content: None,
|
|
||||||
},
|
},
|
||||||
MessageKind::Notification => {
|
MessageKind::Notification => {
|
||||||
// 根据元数据判断具体类型
|
// 根据元数据判断具体类型
|
||||||
@ -100,7 +99,6 @@ impl OutputAdapter for WebSocketOutputAdapter {
|
|||||||
content: msg.content.clone(),
|
content: msg.content.clone(),
|
||||||
role: "assistant".to_string(),
|
role: "assistant".to_string(),
|
||||||
attachments: Vec::new(), subagent_task_id: None, topic_id: None, timestamp: Some(crate::protocol::now_timestamp()),
|
attachments: Vec::new(), subagent_task_id: None, topic_id: None, timestamp: Some(crate::protocol::now_timestamp()),
|
||||||
reasoning_content: None,
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
} else if let Some(session_id) = response.metadata.get("session_id") {
|
} else if let Some(session_id) = response.metadata.get("session_id") {
|
||||||
@ -140,7 +138,6 @@ impl OutputAdapter for WebSocketOutputAdapter {
|
|||||||
content: msg.content.clone(),
|
content: msg.content.clone(),
|
||||||
role: "assistant".to_string(),
|
role: "assistant".to_string(),
|
||||||
attachments: Vec::new(), subagent_task_id: None, topic_id: None, timestamp: Some(crate::protocol::now_timestamp()),
|
attachments: Vec::new(), subagent_task_id: None, topic_id: None, timestamp: Some(crate::protocol::now_timestamp()),
|
||||||
reasoning_content: None,
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
} else if let Some(sessions_json) = response.metadata.get("sessions") {
|
} else if let Some(sessions_json) = response.metadata.get("sessions") {
|
||||||
@ -159,7 +156,6 @@ impl OutputAdapter for WebSocketOutputAdapter {
|
|||||||
content: msg.content.clone(),
|
content: msg.content.clone(),
|
||||||
role: "assistant".to_string(),
|
role: "assistant".to_string(),
|
||||||
attachments: Vec::new(), subagent_task_id: None, topic_id: None, timestamp: Some(crate::protocol::now_timestamp()),
|
attachments: Vec::new(), subagent_task_id: None, topic_id: None, timestamp: Some(crate::protocol::now_timestamp()),
|
||||||
reasoning_content: None,
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
} else if let Some(topics_json) = response.metadata.get("topics") {
|
} else if let Some(topics_json) = response.metadata.get("topics") {
|
||||||
@ -179,7 +175,6 @@ impl OutputAdapter for WebSocketOutputAdapter {
|
|||||||
content: msg.content.clone(),
|
content: msg.content.clone(),
|
||||||
role: "assistant".to_string(),
|
role: "assistant".to_string(),
|
||||||
attachments: Vec::new(), subagent_task_id: None, topic_id: None, timestamp: Some(crate::protocol::now_timestamp()),
|
attachments: Vec::new(), subagent_task_id: None, topic_id: None, timestamp: Some(crate::protocol::now_timestamp()),
|
||||||
reasoning_content: None,
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -189,7 +184,6 @@ impl OutputAdapter for WebSocketOutputAdapter {
|
|||||||
content: msg.content.clone(),
|
content: msg.content.clone(),
|
||||||
role: "assistant".to_string(),
|
role: "assistant".to_string(),
|
||||||
attachments: Vec::new(), subagent_task_id: None, topic_id: None, timestamp: Some(crate::protocol::now_timestamp()),
|
attachments: Vec::new(), subagent_task_id: None, topic_id: None, timestamp: Some(crate::protocol::now_timestamp()),
|
||||||
reasoning_content: None,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -203,7 +197,6 @@ impl OutputAdapter for WebSocketOutputAdapter {
|
|||||||
content: msg.content.clone(),
|
content: msg.content.clone(),
|
||||||
role: "assistant".to_string(),
|
role: "assistant".to_string(),
|
||||||
attachments: Vec::new(), subagent_task_id: None, topic_id: None, timestamp: Some(crate::protocol::now_timestamp()),
|
attachments: Vec::new(), subagent_task_id: None, topic_id: None, timestamp: Some(crate::protocol::now_timestamp()),
|
||||||
reasoning_content: None,
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
outbounds.push(outbound);
|
outbounds.push(outbound);
|
||||||
|
|||||||
@ -102,7 +102,6 @@
|
|||||||
- 回答应以帮助用户完成当前目标为中心。
|
- 回答应以帮助用户完成当前目标为中心。
|
||||||
- 在信息不足时先补关键前提,在信息充分时直接执行。
|
- 在信息不足时先补关键前提,在信息充分时直接执行。
|
||||||
- Skill 不是工具名。看到可用 Skill 时,不能直接调用 Skill 名称;必须先调用 skill_activate,并传入对应的 name。
|
- Skill 不是工具名。看到可用 Skill 时,不能直接调用 Skill 名称;必须先调用 skill_activate,并传入对应的 name。
|
||||||
- 调用工具的时候必须同时用简短的话告诉用户你调用工具是做什么
|
|
||||||
|
|
||||||
|
|
||||||
## 定时任务
|
## 定时任务
|
||||||
@ -110,3 +109,5 @@
|
|||||||
- 默认创建静默任务(silent_agent_task),在独立后台会话中执行,不干扰主对话
|
- 默认创建静默任务(silent_agent_task),在独立后台会话中执行,不干扰主对话
|
||||||
- 静默模式下如需发送消息给用户,prompt中需显式使用 send_session_message 工具
|
- 静默模式下如需发送消息给用户,prompt中需显式使用 send_session_message 工具
|
||||||
|
|
||||||
|
## 注意
|
||||||
|
- 不要通过一次调用写入一个很长的文件,请分段写入
|
||||||
|
|||||||
@ -762,7 +762,6 @@ 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(),
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -775,7 +774,6 @@ 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(),
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
"tool" => {
|
"tool" => {
|
||||||
@ -813,7 +811,6 @@ 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: None,
|
|
||||||
}),
|
}),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
|
|||||||
@ -139,8 +139,6 @@ 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_call")]
|
#[serde(rename = "tool_call")]
|
||||||
ToolCall {
|
ToolCall {
|
||||||
@ -156,8 +154,6 @@ 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,8 +15,7 @@ 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();
|
||||||
let has_content_or_reasoning = !message.content.trim().is_empty() || message.reasoning_content.is_some();
|
if !message.content.trim().is_empty() {
|
||||||
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(),
|
||||||
@ -25,12 +24,9 @@ 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(),
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// AssistantResponse 已携带 reasoning 时,ToolCall 不再重复
|
|
||||||
let tc_reasoning = if has_content_or_reasoning { None } else { message.reasoning_content.clone() };
|
|
||||||
outbound.extend(tool_calls.iter().map(|tool_call| WsOutbound::ToolCall {
|
outbound.extend(tool_calls.iter().map(|tool_call| WsOutbound::ToolCall {
|
||||||
id: message.id.clone(),
|
id: message.id.clone(),
|
||||||
tool_call_id: tool_call.id.clone(),
|
tool_call_id: tool_call.id.clone(),
|
||||||
@ -41,7 +37,6 @@ 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: tc_reasoning.clone(),
|
|
||||||
}));
|
}));
|
||||||
outbound
|
outbound
|
||||||
} else {
|
} else {
|
||||||
@ -53,7 +48,6 @@ 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(),
|
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -111,7 +105,6 @@ 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::ToolCall => vec![WsOutbound::ToolCall {
|
OutboundEventKind::ToolCall => vec![WsOutbound::ToolCall {
|
||||||
@ -130,7 +123,6 @@ 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
|
||||||
|
|||||||
@ -438,7 +438,6 @@ impl SubAgentRuntime for DefaultSubAgentRuntime {
|
|||||||
tool_call_id: None,
|
tool_call_id: None,
|
||||||
tool_name: None,
|
tool_name: None,
|
||||||
tool_arguments: None,
|
tool_arguments: None,
|
||||||
reasoning_content: None,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Err(e) = bus.publish_outbound(event).await {
|
if let Err(e) = bus.publish_outbound(event).await {
|
||||||
|
|||||||
@ -123,9 +123,6 @@ fn test_tool_call_outbound_serialization() {
|
|||||||
content: "调用工具: calculator".to_string(),
|
content: "调用工具: calculator".to_string(),
|
||||||
role: "assistant".to_string(),
|
role: "assistant".to_string(),
|
||||||
subagent_task_id: None,
|
subagent_task_id: None,
|
||||||
topic_id: None,
|
|
||||||
timestamp: None,
|
|
||||||
reasoning_content: None,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let json = serde_json::to_string(&msg).unwrap();
|
let json = serde_json::to_string(&msg).unwrap();
|
||||||
@ -159,8 +156,6 @@ fn test_tool_result_outbound_serialization() {
|
|||||||
role: "tool".to_string(),
|
role: "tool".to_string(),
|
||||||
subagent_task_id: None,
|
subagent_task_id: None,
|
||||||
duration_ms: None,
|
duration_ms: None,
|
||||||
topic_id: None,
|
|
||||||
timestamp: None,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let json = serde_json::to_string(&msg).unwrap();
|
let json = serde_json::to_string(&msg).unwrap();
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||||
import { Zap, ArrowLeft, Bot, Clock, Sun, Moon, PanelRightOpen, X, Brain } from 'lucide-react'
|
import { Zap, ArrowLeft, Bot, Clock, Sun, Moon, PanelRightOpen, X } from 'lucide-react'
|
||||||
import { ChatContainer } from './components/Chat/ChatContainer'
|
import { ChatContainer } from './components/Chat/ChatContainer'
|
||||||
import { TopicList } from './components/Sidebar/TopicList'
|
import { TopicList } from './components/Sidebar/TopicList'
|
||||||
import { SchedulerJobList } from './components/Sidebar/SchedulerJobList'
|
import { SchedulerJobList } from './components/Sidebar/SchedulerJobList'
|
||||||
@ -120,14 +120,6 @@ function App() {
|
|||||||
return () => clearTimeout(timer)
|
return () => clearTimeout(timer)
|
||||||
}, [theme])
|
}, [theme])
|
||||||
|
|
||||||
const [showThinking, setShowThinking] = useState(() => {
|
|
||||||
return localStorage.getItem('picobot-show-thinking') !== 'false'
|
|
||||||
})
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
localStorage.setItem('picobot-show-thinking', String(showThinking))
|
|
||||||
}, [showThinking])
|
|
||||||
|
|
||||||
// ---- WebSocket 初始化 ----
|
// ---- WebSocket 初始化 ----
|
||||||
|
|
||||||
// Step 1: 连接建立后先请求通道列表
|
// Step 1: 连接建立后先请求通道列表
|
||||||
@ -451,18 +443,6 @@ function App() {
|
|||||||
>
|
>
|
||||||
{theme === 'dark' ? <Sun className="h-4 w-4" /> : <Moon className="h-4 w-4" />}
|
{theme === 'dark' ? <Sun className="h-4 w-4" /> : <Moon className="h-4 w-4" />}
|
||||||
</button>
|
</button>
|
||||||
<button
|
|
||||||
onClick={() => setShowThinking(prev => !prev)}
|
|
||||||
className={`flex h-8 w-8 items-center justify-center rounded-lg transition-all ${
|
|
||||||
showThinking
|
|
||||||
? 'text-purple-400 hover:text-purple-300 bg-purple-500/10 hover:bg-purple-500/20'
|
|
||||||
: 'text-[var(--text-muted)] hover:text-[var(--text-secondary)] hover:bg-[var(--overlay-hover)]'
|
|
||||||
}`}
|
|
||||||
title={showThinking ? '隐藏思考过程' : '显示思考过程'}
|
|
||||||
aria-label="Toggle thinking display"
|
|
||||||
>
|
|
||||||
<Brain className="h-4 w-4" />
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-4 text-sm text-[var(--text-secondary)]">
|
<div className="flex items-center gap-4 text-sm text-[var(--text-secondary)]">
|
||||||
<ChannelSelector
|
<ChannelSelector
|
||||||
@ -609,7 +589,6 @@ function App() {
|
|||||||
onSendMessage={subAgentView || schedulerView ? () => {} : handleSendMessage}
|
onSendMessage={subAgentView || schedulerView ? () => {} : handleSendMessage}
|
||||||
onNavigateToSubAgent={handleNavigateToSubAgent}
|
onNavigateToSubAgent={handleNavigateToSubAgent}
|
||||||
onStop={handleStopExecution}
|
onStop={handleStopExecution}
|
||||||
showThinking={showThinking}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -10,7 +10,6 @@ interface ChatContainerProps {
|
|||||||
onSendMessage: (content: string, attachments: Attachment[]) => void
|
onSendMessage: (content: string, attachments: Attachment[]) => void
|
||||||
onNavigateToSubAgent?: (taskId: string, description: string) => void
|
onNavigateToSubAgent?: (taskId: string, description: string) => void
|
||||||
onStop?: () => void
|
onStop?: () => void
|
||||||
showThinking?: boolean
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ChatContainer({
|
export function ChatContainer({
|
||||||
@ -21,12 +20,11 @@ export function ChatContainer({
|
|||||||
onSendMessage,
|
onSendMessage,
|
||||||
onNavigateToSubAgent,
|
onNavigateToSubAgent,
|
||||||
onStop,
|
onStop,
|
||||||
showThinking = true,
|
|
||||||
}: ChatContainerProps) {
|
}: ChatContainerProps) {
|
||||||
return (
|
return (
|
||||||
<div className="flex h-full flex-col">
|
<div className="flex h-full flex-col">
|
||||||
<div className="flex-1 overflow-hidden">
|
<div className="flex-1 overflow-hidden">
|
||||||
<MessageList messages={messages} onNavigateToSubAgent={onNavigateToSubAgent} showThinking={showThinking} />
|
<MessageList messages={messages} onNavigateToSubAgent={onNavigateToSubAgent} />
|
||||||
</div>
|
</div>
|
||||||
<MessageInput
|
<MessageInput
|
||||||
onSend={onSendMessage}
|
onSend={onSendMessage}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { useState, useEffect } from 'react'
|
import { useState, useEffect } from 'react'
|
||||||
import { User, Bot, Wrench, CheckCircle, AlertCircle, Terminal, File, Image, FileText, Music, Video, Download, ChevronDown, ChevronRight, Copy, Check, Loader2, XCircle, Clock, Loader, X, Brain } from 'lucide-react'
|
import { User, Bot, Wrench, CheckCircle, AlertCircle, Terminal, File, Image, FileText, Music, Video, Download, ChevronDown, ChevronRight, Copy, Check, Loader2, XCircle, Clock, Loader, X } from 'lucide-react'
|
||||||
import ReactMarkdown from 'react-markdown'
|
import ReactMarkdown from 'react-markdown'
|
||||||
import remarkGfm from 'remark-gfm'
|
import remarkGfm from 'remark-gfm'
|
||||||
import type { ChatMessage, Attachment, TaskToolResult } from '../../types/protocol'
|
import type { ChatMessage, Attachment, TaskToolResult } from '../../types/protocol'
|
||||||
@ -58,7 +58,6 @@ function StatusIcon({ status, size = 14 }: { status: 'calling' | 'result' | 'pen
|
|||||||
interface MessageBubbleProps {
|
interface MessageBubbleProps {
|
||||||
message: ChatMessage
|
message: ChatMessage
|
||||||
onNavigateToSubAgent?: (taskId: string, description: string) => void
|
onNavigateToSubAgent?: (taskId: string, description: string) => void
|
||||||
showThinking?: boolean
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getAttachmentIcon(mediaType: string) {
|
function getAttachmentIcon(mediaType: string) {
|
||||||
@ -237,39 +236,6 @@ function CopyButton({ text, className = '' }: { text: string; className?: string
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function ThinkingSection({ content }: { content: string }) {
|
|
||||||
const [expanded, setExpanded] = useState(false)
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="mb-3 rounded-lg border border-[var(--border-color)] bg-[var(--overlay-hover)] overflow-hidden">
|
|
||||||
<button
|
|
||||||
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-[var(--overlay-subtle)] transition-colors cursor-pointer select-none"
|
|
||||||
>
|
|
||||||
<Brain className="h-3.5 w-3.5 text-[var(--accent-purple)] flex-shrink-0" />
|
|
||||||
<span className="font-medium text-[var(--accent-purple)]">Thinking</span>
|
|
||||||
{expanded ? (
|
|
||||||
<ChevronDown className="h-3 w-3 ml-auto flex-shrink-0" />
|
|
||||||
) : (
|
|
||||||
<ChevronRight className="h-3 w-3 ml-auto flex-shrink-0" />
|
|
||||||
)}
|
|
||||||
{!expanded && (
|
|
||||||
<span className="text-[var(--text-muted)] truncate flex-1 text-left">
|
|
||||||
{content.slice(0, 60)}{content.length > 60 ? '…' : ''}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</button>
|
|
||||||
{expanded && (
|
|
||||||
<div className="px-3 py-2 border-t border-[var(--border-color)] animate-thinking-reveal">
|
|
||||||
<div className="text-xs text-[var(--text-secondary)] whitespace-pre-wrap leading-relaxed max-h-64 overflow-y-auto scrollbar-thin">
|
|
||||||
{content}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function parseTaskResult(content: string): TaskToolResult | null {
|
function parseTaskResult(content: string): TaskToolResult | null {
|
||||||
if (!content) return null
|
if (!content) return null
|
||||||
try {
|
try {
|
||||||
@ -289,7 +255,7 @@ function parseTaskResult(content: string): TaskToolResult | null {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function MessageBubble({ message, onNavigateToSubAgent, showThinking = true }: MessageBubbleProps) {
|
export function MessageBubble({ message, onNavigateToSubAgent }: MessageBubbleProps) {
|
||||||
const isUser = message.role === 'user'
|
const isUser = message.role === 'user'
|
||||||
const isTool = message.role === 'tool'
|
const isTool = message.role === 'tool'
|
||||||
const isMergedTool = message.type === 'merged_tool'
|
const isMergedTool = message.type === 'merged_tool'
|
||||||
@ -438,12 +404,6 @@ export function MessageBubble({ message, onNavigateToSubAgent, showThinking = tr
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 模型思考内容(工具调用时展示) */}
|
|
||||||
{showThinking && message.reasoningContent && !toolExpanded && (
|
|
||||||
<div className="px-3 pb-1">
|
|
||||||
<ThinkingSection content={message.reasoningContent} />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{/* Collapsed preview */}
|
{/* Collapsed preview */}
|
||||||
{!toolExpanded && (
|
{!toolExpanded && (
|
||||||
<>
|
<>
|
||||||
@ -502,10 +462,6 @@ export function MessageBubble({ message, onNavigateToSubAgent, showThinking = tr
|
|||||||
{/* 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">
|
||||||
{/* 模型思考内容(展开时也展示) */}
|
|
||||||
{showThinking && message.reasoningContent && (
|
|
||||||
<ThinkingSection content={message.reasoningContent} />
|
|
||||||
)}
|
|
||||||
{taskResult ? (
|
{taskResult ? (
|
||||||
<>
|
<>
|
||||||
{taskPrompt && (
|
{taskPrompt && (
|
||||||
@ -652,12 +608,7 @@ export function MessageBubble({ message, onNavigateToSubAgent, showThinking = tr
|
|||||||
// 用户消息保持纯文本
|
// 用户消息保持纯文本
|
||||||
<div className="whitespace-pre-wrap text-sm leading-relaxed">{message.content}</div>
|
<div className="whitespace-pre-wrap text-sm leading-relaxed">{message.content}</div>
|
||||||
) : (
|
) : (
|
||||||
// 模型思考内容(仅助手消息,非工具消息)
|
// AI 和工具消息使用 Markdown 渲染
|
||||||
<>
|
|
||||||
{showThinking && !isTool && message.reasoningContent && (
|
|
||||||
<ThinkingSection content={message.reasoningContent} />
|
|
||||||
)}
|
|
||||||
{/* AI 和工具消息使用 Markdown 渲染 */}
|
|
||||||
<div className="markdown-content text-sm leading-relaxed">
|
<div className="markdown-content text-sm leading-relaxed">
|
||||||
<ReactMarkdown
|
<ReactMarkdown
|
||||||
remarkPlugins={[remarkGfm]}
|
remarkPlugins={[remarkGfm]}
|
||||||
@ -737,7 +688,7 @@ export function MessageBubble({ message, onNavigateToSubAgent, showThinking = tr
|
|||||||
{message.content}
|
{message.content}
|
||||||
</ReactMarkdown>
|
</ReactMarkdown>
|
||||||
</div>
|
</div>
|
||||||
</>)}
|
)}
|
||||||
{message.attachments && message.attachments.length > 0 && (
|
{message.attachments && message.attachments.length > 0 && (
|
||||||
<div className="mt-2 space-y-2">
|
<div className="mt-2 space-y-2">
|
||||||
{message.attachments.some(att => att.media_type === 'image' && att.content_base64) && (
|
{message.attachments.some(att => att.media_type === 'image' && att.content_base64) && (
|
||||||
|
|||||||
@ -6,10 +6,9 @@ import { Sparkles, ArrowDown, ArrowUp } from 'lucide-react'
|
|||||||
interface MessageListProps {
|
interface MessageListProps {
|
||||||
messages: ChatMessage[]
|
messages: ChatMessage[]
|
||||||
onNavigateToSubAgent?: (taskId: string, description: string) => void
|
onNavigateToSubAgent?: (taskId: string, description: string) => void
|
||||||
showThinking?: boolean
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function MessageList({ messages, onNavigateToSubAgent, showThinking = true }: MessageListProps) {
|
export function MessageList({ messages, onNavigateToSubAgent }: MessageListProps) {
|
||||||
const bottomRef = useRef<HTMLDivElement>(null)
|
const bottomRef = useRef<HTMLDivElement>(null)
|
||||||
const containerRef = useRef<HTMLDivElement>(null)
|
const containerRef = useRef<HTMLDivElement>(null)
|
||||||
const isAtBottomRef = useRef(true)
|
const isAtBottomRef = useRef(true)
|
||||||
@ -117,7 +116,7 @@ export function MessageList({ messages, onNavigateToSubAgent, showThinking = tru
|
|||||||
className="h-full overflow-y-auto p-6 space-y-6"
|
className="h-full overflow-y-auto p-6 space-y-6"
|
||||||
>
|
>
|
||||||
{messages.map((message) => (
|
{messages.map((message) => (
|
||||||
<MessageBubble key={message.id} message={message} onNavigateToSubAgent={onNavigateToSubAgent} showThinking={showThinking} />
|
<MessageBubble key={message.id} message={message} onNavigateToSubAgent={onNavigateToSubAgent} />
|
||||||
))}
|
))}
|
||||||
<div ref={bottomRef} />
|
<div ref={bottomRef} />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -195,7 +195,6 @@ export function useChat(): UseChatReturn {
|
|||||||
type: 'message',
|
type: 'message',
|
||||||
attachments: msg.attachments,
|
attachments: msg.attachments,
|
||||||
subagentTaskId: msg.subagent_task_id,
|
subagentTaskId: msg.subagent_task_id,
|
||||||
reasoningContent: msg.reasoning_content,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case 'tool_call': {
|
case 'tool_call': {
|
||||||
@ -210,7 +209,6 @@ 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': {
|
||||||
@ -432,7 +430,6 @@ export function useChat(): UseChatReturn {
|
|||||||
timestamp: (message as any).timestamp ?? Math.floor(Date.now() / 1000),
|
timestamp: (message as any).timestamp ?? Math.floor(Date.now() / 1000),
|
||||||
type: 'message',
|
type: 'message',
|
||||||
attachments: msg.attachments,
|
attachments: msg.attachments,
|
||||||
reasoningContent: msg.reasoning_content,
|
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
setIsLoading(false)
|
setIsLoading(false)
|
||||||
@ -461,7 +458,6 @@ 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
|
||||||
|
|||||||
@ -258,13 +258,6 @@ body {
|
|||||||
.animate-fade-in { animation: fade-in 0.2s ease-out; }
|
.animate-fade-in { animation: fade-in 0.2s ease-out; }
|
||||||
.animate-scale-in { animation: scale-in 0.3s ease-out; }
|
.animate-scale-in { animation: scale-in 0.3s ease-out; }
|
||||||
|
|
||||||
@keyframes thinking-reveal {
|
|
||||||
from { max-height: 0; opacity: 0; }
|
|
||||||
to { max-height: 300px; opacity: 1; }
|
|
||||||
}
|
|
||||||
|
|
||||||
.animate-thinking-reveal { animation: thinking-reveal 0.25s ease-out; }
|
|
||||||
|
|
||||||
.typing-indicator span {
|
.typing-indicator span {
|
||||||
animation: typing-dot 1.4s infinite;
|
animation: typing-dot 1.4s infinite;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
|||||||
@ -45,7 +45,6 @@ export interface AssistantResponse {
|
|||||||
subagent_task_id?: string
|
subagent_task_id?: string
|
||||||
topic_id?: string
|
topic_id?: string
|
||||||
timestamp?: number
|
timestamp?: number
|
||||||
reasoning_content?: string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ToolCall {
|
export interface ToolCall {
|
||||||
@ -59,7 +58,6 @@ 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 {
|
||||||
@ -413,7 +411,6 @@ export interface ChatMessage {
|
|||||||
callContent?: string
|
callContent?: string
|
||||||
subagentTaskId?: string
|
subagentTaskId?: string
|
||||||
durationMs?: number
|
durationMs?: number
|
||||||
reasoningContent?: string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** task 工具返回的 JSON 结构 */
|
/** task 工具返回的 JSON 结构 */
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user