"""smolcode — CLI-parity web UI over the Rust engine.""" from __future__ import annotations import json import os from dataclasses import dataclass, field from pathlib import Path import gradio as gr from engine import Router, load_preset from engine.config import ( Preset, Tier, is_specialty_model, parse_size_b, specialist_sizes, ) from engine.branding import SMOLCODE_CSS from engine.gradio_shell import ( AppSessionState, SlashResult, UiSettings, dispatch_slash, parse_input, ) from engine.preflight import list_models from engine.router import RouteResult from engine.rust_session import ( RustSession, apply_settings, get_session_chat, git_status, list_background_jobs, load_rust_config, parse_session_label, session_choices, workspace_paths, AUTOCOMPLETE_FILE_LIMIT, UI_FILE_LIMIT, ) from engine.trace import build_trace, save_trace from engine.themes import theme_at from engine.web_tui import ( Transcript, agent_choices, cycle_agent, cycle_mode, cycle_model, cycle_think, header_bar_html, help_overlay_html, host_from_url, ingest_agent_event, parse_git_header, render_picker_html, render_sidebar_html, shell_theme_html, slash_commands, status_bar_html, theme_picker_items, whichkey_overlay_html, ) PRESET = load_preset() _JS_HEAD = (Path(__file__).parent / "static" / "web_tui.js").read_text() @dataclass class WebUiState: sidebar_visible: bool = True sidebar_view: str = "files" sidebar_sel: int = 0 theme_idx: int = 0 overlay: str = "" picker_kind: str = "" picker_items: list[str] = field(default_factory=list) picker_sel: int = 0 file_total: int = 0 # Blocking startup model pick: true until the user chooses from the modal. needs_model_pick: bool = True def _normalize_paths(files: list[str] | dict[str, str] | None) -> list[str]: if not files: return [] if isinstance(files, dict): paths = sorted(files.keys()) else: paths = sorted(files) return paths[:UI_FILE_LIMIT] def _cfg() -> dict: return load_rust_config() def _ensure_rust(app_state: AppSessionState, settings: UiSettings) -> RustSession: if app_state.rust is None: app_state.rust = RustSession( workspace=settings.workspace, agent=settings.agent, yolo=settings.yolo, model=_pinned_model(settings.model), # None for Auto -> router sets it base_url=_cfg().get("base_url"), approval_handler=app_state.approval.ask, ) apply_settings(app_state.rust, settings) return app_state.rust # --- curated model picker (Auto-first, <=32B, specialty fine-tunes collapsed) ------- # Each row is (label, model, think). model "auto"/"auto:" are router pseudo-tags # interpreted by engine/router.py + rust_session.apply_settings; think "off" means the # router derives the level. _AUTO_ENTRIES: list[tuple[str, str, str]] = [ ("Auto", "auto", "off"), ("Auto · think low", "auto", "low"), ("Auto · think high", "auto", "high"), ("Auto · think xtra", "auto", "xtra"), ] def _model_entries() -> list[tuple[str, str, str]]: """All picker rows: Auto options, one Auto· per served specialist size, then generic concrete models filtered to <=32B with the per-specialty fine-tunes hidden.""" entries = list(_AUTO_ENTRIES) for sz in specialist_sizes(PRESET): entries.append((f"Auto · {sz.upper()}", f"auto:{sz}", "off")) seen: set[str] = set() base = [t.model for t in PRESET.tiers if t.model] api = list_models(_cfg().get("base_url", PRESET.base_url)) for m in api + base: if not m or m in seen or is_specialty_model(m) or parse_size_b(m) > 32: continue seen.add(m) entries.append((m, m, "off")) return entries def _model_labels() -> list[str]: return [lbl for lbl, _m, _t in _model_entries()] def _label_to_selection(label: str) -> tuple[str, str] | None: """(model, think) for a picker label, or None if unknown.""" for lbl, m, t in _model_entries(): if lbl == label: return m, t return None def _model_sel_index(settings: UiSettings) -> int: """Row index matching the current (model, think); falls back to 0 (Auto).""" entries = _model_entries() cur_m = settings.model or "auto" cur_t = settings.think or "off" for i, (_l, m, t) in enumerate(entries): # exact (model, think) wins if m == cur_m and t == cur_t: return i for i, (_l, m, _t) in enumerate(entries): # else first model match if m == cur_m: return i return 0 def _selection_label(settings: UiSettings) -> str: """Friendly label for the current selection (model chip in header/status).""" entries = _model_entries() return entries[_model_sel_index(settings)][0] if entries else "Auto" def _pinned_model(model_sel: str | None) -> str | None: """The concrete model tag to pin, or None for Auto/Auto·size (router-driven).""" m = model_sel or "" return None if (not m or m == "auto" or m.startswith("auto:")) else m def _effective_preset(model_sel: str | None): """(preset, size_floor) for a picker selection. 'auto' -> matrix preset (router picks size); 'auto:' -> matrix + start pinned to that size (still escalates); '' -> single-tier preset (pinned, no escalation). """ sel = model_sel or "auto" if sel == "auto": return PRESET, None if sel.startswith("auto:"): return PRESET, (sel.split(":", 1)[1] or None) return ( Preset(key=PRESET.key, base_url=PRESET.base_url, api_key=PRESET.api_key, tiers=[Tier("custom", sel)]), None, ) def _picker_items(kind: str, settings: UiSettings) -> list[str]: if kind == "models": return _model_labels() if kind == "themes": return theme_picker_items() if kind == "agents": return agent_choices() if kind == "sessions": return session_choices() return [] def _picker_sel_for(kind: str, settings: UiSettings, ui: WebUiState, items: list[str]) -> int: if not items: return 0 if kind == "models": return _model_sel_index(settings) if kind == "themes": name = theme_at(ui.theme_idx).name return items.index(name) if name in items else 0 if kind == "agents": cur = settings.agent if settings.mode != "plan" else "plan" return items.index(cur) if cur in items else 0 return 0 def _header(settings: UiSettings, ui: WebUiState) -> str: git = git_status(settings.workspace) branch, dirty = parse_git_header(git) return header_bar_html( git_branch=branch, git_dirty=dirty, model=_selection_label(settings), host=host_from_url(_cfg().get("base_url", "")), theme=theme_at(ui.theme_idx).name, ) def _status(settings: UiSettings, app_state: AppSessionState, *, running: bool = False) -> str: title = f"session {app_state.rust.session_id[:8]}" if app_state.rust else "new session" return status_bar_html( settings, session_title=title, model=_selection_label(settings), running=running, ) def _sidebar_html(ui: WebUiState, settings: UiSettings, files: list[str], app_state: AppSessionState) -> str: sid = app_state.rust.session_id if app_state.rust else "(none)" return render_sidebar_html( view=ui.sidebar_view, files=files, selected=ui.sidebar_sel, session_id=sid, agent=settings.agent, file_total=ui.file_total or len(files), ) def _overlay_html(ui: WebUiState) -> str: if ui.overlay == "help": return f'
{help_overlay_html()}
' if ui.overlay == "whichkey": return f'
{whichkey_overlay_html()}
' if ui.overlay == "picker" and ui.picker_kind: panel = render_picker_html( ui.picker_kind, ui.picker_items, ui.picker_sel, title=ui.picker_kind, ) return f'
{panel}
' return "" def _js_boot_lines(settings: UiSettings, files: list[str]) -> str: cmds = slash_commands(settings.workspace) paths = sorted(files)[:AUTOCOMPLETE_FILE_LIMIT] return ( f"window.__smolcode_workspace={json.dumps(settings.workspace)};" f"window.__smolcode_commands={json.dumps(cmds)};" f"window.__smolcode_files={json.dumps(paths)};" ) def _embed_js(settings: UiSettings, files: list[str]) -> str: return f"" def _outputs( transcript: Transcript, app_state: AppSessionState, settings: UiSettings, ui: WebUiState, files: list[str], *, running: bool = False, trace_path: str | None = None, ): overlay_val = _overlay_html(ui) return ( transcript.render_html(running=running), _header(settings, ui), _status(settings, app_state, running=running), gr.update(value=_sidebar_html(ui, settings, files, app_state), visible=ui.sidebar_visible), gr.update(value=overlay_val, visible=bool(overlay_val)), shell_theme_html(ui.theme_idx), gr.update(visible=bool(app_state.approval.pending_desc)), app_state.approval.pending_desc or "", files, trace_path, app_state, settings, ui, transcript, "", # clear editor ) def _apply_slash_ui(sr: SlashResult, settings: UiSettings, ui: WebUiState, transcript: Transcript): if sr.cycle_mode: settings.mode = cycle_mode(settings.mode) transcript.append_info(f"mode → {settings.mode}") if sr.cycle_think: settings.think = cycle_think(settings.think) transcript.append_info(f"think → {settings.think}") if sr.set_think: settings.think = sr.set_think transcript.append_info(f"think → {settings.think}") if sr.toggle_sidebar: ui.sidebar_visible = not ui.sidebar_visible if sr.toggle_sidebar_view: ui.sidebar_view = "stats" if ui.sidebar_view == "files" else "files" if sr.show_help: ui.overlay = "help" if sr.show_whichkey: ui.overlay = "whichkey" if sr.open_picker: ui.overlay = "picker" ui.picker_kind = sr.open_picker ui.picker_items = _picker_items(sr.open_picker, settings) ui.picker_sel = _picker_sel_for(sr.open_picker, settings, ui, ui.picker_items) transcript.append_info(f"picker → {sr.open_picker}") async def _run_agent_turn( task: str, transcript: Transcript, app_state: AppSessionState, settings: UiSettings, ui: WebUiState, files: list[str], ): # Blocking model pick: refuse to run until the user has chosen from the modal. if ui.needs_model_pick: ui.overlay = "picker" ui.picker_kind = "models" ui.picker_items = _model_labels() ui.picker_sel = _model_sel_index(settings) transcript.append_info("pick a model to start — Auto is recommended") yield _outputs(transcript, app_state, settings, ui, files) return rust = _ensure_rust(app_state, settings) rust.clear_cancel() preset, size_floor = _effective_preset(settings.model) router = Router( preset=preset, approval_handler=app_state.approval.ask, workspace_dir=settings.workspace, think=settings.think, yolo=settings.yolo, agent=settings.agent, size_floor=size_floor, ) ladder, start, _think = router._route(task) # real routing for the badge transcript.append_user(task) transcript.append_info(f"routed to {ladder.tiers[start].name}") ui.overlay = "" yield _outputs(transcript, app_state, settings, ui, files, running=True) result: RouteResult | None = None async for frame in router.run_live(task, rust_session=rust): if frame.raw_event: ingest_agent_event(transcript, frame.raw_event) if frame.files: files = _normalize_paths(frame.files) if frame.done and isinstance(frame.result, RouteResult): result = frame.result if rust.cancelled: transcript.append_error("interrupted") yield _outputs(transcript, app_state, settings, ui, files, running=not frame.done) trace_path = None if result and result.agent and not rust.cancelled: app_state.bg_jobs = list_background_jobs() rust.save() try: trace_path = str(save_trace(build_trace( result.agent, task, result.final, preset=PRESET.key, model=result.tier_model, ))) except Exception: pass yield _outputs(transcript, app_state, settings, ui, files, trace_path=trace_path) async def respond( message: str, transcript: Transcript, app_state: AppSessionState, settings: UiSettings, ui: WebUiState, files: list[str], ): message = (message or "").strip() app_state.settings = settings if not message: yield _outputs(transcript, app_state, settings, ui, files) return _task, slash, shell_cmd = parse_input( message, workspace_files=files, workspace=settings.workspace, rust=app_state.rust, ) if shell_cmd: rust = _ensure_rust(app_state, settings) out = rust.run_shell(shell_cmd) transcript.append_user(f"!{shell_cmd}") transcript.append_info(out) yield _outputs(transcript, app_state, settings, ui, files) return if slash: if slash.startswith("/search "): q = slash.split(maxsplit=1)[1] hits = transcript.search(q) transcript.append_user(slash) transcript.append_info("\n".join(hits) if hits else f"no matches for '{q}'") yield _outputs(transcript, app_state, settings, ui, files) return sr = dispatch_slash(slash, app_state) _apply_slash_ui(sr, settings, ui, transcript) if sr.clear_chat: transcript.clear() if sr.reply: transcript.append_user(slash) plain = sr.reply.replace("**", "").replace("`", "") transcript.append_info(plain) if sr.queued_task: async for out in _run_agent_turn(sr.queued_task, transcript, app_state, settings, ui, files): yield out return yield _outputs(transcript, app_state, settings, ui, files, trace_path=sr.download_path) return async for out in _run_agent_turn(_task, transcript, app_state, settings, ui, files): yield out def on_interrupt(app_state: AppSessionState): if app_state.rust: app_state.rust.request_cancel() return app_state def on_clear(transcript: Transcript, ui: WebUiState): transcript.clear() ui.overlay = "" ui.picker_kind = "" ui.picker_items = [] ui.picker_sel = 0 return transcript, ui, "" def on_close_overlay(ui: WebUiState): ui.overlay = "" ui.picker_kind = "" ui.picker_items = [] ui.picker_sel = 0 return ui, gr.update(value="", visible=False) def on_open_picker(kind: str, ui: WebUiState, settings: UiSettings): ui.overlay = "picker" ui.picker_kind = kind ui.picker_items = _picker_items(kind, settings) ui.picker_sel = _picker_sel_for(kind, settings, ui, ui.picker_items) val = _overlay_html(ui) return ui, gr.update(value=val, visible=True) def on_picker_nav(delta: int, ui: WebUiState): if ui.picker_items: ui.picker_sel = max(0, min(len(ui.picker_items) - 1, ui.picker_sel + delta)) val = _overlay_html(ui) return ui, gr.update(value=val, visible=bool(val)) def on_picker_select( pick_idx: str, ui: WebUiState, settings: UiSettings, app_state: AppSessionState, transcript: Transcript, files: list[str], ): try: idx = int(pick_idx) if pick_idx else ui.picker_sel except ValueError: idx = ui.picker_sel kind = ui.picker_kind items = ui.picker_items if items: idx = max(0, min(len(items) - 1, idx)) item = items[idx] if kind == "models": sel = _label_to_selection(item) if sel: settings.model, settings.think = sel ui.needs_model_pick = False transcript.append_info(f"model → {item}") elif kind == "themes": if item in theme_names(): ui.theme_idx = theme_names().index(item) transcript.append_info(f"theme → {item}") elif kind == "agents": if settings.mode != "plan": settings.agent = item transcript.append_info(f"agent → {item}") elif kind == "sessions": sid = parse_session_label(item) if sid: rust = RustSession(workspace=settings.workspace, agent=settings.agent, yolo=settings.yolo) if rust.load_session(sid): app_state.rust = rust transcript.clear() transcript.from_stored_chat(get_session_chat(sid)) transcript.append_info(f"loaded session {sid[:8]}") ui.overlay = "" ui.picker_kind = "" ui.picker_items = [] ui.picker_sel = 0 overlay_val = _overlay_html(ui) return ( transcript.render_html(), _header(settings, ui), _status(settings, app_state), gr.update(value=overlay_val, visible=False), shell_theme_html(ui.theme_idx), settings, ui, transcript, app_state, ) def _cycle_outputs( settings: UiSettings, ui: WebUiState, app_state: AppSessionState, transcript: Transcript, ): return ( settings, transcript, transcript.render_html(), _header(settings, ui), _status(settings, app_state), shell_theme_html(ui.theme_idx), ) def on_toggle_sidebar(ui: WebUiState, settings: UiSettings, files: list[str], app_state: AppSessionState): ui.sidebar_visible = not ui.sidebar_visible return ui, gr.update( value=_sidebar_html(ui, settings, files, app_state), visible=ui.sidebar_visible, ) def on_toggle_sidebar_view( ui: WebUiState, settings: UiSettings, files: list[str], app_state: AppSessionState, ): ui.sidebar_view = "stats" if ui.sidebar_view == "files" else "files" return ui, gr.update(value=_sidebar_html(ui, settings, files, app_state)) def on_load(settings: UiSettings, app_state: AppSessionState, ui: WebUiState): paths, total = workspace_paths(settings.workspace) ui.file_total = total overlay_val = "" if ui.needs_model_pick: # blocking startup model picker ui.overlay = "picker" ui.picker_kind = "models" ui.picker_items = _model_labels() ui.picker_sel = _model_sel_index(settings) overlay_val = _overlay_html(ui) return ( _sidebar_html(ui, settings, paths, app_state), paths, _embed_js(settings, paths), gr.update(choices=session_choices()), gr.update(value=overlay_val, visible=bool(overlay_val)), ui, ) def on_cycle_mode(settings: UiSettings, ui: WebUiState, app_state: AppSessionState, transcript: Transcript): settings.mode = cycle_mode(settings.mode) if settings.mode == "plan": settings.agent = "plan" elif settings.agent == "plan": settings.agent = "build" settings.yolo = settings.mode == "auto" transcript.append_info(f"mode → {settings.mode}") return _cycle_outputs(settings, ui, app_state, transcript) def on_cycle_agent(settings: UiSettings, ui: WebUiState, app_state: AppSessionState, transcript: Transcript): if settings.mode != "plan": settings.agent = cycle_agent(settings.agent) transcript.append_info(f"agent → {settings.agent}") return _cycle_outputs(settings, ui, app_state, transcript) def on_cycle_model(settings: UiSettings, ui: WebUiState, app_state: AppSessionState, transcript: Transcript): labels = _model_labels() nxt = cycle_model(labels, _selection_label(settings)) sel = _label_to_selection(nxt) if sel: settings.model, settings.think = sel ui.needs_model_pick = False transcript.append_info(f"model → {nxt}") return _cycle_outputs(settings, ui, app_state, transcript) def on_cycle_think(settings: UiSettings, ui: WebUiState, app_state: AppSessionState, transcript: Transcript): settings.think = cycle_think(settings.think) transcript.append_info(f"think → {settings.think}") return _cycle_outputs(settings, ui, app_state, transcript) def on_help(ui: WebUiState): ui.overlay = "help" val = _overlay_html(ui) return ui, gr.update(value=val, visible=True) def on_whichkey(ui: WebUiState): ui.overlay = "whichkey" val = _overlay_html(ui) return ui, gr.update(value=val, visible=True) def on_new_session(): settings = UiSettings(workspace=os.environ.get("SMALLCODE_WORKSPACE", "."), model="auto") ui = WebUiState() # needs_model_pick defaults True -> reopen the blocking picker ui.overlay = "picker" ui.picker_kind = "models" ui.picker_items = _model_labels() ui.picker_sel = _model_sel_index(settings) return ( Transcript(), AppSessionState(), settings, ui, [], None, gr.update(value=_overlay_html(ui), visible=True), ) def on_approval(yes: bool, app_state: AppSessionState): if app_state: app_state.approval.approve(yes) return gr.update(visible=False), "" def on_session_pick(label: str, app_state: AppSessionState, settings: UiSettings): sid = parse_session_label(label or "") if not sid: return Transcript(), app_state rust = RustSession(workspace=settings.workspace, agent=settings.agent, yolo=settings.yolo) if not rust.load_session(sid): return Transcript(), app_state app_state.rust = rust t = Transcript() t.from_stored_chat(get_session_chat(sid)) return t, app_state def build() -> gr.Blocks: default_ws = os.environ.get("SMALLCODE_WORKSPACE", ".") # Default selection is Auto (router-driven); the blocking startup modal lets the # user confirm or change it before the first task. settings = UiSettings(workspace=default_ws, model="auto") with gr.Blocks( css=SMOLCODE_CSS, title="smolcode", theme=gr.themes.Soft(primary_hue="purple", neutral_hue="slate"), head=f"", fill_height=True, fill_width=True, ) as demo: transcript = gr.State(Transcript()) app_state = gr.State(AppSessionState(settings=settings)) settings_state = gr.State(settings) ui_state = gr.State(WebUiState()) files_state = gr.State([]) trace_state = gr.State(None) with gr.Column(elem_classes="sc-tui-shell"): header = gr.HTML(_header(settings, WebUiState())) shell_theme = gr.HTML(shell_theme_html(0), visible=False) with gr.Row(elem_classes="sc-main-row"): sidebar = gr.HTML( _sidebar_html(WebUiState(), settings, [], AppSessionState()), elem_classes="sc-sidebar", visible=True, ) with gr.Column(elem_classes="sc-main-col"): transcript_view = gr.HTML(Transcript().render_html()) with gr.Group(elem_classes="sc-editor-wrap"): gr.HTML( '
' "Enter run · Shift+Enter newline · / commands · ctrl+x leader" "
" ) editor = gr.Textbox( placeholder="type a task…", lines=5, max_lines=8, show_label=False, elem_id="sc-editor", interactive=True, autofocus=True, ) with gr.Group(visible=False) as approval_box: approval_desc = gr.Markdown("", elem_classes="sc-approval") with gr.Row(): gr.Button("Approve", variant="primary").click( lambda s: on_approval(True, s), app_state, [approval_box, approval_desc]) gr.Button("Deny").click( lambda s: on_approval(False, s), app_state, [approval_box, approval_desc]) status = gr.HTML(_status(settings, AppSessionState()), elem_classes="sc-status-wrap") overlay = gr.HTML("", visible=False) js_boot = gr.HTML(_embed_js(settings, []), elem_classes=["sc-hidden-controls"]) # Off-screen controls (visible=True so Gradio mounts them for JS shortcuts). _hid = ["sc-hidden-btn"] with gr.Row(elem_classes="sc-hidden-controls"): btn_submit = gr.Button("submit", elem_id="sc-submit", elem_classes=_hid) btn_clear = gr.Button("clear", elem_id="sc-clear", elem_classes=_hid) btn_interrupt = gr.Button("interrupt", elem_id="sc-interrupt", elem_classes=_hid) btn_toggle_sidebar = gr.Button("sidebar", elem_id="sc-toggle-sidebar", elem_classes=_hid) btn_toggle_view = gr.Button("view", elem_id="sc-toggle-sidebar-view", elem_classes=_hid) btn_cycle_mode = gr.Button("mode", elem_id="sc-cycle-mode", elem_classes=_hid) btn_cycle_agent = gr.Button("agent", elem_id="sc-cycle-agent", elem_classes=_hid) btn_cycle_model = gr.Button("model", elem_id="sc-cycle-model", elem_classes=_hid) btn_cycle_think = gr.Button("think", elem_id="sc-cycle-think", elem_classes=_hid) btn_help = gr.Button("help", elem_id="sc-help", elem_classes=_hid) btn_whichkey = gr.Button("wk", elem_id="sc-whichkey", elem_classes=_hid) btn_close = gr.Button("close", elem_id="sc-close-overlay", elem_classes=_hid) btn_new = gr.Button("new", elem_id="sc-new-session", elem_classes=_hid) btn_open_models = gr.Button("models", elem_id="sc-open-picker-models", elem_classes=_hid) btn_open_themes = gr.Button("themes", elem_id="sc-open-picker-themes", elem_classes=_hid) btn_open_agents = gr.Button("agents", elem_id="sc-open-picker-agents", elem_classes=_hid) btn_open_sessions = gr.Button("sessions", elem_id="sc-open-picker-sessions", elem_classes=_hid) btn_picker_up = gr.Button("up", elem_id="sc-picker-up", elem_classes=_hid) btn_picker_down = gr.Button("down", elem_id="sc-picker-down", elem_classes=_hid) btn_picker_confirm = gr.Button("confirm", elem_id="sc-picker-confirm", elem_classes=_hid) picker_pick = gr.Textbox("", elem_id="sc-picker-pick", elem_classes=_hid, show_label=False) session_pick = gr.Dropdown(choices=session_choices(), label="session", elem_id="sc-pick-sessions", elem_classes=_hid) trace_dl = gr.DownloadButton("trace", elem_classes=_hid) out = [ transcript_view, header, status, sidebar, overlay, shell_theme, approval_box, approval_desc, files_state, trace_state, app_state, settings_state, ui_state, transcript, editor, ] cycle_out = [ settings_state, transcript, transcript_view, header, status, shell_theme, ] picker_out = [ transcript_view, header, status, overlay, shell_theme, settings_state, ui_state, transcript, app_state, ] respond_in = [editor, transcript, app_state, settings_state, ui_state, files_state] btn_submit.click(respond, respond_in, out).then(lambda p: p, trace_state, trace_dl) editor.submit(respond, respond_in, out).then(lambda p: p, trace_state, trace_dl) btn_clear.click(on_clear, [transcript, ui_state], [transcript, ui_state, editor]) btn_interrupt.click(on_interrupt, app_state, app_state) btn_toggle_sidebar.click( on_toggle_sidebar, [ui_state, settings_state, files_state, app_state], [ui_state, sidebar]) btn_toggle_view.click( on_toggle_sidebar_view, [ui_state, settings_state, files_state, app_state], [ui_state, sidebar], ) btn_cycle_mode.click( on_cycle_mode, [settings_state, ui_state, app_state, transcript], cycle_out) btn_cycle_agent.click( on_cycle_agent, [settings_state, ui_state, app_state, transcript], cycle_out) btn_cycle_model.click( on_cycle_model, [settings_state, ui_state, app_state, transcript], cycle_out) btn_cycle_think.click( on_cycle_think, [settings_state, ui_state, app_state, transcript], cycle_out) btn_help.click(on_help, ui_state, [ui_state, overlay]) btn_whichkey.click(on_whichkey, ui_state, [ui_state, overlay]) btn_close.click(on_close_overlay, ui_state, [ui_state, overlay]) btn_new.click(on_new_session, None, [transcript, app_state, settings_state, ui_state, files_state, trace_state, overlay]) btn_open_models.click(lambda ui, s: on_open_picker("models", ui, s), [ui_state, settings_state], [ui_state, overlay]) btn_open_themes.click(lambda ui, s: on_open_picker("themes", ui, s), [ui_state, settings_state], [ui_state, overlay]) btn_open_agents.click(lambda ui, s: on_open_picker("agents", ui, s), [ui_state, settings_state], [ui_state, overlay]) btn_open_sessions.click(lambda ui, s: on_open_picker("sessions", ui, s), [ui_state, settings_state], [ui_state, overlay]) btn_picker_up.click(lambda ui: on_picker_nav(-1, ui), ui_state, [ui_state, overlay]) btn_picker_down.click(lambda ui: on_picker_nav(1, ui), ui_state, [ui_state, overlay]) btn_picker_confirm.click( on_picker_select, [picker_pick, ui_state, settings_state, app_state, transcript, files_state], picker_out, ) session_pick.change(on_session_pick, [session_pick, app_state, settings_state], [transcript, app_state]) demo.load( on_load, [settings_state, app_state, ui_state], [sidebar, files_state, js_boot, session_pick, overlay, ui_state], ) return demo if __name__ == "__main__": from engine.preflight import preflight preflight(PRESET) host = os.environ.get("SMOLCODE_HOST", "127.0.0.1") os.environ["GRADIO_SERVER_PORT"] = os.environ.get("SMOLCODE_PORT", "7860") os.environ["GRADIO_SERVER_NAME"] = host # server_port=None lets Gradio scan GRADIO_SERVER_PORT..+99 (skips ghost 7860-7862). # ssr_mode=False: SSR (default on HF when Node is present) renders before the # custom web_tui.js applies the fixed-height layout, leaving the file sidebar # uncapped (grows forever, hides the bottom bar/model picker). Client-side render # applies the layout immediately. build().queue().launch(server_name=host, server_port=None, show_api=False, ssr_mode=False)