Module: Protege::Components::IntrospectionHelper
- Included in:
- ComponentsHelper
- Defined in:
- app/helpers/protege/components/introspection_helper.rb
Overview
View helpers for the thread introspection panel — the collapsible right sidebar in the console that surfaces the agent's reasoning and tool use for the open thread. Mirrors +LayoutHelper+'s sidebar builders: small composable pieces (panel shell, section, tool card, toggle) so the view stays declarative and the panel's markup lives in one place as it grows.
Constant Summary collapse
- INTROSPECTION_PANEL_ID =
DOM id of the panel
<aside>— the toggle shows/hides this element. 'thread-introspection'
Instance Method Summary collapse
-
#introspection_feed(thread) ⇒ ActiveSupport::SafeBuffer
The live activity feed container — the single scrollable target the
IntrospectionBroadcasterappends reasoning tokens and tool-call cards into, interleaved chronologically. -
#introspection_panel { ... } ⇒ ActiveSupport::SafeBuffer
Render the panel shell: a full-height right
<aside>(mirroring the left inbox aside) with a fixed header and a scrollable body that yields its content (the activity feed). -
#introspection_reasoning(id:, content: nil) ⇒ ActiveSupport::SafeBuffer
A reasoning block — one paragraph of the model's streamed reasoning.
-
#introspection_run_marker(id:, status: :processing, detail: nil) ⇒ ActiveSupport::SafeBuffer
A run-lifecycle marker in the activity feed: a pulsing "Processing…" line while the run is in flight, or a red error when it fails.
-
#introspection_toggle ⇒ ActiveSupport::SafeBuffer
The toggle button (placed in the thread header) that shows/hides the panel.
-
#introspection_tool_card(name:, args: nil, status: :done, preview: nil, id: nil) ⇒ ActiveSupport::SafeBuffer
A single tool-call card: a status glyph, the tool name, an optional one-line args summary, and an optional response preview (done) or error (failed).
Instance Method Details
#introspection_feed(thread) ⇒ ActiveSupport::SafeBuffer
The live activity feed container — the single scrollable target the IntrospectionBroadcaster
appends reasoning tokens and tool-call cards into, interleaved chronologically. Live-only: the
introspection-empty placeholder (see tailwind/application.css) shows until the next run streams in.
Marked data-turbo-permanent so the accumulated feed survives Turbo Drive navigations (the id is
per-thread, so it is only preserved within the same thread). A hard page reload still starts it
empty — permanence spans Drive visits only — keeping the feed live-only.
60 61 62 63 64 65 |
# File 'app/helpers/protege/components/introspection_helper.rb', line 60 def introspection_feed(thread) tag.div(id: dom_id(thread, :activity), class: 'introspection-empty text-xs font-mono leading-relaxed', style: 'color: var(--text-muted)', data: { empty: 'Waiting for activity…', turbo_permanent: '' }) end |
#introspection_panel { ... } ⇒ ActiveSupport::SafeBuffer
Render the panel shell: a full-height right <aside> (mirroring the left inbox aside) with a fixed
header and a scrollable body that yields its content (the activity feed).
The user can resize its width by dragging the left-edge handle (the general resize_handle paired
with the engine's self-contained resize.js), bounded by min/max and remembered across page loads.
Collapsing (open/close) is handled by introspection_toggle in the thread header.
22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
# File 'app/helpers/protege/components/introspection_helper.rb', line 22 def introspection_panel(&block) class_names = 'relative flex w-80 flex-shrink-0 flex-col overflow-hidden' data = { resize: '', resize_edge: 'left', resize_min: 240, resize_max: 720, resize_key: 'protege:introspection-width' } tag.aside(id: INTROSPECTION_PANEL_ID, class: class_names, data:) do resize_handle(edge: :left) + introspection_header + tag.div(class: 'flex-1 overflow-y-auto px-4 py-3', &block) end end |
#introspection_reasoning(id:, content: nil) ⇒ ActiveSupport::SafeBuffer
A reasoning block — one paragraph of the model's streamed reasoning. The IntrospectionBroadcaster
appends a fresh block after each tool call and streams that turn's tokens into it (by id), so
reasoning is grouped into spaced, whitespace-preserving paragraphs rather than loose text nodes.
74 75 76 |
# File 'app/helpers/protege/components/introspection_helper.rb', line 74 def introspection_reasoning(id:, content: nil) tag.div(content, id:, class: 'my-3 whitespace-pre-wrap') end |
#introspection_run_marker(id:, status: :processing, detail: nil) ⇒ ActiveSupport::SafeBuffer
A run-lifecycle marker in the activity feed: a pulsing "Processing…" line while the run is in
flight, or a red error when it fails. Appended on run start; on success it is removed, on failure
replaced in place (by its id) with the error — so each run reads as processing → reasoning/tool
use, then repeats for the next reply.
110 111 112 113 114 115 116 117 118 119 120 121 122 |
# File 'app/helpers/protege/components/introspection_helper.rb', line 110 def introspection_run_marker(id:, status: :processing, detail: nil) if status == :failed tag.div(id:, class: 'my-3 text-xs font-mono', style: 'color: #ef4444') do tag.div('✗ Run failed', class: 'font-medium') + introspection_card_line(detail, color: '#ef4444') end else tag.div(id:, class: 'my-3 flex items-center gap-2 text-xs animate-pulse', style: 'color: var(--text-faint)') do tag.span('•') + tag.span('Processing…') end end end |
#introspection_toggle ⇒ ActiveSupport::SafeBuffer
The toggle button (placed in the thread header) that shows/hides the panel.
43 44 45 46 47 48 |
# File 'app/helpers/protege/components/introspection_helper.rb', line 43 def introspection_toggle ('Introspection', variant: :secondary, type: 'button', onclick: "document.getElementById('#{INTROSPECTION_PANEL_ID}').classList.toggle('hidden')") end |
#introspection_tool_card(name:, args: nil, status: :done, preview: nil, id: nil) ⇒ ActiveSupport::SafeBuffer
A single tool-call card: a status glyph, the tool name, an optional one-line args summary, and an
optional response preview (done) or error (failed). Streamed into the panel as a :pending card
the instant the model requests the call, then replaced in place with the outcome — so pass a stable
id: (the card's DOM id) that the replace can target.
89 90 91 92 93 94 95 96 97 98 99 |
# File 'app/helpers/protege/components/introspection_helper.rb', line 89 def introspection_tool_card(name:, args: nil, status: :done, preview: nil, id: nil) glyph, color, pulsing = tool_card_glyph(status) tag.div(id:, class: 'my-3 pl-2 text-xs font-mono', style: 'border-left: 2px solid var(--border)') do tag.div(class: 'flex items-center gap-2') do tag.span(glyph, class: pulsing ? 'animate-pulse' : '', style: "color: #{color}") + tag.span(name, class: 'font-medium', style: 'color: var(--text)') end + introspection_card_line(args, color: 'var(--text-muted)') + introspection_card_line(preview.present? ? "→ #{preview}" : nil, color: 'var(--text-faint)') end end |