@@ -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+ } ;
5355use crate :: persistence;
5456use crate :: resource_display:: first_visible_alias;
5557use 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) ]
28982921mod 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" ) ;
0 commit comments