@@ -125,6 +125,8 @@ pub struct App {
125125 cloud_response : Option < AtomicNote > ,
126126 synthesis_scroll : u16 ,
127127 coaching_tip : ( String , String ) ,
128+ local_tokens_used : u32 , // Token count for current local request
129+ cloud_tokens_used : u32 , // Token count for current cloud request
128130}
129131
130132impl App {
@@ -154,6 +156,8 @@ impl App {
154156 cloud_response : None ,
155157 synthesis_scroll : 0 ,
156158 coaching_tip : ( String :: new ( ) , String :: new ( ) ) ,
159+ local_tokens_used : 0 ,
160+ cloud_tokens_used : 0 ,
157161 }
158162 }
159163
@@ -345,6 +349,8 @@ impl App {
345349 & self . theme ,
346350 self . agent_status ,
347351 & self . settings ,
352+ self . local_tokens_used ,
353+ self . cloud_tokens_used ,
348354 ) ;
349355 render_footer (
350356 frame,
@@ -473,10 +479,10 @@ impl App {
473479 . alignment ( ratatui:: prelude:: Alignment :: Center )
474480 } ;
475481
476- // Create a centered area for the synthesis (70 % width, centered vertically )
482+ // Create a compact area for the synthesis (60 % width, ~12 lines height, centered )
477483 let main_area = app_chunks[ 1 ] ;
478- let synthesis_width = ( main_area. width * 70 / 100 ) . max ( 40 ) . min ( main_area . width ) ;
479- let synthesis_height = 10 ; // Fixed height for the synthesis box
484+ let synthesis_width = ( main_area. width * 60 / 100 ) . max ( 40 ) . min ( 80 ) ;
485+ let synthesis_height = 12 . min ( main_area . height - 6 ) ;
480486
481487 let synthesis_area = Rect :: new (
482488 main_area. x + ( main_area. width . saturating_sub ( synthesis_width) ) / 2 ,
@@ -655,6 +661,9 @@ impl App {
655661 if self . agent_status == AgentStatus :: ValidatingCloud {
656662 self . agent_status = AgentStatus :: Ready ;
657663 self . start_agent_services ( ) ;
664+ // Automatically enter chat mode after successful validation
665+ self . mode = AppMode :: Chat ;
666+ self . edit_buffer . clear ( ) ;
658667 }
659668 }
660669
@@ -904,6 +913,8 @@ impl App {
904913 self . proposals . clear ( ) ;
905914 self . current_proposal_index = 0 ;
906915 self . original_user_query . clear ( ) ;
916+ self . local_tokens_used = 0 ;
917+ self . cloud_tokens_used = 0 ;
907918 }
908919 _ => { }
909920 } ,
@@ -919,6 +930,8 @@ impl App {
919930 self . cloud_response = None ;
920931 self . synthesis_scroll = 0 ;
921932 self . agent_status = AgentStatus :: Ready ;
933+ self . local_tokens_used = 0 ;
934+ self . cloud_tokens_used = 0 ;
922935 }
923936 KeyCode :: Down => {
924937 // Discard synthesis (negative action)
@@ -931,10 +944,18 @@ impl App {
931944 self . synthesis_scroll = 0 ;
932945 self . agent_status = AgentStatus :: Ready ;
933946 self . edit_buffer . clear ( ) ;
947+ self . local_tokens_used = 0 ;
948+ self . cloud_tokens_used = 0 ;
934949 }
935- KeyCode :: Left | KeyCode :: Right => {
936- // Keep horizontal scrolling for long content
937- // Currently no horizontal scroll implemented
950+ KeyCode :: Left => {
951+ // Scroll up through synthesis content
952+ if self . synthesis_scroll > 0 {
953+ self . synthesis_scroll -= 1 ;
954+ }
955+ }
956+ KeyCode :: Right => {
957+ // Scroll down through synthesis content
958+ self . synthesis_scroll += 1 ;
938959 }
939960 KeyCode :: Enter | KeyCode :: Esc => {
940961 // Fallback: return to normal without saving
@@ -973,6 +994,10 @@ impl App {
973994 // Store the original user query for metadata
974995 self . original_user_query = message. clone ( ) ;
975996
997+ // Estimate tokens for local request (rough: chars/4 + prompt overhead)
998+ self . local_tokens_used = ( message. len ( ) / 4 ) as u32 + 500 ; // ~500 tokens for prompt template
999+ self . cloud_tokens_used = 0 ; // Reset cloud tokens for new session
1000+
9761001 self . agent_status = AgentStatus :: Orchestrating ;
9771002 let settings = self . settings . clone ( ) ;
9781003 let tx = self . agent_tx . clone ( ) ;
@@ -1016,17 +1041,37 @@ impl App {
10161041 // Use proposal text directly since the new prompt ensures proper format
10171042 let clean_proposal = proposal_text;
10181043
1044+ // Get model names for usage metadata
1045+ let local_model = if self . settings . local_model . is_empty ( ) || self . settings . local_model == "[SELECT]" {
1046+ "unknown"
1047+ } else {
1048+ & self . settings . local_model
1049+ } ;
1050+
1051+ let cloud_model = if self . settings . cloud_model . is_empty ( ) || self . settings . cloud_model == "[SELECT]" {
1052+ "anthropic/claude-3.5-sonnet"
1053+ } else {
1054+ & self . settings . cloud_model
1055+ } ;
1056+
1057+ // Estimate token breakdown (rough estimates)
1058+ let local_prompt_tokens = ( self . original_user_query . len ( ) / 4 ) as u32 + 200 ; // Query + template
1059+ let local_completion_tokens = self . local_tokens_used . saturating_sub ( local_prompt_tokens) ;
1060+ let cloud_prompt_tokens = ( self . final_prompt . len ( ) / 4 ) as u32 + 150 ; // Proposal + synthesis template
1061+ let cloud_completion_tokens = self . cloud_tokens_used . saturating_sub ( cloud_prompt_tokens) ;
1062+
10191063 let markdown_content = format ! (
1020- "---\n date: {}\n provider: \" OPENROUTER\" \n model : \" {}\" \n query : \" {}\" \n proposal: \ " {}\" \n tags: [{}] \n ---\n \n # {}\n \n {}\n " ,
1064+ "---\n date: {}\n provider: \" OPENROUTER\" \n query : \" {}\" \n proposal : \" {}\" \n tags: [{}] \n \n usage: \n local_model: \ " {}\" \n local_prompt_tokens: {} \n local_completion_tokens: {} \n cloud_model: \" {} \" \n cloud_prompt_tokens: {} \n cloud_completion_tokens: {} \n ---\n \n # {}\n \n {}\n " ,
10211065 chrono:: Utc :: now( ) . format( "%Y-%m-%d %H:%M:%S UTC" ) ,
1022- if self . settings. cloud_model. is_empty( ) || self . settings. cloud_model == "[SELECT]" {
1023- "anthropic/claude-3.5-sonnet"
1024- } else {
1025- & self . settings. cloud_model
1026- } ,
10271066 self . original_user_query. replace( "\" " , "\\ \" " ) ,
10281067 clean_proposal. replace( "\" " , "\\ \" " ) ,
10291068 note. header_tags. iter( ) . map( |tag| format!( "\" {}\" " , tag) ) . collect:: <Vec <_>>( ) . join( ", " ) ,
1069+ local_model,
1070+ local_prompt_tokens,
1071+ local_completion_tokens,
1072+ cloud_model,
1073+ cloud_prompt_tokens,
1074+ cloud_completion_tokens,
10301075 note. header_tags. join( " • " ) ,
10311076 note. body_text
10321077 ) ;
@@ -1048,6 +1093,9 @@ impl App {
10481093 fn handle_cloud_synthesis ( & mut self ) {
10491094 // Set status to searching and trigger cloud API call
10501095 self . agent_status = AgentStatus :: Searching ;
1096+
1097+ // Estimate tokens for cloud request (prompt + synthesis template)
1098+ self . cloud_tokens_used = ( self . final_prompt . len ( ) / 4 ) as u32 + 300 ; // ~300 tokens for synthesis template
10511099
10521100 let prompt = self . final_prompt . clone ( ) ;
10531101 let api_key = self . settings . api_key . clone ( ) ;
0 commit comments