11use std:: sync:: Arc ;
22
3- use collections:: HashMap ;
3+ use assistant_scripting:: { ScriptId , ScriptState } ;
4+ use collections:: { HashMap , HashSet } ;
45use editor:: { Editor , MultiBuffer } ;
56use gpui:: {
67 list, AbsoluteLength , AnyElement , App , ClickEvent , DefiniteLength , EdgesRefinement , Empty ,
78 Entity , Focusable , Length , ListAlignment , ListOffset , ListState , StyleRefinement , Subscription ,
8- Task , TextStyleRefinement , UnderlineStyle ,
9+ Task , TextStyleRefinement , UnderlineStyle , WeakEntity ,
910} ;
1011use language:: { Buffer , LanguageRegistry } ;
1112use language_model:: { LanguageModelRegistry , LanguageModelToolUseId , Role } ;
@@ -14,13 +15,15 @@ use settings::Settings as _;
1415use theme:: ThemeSettings ;
1516use ui:: { prelude:: * , Disclosure , KeyBinding } ;
1617use util:: ResultExt as _;
18+ use workspace:: Workspace ;
1719
1820use crate :: thread:: { MessageId , RequestKind , Thread , ThreadError , ThreadEvent } ;
1921use crate :: thread_store:: ThreadStore ;
2022use crate :: tool_use:: { ToolUse , ToolUseStatus } ;
2123use crate :: ui:: ContextPill ;
2224
2325pub struct ActiveThread {
26+ workspace : WeakEntity < Workspace > ,
2427 language_registry : Arc < LanguageRegistry > ,
2528 thread_store : Entity < ThreadStore > ,
2629 thread : Entity < Thread > ,
@@ -30,6 +33,7 @@ pub struct ActiveThread {
3033 rendered_messages_by_id : HashMap < MessageId , Entity < Markdown > > ,
3134 editing_message : Option < ( MessageId , EditMessageState ) > ,
3235 expanded_tool_uses : HashMap < LanguageModelToolUseId , bool > ,
36+ expanded_scripts : HashSet < ScriptId > ,
3337 last_error : Option < ThreadError > ,
3438 _subscriptions : Vec < Subscription > ,
3539}
@@ -40,6 +44,7 @@ struct EditMessageState {
4044
4145impl ActiveThread {
4246 pub fn new (
47+ workspace : WeakEntity < Workspace > ,
4348 thread : Entity < Thread > ,
4449 thread_store : Entity < ThreadStore > ,
4550 language_registry : Arc < LanguageRegistry > ,
@@ -52,13 +57,15 @@ impl ActiveThread {
5257 ] ;
5358
5459 let mut this = Self {
60+ workspace,
5561 language_registry,
5662 thread_store,
5763 thread : thread. clone ( ) ,
5864 save_thread_task : None ,
5965 messages : Vec :: new ( ) ,
6066 rendered_messages_by_id : HashMap :: default ( ) ,
6167 expanded_tool_uses : HashMap :: default ( ) ,
68+ expanded_scripts : HashSet :: default ( ) ,
6269 list_state : ListState :: new ( 0 , ListAlignment :: Bottom , px ( 1024. ) , {
6370 let this = cx. entity ( ) . downgrade ( ) ;
6471 move |ix, window : & mut Window , cx : & mut App | {
@@ -241,7 +248,7 @@ impl ActiveThread {
241248
242249 fn handle_thread_event (
243250 & mut self ,
244- _ : & Entity < Thread > ,
251+ _thread : & Entity < Thread > ,
245252 event : & ThreadEvent ,
246253 window : & mut Window ,
247254 cx : & mut Context < Self > ,
@@ -306,6 +313,14 @@ impl ActiveThread {
306313 }
307314 }
308315 }
316+ ThreadEvent :: ScriptFinished => {
317+ let model_registry = LanguageModelRegistry :: read_global ( cx) ;
318+ if let Some ( model) = model_registry. active_model ( ) {
319+ self . thread . update ( cx, |thread, cx| {
320+ thread. send_to_model ( model, RequestKind :: Chat , false , cx) ;
321+ } ) ;
322+ }
323+ }
309324 }
310325 }
311326
@@ -445,12 +460,16 @@ impl ActiveThread {
445460 return Empty . into_any ( ) ;
446461 } ;
447462
448- let context = self . thread . read ( cx) . context_for_message ( message_id) ;
449- let tool_uses = self . thread . read ( cx) . tool_uses_for_message ( message_id) ;
450- let colors = cx. theme ( ) . colors ( ) ;
463+ let thread = self . thread . read ( cx) ;
464+
465+ let context = thread. context_for_message ( message_id) ;
466+ let tool_uses = thread. tool_uses_for_message ( message_id) ;
451467
452468 // Don't render user messages that are just there for returning tool results.
453- if message. role == Role :: User && self . thread . read ( cx) . message_has_tool_results ( message_id) {
469+ if message. role == Role :: User
470+ && ( thread. message_has_tool_results ( message_id)
471+ || thread. message_has_script_output ( message_id) )
472+ {
454473 return Empty . into_any ( ) ;
455474 }
456475
@@ -463,6 +482,8 @@ impl ActiveThread {
463482 . filter ( |( id, _) | * id == message_id)
464483 . map ( |( _, state) | state. editor . clone ( ) ) ;
465484
485+ let colors = cx. theme ( ) . colors ( ) ;
486+
466487 let message_content = v_flex ( )
467488 . child (
468489 if let Some ( edit_message_editor) = edit_message_editor. clone ( ) {
@@ -597,6 +618,7 @@ impl ActiveThread {
597618 Role :: Assistant => div ( )
598619 . id ( ( "message-container" , ix) )
599620 . child ( message_content)
621+ . children ( self . render_script ( message_id, cx) )
600622 . map ( |parent| {
601623 if tool_uses. is_empty ( ) {
602624 return parent;
@@ -716,6 +738,139 @@ impl ActiveThread {
716738 } ) ,
717739 )
718740 }
741+
742+ fn render_script ( & self , message_id : MessageId , cx : & mut Context < Self > ) -> Option < AnyElement > {
743+ let script = self . thread . read ( cx) . script_for_message ( message_id, cx) ?;
744+
745+ let is_open = self . expanded_scripts . contains ( & script. id ) ;
746+ let colors = cx. theme ( ) . colors ( ) ;
747+
748+ let element = div ( ) . px_2p5 ( ) . child (
749+ v_flex ( )
750+ . gap_1 ( )
751+ . rounded_lg ( )
752+ . border_1 ( )
753+ . border_color ( colors. border )
754+ . child (
755+ h_flex ( )
756+ . justify_between ( )
757+ . py_0p5 ( )
758+ . pl_1 ( )
759+ . pr_2 ( )
760+ . bg ( colors. editor_foreground . opacity ( 0.02 ) )
761+ . when ( is_open, |element| element. border_b_1 ( ) . rounded_t ( px ( 6. ) ) )
762+ . when ( !is_open, |element| element. rounded_md ( ) )
763+ . border_color ( colors. border )
764+ . child (
765+ h_flex ( )
766+ . gap_1 ( )
767+ . child ( Disclosure :: new ( "script-disclosure" , is_open) . on_click (
768+ cx. listener ( {
769+ let script_id = script. id ;
770+ move |this, _event, _window, _cx| {
771+ if this. expanded_scripts . contains ( & script_id) {
772+ this. expanded_scripts . remove ( & script_id) ;
773+ } else {
774+ this. expanded_scripts . insert ( script_id) ;
775+ }
776+ }
777+ } ) ,
778+ ) )
779+ // TODO: Generate script description
780+ . child ( Label :: new ( "Script" ) ) ,
781+ )
782+ . child (
783+ h_flex ( )
784+ . gap_1 ( )
785+ . child (
786+ Label :: new ( match script. state {
787+ ScriptState :: Generating => "Generating" ,
788+ ScriptState :: Running { .. } => "Running" ,
789+ ScriptState :: Succeeded { .. } => "Finished" ,
790+ ScriptState :: Failed { .. } => "Error" ,
791+ } )
792+ . size ( LabelSize :: XSmall )
793+ . buffer_font ( cx) ,
794+ )
795+ . child (
796+ IconButton :: new ( "view-source" , IconName :: Eye )
797+ . icon_color ( Color :: Muted )
798+ . disabled ( matches ! ( script. state, ScriptState :: Generating ) )
799+ . on_click ( cx. listener ( {
800+ let source = script. source . clone ( ) ;
801+ move |this, _event, window, cx| {
802+ this. open_script_source ( source. clone ( ) , window, cx) ;
803+ }
804+ } ) ) ,
805+ ) ,
806+ ) ,
807+ )
808+ . when ( is_open, |parent| {
809+ let stdout = script. stdout_snapshot ( ) ;
810+ let error = script. error ( ) ;
811+
812+ parent. child (
813+ v_flex ( )
814+ . p_2 ( )
815+ . bg ( colors. editor_background )
816+ . gap_2 ( )
817+ . child ( if stdout. is_empty ( ) && error. is_none ( ) {
818+ Label :: new ( "No output yet" )
819+ . size ( LabelSize :: Small )
820+ . color ( Color :: Muted )
821+ } else {
822+ Label :: new ( stdout) . size ( LabelSize :: Small ) . buffer_font ( cx)
823+ } )
824+ . children ( script. error ( ) . map ( |err| {
825+ Label :: new ( err. to_string ( ) )
826+ . size ( LabelSize :: Small )
827+ . color ( Color :: Error )
828+ } ) ) ,
829+ )
830+ } ) ,
831+ ) ;
832+
833+ Some ( element. into_any ( ) )
834+ }
835+
836+ fn open_script_source (
837+ & mut self ,
838+ source : SharedString ,
839+ window : & mut Window ,
840+ cx : & mut Context < ' _ , ActiveThread > ,
841+ ) {
842+ let language_registry = self . language_registry . clone ( ) ;
843+ let workspace = self . workspace . clone ( ) ;
844+ let source = source. clone ( ) ;
845+
846+ cx. spawn_in ( window, |_, mut cx| async move {
847+ let lua = language_registry. language_for_name ( "Lua" ) . await . log_err ( ) ;
848+
849+ workspace. update_in ( & mut cx, |workspace, window, cx| {
850+ let project = workspace. project ( ) . clone ( ) ;
851+
852+ let buffer = project. update ( cx, |project, cx| {
853+ project. create_local_buffer ( & source. trim ( ) , lua, cx)
854+ } ) ;
855+
856+ let buffer = cx. new ( |cx| {
857+ MultiBuffer :: singleton ( buffer, cx)
858+ // TODO: Generate script description
859+ . with_title ( "Assistant script" . into ( ) )
860+ } ) ;
861+
862+ let editor = cx. new ( |cx| {
863+ let mut editor =
864+ Editor :: for_multibuffer ( buffer, Some ( project) , true , window, cx) ;
865+ editor. set_read_only ( true ) ;
866+ editor
867+ } ) ;
868+
869+ workspace. add_item_to_active_pane ( Box :: new ( editor) , None , true , window, cx) ;
870+ } )
871+ } )
872+ . detach_and_log_err ( cx) ;
873+ }
719874}
720875
721876impl Render for ActiveThread {
0 commit comments