@@ -42,13 +42,15 @@ use crossterm::style::{
4242 Color ,
4343 Stylize ,
4444} ;
45+ use crossterm:: terminal:: ClearType ;
4546use crossterm:: {
4647 cursor,
4748 execute,
4849 queue,
4950 style,
5051 terminal,
5152} ;
53+ use dialoguer:: console:: strip_ansi_codes;
5254use eyre:: {
5355 ErrReport ,
5456 Result ,
@@ -66,7 +68,10 @@ use fig_api_client::model::{
6668 ToolResultStatus ,
6769} ;
6870use fig_os_shim:: Context ;
69- use fig_settings:: Settings ;
71+ use fig_settings:: {
72+ Settings ,
73+ State ,
74+ } ;
7075use fig_util:: CLI_BINARY_NAME ;
7176use hooks:: {
7277 Hook ,
@@ -151,27 +156,45 @@ use crate::util::token_counter::TokenCounter;
151156
152157const WELCOME_TEXT : & str = color_print:: cstr! { "
153158
154- <em>Hi, I'm <magenta,em>Amazon Q</magenta,em>. Ask me anything.</em>
155-
156- <cyan!>Things to try</cyan!>
157- • Fix the build failures in this project.
158- • List my s3 buckets in us-west-2.
159- • Write unit tests for my application.
160- • Help me understand my git status.
161-
162- <em>/tools</em> <black!>View and manage tools and permissions</black!>
163- <em>/issue</em> <black!>Report an issue or make a feature request</black!>
164- <em>/profile</em> <black!>(Beta) Manage profiles for the chat session</black!>
165- <em>/context</em> <black!>(Beta) Manage context files and hooks for a profile</black!>
166- <em>/compact</em> <black!>Summarize the conversation to free up context space</black!>
167- <em>/help</em> <black!>Show the help dialogue</black!>
168- <em>/quit</em> <black!>Quit the application</black!>
169-
170- <cyan!>Use Ctrl(^) + j to provide multi-line prompts.</cyan!>
171- <cyan!>Use Ctrl(^) + k to fuzzily search commands and context.</cyan!>
159+ <em>Welcome to </em>
160+ <cyan!>
161+ █████╗ ███╗ ███╗ █████╗ ███████╗ ██████╗ ███╗ ██╗ ██████╗
162+ ██╔══██╗████╗ ████║██╔══██╗╚══███╔╝██╔═══██╗████╗ ██║ ██╔═══██╗
163+ ███████║██╔████╔██║███████║ ███╔╝ ██║ ██║██╔██╗ ██║ ██║ ██║
164+ ██╔══██║██║╚██╔╝██║██╔══██║ ███╔╝ ██║ ██║██║╚██╗██║ ██║▄▄ ██║
165+ ██║ ██║██║ ╚═╝ ██║██║ ██║███████╗╚██████╔╝██║ ╚████║ ╚██████╔╝
166+ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝╚══════╝ ╚═════╝ ╚═╝ ╚═══╝ ╚══▀▀═╝
167+ </cyan!>
168+ " } ;
172169
170+ const SMALL_SCREEN_WECLOME_TEXT : & str = color_print:: cstr! { "
171+ <em>Welcome to <cyan!>Amazon Q</cyan!>!</em>
173172" } ;
174173
174+ const ROTATING_TIPS : [ & str ; 7 ] = [
175+ color_print:: cstr! { "You can use <green!>/editor</green!> to edit your prompt with a vim-like experience" } ,
176+ color_print:: cstr! { "You can execute bash commands by typing <green!>!</green!> followed by the command" } ,
177+ color_print:: cstr! { "Q can use tools without asking for confirmation every time. Give <green!>/tools trust</green!> a try" } ,
178+ color_print:: cstr! { "You can programmatically inject context to your prompts by using hooks. Check out <green!>/context hooks help</green!>" } ,
179+ color_print:: cstr! { "You can use <green!>/compact</green!> to replace the conversation history with its summary to free up the context space" } ,
180+ color_print:: cstr! { "<green!>/usage</green!> shows you a visual breakdown of your current context window usage" } ,
181+ color_print:: cstr! { "If you want to file an issue to the Q CLI team, just tell me, or run <green!>q issue</green!>" } ,
182+ ] ;
183+
184+ const GREETING_BREAK_POINT : usize = 67 ;
185+
186+ const POPULAR_SHORTCUTS : & str = color_print:: cstr! { "
187+ <black!>
188+ <green!>/help</green!> all commands <em>•</em> <green!>ctrl + j</green!> new lines <em>•</em> <green!>ctrl + k</green!> fuzzy search
189+ </black!>" } ;
190+
191+ const SMALL_SCREEN_POPULAR_SHORTCUTS : & str = color_print:: cstr! { "
192+ <black!>
193+ <green!>/help</green!> all commands
194+ <green!>ctrl + j</green!> new lines
195+ <green!>ctrl + k</green!> fuzzy search
196+ </black!>
197+ " } ;
175198const HELP_TEXT : & str = color_print:: cstr! { "
176199
177200<magenta,em>q</magenta,em> (Amazon Q Chat)
@@ -310,6 +333,7 @@ pub async fn chat(
310333 let mut chat = ChatContext :: new (
311334 ctx,
312335 Settings :: new ( ) ,
336+ State :: new ( ) ,
313337 output,
314338 input,
315339 InputSource :: new ( ) ?,
@@ -362,6 +386,8 @@ pub enum ChatError {
362386pub struct ChatContext < W : Write > {
363387 ctx : Arc < Context > ,
364388 settings : Settings ,
389+ /// The [State] to use for the chat context.
390+ state : State ,
365391 /// The [Write] destination for printing conversation text.
366392 output : W ,
367393 initial_input : Option < String > ,
@@ -391,6 +417,7 @@ impl<W: Write> ChatContext<W> {
391417 pub async fn new (
392418 ctx : Arc < Context > ,
393419 settings : Settings ,
420+ state : State ,
394421 output : W ,
395422 input : Option < String > ,
396423 input_source : InputSource ,
@@ -405,6 +432,7 @@ impl<W: Write> ChatContext<W> {
405432 Ok ( Self {
406433 ctx,
407434 settings,
435+ state,
408436 output,
409437 initial_input : input,
410438 input_source,
@@ -540,11 +568,135 @@ where
540568 Ok ( content. trim ( ) . to_string ( ) )
541569 }
542570
571+ fn draw_tip_box ( & mut self , text : & str ) -> Result < ( ) > {
572+ let box_width = GREETING_BREAK_POINT ;
573+ let inner_width = box_width - 4 ; // account for │ and padding
574+
575+ // wrap the single line into multiple lines respecting inner width
576+ // Manually wrap the text by splitting at word boundaries
577+ let mut wrapped_lines = Vec :: new ( ) ;
578+ let mut line = String :: new ( ) ;
579+
580+ for word in text. split_whitespace ( ) {
581+ if line. len ( ) + word. len ( ) < inner_width {
582+ if !line. is_empty ( ) {
583+ line. push ( ' ' ) ;
584+ }
585+ line. push_str ( word) ;
586+ } else {
587+ wrapped_lines. push ( line) ;
588+ line = word. to_string ( ) ;
589+ }
590+ }
591+
592+ if !line. is_empty ( ) {
593+ wrapped_lines. push ( line) ;
594+ }
595+
596+ // ───── Did you know? ─────
597+ let label = " Did you know? " ;
598+ let side_len = ( box_width. saturating_sub ( label. len ( ) ) ) / 2 ;
599+ let top_border = format ! (
600+ "╭{}{}{}╮" ,
601+ "─" . repeat( side_len - 1 ) ,
602+ label,
603+ "─" . repeat( box_width - side_len - label. len( ) - 1 )
604+ ) ;
605+
606+ // Build output
607+ execute ! (
608+ self . output,
609+ terminal:: Clear ( ClearType :: CurrentLine ) ,
610+ cursor:: MoveToColumn ( 0 ) ,
611+ style:: Print ( format!( "{top_border}\n " ) ) ,
612+ ) ?;
613+
614+ // Top vertical padding
615+ execute ! (
616+ self . output,
617+ style:: Print ( format!( "│{: <width$}│\n " , "" , width = box_width - 2 ) )
618+ ) ?;
619+
620+ // Centered wrapped content
621+ for line in wrapped_lines {
622+ let visible_line_len = strip_ansi_codes ( & line) . len ( ) ;
623+ let left_pad = ( box_width - 4 - visible_line_len) / 2 ;
624+
625+ let content = format ! (
626+ "│ {: <pad$}{}{: <rem$} │" ,
627+ "" ,
628+ line,
629+ "" ,
630+ pad = left_pad,
631+ rem = box_width - 4 - left_pad - visible_line_len
632+ ) ;
633+ execute ! ( self . output, style:: Print ( format!( "{}\n " , content) ) ) ?;
634+ }
635+
636+ // Bottom vertical padding
637+ execute ! (
638+ self . output,
639+ style:: Print ( format!( "│{: <width$}│\n " , "" , width = box_width - 2 ) )
640+ ) ?;
641+
642+ // Bottom rounded corner line: ╰────────────╯
643+ let bottom = format ! ( "╰{}╯" , "─" . repeat( box_width - 2 ) ) ;
644+ execute ! ( self . output, style:: Print ( format!( "{}\n " , bottom) ) ) ?;
645+
646+ Ok ( ( ) )
647+ }
648+
543649 async fn try_chat ( & mut self ) -> Result < ( ) > {
650+ let is_small_screen = self . terminal_width ( ) < GREETING_BREAK_POINT ;
544651 if self . interactive && self . settings . get_bool_or ( "chat.greeting.enabled" , true ) {
545- execute ! ( self . output, style:: Print ( WELCOME_TEXT ) ) ?;
652+ execute ! (
653+ self . output,
654+ style:: Print ( if is_small_screen {
655+ SMALL_SCREEN_WECLOME_TEXT
656+ } else {
657+ WELCOME_TEXT
658+ } ) ,
659+ style:: Print ( "\n \n " ) ,
660+ ) ?;
661+
662+ let current_tip_index =
663+ ( self . state . get_int_or ( "chat.greeting.rotating_tips_current_index" , 0 ) as usize ) % ROTATING_TIPS . len ( ) ;
664+
665+ let tip = ROTATING_TIPS [ current_tip_index] ;
666+ if is_small_screen {
667+ // If the screen is small, print the tip in a single line
668+ execute ! (
669+ self . output,
670+ style:: Print ( "💡 " . to_string( ) ) ,
671+ style:: Print ( tip) ,
672+ style:: Print ( "\n " )
673+ ) ?;
674+ } else {
675+ self . draw_tip_box ( tip) ?;
676+ }
677+
678+ // update the current tip index
679+ let next_tip_index = ( current_tip_index + 1 ) % ROTATING_TIPS . len ( ) ;
680+ self . state
681+ . set_value ( "chat.greeting.rotating_tips_current_index" , next_tip_index) ?;
546682 }
547683
684+ execute ! (
685+ self . output,
686+ style:: Print ( if is_small_screen {
687+ SMALL_SCREEN_POPULAR_SHORTCUTS
688+ } else {
689+ POPULAR_SHORTCUTS
690+ } ) ,
691+ style:: Print (
692+ "━"
693+ . repeat( if is_small_screen { 0 } else { GREETING_BREAK_POINT } )
694+ . dark_grey( )
695+ )
696+ ) ?;
697+ execute ! ( self . output, style:: Print ( "\n " ) , style:: SetForegroundColor ( Color :: Reset ) ) ?;
698+ self . output . flush ( ) ?;
699+
548700 let mut ctrl_c_stream = signal ( SignalKind :: interrupt ( ) ) ?;
549701
550702 let mut next_state = Some ( ChatState :: PromptUser {
@@ -2924,6 +3076,7 @@ mod tests {
29243076 ChatContext :: new (
29253077 Arc :: clone ( & ctx) ,
29263078 Settings :: new_fake ( ) ,
3079+ State :: new_fake ( ) ,
29273080 std:: io:: stdout ( ) ,
29283081 None ,
29293082 InputSource :: new_mock ( vec ! [
@@ -3047,6 +3200,7 @@ mod tests {
30473200 ChatContext :: new (
30483201 Arc :: clone ( & ctx) ,
30493202 Settings :: new_fake ( ) ,
3203+ State :: new_fake ( ) ,
30503204 std:: io:: stdout ( ) ,
30513205 None ,
30523206 InputSource :: new_mock ( vec ! [
@@ -3145,6 +3299,7 @@ mod tests {
31453299 ChatContext :: new (
31463300 Arc :: clone ( & ctx) ,
31473301 Settings :: new_fake ( ) ,
3302+ State :: new_fake ( ) ,
31483303 std:: io:: stdout ( ) ,
31493304 None ,
31503305 InputSource :: new_mock ( vec ! [
@@ -3215,6 +3370,7 @@ mod tests {
32153370 ChatContext :: new (
32163371 Arc :: clone ( & ctx) ,
32173372 Settings :: new_fake ( ) ,
3373+ State :: new_fake ( ) ,
32183374 std:: io:: stdout ( ) ,
32193375 None ,
32203376 InputSource :: new_mock ( vec ! [
0 commit comments