Skip to content

Commit 94c408d

Browse files
committed
Merge branch 'terminal-internal-tool-dock' into dev
2 parents b37e92e + 41830a4 commit 94c408d

9 files changed

Lines changed: 1876 additions & 361 deletions

File tree

‎crates/ai_chat_view/src/agent_cards.rs‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,7 @@ impl ChatCard for ToolCard {
263263
let hover_bg = theme.panel_hover;
264264

265265
let mut card = v_flex()
266+
.debug_selector(|| "agent-tool-card".to_string())
266267
.w_full()
267268
.min_w_0()
268269
.gap_2()

‎crates/ai_chat_view/src/agent_view.rs‎

Lines changed: 199 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,9 @@ use crate::input::{
4949
ComposerResourceSourceOption, ComposerResourceTypeFilter, ComposerScope, ComposerSkillItem,
5050
ComposerSkillSummary, ComposerSubAgentItem, ComposerTarget, MentionItem,
5151
};
52-
use crate::message_view::render_messages_with_code_actions;
52+
use crate::message_view::{
53+
render_messages_with_code_actions, render_sidebar_messages_with_code_actions,
54+
};
5355
use crate::persistence;
5456
use crate::resource_display::first_visible_alias;
5557
use crate::session_sidebar::{self, SessionRowStyle, SessionSummary};
@@ -1967,24 +1969,38 @@ impl Render for AgentChatView {
19671969
self.scroll_handle.scroll_to_bottom();
19681970
}
19691971
let chat_theme = resolve_agent_chat_theme(self.theme.as_ref(), cx);
1970-
let messages = render_messages_with_code_actions(
1971-
&self.transcript.messages,
1972-
&self.scroll_handle,
1973-
Some(&self.code_block_actions),
1974-
Some(&chat_theme),
1975-
window,
1976-
cx,
1977-
);
1972+
let messages = if self.sidebar_mode {
1973+
render_sidebar_messages_with_code_actions(
1974+
&self.transcript.messages,
1975+
&self.scroll_handle,
1976+
Some(&self.code_block_actions),
1977+
Some(&chat_theme),
1978+
window,
1979+
cx,
1980+
)
1981+
} else {
1982+
render_messages_with_code_actions(
1983+
&self.transcript.messages,
1984+
&self.scroll_handle,
1985+
Some(&self.code_block_actions),
1986+
Some(&chat_theme),
1987+
window,
1988+
cx,
1989+
)
1990+
};
19781991
let input_area = div()
19791992
.debug_selector(|| "agent-input-area".to_string())
19801993
.w_full()
1994+
.min_w_0()
19811995
.flex_shrink_0()
1996+
.overflow_hidden()
19821997
.border_t_1()
19831998
.border_color(chat_theme.border)
19841999
.bg(chat_theme.background)
19852000
.child(
19862001
v_flex()
19872002
.w_full()
2003+
.min_w_0()
19882004
.when(!self.sidebar_mode, |this| this.p_3())
19892005
.child(self.input.clone()),
19902006
);
@@ -1993,14 +2009,21 @@ impl Render for AgentChatView {
19932009
// 侧边栏视图:紧凑头部(新建对话 / 历史记录) + 消息 + 输入。
19942010
let header = self.render_sidebar_mode_header(cx);
19952011
div()
2012+
.debug_selector(|| "agent-sidebar-root".to_string())
19962013
.size_full()
2014+
.min_w_0()
2015+
.overflow_hidden()
19972016
.text_color(chat_theme.foreground)
19982017
.bg(chat_theme.background)
19992018
.on_action(cx.listener(Self::approve_tool_call))
20002019
.on_action(cx.listener(Self::reject_tool_call))
20012020
.child(
20022021
v_flex()
2022+
.debug_selector(|| "agent-sidebar-stack".to_string())
20032023
.size_full()
2024+
.min_w_0()
2025+
.min_h_0()
2026+
.overflow_hidden()
20042027
.child(header)
20052028
.child(messages)
20062029
.child(input_area),
@@ -2897,6 +2920,7 @@ fn now_secs() -> i64 {
28972920
#[cfg(test)]
28982921
mod tests {
28992922
use super::*;
2923+
use crate::agent_cards::{TOOL_CARD, ToolCardData};
29002924
use agent_runtime::RuntimeServices;
29012925
use agent_runtime::model::MockModelClient;
29022926
use agent_runtime::model::function_tool_call;
@@ -2909,15 +2933,55 @@ mod tests {
29092933
};
29102934
use async_trait::async_trait;
29112935
use gpui::{
2912-
Modifiers, ScrollDelta, ScrollWheelEvent, TestAppContext, TouchPhase, VisualTestContext,
2913-
point,
2936+
Entity, IntoElement, Modifiers, ParentElement, Render, ScrollDelta, ScrollWheelEvent,
2937+
Styled, TestAppContext, TouchPhase, VisualTestContext, Window, div, point, px,
29142938
};
29152939
use one_core::llm::{ProviderConfig, ProviderType};
29162940
use serde_json::json;
29172941
use std::sync::atomic::{AtomicUsize, Ordering};
29182942

29192943
struct WriteTool;
29202944

2945+
struct FixedSidebarHost {
2946+
view: Entity<AgentChatView>,
2947+
}
2948+
2949+
impl FixedSidebarHost {
2950+
fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {
2951+
let config =
2952+
AgentChatViewConfig::new(test_runtime("m"), ResourceContext::new(), vec![])
2953+
.sidebar_mode(true);
2954+
let view = cx.new(|cx| AgentChatView::new(config, window, cx));
2955+
Self { view }
2956+
}
2957+
}
2958+
2959+
impl Render for FixedSidebarHost {
2960+
fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
2961+
v_flex()
2962+
.debug_selector(|| "fixed-sidebar-host".to_string())
2963+
.w(px(420.0))
2964+
.h(px(640.0))
2965+
.overflow_hidden()
2966+
.child(
2967+
div()
2968+
.debug_selector(|| "fixed-sidebar-header".to_string())
2969+
.h(px(34.0))
2970+
.w_full()
2971+
.flex_shrink_0(),
2972+
)
2973+
.child(
2974+
div()
2975+
.debug_selector(|| "fixed-sidebar-content-slot".to_string())
2976+
.flex_1()
2977+
.min_h_0()
2978+
.min_w_0()
2979+
.overflow_hidden()
2980+
.child(self.view.clone()),
2981+
)
2982+
}
2983+
}
2984+
29212985
#[async_trait]
29222986
impl Tool for WriteTool {
29232987
fn name(&self) -> ToolName {
@@ -4118,6 +4182,130 @@ mod tests {
41184182
);
41194183
}
41204184

4185+
#[gpui::test]
4186+
fn sidebar_mode_user_message_row_fills_message_column(cx: &mut TestAppContext) {
4187+
init_test_ui(cx);
4188+
let config = AgentChatViewConfig::new(test_runtime("m"), ResourceContext::new(), vec![])
4189+
.sidebar_mode(true);
4190+
let (view, cx) =
4191+
cx.add_window_view(move |window, cx| AgentChatView::new(config, window, cx));
4192+
view.update(cx, |view, cx| {
4193+
view.transcript
4194+
.messages
4195+
.push(crate::ChatMessageUI::user("帮我看看内存占用"));
4196+
cx.notify();
4197+
});
4198+
let cx: &mut VisualTestContext = cx;
4199+
4200+
let column = cx
4201+
.debug_bounds("ai-chat-message-column")
4202+
.expect("message column should render");
4203+
let scroll = cx
4204+
.debug_bounds("ai-chat-messages-scroll")
4205+
.expect("message scroll area should render");
4206+
let user_row = cx
4207+
.debug_bounds("ai-chat-user-row")
4208+
.expect("user row should render");
4209+
4210+
let expected_column_width = scroll.size.width - px(32.0);
4211+
assert_eq!(
4212+
expected_column_width, column.size.width,
4213+
"sidebar message column should fill the padded scroll area: scroll={scroll:?}, column={column:?}"
4214+
);
4215+
assert_eq!(
4216+
column.size.width, user_row.size.width,
4217+
"user message row should fill the message column: column={column:?}, row={user_row:?}"
4218+
);
4219+
assert_eq!(
4220+
column.origin.x, user_row.origin.x,
4221+
"user message row should not drift horizontally: column={column:?}, row={user_row:?}"
4222+
);
4223+
}
4224+
4225+
#[gpui::test]
4226+
fn sidebar_mode_fills_fixed_host_frame(cx: &mut TestAppContext) {
4227+
init_test_ui(cx);
4228+
let (host, cx) = cx.add_window_view(FixedSidebarHost::new);
4229+
let chat = host.read_with(cx, |host, _| host.view.clone());
4230+
chat.update(cx, |view, cx| {
4231+
view.transcript
4232+
.messages
4233+
.push(crate::ChatMessageUI::user("帮我检查终端侧边栏布局"));
4234+
cx.notify();
4235+
});
4236+
let cx: &mut VisualTestContext = cx;
4237+
4238+
let slot = cx
4239+
.debug_bounds("fixed-sidebar-content-slot")
4240+
.expect("fixed sidebar content slot should render");
4241+
let root = cx
4242+
.debug_bounds("agent-sidebar-root")
4243+
.expect("sidebar root should render");
4244+
let stack = cx
4245+
.debug_bounds("agent-sidebar-stack")
4246+
.expect("sidebar stack should render");
4247+
let messages = cx
4248+
.debug_bounds("ai-chat-messages")
4249+
.expect("messages area should render");
4250+
let input_area = cx
4251+
.debug_bounds("agent-input-area")
4252+
.expect("input area should render");
4253+
let input = cx
4254+
.debug_bounds("agent-input-root")
4255+
.expect("input root should render");
4256+
4257+
assert_eq!(slot.origin.x, root.origin.x);
4258+
assert_eq!(slot.size.width, root.size.width);
4259+
assert_eq!(root.origin.x, stack.origin.x);
4260+
assert_eq!(root.size.width, stack.size.width);
4261+
assert_eq!(root.origin.x, messages.origin.x);
4262+
assert_eq!(root.size.width, messages.size.width);
4263+
assert_eq!(root.origin.x, input_area.origin.x);
4264+
assert_eq!(root.size.width, input_area.size.width);
4265+
assert_eq!(input_area.origin.x, input.origin.x);
4266+
assert_eq!(input_area.size.width, input.size.width);
4267+
}
4268+
4269+
#[gpui::test]
4270+
fn sidebar_mode_tool_card_fills_message_column(cx: &mut TestAppContext) {
4271+
init_test_ui(cx);
4272+
let (host, cx) = cx.add_window_view(FixedSidebarHost::new);
4273+
let chat = host.read_with(cx, |host, _| host.view.clone());
4274+
chat.update(cx, |view, cx| {
4275+
view.transcript.messages.push(crate::ChatMessageUI::card(
4276+
TOOL_CARD,
4277+
ToolCardData {
4278+
call_id: "call-layout".to_string(),
4279+
tool_name: "terminal.exec".to_string(),
4280+
target_id: Some("ssh-prod-with-a-very-long-target-id".to_string()),
4281+
target_label: Some("生产终端节点-很长的展示名称".to_string()),
4282+
input_summary: "ps aux | sort -nrk 3,3 | head -20".to_string(),
4283+
input_json: r#"{"command":"ps aux | sort -nrk 3,3 | head -20"}"#.to_string(),
4284+
running: true,
4285+
success: None,
4286+
summary: String::new(),
4287+
data_text: String::new(),
4288+
}
4289+
.to_json(),
4290+
));
4291+
cx.notify();
4292+
});
4293+
let cx: &mut VisualTestContext = cx;
4294+
4295+
let column = cx
4296+
.debug_bounds("ai-chat-message-column")
4297+
.expect("message column should render");
4298+
let card = cx
4299+
.debug_bounds("agent-tool-card")
4300+
.expect("tool card should render");
4301+
4302+
assert_eq!(column.origin.x, card.origin.x);
4303+
assert_eq!(
4304+
column.size.width, card.size.width,
4305+
"tool card should fill sidebar message column: column={column:?}, card={card:?}"
4306+
);
4307+
}
4308+
41214309
#[test]
41224310
fn runtime_binding_switches_runtime_from_structured_model_option() {
41234311
let first = ComposerModelOption::new("openai:gpt-a", "openai", "OpenAI", "gpt-a");

‎crates/ai_chat_view/src/input/agent_input.rs‎

Lines changed: 28 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -993,6 +993,7 @@ impl AgentInput {
993993

994994
h_flex()
995995
.w_full()
996+
.min_w_0()
996997
.items_center()
997998
.text_color(theme.foreground)
998999
.gap_2()
@@ -2311,6 +2312,7 @@ impl Render for AgentInput {
23112312
.debug_selector(|| "agent-input-root".to_string())
23122313
.track_focus(&self.focus_handle)
23132314
.w_full()
2315+
.min_w_0()
23142316
.flex_shrink_0()
23152317
.bg(theme.background)
23162318
.text_color(theme.foreground)
@@ -2333,25 +2335,32 @@ impl Render for AgentInput {
23332335
.child(editor_top_bar)
23342336
// 中部:多行输入框
23352337
.child(
2336-
div().w_full().px_3().pt_1().max_h(px(220.0)).child(
2337-
div()
2338-
.w_full()
2339-
.border_1()
2340-
.rounded(cx.theme().radius)
2341-
.border_color(if input_focused {
2342-
theme.accent
2343-
} else {
2344-
theme.border
2345-
})
2346-
.bg(theme.background)
2347-
.child(
2348-
Input::new(&self.input_state)
2349-
.size_full()
2350-
.appearance(false)
2351-
.text_color(theme.foreground)
2352-
.caret_color(theme.foreground),
2353-
),
2354-
),
2338+
div()
2339+
.w_full()
2340+
.min_w_0()
2341+
.px_3()
2342+
.pt_1()
2343+
.max_h(px(220.0))
2344+
.child(
2345+
div()
2346+
.w_full()
2347+
.min_w_0()
2348+
.border_1()
2349+
.rounded(cx.theme().radius)
2350+
.border_color(if input_focused {
2351+
theme.accent
2352+
} else {
2353+
theme.border
2354+
})
2355+
.bg(theme.background)
2356+
.child(
2357+
Input::new(&self.input_state)
2358+
.size_full()
2359+
.appearance(false)
2360+
.text_color(theme.foreground)
2361+
.caret_color(theme.foreground),
2362+
),
2363+
),
23552364
)
23562365
// 底部:执行参数、模型和发送按钮
23572366
.child(toolbar)

0 commit comments

Comments
 (0)