cells

The SolveIt-style cell GUI: a stack of typed cells (code / note / prompt / raw),

DaisyUI + app setup

CDN headers for DaisyUI + Tailwind, plus KatexMarkdownJS() (renders .marked elements as markdown + KaTeX) and the command-mode hotkey listener.


source

read_file_content

def read_file_content(
    file_path
):

Call self as a function.

Authentication

A ?token= query param (printed at startup) or the /login form gates every route except health-check/login itself – see _check_auth, the Beforeware wired into app below. Every code cell already runs with this process’s own OS permissions, so an unauthenticated boopiter listening on a shared network is equivalent to an open shell; boopiter launch --no_auth opts out explicitly for a fully trusted network.


source

login

def login(
    token:str=None, session:NoneType=None
):

*Token-gate entry point. GET (no token field submitted) shows the sign-in form; POST checks it against BOOPITER_TOKEN and, on match, stamps the session so _check_auth lets every other route through. The other way in is a ?token= query param on any URL – what the server prints at startup – this form is the fallback for typing the token in by hand.*

Code execution

A single IPython shell backs every code cell (like the lesson’s ex), with errors returned as text instead of raised.


source

run_code_poll

def run_code_poll(
    id:int, poll_n:int=0
)->fastcore.xml.FT | tuple:

