@@ -99,17 +99,23 @@ def _read_constant(script_key: str, name: str) -> str:
9999
100100
101101def _write_constant (script_key : str , name : str , new_display_val : str ) -> bool :
102- """Write a constant back to its source file, preserving the original quote style ."""
102+ """Write a constant back to its source file with the correct Python literal type ."""
103103 path = SCRIPTS [script_key ]
104104 try :
105105 src = path .read_text (errors = "ignore" )
106106
107107 def _replacer (m : re .Match ) -> str :
108- old_raw = m .group (2 ).lstrip ()
109- # Re-quote if original was a string literal
110- if old_raw .startswith (('"' , "'" )):
111- return m .group (1 ) + f'"{ new_display_val } "'
112- return m .group (1 ) + new_display_val
108+ # None keyword → bare None
109+ if new_display_val == "None" :
110+ return m .group (1 ) + "None"
111+ # Numeric → bare (int or float)
112+ try :
113+ float (new_display_val )
114+ return m .group (1 ) + new_display_val
115+ except ValueError :
116+ pass
117+ # String → always quoted
118+ return m .group (1 ) + f'"{ new_display_val } "'
113119
114120 new_src , n = re .subn (
115121 rf'^({ name } \s*=\s*)([^\n#]+)' ,
@@ -976,11 +982,70 @@ def _snack(msg: str, ok: bool = True) -> None:
976982 page .snack_bar .open = True
977983 page .update ()
978984
985+ def _key_row (label : str , tf : ft .TextField , on_save ) -> ft .Row :
986+ return ft .Row (controls = [
987+ ft .Text (label , size = 12 , color = ft .Colors .WHITE , width = 140 ),
988+ tf ,
989+ ft .FilledButton ("Save" , icon = ft .Icons .SAVE_OUTLINED ,
990+ on_click = on_save , style = ft .ButtonStyle (
991+ bgcolor = C_PRIMARY , color = ft .Colors .BLACK )),
992+ ], spacing = 8 )
993+
994+ # ── Connection settings (config.json) ────────────────────────────────────
995+
996+ config_file = PROJECT_DIR / "config.json"
997+
998+ def _load_config () -> dict :
999+ return json .load (open (config_file )) if config_file .exists () else {}
1000+
1001+ def _save_config (key : str , value : str ) -> None :
1002+ cfg = _load_config ()
1003+ cfg [key ] = value
1004+ with open (config_file , "w" ) as f :
1005+ json .dump (cfg , f , indent = 2 )
1006+
1007+ _cfg = _load_config ()
1008+ tf_canvas_url = ft .TextField (
1009+ value = _cfg .get ("CANVAS_URL" , "" ),
1010+ hint_text = "https://canvas.yourschool.edu" , expand = True , dense = True ,
1011+ bgcolor = C_OUTPUT_BG , border_color = C_PRIMARY , text_size = 12 ,
1012+ )
1013+ tf_panopto = ft .TextField (
1014+ value = _cfg .get ("PANOPTO_HOST" , "" ),
1015+ hint_text = "mediaweb.ap.panopto.com" , expand = True , dense = True ,
1016+ bgcolor = C_OUTPUT_BG , border_color = C_PRIMARY , text_size = 12 ,
1017+ )
1018+
1019+ def _save_canvas_url (_ ):
1020+ try :
1021+ _save_config ("CANVAS_URL" , tf_canvas_url .value .strip ())
1022+ _snack ("Canvas URL saved." )
1023+ except Exception as e :
1024+ _snack (f"Error: { e } " , ok = False )
1025+
1026+ def _save_panopto (_ ):
1027+ try :
1028+ _save_config ("PANOPTO_HOST" , tf_panopto .value .strip ())
1029+ _snack ("Panopto host saved." )
1030+ except Exception as e :
1031+ _snack (f"Error: { e } " , ok = False )
1032+
1033+ conn_card = _card (ft .Column (controls = [
1034+ ft .Text ("Connection" , size = 13 ,
1035+ weight = ft .FontWeight .BOLD , color = ft .Colors .WHITE ),
1036+ ft .Text ("Saved to config.json in the project directory." ,
1037+ size = 11 , color = ft .Colors .with_opacity (0.5 , ft .Colors .WHITE )),
1038+ ft .Container (height = 4 ),
1039+ _key_row ("Canvas URL" , tf_canvas_url , _save_canvas_url ),
1040+ _key_row ("Panopto Host" , tf_panopto , _save_panopto ),
1041+ ], spacing = 10 ))
1042+
9791043 # ── API Keys ──────────────────────────────────────────────────────────────
9801044
9811045 canvas_file = PROJECT_DIR / "canvas_token.txt"
9821046 openai_file = PROJECT_DIR / "openai_api.txt"
9831047 anthropic_file = PROJECT_DIR / "anthropic_key.txt"
1048+ gemini_file = PROJECT_DIR / "gemini_api.txt"
9841049
9851050 tf_canvas = ft .TextField (
9861051 value = canvas_file .read_text ().strip () if canvas_file .exists () else "" ,
@@ -1000,6 +1065,12 @@ def _snack(msg: str, ok: bool = True) -> None:
10001065 hint_text = "sk-ant-… (no default)" , expand = True , dense = True ,
10011066 bgcolor = C_OUTPUT_BG , border_color = C_PRIMARY , text_size = 12 ,
10021067 )
1068+ tf_gemini = ft .TextField (
1069+ value = gemini_file .read_text ().strip () if gemini_file .exists () else "" ,
1070+ password = True , can_reveal_password = True ,
1071+ hint_text = "AIza… (Google AI Studio key, no default)" , expand = True , dense = True ,
1072+ bgcolor = C_OUTPUT_BG , border_color = C_PRIMARY , text_size = 12 ,
1073+ )
10031074
10041075 def _save_canvas (_ ):
10051076 try :
@@ -1022,14 +1093,12 @@ def _save_anthropic(_):
10221093 except Exception as e :
10231094 _snack (f"Error: { e } " , ok = False )
10241095
1025- def _key_row (label : str , tf : ft .TextField , on_save ) -> ft .Row :
1026- return ft .Row (controls = [
1027- ft .Text (label , size = 12 , color = ft .Colors .WHITE , width = 140 ),
1028- tf ,
1029- ft .FilledButton ("Save" , icon = ft .Icons .SAVE_OUTLINED ,
1030- on_click = on_save , style = ft .ButtonStyle (
1031- bgcolor = C_PRIMARY , color = ft .Colors .BLACK )),
1032- ], spacing = 8 )
1096+ def _save_gemini (_ ):
1097+ try :
1098+ gemini_file .write_text (tf_gemini .value .strip ())
1099+ _snack ("Gemini key saved to gemini_api.txt." )
1100+ except Exception as e :
1101+ _snack (f"Error: { e } " , ok = False )
10331102
10341103 keys_card = _card (ft .Column (controls = [
10351104 ft .Text ("API Keys & Credentials" , size = 13 ,
@@ -1040,6 +1109,7 @@ def _key_row(label: str, tf: ft.TextField, on_save) -> ft.Row:
10401109 _key_row ("Canvas Token" , tf_canvas , _save_canvas ),
10411110 _key_row ("OpenAI API Key" , tf_openai , _save_openai ),
10421111 _key_row ("Anthropic API Key" ,tf_anthropic , _save_anthropic ),
1112+ _key_row ("Gemini API Key" , tf_gemini , _save_gemini ),
10431113 ft .Row (controls = [
10441114 ft .Text ("Project dir" , size = 11 ,
10451115 color = ft .Colors .with_opacity (0.5 , ft .Colors .WHITE )),
@@ -1056,55 +1126,143 @@ def _key_row(label: str, tf: ft.TextField, on_save) -> ft.Row:
10561126 "generate" : ft .Colors .PURPLE_200 ,
10571127 }
10581128
1129+ # options: list of (display_label, stored_value) pairs, or None for free text
10591130 CONSTANTS = [
1060- # script_key name description default
1061- ("transcribe" , "WHISPER_MODEL_SIZE" , "Whisper model variant" , "large-v3" ),
1062- ("transcribe" , "WHISPER_LANGUAGE" , "Language (None = auto)" , "None" ),
1063- ("align" , "EMBED_MODEL" , "Sentence-transformer" , "all-mpnet-base-v2" ),
1064- ("align" , "CONTEXT_SEC" , "Context window (s)" , "30" ),
1065- ("align" , "OFF_SLIDE_THRESHOLD" , "Off-slide cosine cutoff" ,"0.28" ),
1066- ("align" , "PRIOR_SIGMA" , "Temporal prior σ" , "5" ),
1067- ("generate" , "NOTE_MODEL" , "Note generation LLM" , "gpt-5.1" ),
1068- ("generate" , "VERIFY_MODEL" , "Verification LLM" , "gpt-4.1-mini" ),
1069- ("generate" , "DETAIL_LEVEL" , "Default detail level" , "8" ),
1070- ("generate" , "CHAPTER_SIZE" , "Slides per GPT call" , "15" ),
1071- ("generate" , "QUALITY_TARGET" , "Self-score target" , "8.0" ),
1131+ # script name desc default options
1132+ ("transcribe" , "WHISPER_MODEL_SIZE" , "Whisper model variant" , "large-v3" , [
1133+ ("tiny" , "tiny" ),
1134+ ("base" , "base" ),
1135+ ("small" , "small" ),
1136+ ("medium" , "medium" ),
1137+ ("large" , "large" ),
1138+ ("large-v2" , "large-v2" ),
1139+ ("large-v3" , "large-v3" ),
1140+ ("large-v3-turbo" , "large-v3-turbo" ),
1141+ ("distil-large-v3" , "distil-large-v3" ),
1142+ ]),
1143+ ("transcribe" , "WHISPER_LANGUAGE" , "Transcription language" , "None" , [
1144+ ("Auto-detect" , "None" ),
1145+ ("English" , "en" ),
1146+ ("Chinese" , "zh" ),
1147+ ("Japanese" , "ja" ),
1148+ ("Korean" , "ko" ),
1149+ ("French" , "fr" ),
1150+ ("German" , "de" ),
1151+ ("Spanish" , "es" ),
1152+ ]),
1153+ ("align" , "EMBED_MODEL" , "Sentence-transformer model" , "all-mpnet-base-v2" , [
1154+ ("all-mpnet-base-v2 (best quality)" , "all-mpnet-base-v2" ),
1155+ ("all-MiniLM-L12-v2 (balanced)" , "all-MiniLM-L12-v2" ),
1156+ ("all-MiniLM-L6-v2 (fast)" , "all-MiniLM-L6-v2" ),
1157+ ("paraphrase-multilingual-mpnet-base-v2" , "paraphrase-multilingual-mpnet-base-v2" ),
1158+ ]),
1159+ ("align" , "CONTEXT_SEC" , "Context window (s)" , "30" , None ),
1160+ ("align" , "OFF_SLIDE_THRESHOLD" , "Off-slide cosine cutoff" , "0.28" , None ),
1161+ ("align" , "PRIOR_SIGMA" , "Temporal prior σ" , "5" , None ),
1162+ ("generate" , "NOTE_LANGUAGE" , "Note language" , "en" , [
1163+ ("English" , "en" ),
1164+ ("Chinese (中文)" , "zh" ),
1165+ ]),
1166+ ("generate" , "NOTE_MODEL" , "Note generation LLM" , "gpt-5.1" , [
1167+ # OpenAI
1168+ ("gpt-5.1" , "gpt-5.1" ),
1169+ ("gpt-5.2" , "gpt-5.2" ),
1170+ ("gpt-5.3" , "gpt-5.3" ),
1171+ ("gpt-5.4" , "gpt-5.4" ),
1172+ ("gpt-4.1" , "gpt-4.1" ),
1173+ ("gpt-4.1-mini" , "gpt-4.1-mini" ),
1174+ ("o3" , "o3" ),
1175+ ("o1" , "o1" ),
1176+ # Gemini
1177+ ("Gemini 2.5 Pro" , "gemini-2.5-pro" ),
1178+ ("Gemini 2.5 Flash" , "gemini-2.5-flash" ),
1179+ ("Gemini 2.0 Flash" , "gemini-2.0-flash" ),
1180+ ("Gemini 1.5 Pro" , "gemini-1.5-pro" ),
1181+ # Anthropic
1182+ ("Claude Opus 4.6" , "claude-opus-4-6" ),
1183+ ("Claude Sonnet 4.6" , "claude-sonnet-4-6" ),
1184+ ("Claude Sonnet 4.5" , "claude-sonnet-4-5" ),
1185+ ("Claude Sonnet 3.5" , "claude-3-5-sonnet-20241022" ),
1186+ ("Claude Haiku 4.5" , "claude-haiku-4-5-20251001" ),
1187+ ]),
1188+ ("generate" , "VERIFY_MODEL" , "Verification LLM" , "gpt-4.1-mini" , [
1189+ # OpenAI
1190+ ("gpt-4.1-mini" , "gpt-4.1-mini" ),
1191+ ("gpt-4.1" , "gpt-4.1" ),
1192+ ("gpt-5.1" , "gpt-5.1" ),
1193+ # Gemini
1194+ ("Gemini 2.5 Flash" , "gemini-2.5-flash" ),
1195+ ("Gemini 2.0 Flash" , "gemini-2.0-flash" ),
1196+ # Anthropic
1197+ ("Claude Haiku 4.5" , "claude-haiku-4-5-20251001" ),
1198+ ("Claude Sonnet 3.5" , "claude-3-5-sonnet-20241022" ),
1199+ ("Claude Sonnet 4.5" , "claude-sonnet-4-5" ),
1200+ ]),
1201+ ("generate" , "DETAIL_LEVEL" , "Default detail level" , "8" , None ),
1202+ ("generate" , "CHAPTER_SIZE" , "Slides per GPT call" , "15" , None ),
1203+ ("generate" , "QUALITY_TARGET" , "Self-score target" , "8.0" , None ),
10721204 ]
10731205
1074- def _const_row (script : str , name : str , desc : str , default : str ) -> ft .Row :
1075- cur = _read_constant (script , name )
1076- tf = ft .TextField (
1077- value = cur , expand = True , dense = True ,
1078- bgcolor = C_OUTPUT_BG , border_color = _SCRIPT_COLORS .get (script , C_PRIMARY ),
1079- text_size = 12 , content_padding = ft .padding .symmetric (horizontal = 8 , vertical = 6 ),
1080- )
1206+ def _const_row (script : str , name : str , desc : str , default : str ,
1207+ options : list [tuple [str , str ]] | None = None ) -> ft .Row :
1208+ cur = _read_constant (script , name )
1209+ accent = _SCRIPT_COLORS .get (script , C_PRIMARY )
1210+
1211+ if options :
1212+ ctrl = ft .Dropdown (
1213+ value = cur if any (v == cur for _ , v in options ) else default ,
1214+ options = [ft .dropdown .Option (key = val , text = label ) for label , val in options ],
1215+ expand = True , dense = True ,
1216+ bgcolor = C_OUTPUT_BG , border_color = accent , text_size = 12 ,
1217+ content_padding = ft .padding .symmetric (horizontal = 8 , vertical = 4 ),
1218+ )
1219+
1220+ def _save (_ , c = ctrl , s = script , n = name ):
1221+ if _write_constant (s , n , c .value or default ):
1222+ _snack (f"{ n } saved." )
1223+ else :
1224+ _snack (f"Failed to save { n } ." , ok = False )
10811225
1082- def _save (_ ):
1083- if _write_constant (script , name , tf .value .strip ()):
1084- _snack (f"{ name } saved." )
1085- else :
1086- _snack (f"Failed to save { name } ." , ok = False )
1226+ def _reset (_ , c = ctrl , s = script , n = name , d = default ):
1227+ c .value = d
1228+ c .update ()
1229+ if _write_constant (s , n , d ):
1230+ _snack (f"{ n } reset to default." )
1231+ else :
1232+ _snack (f"Failed to reset { n } ." , ok = False )
1233+
1234+ else :
1235+ ctrl = ft .TextField (
1236+ value = cur , expand = True , dense = True ,
1237+ bgcolor = C_OUTPUT_BG , border_color = accent ,
1238+ text_size = 12 , content_padding = ft .padding .symmetric (horizontal = 8 , vertical = 6 ),
1239+ )
10871240
1088- def _reset (_ ):
1089- tf .value = default
1090- tf .update ()
1091- if _write_constant (script , name , default ):
1092- _snack (f"{ name } reset to default ({ default } )." )
1093- else :
1094- _snack (f"Failed to reset { name } ." , ok = False )
1241+ def _save (_ , c = ctrl , s = script , n = name ):
1242+ if _write_constant (s , n , c .value .strip ()):
1243+ _snack (f"{ n } saved." )
1244+ else :
1245+ _snack (f"Failed to save { n } ." , ok = False )
1246+
1247+ def _reset (_ , c = ctrl , s = script , n = name , d = default ):
1248+ c .value = d
1249+ c .update ()
1250+ if _write_constant (s , n , d ):
1251+ _snack (f"{ n } reset to default ({ d } )." )
1252+ else :
1253+ _snack (f"Failed to reset { n } ." , ok = False )
10951254
10961255 return ft .Row (controls = [
10971256 ft .Container (
1098- ft .Text (script , size = 10 , color = _SCRIPT_COLORS .get (script , C_PRIMARY ),
1099- weight = ft .FontWeight .BOLD ),
1257+ ft .Text (script , size = 10 , color = accent , weight = ft .FontWeight .BOLD ),
11001258 width = 74 , padding = ft .padding .symmetric (horizontal = 4 , vertical = 2 ),
11011259 border_radius = 4 ,
1102- bgcolor = ft .Colors .with_opacity (0.12 , _SCRIPT_COLORS . get ( script , C_PRIMARY ) ),
1260+ bgcolor = ft .Colors .with_opacity (0.12 , accent ),
11031261 ),
11041262 ft .Text (desc , size = 12 , color = ft .Colors .WHITE , width = 190 ),
11051263 ft .Text (name , size = 11 , color = ft .Colors .with_opacity (0.5 , ft .Colors .WHITE ),
11061264 width = 160 , italic = True ),
1107- tf ,
1265+ ctrl ,
11081266 ft .FilledButton (
11091267 "Save" , icon = ft .Icons .SAVE_OUTLINED , on_click = _save ,
11101268 style = ft .ButtonStyle (bgcolor = C_PRIMARY , color = ft .Colors .BLACK ),
@@ -1113,7 +1271,7 @@ def _reset(_):
11131271 "Default" , on_click = _reset ,
11141272 style = ft .ButtonStyle (side = ft .BorderSide (1 , C_SECONDARY ),
11151273 color = C_SECONDARY ),
1116- tooltip = f"Reset to : { default } " ,
1274+ tooltip = f"Default : { default } " ,
11171275 ),
11181276 ], spacing = 8 )
11191277
@@ -1132,6 +1290,7 @@ def _reset(_):
11321290 controls = [
11331291 ft .Column (controls = [
11341292 _section_title ("Settings" , ft .Icons .SETTINGS_OUTLINED ),
1293+ conn_card ,
11351294 keys_card ,
11361295 const_card ,
11371296 ], spacing = 12 , scroll = ft .ScrollMode .AUTO , expand = True ),
0 commit comments