Skip to content

Commit beb6a5a

Browse files
committed
feat: add model selector dropdown and improved setup intro
- Add model dropdown in status bar to switch AI models like OpenCode Zen - Fix model loading with fallback from /config/providers to /provider endpoint - Add intro text in setup screen explaining where to get bot token and user ID - Add selected_model tracking for use with bot handler - Rebuild executable with all latest fixes
1 parent 27255c6 commit beb6a5a

1 file changed

Lines changed: 75 additions & 12 deletions

File tree

  • src/opencode_telegram_bot

src/opencode_telegram_bot/gui.py

Lines changed: 75 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -165,13 +165,17 @@ def _build(self) -> None:
165165
self.grid_columnconfigure(0, weight=1)
166166
self.grid_rowconfigure(4, weight=1)
167167

168-
header = ctk.CTkFrame(self, fg_color=COLORS["bg"], height=120)
168+
header = ctk.CTkFrame(self, fg_color=COLORS["bg"], height=160)
169169
header.grid(row=0, column=0, sticky="ew", padx=0, pady=0)
170170
header.grid_columnconfigure(0, weight=1)
171171
header.grid_propagate(False)
172172

173-
_CTkLabelTitle(header, text="Setup").grid(row=0, column=0, padx=(40, 40), pady=(30, 2), sticky="w")
174-
_CTkLabelSubtitle(header, text="Configure your OpenCode Telegram Bot").grid(row=1, column=0, padx=(40, 40), pady=(0, 10), sticky="w")
173+
_CTkLabelTitle(header, text="Setup").grid(row=0, column=0, padx=(40, 40), pady=(20, 2), sticky="w")
174+
_CTkLabelSubtitle(header, text="Configure your OpenCode Telegram Bot").grid(row=1, column=0, padx=(40, 40), pady=(0, 6), sticky="w")
175+
intro_text = ctk.CTkTextbox(header, height=48, corner_radius=0, border_width=0, fg_color=COLORS["bg"], text_color=COLORS["text_secondary"], font=ctk.CTkFont(size=13))
176+
intro_text.grid(row=2, column=0, padx=(40, 40), pady=(0, 8), sticky="ew")
177+
intro_text.insert("1.0", "Connect your Telegram bot to OpenCode AI. Get your bot token from @BotFather and user ID from @userinfobot. Then enter your OpenCode server details below to link them together.")
178+
intro_text.configure(state="disabled")
175179

176180
content = ctk.CTkScrollableFrame(self, fg_color=COLORS["bg"])
177181
content.grid(row=1, column=0, sticky="nsew", padx=40, pady=10)
@@ -262,6 +266,8 @@ def __init__(self, master: Any, settings: Settings) -> None:
262266
self._bot_app = None
263267
self._bot_handler = None
264268
self._alive = True
269+
self.selected_model = ""
270+
self._available_models: list[str] = []
265271

266272
self._build()
267273
self._start_status_poll()
@@ -308,7 +314,9 @@ def _build(self) -> None:
308314

309315
status_bar = ctk.CTkFrame(self, fg_color=COLORS["bg_secondary"], corner_radius=16, height=56)
310316
status_bar.grid(row=1, column=0, columnspan=2, padx=40, pady=(0, 16), sticky="ew")
311-
status_bar.grid_columnconfigure((0, 1, 2), weight=1)
317+
status_bar.grid_columnconfigure(0, weight=1)
318+
status_bar.grid_columnconfigure(1, weight=1)
319+
status_bar.grid_columnconfigure(2, weight=1)
312320
status_bar.grid_propagate(False)
313321

314322
self.server_label = _CTkLabelSmall(status_bar, text="Server: Checking...", anchor="w")
@@ -317,8 +325,32 @@ def _build(self) -> None:
317325
self.bot_label = _CTkLabelSmall(status_bar, text="Bot: Stopped", anchor="w")
318326
self.bot_label.grid(row=0, column=1, padx=10, pady=16, sticky="w")
319327

320-
self.model_label = _CTkLabelSmall(status_bar, text=f"Model: {self.settings.opencode_model_id}", anchor="w")
321-
self.model_label.grid(row=0, column=2, padx=(10, 20), pady=16, sticky="w")
328+
model_bar = ctk.CTkFrame(status_bar, fg_color=COLORS["bg_secondary"])
329+
model_bar.grid(row=0, column=2, padx=(10, 20), pady=16, sticky="w")
330+
model_bar.grid_columnconfigure(0, weight=0)
331+
332+
self.model_label = _CTkLabelSmall(model_bar, text=f"Model: {self.settings.opencode_model_id}", anchor="w")
333+
self.model_label.grid(row=0, column=0, padx=(0, 8), sticky="w")
334+
335+
self.model_var = ctk.StringVar(value="Loading...")
336+
self.model_dropdown = ctk.CTkOptionMenu(
337+
model_bar,
338+
variable=self.model_var,
339+
values=["Loading..."],
340+
command=self._on_model_selected,
341+
width=180,
342+
height=32,
343+
corner_radius=8,
344+
fg_color=COLORS["bg"],
345+
button_color=COLORS["border"],
346+
button_hover_color=COLORS["accent"],
347+
text_color=COLORS["text_primary"],
348+
dropdown_fg_color=COLORS["bg"],
349+
dropdown_text_color=COLORS["text_primary"],
350+
dropdown_hover_color=COLORS["bg_secondary"],
351+
font=ctk.CTkFont(size=12),
352+
)
353+
self.model_dropdown.grid(row=0, column=1, sticky="w")
322354

