Coverage for src/chuck_data/commands/wizard/renderer.py: 0%

89 statements  

« prev     ^ index     » next       coverage.py v7.8.0, created at 2025-06-05 22:56 -0700

1""" 

2UI rendering for setup wizard. 

3""" 

4 

5import platform 

6import subprocess 

7import logging 

8from typing import List, Dict, Any 

9from rich.console import Console 

10from rich.table import Table 

11from rich import box 

12 

13from .state import WizardState, WizardStep 

14from .steps import SetupStep 

15 

16from ...ui.theme import ( 

17 INFO_STYLE, 

18 TABLE_TITLE_STYLE, 

19 SUCCESS_STYLE, 

20 ERROR_STYLE, 

21 WARNING_STYLE, 

22) 

23 

24 

25class WizardRenderer: 

26 """Handles UI rendering for the setup wizard.""" 

27 

28 def __init__(self, console: Console): 

29 self.console = console 

30 

31 def clear_terminal(self): 

32 """Clear the terminal screen.""" 

33 system = platform.system().lower() 

34 

35 try: 

36 if system == "windows": 

37 subprocess.run("cls", shell=True, check=False) 

38 else: 

39 subprocess.run("clear", shell=True, check=False) 

40 except Exception as e: 

41 logging.debug(f"Failed to clear terminal: {e}") 

42 

43 def render_step_header( 

44 self, step_number: int, title: str, clear_screen: bool = True 

45 ): 

46 """Display a header for the current step.""" 

47 if clear_screen: 

48 self.clear_terminal() 

49 self.console.print(f"\n[bold]Step {step_number}: {title}[/bold]") 

50 self.console.print("=" * 50) 

51 

52 def render_error(self, message: str): 

53 """Render an error message.""" 

54 self.console.print(f"[{ERROR_STYLE}]Error: {message}[/{ERROR_STYLE}]") 

55 

56 def render_warning(self, message: str): 

57 """Render a warning message.""" 

58 self.console.print(f"[{WARNING_STYLE}]{message}[/{WARNING_STYLE}]") 

59 

60 def render_success(self, message: str): 

61 """Render a success message.""" 

62 self.console.print(f"[{SUCCESS_STYLE}]{message}[/{SUCCESS_STYLE}]") 

63 

64 def render_info(self, message: str): 

65 """Render an info message.""" 

66 self.console.print(f"[{INFO_STYLE}]{message}[/{INFO_STYLE}]") 

67 

68 def render_prompt(self, message: str): 

69 """Render a prompt message.""" 

70 self.console.print(message) 

71 

72 def render_step( 

73 self, 

74 step: "SetupStep", 

75 state: WizardState, 

76 step_number: int, 

77 clear_screen: bool = False, 

78 ): 

79 """Render a complete step including header and prompt.""" 

80 # Clear screen first if needed 

81 if clear_screen: 

82 self.clear_terminal() 

83 

84 # Render any error message BEFORE the step header so it's clear the error is from previous step 

85 if state.error_message: 

86 self.render_error(state.error_message) 

87 self.console.print() # Add blank line after error 

88 

89 # Render step header (but don't clear screen again since we already did it) 

90 self.render_step_header(step_number, step.get_step_title(), clear_screen=False) 

91 

92 # Handle special rendering for specific steps 

93 if state.current_step == WizardStep.MODEL_SELECTION: 

94 self._render_models_list(state.models) 

95 elif state.current_step == WizardStep.USAGE_CONSENT: 

96 self._render_usage_consent_info() 

97 

98 # Render the prompt 

99 prompt_message = step.get_prompt_message(state) 

100 self.render_prompt(prompt_message) 

101 

102 def render_completion(self): 

103 """Render wizard completion message.""" 

104 self.console.print("\n[bold]Setup wizard completed successfully![/bold]") 

105 self.console.print("You are now ready to use Chuck with all features enabled.") 

106 self.console.print("Type /help to see available commands.") 

107 

108 def _render_models_list(self, models: List[Dict[str, Any]]): 

109 """Render the list of available models.""" 

110 if not models: 

111 self.render_warning("No models available.") 

112 return 

113 

114 self.console.print("\nAvailable models:") 

115 

116 # Define recommended models 

117 recommended_models = [ 

118 "databricks-meta-llama-3-3-70b-instruct", 

119 "databricks-claude-3-7-sonnet", 

120 ] 

121 

122 # Sort models - recommended first 

123 sorted_models = [] 

124 

125 # Add recommended models first 

126 for rec_model in recommended_models: 

127 for model in models: 

128 if model["name"] == rec_model: 

129 sorted_models.append(model) 

130 break 

131 

132 # Add remaining models 

133 for model in models: 

134 if model["name"] not in recommended_models: 

135 sorted_models.append(model) 

136 

137 # Display the models 

138 for i, model in enumerate(sorted_models, 1): 

139 model_name = model["name"] 

140 if model_name in recommended_models: 

141 self.console.print(f"{i}. {model_name} [green](recommended)[/green]") 

142 else: 

143 self.console.print(f"{i}. {model_name}") 

144 

145 def _render_usage_consent_info(self): 

146 """Render usage consent information.""" 

147 self.console.print( 

148 "\nChuck is a research preview application meant to showcase a new wave of data engineering tooling powered by AI.\n" 

149 ) 

150 self.console.print( 

151 "Our goal is to learn as much about what is working and not working as possible, and your usage is key to that!\n" 

152 ) 

153 self.console.print( 

154 "Chuck can log usage to Amperity so that we can see how users are using the application. " 

155 "This is a key piece of information that we will use to inform our roadmap, prioritize bug fixes, and refine existing features.\n" 

156 ) 

157 self.console.print( 

158 "Chuck runs locally and Amperity will never have access to your data.\n" 

159 ) 

160 

161 # Create table showing what Chuck shares vs never shares 

162 table = Table(show_header=True, header_style=TABLE_TITLE_STYLE, box=box.SIMPLE) 

163 table.add_column("Chuck shares", style="green") 

164 table.add_column("Chuck NEVER shares", style="red bold") 

165 

166 table.add_row( 

167 "1. Prompts you type", 

168 "1. Your data or the values in the tables you interact with", 

169 ) 

170 table.add_row("2. Tools/context the LLM uses", "2. Credentials of any form") 

171 table.add_row( 

172 "3. Errors you encounter", 

173 "3. Security details about your Databricks account", 

174 ) 

175 

176 self.console.print(table) 

177 

178 self.console.print( 

179 "\nChuck is an Open Source CLI and you can always review the code for security at https://github.com/amperity/chuck-data\n" 

180 ) 

181 

182 def get_step_number(self, step: WizardStep) -> int: 

183 """Get display step number for a wizard step.""" 

184 step_numbers = { 

185 WizardStep.AMPERITY_AUTH: 1, 

186 WizardStep.WORKSPACE_URL: 2, 

187 WizardStep.TOKEN_INPUT: 3, 

188 WizardStep.MODEL_SELECTION: 4, 

189 WizardStep.USAGE_CONSENT: 5, 

190 WizardStep.COMPLETE: 6, 

191 } 

192 return step_numbers.get(step, 1)