1
1
mod api;
2
2
mod parse;
3
3
mod prompt;
4
- mod terminal ;
4
+ mod stdio ;
5
5
6
6
use std:: io:: {
7
7
IsTerminal ,
@@ -16,13 +16,13 @@ use color_eyre::owo_colors::OwoColorize;
16
16
use crossterm:: style:: {
17
17
Attribute ,
18
18
Color ,
19
- Print ,
20
19
} ;
21
20
use crossterm:: {
22
21
cursor,
23
22
execute,
24
23
queue,
25
24
style,
25
+ terminal,
26
26
} ;
27
27
use eyre:: {
28
28
Result ,
@@ -40,7 +40,7 @@ use spinners::{
40
40
Spinner ,
41
41
Spinners ,
42
42
} ;
43
- use terminal :: StdioOutput ;
43
+ use stdio :: StdioOutput ;
44
44
use winnow:: Partial ;
45
45
use winnow:: stream:: Offset ;
46
46
@@ -79,7 +79,8 @@ pub async fn chat(mut input: String) -> Result<ExitCode> {
79
79
}
80
80
81
81
let mut output = StdioOutput :: new ( is_interactive) ;
82
- let result = try_chat ( & mut output, input, is_interactive) . await ;
82
+ let client = StreamingClient :: new ( ) . await ?;
83
+ let result = try_chat ( & mut output, input, is_interactive, & client) . await ;
83
84
84
85
if is_interactive {
85
86
queue ! ( output, style:: SetAttribute ( Attribute :: Reset ) , style:: ResetColor ) . ok ( ) ;
@@ -89,9 +90,13 @@ pub async fn chat(mut input: String) -> Result<ExitCode> {
89
90
result. map ( |_| ExitCode :: SUCCESS )
90
91
}
91
92
92
- async fn try_chat < W : Write > ( output : & mut W , mut input : String , interactive : bool ) -> Result < ( ) > {
93
+ async fn try_chat < W : Write > (
94
+ output : & mut W ,
95
+ mut input : String ,
96
+ interactive : bool ,
97
+ client : & StreamingClient ,
98
+ ) -> Result < ( ) > {
93
99
let mut rl = if interactive { Some ( rl ( ) ?) } else { None } ;
94
- let client = StreamingClient :: new ( ) . await ?;
95
100
let mut rx = None ;
96
101
let mut conversation_id: Option < String > = None ;
97
102
let mut message_id = None ;
@@ -158,8 +163,8 @@ You can include additional context by adding the following to your prompt:
158
163
let mut offset = 0 ;
159
164
let mut ended = false ;
160
165
161
- let columns = crossterm :: terminal:: window_size ( ) ? . columns . into ( ) ;
162
- let mut state = ParseState :: new ( columns ) ;
166
+ let terminal_width = terminal:: window_size ( ) . map ( |s| s . columns . into ( ) ) . ok ( ) ;
167
+ let mut state = ParseState :: new ( terminal_width ) ;
163
168
164
169
loop {
165
170
if let Some ( response) = rx. recv ( ) . await {
@@ -229,11 +234,11 @@ You can include additional context by adding the following to your prompt:
229
234
buf. push ( '\n' ) ;
230
235
}
231
236
232
- if !buf. is_empty ( ) && interactive {
237
+ if !buf. is_empty ( ) && interactive && spinner . is_some ( ) {
233
238
drop ( spinner. take ( ) ) ;
234
239
queue ! (
235
240
output,
236
- crossterm :: terminal:: Clear ( crossterm :: terminal:: ClearType :: CurrentLine ) ,
241
+ terminal:: Clear ( terminal:: ClearType :: CurrentLine ) ,
237
242
cursor:: MoveToColumn ( 0 ) ,
238
243
cursor:: Show
239
244
) ?;
@@ -259,32 +264,26 @@ You can include additional context by adding the following to your prompt:
259
264
}
260
265
261
266
if ended {
267
+ if let ( Some ( conversation_id) , Some ( message_id) ) = ( & conversation_id, & message_id) {
268
+ fig_telemetry:: send_chat_added_message ( conversation_id. to_owned ( ) , message_id. to_owned ( ) ) . await ;
269
+ }
270
+
262
271
if interactive {
263
- queue ! (
264
- output,
265
- style:: ResetColor ,
266
- style:: SetAttribute ( Attribute :: Reset ) ,
267
- Print ( "\n " )
268
- ) ?;
272
+ queue ! ( output, style:: ResetColor , style:: SetAttribute ( Attribute :: Reset ) ) ?;
269
273
270
274
for ( i, citation) in & state. citations {
271
275
queue ! (
272
276
output,
277
+ style:: Print ( "\n " ) ,
273
278
style:: SetForegroundColor ( Color :: Blue ) ,
274
- style:: Print ( format!( "{i} " ) ) ,
279
+ style:: Print ( format!( "[^ {i}]: " ) ) ,
275
280
style:: SetForegroundColor ( Color :: DarkGrey ) ,
276
281
style:: Print ( format!( "{citation}\n " ) ) ,
277
282
style:: SetForegroundColor ( Color :: Reset )
278
283
) ?;
279
284
}
280
285
281
- if !state. citations . is_empty ( ) {
282
- execute ! ( output, Print ( "\n " ) ) ?;
283
- }
284
- }
285
-
286
- if let ( Some ( conversation_id) , Some ( message_id) ) = ( & conversation_id, & message_id) {
287
- fig_telemetry:: send_chat_added_message ( conversation_id. to_owned ( ) , message_id. to_owned ( ) ) . await ;
286
+ execute ! ( output, style:: Print ( "\n " ) ) ?;
288
287
}
289
288
290
289
break ;
@@ -313,6 +312,64 @@ You can include additional context by adding the following to your prompt:
313
312
} ,
314
313
}
315
314
}
315
+ } else {
316
+ break Ok ( ( ) ) ;
316
317
}
317
318
}
318
319
}
320
+
321
+ #[ cfg( test) ]
322
+ mod test {
323
+ use fig_api_client:: model:: ChatResponseStream ;
324
+
325
+ use super :: * ;
326
+
327
+ fn mock_client ( s : impl IntoIterator < Item = & ' static str > ) -> StreamingClient {
328
+ StreamingClient :: mock (
329
+ s. into_iter ( )
330
+ . map ( |s| ChatResponseStream :: AssistantResponseEvent { content : s. into ( ) } )
331
+ . collect ( ) ,
332
+ )
333
+ }
334
+
335
+ #[ tokio:: test]
336
+ async fn try_chat_non_interactive ( ) {
337
+ let client = mock_client ( [ "Hello," , " World" , "!" ] ) ;
338
+ let mut output = Vec :: new ( ) ;
339
+ try_chat ( & mut output, "test" . into ( ) , false , & client) . await . unwrap ( ) ;
340
+
341
+ let mut expected = Vec :: new ( ) ;
342
+ execute ! (
343
+ expected,
344
+ style:: Print ( "Hello, World!" ) ,
345
+ style:: ResetColor ,
346
+ style:: SetAttribute ( Attribute :: Reset ) ,
347
+ style:: Print ( "\n " )
348
+ )
349
+ . unwrap ( ) ;
350
+
351
+ assert_eq ! ( expected, output) ;
352
+ }
353
+
354
+ #[ tokio:: test]
355
+ async fn try_chat_non_interactive_citation ( ) {
356
+ let client = mock_client ( [ "Citation [[1]](https://aws.com)" ] ) ;
357
+ let mut output = Vec :: new ( ) ;
358
+ try_chat ( & mut output, "test" . into ( ) , false , & client) . await . unwrap ( ) ;
359
+
360
+ let mut expected = Vec :: new ( ) ;
361
+ execute ! (
362
+ expected,
363
+ style:: Print ( "Citation " ) ,
364
+ style:: SetForegroundColor ( Color :: Blue ) ,
365
+ style:: Print ( "[^1]" ) ,
366
+ style:: ResetColor ,
367
+ style:: ResetColor ,
368
+ style:: SetAttribute ( Attribute :: Reset ) ,
369
+ style:: Print ( "\n " )
370
+ )
371
+ . unwrap ( ) ;
372
+
373
+ assert_eq ! ( expected, output) ;
374
+ }
375
+ }
0 commit comments