323355
ctrl_frame = ctk.CTkFrame(self, fg_color=COLORS["bg"], height=60)
324356
ctrl_frame.grid(row=2, column=0, columnspan=2, padx=40, pady=(0, 16), sticky="ew")
@@ -352,7 +384,7 @@ def _build(self) -> None:
352384
self.sessions_text = ctk.CTkTextbox(left_panel, height=150, corner_radius=12, border_width=1, border_color=COLORS["border"], fg_color=COLORS["bg_secondary"], text_color=COLORS["text_primary"], font=ctk.CTkFont(size=13))
353385
self.sessions_text.grid(row=1, column=0, sticky="ew", pady=(0, 16))
354386

355-
_CTkLabel(left_panel, text="Models", font=ctk.CTkFont(size=18, weight="bold"), anchor="w").grid(row=2, column=0, pady=(0, 8), sticky="w")
387+
_CTkLabel(left_panel, text="Available Models", font=ctk.CTkFont(size=18, weight="bold"), anchor="w").grid(row=2, column=0, pady=(0, 8), sticky="w")
356388
self.models_frame = ctk.CTkScrollableFrame(left_panel, fg_color=COLORS["bg_secondary"], corner_radius=12, height=250)
357389
self.models_frame.grid(row=3, column=0, sticky="nsew")
358390

@@ -586,7 +618,13 @@ def fetch():
586618
try:
587619
loop = asyncio.new_event_loop()
588620
asyncio.set_event_loop(loop)
589-
providers = loop.run_until_complete(self.client.get_config_providers())
621+
try:
622+
providers = loop.run_until_complete(self.client.get_config_providers())
623+
except Exception:
624+
try:
625+
providers = loop.run_until_complete(self.client.get_providers())
626+
except Exception:
627+
providers = {}
590628
loop.close()
591629
self._safe_after(0, self._render_models, providers)
592630
except Exception as exc:
@@ -603,14 +641,18 @@ def _render_models(self, providers_data: dict[str, Any]) -> None:
603641
if not isinstance(providers, list):
604642
providers = []
605643

644+
model_choices = []
645+
606646
for p in providers:
607647
pid = p.get("id", p.get("name", "unknown"))
608648
models = p.get("models", [])
609649
if not isinstance(models, list):
610650
models = []
611651
_CTkLabel(self.models_frame, text=pid, font=ctk.CTkFont(weight="bold", size=13), text_color=COLORS["text_primary"]).pack(anchor="w", padx=12, pady=(12, 4))
612-
for m in models[:8]:
652+
for m in models:
613653
mid = m.get("id", m.get("name", ""))
654+
full = f"{pid}/{mid}"
655+
model_choices.append(full)
614656
btn = ctk.CTkButton(
615657
self.models_frame,
616658
text=mid,
@@ -627,12 +669,33 @@ def _render_models(self, providers_data: dict[str, Any]) -> None:
627669
)
628670
btn.pack(anchor="w", padx=12, pady=2)
629671

630-
if not providers:
672+
if not model_choices:
631673
_CTkLabelSubtitle(self.models_frame, text="No models found. Configure providers in OpenCode.").pack(pady=20)
674+
model_choices = ["No models available"]
675+
676+
self._available_models = model_choices
677+
self.model_dropdown.configure(values=model_choices)
678+
679+
if self.selected_model and self.selected_model in model_choices:
680+
self.model_var.set(self.selected_model)
681+
elif model_choices and model_choices[0] != "No models available":
682+
self.model_var.set(model_choices[0])
683+
self.selected_model = model_choices[0]
684+
else:
685+
self.model_var.set("No models available")
686+
687+
def _on_model_selected(self, choice: str) -> None:
688+
if choice and choice != "No models available" and choice != "Loading...":
689+
self.selected_model = choice
690+
self.model_label.configure(text=f"Model: {choice}")
691+
self._append_log(f"Model switched to: {choice}")
632692

633693
def _switch_model(self, provider: str, model: str) -> None:
634-
self.model_label.configure(text=f"Model: {provider}/{model}")
635-
self._append_log(f"Model selected: {provider}/{model}")
694+
full = f"{provider}/{model}"
695+
self.selected_model = full
696+
self.model_label.configure(text=f"Model: {full}")
697+
self.model_var.set(full)
698+
self._append_log(f"Model selected: {full}")
636699

637700

638701
class App(ctk.CTk):

0 commit comments

Comments
 (0)