*Poll a running code cell’s execution. While still running, returns just the small output div (re-triggering itself, with an escalating delay – see _POLL_SCHEDULE) so the code block above it never flickers. Once done, replaces the whole cell out-of-band – the primary target (#run-out-N) is about to be destroyed along with it, so the primary response body is empty.*

Cell + Notebook model

A Cell carries its type, source, optional output, and a visible flag (the eye toggle — whether the LLM sees it). Notebook is the in-memory store of cells, the composer’s selected type, and the currently selected cell (for hotkeys).


source

pending_code_cell

def pending_code_cell(
    c:Cell, text:str='', scroll:bool=False
)->FT:

*Placeholder for a code cell whose execution is still running in the background: a static code view plus a self-polling output area (_run_output_div) that swaps itself out for the real render_cell() once done. scroll=True (set only by Run All) tags the cell div so the client scrolls it into view as it starts – see boopScrollRunAll() in edit.js.*


source

run_code_cell

def run_code_cell(
    c:Cell, scroll:bool=False
)->FT:

Tell one code cell to run: stamp it with the current time, then kick off its streaming background execution. The single shared entry point for the play button, Shift-Enter, and Run All (which passes scroll=True so each cell scrolls into view as it starts). A solo run (scroll=False) cancels any in-flight or leftover Run All, so it can never chain into unrelated cells.

LLM Interaction

The whole point of the visibility toggle: context is only the visible cells. The stub proves the plumbing by reporting what it can see; swap stub_reply for a real model call later.


source

pending_prompt_cell

def pending_prompt_cell(
    prompt_id:int, text:str='', oob_swap:str=None
)->FT:

*Placeholder shown while a Prompt’s LLM reply streams in the background: a pulsing ‘Tricky…’ indicator plus whatever text has accumulated so far, shown as plain preformatted text (not markdown-rendered – mid-stream markdown is often invalid, e.g. an unclosed code fence; only the finished reply gets the full ‘.marked’ treatment). hx-trigger=load polls run_prompt_poll() every 300ms until the reply is complete. oob_swap, if given, delivers this placeholder out-of-band (see _oob()) instead of being the response’s main swap target.*


source

run_prompt_poll

def run_prompt_poll(
    id:int
)->fastcore.xml.FT | str:

Poll a streaming Prompt reply – returns the current partial text while still running (re-triggering itself), or finalizes into the real Assistant cell once done.


source

add_tool

def add_tool(
    fn:callable
)->callable:

Register fn as a tool the LLM can call on future Prompt-cell runs. Also usable as a decorator: @add_tool.


source

llm_context

def llm_context(
    nb:Notebook, cur_id:int | None=None
)->str:

*Exactly what a real model would receive: the visible cells up through cur_id (default: all), in order, with each code cell’s outputs appended (via _output_text) so ‘run this, then ask about the result’ actually works. The cur_id cell itself is always included even if toggled invisible – it’s the question being asked, and dropping it would silently send a context with no prompt at the end.*


source

stub_reply

def stub_reply(
    nb:Notebook, prompt:str
)->str:

Fake/placeholder LLM reply used when no model is available (lets you still test the GUI).


source

ensure_models

def ensure_models()->None:

Populate nb.models (info dicts, see get_model_list()) plus default picks for both models, tolerating an unreachable local LLM server. Run once at startup; refresh_models() takes over from there, so an Ollama that wasn’t running yet when boopiter started is picked up on the next brain-menu hover rather than needing a server restart.

Rendering

Each type gets a colored left border (matching the SolveIt screenshot: raw=yellow, code=blue, note=green, prompt/assistant=red). The selected cell gets a ring; hidden-from-LLM cells are dimmed. Note cells render as markdown via the .marked class (KaTeX, images, HTML).


source

Icon

def Icon(
    name:str, cls:str='size-4', filled:bool=False
)->FT:

*A heroicons SVG by name (see ICONS), inlined so stroke='currentColor' (and, if filled, fill='currentColor' too) matches the button’s text color. Thin wrapper over _svg_icon().*


source

IconBtn

def IconBtn(
    name:str, title:str, **kw
)->FT:

A small ghost-style button showing heroicon name, with a hover tooltip of title.


source

cell_toolbar

def cell_toolbar(
    c:Cell
)->FT:

The row of icon buttons (copy, export toggle, visibility, run, move, delete) shown in a cell’s header.


source

type_dropdown

def type_dropdown(
    c:Cell
)->FT:

Click the cell-type word to switch it (code/note/prompt/raw). Scoped to just this cell.


source

cell_header

def cell_header(
    c:Cell, running:bool=False
)->FT:

The top row of a cell: type dropdown, id/timestamp, and the toolbar. running=True (used by pending_code_cell() while a background execution is still in flight) adds a pulsing ‘Running…’ indicator that disappears once the cell finishes, whether it succeeded or errored. flex-wrap lets the toolbar drop to its own line on narrow (mobile) viewports instead of squeezing/overlapping.


source

cell_body

def cell_body(
    c:Cell
)->FT:
*Note/prompt/assistant render as markdown; raw is bare text. Code cells never reach here – render_cell() routes them to code_view()/code_editor(). An Assistant cell with call metadata (Cell.details) shows it in a collapsed

block above the reply.*


source

render_output_blocks

def render_output_blocks(
    blocks:list
)->list:
*Render each of a code cell’s output blocks (see Cell.output / run_code()) to its appropriate FT: an for images, raw markup for HTML/SVG, client-side-rendered markdown for text/markdown (same ‘.marked’ pipeline as note cells), pretty-printed for JSON, and a plain (ANSI-aware)

for stream/error text.*


source

code_view

def code_view(
    c:Cell
)->FT:

Static, syntax-highlighted (no live CodeMirror) view of a code cell – click to load the real editor. Keeping non-focused cells static is what makes theme switches etc. fast on notebooks with many code cells.


source

code_editor

def code_editor(
    c:Cell
)->FT:

The live CodeMirror editor for a code cell – only rendered for the cell currently being edited. Shift/Ctrl/Cmd+Enter or the play button runs. c.source never contains the ‘#| export’ pragma – see Cell.export / the bookmark toggle in cell_toolbar.


source

render_cell

def render_cell(
    c:Cell, oob:NoneType=None
)->FT:

Every cell type renders statically and opens its editor on click; only the actively-edited cell gets a live widget (CodeMirror for code, a plain textarea otherwise). oob is forwarded to _cell_outer – see there.


source

render_cell_edit

def render_cell_edit(
    c:Cell
)->FT:

Inline editor. Code cells use CodeMirror (Python highlight, no wrap); notes/raw use a textarea. Shift/Ctrl/Cmd+Enter saves.


source

render_nb

def render_nb()->FT:

Render every cell in the notebook, in order, inside the #notebook container div.

Composer

The bottom bar: type tabs, a textarea, Submit. Picking a tab sets the type server-side; Submit creates the cell (running it, if code; spawning an Assistant reply, if prompt).


source

composer

def composer(
    draft:str='', oob:bool=False
)->FT:

The bottom-of-page input bar: type tabs, a source textarea, and a Boop (submit) button.


source

render_app

def render_app(
    draft:str=''
)->FT:

The whole notebook view: all cells plus the composer, wrapped in one container div.

Routes

Composer/toolbar routes plus the command-mode routes driven by hotkeys (select, select_delta, insert, del_selected, settype_selected).


source

theme_swap

def theme_swap()->FT:

DaisyUI sun/moon swap; drives boopApplyTheme (default dark).


source

fname_display

def fname_display()->FT:

The clickable filename shown in the top bar; click to rename.


source

rename_form

def rename_form()->FT:

Swap the filename display for a text input, focused and pre-selected, to rename the notebook.


source

brain_menu

def brain_menu(
    icon_cls:str
)->FT:

Hover menu (styled like tools_menu) for picking the Standard and Reasoning models, SolveIt-style – the Reasoning picker only lists models advertising Ollama’s ‘thinking’ capability (see get_ollama_list()/ensure_models()), with an L/M/H effort control alongside it (passed through as Chat(…)(think=…) – see stream_llm_reply()). The brain icon itself doubles as a toggle: click it to switch whether the reasoning or standard model actually answers Prompt cells (nb.use_reasoning/active_model()) – its SVG strokes turn cyan (matching the ‘Tricky…’ streaming indicator’s color, our existing ‘this is thinking’ cue) while on. Re-renders itself wholesale on toggle (hx_target=self) since the button’s own color has to change along with the menu.


source

set_standard_model

def set_standard_model(
    model:str
)->FT:

*Change the Standard-model pick in the brain menu, then re-render the menu so the new pick’s checkmark shows (see _model_select). Only affects Prompt answers directly if the reasoning toggle is currently off – see nb.active_model().*


source

set_reasoning_model

def set_reasoning_model(
    model:str
)->FT:

*Change the Reasoning-model pick in the brain menu (restricted there to ‘thinking’-capable models), then re-render the menu so the new pick’s checkmark shows (see _model_select). Only affects Prompt answers directly if the reasoning toggle is currently on – see nb.active_model().*


source

toggle_reasoning

def toggle_reasoning()->FT:

The brain-icon click: flip whether the reasoning or standard model answers Prompt cells. Returns the whole re-rendered brain_menu(), since the button itself needs to pick up its new highlighted state.


source

refresh_models

def refresh_models():

Re-list the available models and re-render the brain menu if anything actually changed – wired to mouseenter on the menu itself (see brain_menu()), so starting Ollama after* boopiter no longer means restarting the server to see its models; hovering the brain icon is enough. Returns 204 No Content whenever nothing changed, which tells htmx to swap nothing at all: re-rendering an open dropdown on every hover would make the menu visibly flicker, and re-rendering it while the pointer is inside it risks disturbing the CSS :hover state keeping it open. Cheap enough to run on hover – ~8ms for the /api/tags listing, with the expensive per-model capability probes served from cache (see _ollama_caps) – and it runs alongside the CSS-driven dropdown rather than gating it, so the menu opens instantly either way. An unreachable Ollama does NOT clear the list (that’s what strict=True distinguishes: ‘Ollama is down’ vs ‘Ollama has no models’); it flips nb.ollama_ok so the existing entries render greyed and unclickable, keeping your current pick intact through a restart or a network hiccup. Picks are otherwise repaired, not reset (see _default_picks): a model that has genuinely vanished gets replaced, one you chose is left alone.*


source

set_keep_alive

def set_keep_alive(
    minutes:int=None
)->str:

Persist a new keep-alive from the spinner. Clamped at -1, the lowest value Ollama gives a distinct meaning: any negative keep_alive means ‘keep this model loaded indefinitely’, so -2 and -1 do the same thing and there’s no reason to let the box go lower. Takes effect on the next model call; nothing to re-render, hence hx-swap=none.


source

share_dismiss

def share_dismiss()->FT:

Close the share notice, emptying its slot. Doesn’t cancel anything – an in-flight publish finishes regardless; this only stops showing it.


source

share_poll

def share_poll()->fastcore.xml.FT | starlette.responses.Response:

Re-render the share notice, but only when its state has actually changed – otherwise 204 No Content, which tells htmx to swap nothing. With the notice polling on a repeating trigger, that means the element is left completely untouched between state changes, instead of being rebuilt every couple of seconds: rebuilding restarted the spinner and made the whole notice blink.


source

share_nb

def share_nb(
    vis:str='public'
)->FT:

Share the current notebook, listed on the boops index or not (see share_menu). Saves it to disk first – the render reads the file, and the published page should match what’s on screen – then publishes on a background thread, since a Quarto render plus a push takes far too long to hold an HTTP response open. Returns the notice immediately so the click visibly does something; it polls itself from there.


source

share_panel

def share_panel()->FT:

The share notice: progress while rendering and publishing, then the published URL with a button to copy it. Persistent – it stays until dismissed, because its whole job is to leave the link on screen long enough to paste somewhere.


source

share_menu

def share_menu(
    icon_cls:str
)->FT:

*The share button: a hover menu (same idiom as tools_menu/brain_menu) offering Public or Unlisted, rather than a plain button. Both publish the same page to the same URL; the choice only decides whether the site’s index links to it, and re-sharing with the other choice flips that – so this is a state toggle, not a one-way door. A hover menu rather than a modal or a native