smolcode / app.py
seanpoyner's picture
Upload folder using huggingface_hub
daea45b verified
Raw
History Blame Contribute Delete
31.7 kB
"""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:<size>" 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·<size> 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:<size>' -> matrix + start pinned
to that size (still escalates); '<tag>' -> 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'<div class="sc-overlay"><div class="sc-overlay-panel">{help_overlay_html()}</div></div>'
if ui.overlay == "whichkey":
return f'<div class="sc-overlay"><div class="sc-overlay-panel">{whichkey_overlay_html()}</div></div>'
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'<div class="sc-overlay"><div class="sc-overlay-panel">{panel}</div></div>'
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"<script>{_js_boot_lines(settings, files)}</script>"
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"<script>{_JS_HEAD}\n{_js_boot_lines(settings, [])}</script>",
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(
'<div class="sc-editor-hint">'
"Enter run · Shift+Enter newline · / commands · ctrl+x leader"
"</div>"
)
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)