1
1
use std:: sync:: Arc ;
2
2
3
- use collections:: HashMap ;
3
+ use assistant_scripting:: { ScriptId , ScriptState } ;
4
+ use collections:: { HashMap , HashSet } ;
4
5
use editor:: { Editor , MultiBuffer } ;
5
6
use gpui:: {
6
7
list, AbsoluteLength , AnyElement , App , ClickEvent , DefiniteLength , EdgesRefinement , Empty ,
7
8
Entity , Focusable , Length , ListAlignment , ListOffset , ListState , StyleRefinement , Subscription ,
8
- Task , TextStyleRefinement , UnderlineStyle ,
9
+ Task , TextStyleRefinement , UnderlineStyle , WeakEntity ,
9
10
} ;
10
11
use language:: { Buffer , LanguageRegistry } ;
11
12
use language_model:: { LanguageModelRegistry , LanguageModelToolUseId , Role } ;
@@ -14,13 +15,15 @@ use settings::Settings as _;
14
15
use theme:: ThemeSettings ;
15
16
use ui:: { prelude:: * , Disclosure , KeyBinding } ;
16
17
use util:: ResultExt as _;
18
+ use workspace:: Workspace ;
17
19
18
20
use crate :: thread:: { MessageId , RequestKind , Thread , ThreadError , ThreadEvent } ;
19
21
use crate :: thread_store:: ThreadStore ;
20
22
use crate :: tool_use:: { ToolUse , ToolUseStatus } ;
21
23
use crate :: ui:: ContextPill ;
22
24
23
25
pub struct ActiveThread {
26
+ workspace : WeakEntity < Workspace > ,
24
27
language_registry : Arc < LanguageRegistry > ,
25
28
thread_store : Entity < ThreadStore > ,
26
29
thread : Entity < Thread > ,
@@ -30,6 +33,7 @@ pub struct ActiveThread {
30
33
rendered_messages_by_id : HashMap < MessageId , Entity < Markdown > > ,
31
34
editing_message : Option < ( MessageId , EditMessageState ) > ,
32
35
expanded_tool_uses : HashMap < LanguageModelToolUseId , bool > ,
36
+ expanded_scripts : HashSet < ScriptId > ,
33
37
last_error : Option < ThreadError > ,
34
38
_subscriptions : Vec < Subscription > ,
35
39
}
@@ -40,6 +44,7 @@ struct EditMessageState {
40
44
41
45
impl ActiveThread {
42
46
pub fn new (
47
+ workspace : WeakEntity < Workspace > ,
43
48
thread : Entity < Thread > ,
44
49
thread_store : Entity < ThreadStore > ,
45
50
language_registry : Arc < LanguageRegistry > ,
@@ -52,13 +57,15 @@ impl ActiveThread {
52
57
] ;
53
58
54
59
let mut this = Self {
60
+ workspace,
55
61
language_registry,
56
62
thread_store,
57
63
thread : thread. clone ( ) ,
58
64
save_thread_task : None ,
59
65
messages : Vec :: new ( ) ,
60
66
rendered_messages_by_id : HashMap :: default ( ) ,
61
67
expanded_tool_uses : HashMap :: default ( ) ,
68
+ expanded_scripts : HashSet :: default ( ) ,
62
69
list_state : ListState :: new ( 0 , ListAlignment :: Bottom , px ( 1024. ) , {
63
70
let this = cx. entity ( ) . downgrade ( ) ;
64
71
move |ix, window : & mut Window , cx : & mut App | {
@@ -241,7 +248,7 @@ impl ActiveThread {
241
248
242
249
fn handle_thread_event (
243
250
& mut self ,
244
- _ : & Entity < Thread > ,
251
+ _thread : & Entity < Thread > ,
245
252
event : & ThreadEvent ,
246
253
window : & mut Window ,
247
254
cx : & mut Context < Self > ,
@@ -306,6 +313,14 @@ impl ActiveThread {
306
313
}
307
314
}
308
315
}
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
+ }
309
324
}
310
325
}
311
326
@@ -445,12 +460,16 @@ impl ActiveThread {
445
460
return Empty . into_any ( ) ;
446
461
} ;
447
462
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) ;
451
467
452
468
// 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
+ {
454
473
return Empty . into_any ( ) ;
455
474
}
456
475
@@ -463,6 +482,8 @@ impl ActiveThread {
463
482
. filter ( |( id, _) | * id == message_id)
464
483
. map ( |( _, state) | state. editor . clone ( ) ) ;
465
484
485
+ let colors = cx. theme ( ) . colors ( ) ;
486
+
466
487
let message_content = v_flex ( )
467
488
. child (
468
489
if let Some ( edit_message_editor) = edit_message_editor. clone ( ) {
@@ -597,6 +618,7 @@ impl ActiveThread {
597
618
Role :: Assistant => div ( )
598
619
. id ( ( "message-container" , ix) )
599
620
. child ( message_content)
621
+ . children ( self . render_script ( message_id, cx) )
600
622
. map ( |parent| {
601
623
if tool_uses. is_empty ( ) {
602
624
return parent;
@@ -716,6 +738,139 @@ impl ActiveThread {
716
738
} ) ,
717
739
)
718
740
}
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
+ }
719
874
}
720
875
721
876
impl Render for ActiveThread {
0 commit comments