samagotchi 0.134.0 → 0.136.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +78 -1
- data/bin/chi +2 -3
- data/docs/architecture.md +6 -3
- data/docs/cli.md +43 -38
- data/docs/configuration.md +11 -9
- data/docs/guardrails.md +53 -9
- data/docs/internals/engine.md +6 -3
- data/docs/internals/participants.md +1 -1
- data/docs/internals/prompt-caching.md +1 -1
- data/docs/internals/web-frontend.md +263 -0
- data/docs/internals/working-set.md +2 -2
- data/docs/sessions.md +5 -3
- data/docs/testing.md +36 -6
- data/lib/samagotchi/bridge.rb +11 -6
- data/lib/samagotchi/bridge_client.rb +8 -5
- data/lib/samagotchi/bundles/system/manifest.yml +1 -1
- data/lib/samagotchi/config.rb +0 -2
- data/lib/samagotchi/engine.rb +69 -12
- data/lib/samagotchi/guardrail_wiring.rb +8 -6
- data/lib/samagotchi/guardrails/approval.rb +41 -5
- data/lib/samagotchi/guardrails/implicit_writers.rb +19 -4
- data/lib/samagotchi/guardrails/pair_boundary.rb +5 -1
- data/lib/samagotchi/guardrails/pair_command.rb +387 -78
- data/lib/samagotchi/guardrails/parent_approvals.rb +12 -15
- data/lib/samagotchi/guardrails/read_only_shell.rb +24 -2
- data/lib/samagotchi/guardrails/shared_git.rb +1 -1
- data/lib/samagotchi/guardrails/shell_git_dirs.rb +1 -1
- data/lib/samagotchi/guardrails/shell_lex.rb +22 -0
- data/lib/samagotchi/guardrails_command.rb +13 -10
- data/lib/samagotchi/llm/chat_loop.rb +4 -3
- data/lib/samagotchi/message_parts.rb +1 -1
- data/lib/samagotchi/revise_anchor.rb +33 -0
- data/lib/samagotchi/session_inbox.rb +20 -12
- data/lib/samagotchi/session_manager.rb +13 -11
- data/lib/samagotchi/system_one/approval_question.rb +21 -16
- data/lib/samagotchi/system_one/self_protect.rb +13 -15
- data/lib/samagotchi/tools/delegate_relay.rb +6 -5
- data/lib/samagotchi/tools/delegate_wait.rb +5 -5
- data/lib/samagotchi/tools/execute.rb +6 -6
- data/lib/samagotchi/tools/output_guardrails.rb +9 -2
- data/lib/samagotchi/tools/read.rb +5 -5
- data/lib/samagotchi/turn_anchors.rb +49 -0
- data/lib/samagotchi/verify_anchor.rb +5 -5
- data/lib/samagotchi/version.rb +1 -1
- data/lib/samagotchi/web/app.rb +6 -18
- data/lib/samagotchi/web/message_display.rb +33 -1
- data/lib/samagotchi/web/participant_access.rb +2 -2
- data/lib/samagotchi/web/participant_view.rb +8 -0
- data/lib/samagotchi/web/public/activity.js +1 -1
- data/lib/samagotchi/web/public/annotations.js +1 -1
- data/lib/samagotchi/web/public/answer_fold.js +100 -31
- data/lib/samagotchi/web/public/app.js +451 -367
- data/lib/samagotchi/web/public/bubbles.js +30 -10
- data/lib/samagotchi/web/public/cards_place.js +4 -2
- data/lib/samagotchi/web/public/chip_popover.js +2 -2
- data/lib/samagotchi/web/public/composer.css +8 -0
- data/lib/samagotchi/web/public/context_chips.js +9 -6
- data/lib/samagotchi/web/public/data.js +4 -2
- data/lib/samagotchi/web/public/dock_reserve.js +13 -15
- data/lib/samagotchi/web/public/feature_gates.js +33 -0
- data/lib/samagotchi/web/public/history_reveal.js +10 -6
- data/lib/samagotchi/web/public/index.html +105 -53
- data/lib/samagotchi/web/public/info_bar.js +11 -12
- data/lib/samagotchi/web/public/info_bar_model.js +8 -10
- data/lib/samagotchi/web/public/latest.js +46 -0
- data/lib/samagotchi/web/public/level_chip.js +33 -95
- data/lib/samagotchi/web/public/lists_view.js +15 -13
- data/lib/samagotchi/web/public/live_timing.js +2 -0
- data/lib/samagotchi/web/public/llm_context_chip.js +13 -144
- data/lib/samagotchi/web/public/markdown_place.js +1 -1
- data/lib/samagotchi/web/public/method_api.js +14 -0
- data/lib/samagotchi/web/public/notifications.js +4 -11
- data/lib/samagotchi/web/public/participant.js +11 -6
- data/lib/samagotchi/web/public/participant_names.js +1 -1
- data/lib/samagotchi/web/public/participants_panel.js +4 -2
- data/lib/samagotchi/web/public/project_picker.js +18 -22
- data/lib/samagotchi/web/public/question_cards.js +2 -2
- data/lib/samagotchi/web/public/reasoning_history_chip.js +8 -34
- data/lib/samagotchi/web/public/restore.js +2 -0
- data/lib/samagotchi/web/public/revise_stack.js +378 -0
- data/lib/samagotchi/web/public/scope.js +12 -20
- data/lib/samagotchi/web/public/send_turn.js +29 -16
- data/lib/samagotchi/web/public/session_actions.js +15 -14
- data/lib/samagotchi/web/public/session_fields.js +152 -0
- data/lib/samagotchi/web/public/session_payload.js +35 -0
- data/lib/samagotchi/web/public/session_state.js +9 -5
- data/lib/samagotchi/web/public/session_store.js +191 -0
- data/lib/samagotchi/web/public/settings_chip.js +212 -0
- data/lib/samagotchi/web/public/settings_panel.js +121 -0
- data/lib/samagotchi/web/public/shell_command.js +26 -0
- data/lib/samagotchi/web/public/stage_model.js +4 -4
- data/lib/samagotchi/web/public/stage_view.js +11 -16
- data/lib/samagotchi/web/public/start_settings.js +25 -88
- data/lib/samagotchi/web/public/stream_handlers.js +88 -90
- data/lib/samagotchi/web/public/task_stop.js +1 -1
- data/lib/samagotchi/web/public/thinking_chip.js +7 -45
- data/lib/samagotchi/web/public/time_gaps.js +5 -2
- data/lib/samagotchi/web/public/timing.js +15 -5
- data/lib/samagotchi/web/public/toolkit_chip.js +37 -21
- data/lib/samagotchi/web/public/{turn_view.js → turn_block.js} +33 -93
- data/lib/samagotchi/web/public/turn_events.js +3 -3
- data/lib/samagotchi/web/public/turn_html.js +20 -16
- data/lib/samagotchi/web/public/turn_model.js +6 -6
- data/lib/samagotchi/web/public/verdict_fresh.js +2 -0
- data/lib/samagotchi/web/public/working_set_view.js +3 -1
- data/lib/samagotchi/web/server.rb +4 -4
- data/lib/samagotchi/web/session_control.rb +2 -2
- data/lib/samagotchi/web/session_create.rb +1 -1
- data/lib/samagotchi/web/session_show.rb +6 -3
- data/lib/samagotchi/worker.rb +9 -9
- data/lib/samagotchi/worker_inbound.rb +6 -5
- metadata +15 -3
- data/lib/samagotchi/web/public/hold.js +0 -78
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
// The session bar's ⚙ chip and its panel: the open session's thinking,
|
|
2
|
+
// reasoning history and LLM context in one place, as the start page's ⚙
|
|
3
|
+
// panel has them for a new session (settings_panel.js draws both). The chip
|
|
4
|
+
// reads "⚙" alone while the session follows the model for all three, else
|
|
5
|
+
// "⚙" and the session's own in the start page's words ("⚙ think low · ctx
|
|
6
|
+
// stale"; class own, the accent). A pick in the panel runs that row's
|
|
7
|
+
// command with --yes in the session's worker at once (one reply line in the
|
|
8
|
+
// conversation, like a typed /thinking low) and the panel stays open; the
|
|
9
|
+
// pressed segment follows the session's summary when the reply's command_ran
|
|
10
|
+
// lands, never the tap. The summaries are the session's thinking,
|
|
11
|
+
// reasoning_history and llm_context (SettingExplained#summary,
|
|
12
|
+
// LLMContextStrategy::Explained#summary: the worker's snapshot, a
|
|
13
|
+
// command_ran, or the server's own reading of the session file).
|
|
14
|
+
//
|
|
15
|
+
// A new module: the page's modules are cached at unversioned URLs, and a
|
|
16
|
+
// new export of an old module would leave a cached copy without it.
|
|
17
|
+
|
|
18
|
+
import { createChipPopover, SHEET_TALL } from "./chip_popover.js";
|
|
19
|
+
import { DEFAULT, commandLine as levelLine, segments } from "./level_chip.js";
|
|
20
|
+
import { BUDGET_ERROR, commandLine as contextLine, ownValues, parseBudget } from "./llm_context_chip.js";
|
|
21
|
+
import { CONFIG as REASONING } from "./reasoning_history_chip.js";
|
|
22
|
+
import { contextSegments, contextWords, settingsPanelTemplate } from "./settings_panel.js";
|
|
23
|
+
import { CONFIG as THINKING } from "./thinking_chip.js";
|
|
24
|
+
import { html, nothing, render } from "./vendor/lit-html.js";
|
|
25
|
+
|
|
26
|
+
export const PANEL_LABEL = "Session settings";
|
|
27
|
+
export const NOTE = "From the next turn's start.";
|
|
28
|
+
export const RUNNING_NOTE = "A turn is running: it applies after this turn.";
|
|
29
|
+
const CONTEXT_COST = "Turning forget on or off re-reads the whole prompt once.";
|
|
30
|
+
|
|
31
|
+
// A level's own value ("low"), null when it follows the model.
|
|
32
|
+
const levelOwn = (summary) => summary?.own || null;
|
|
33
|
+
|
|
34
|
+
// The context's own values in a few words ("stale · budget 64000"), null
|
|
35
|
+
// when the session has none.
|
|
36
|
+
const contextOwn = (summary) => (summary?.own ? contextWords(summary.own) : null);
|
|
37
|
+
|
|
38
|
+
function levelTitle(cfg, summary) {
|
|
39
|
+
return `${cfg.name}: ${summary.level} (${summary.own ? "this session" : summary.source || "the model's own"})`;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function contextTitle(summary) {
|
|
43
|
+
const budget = summary.budget_tokens ? `${summary.budget_tokens} tokens` : "off";
|
|
44
|
+
return `LLM context: ${summary.strategy} (${summary.strategy_where}); apply ${summary.apply} (${summary.apply_where}); ` +
|
|
45
|
+
`budget ${budget} (${summary.budget_where})`;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// The chip for +summaries+ ({thinking, reasoningHistory, llmContext}, each
|
|
49
|
+
// a summary or null): {text (the own values past ⚙, "" for none), own,
|
|
50
|
+
// title (each value and where it comes from), label (the accessible name:
|
|
51
|
+
// the panel and what is set, settingsLabel's shape)}; null without any
|
|
52
|
+
// summary (an older worker: no chip).
|
|
53
|
+
export function chipModel({ thinking = null, reasoningHistory = null, llmContext = null } = {}) {
|
|
54
|
+
const think = thinking?.level ? thinking : null;
|
|
55
|
+
const rh = reasoningHistory?.level ? reasoningHistory : null;
|
|
56
|
+
const ctx = llmContext?.strategy ? llmContext : null;
|
|
57
|
+
if (!think && !rh && !ctx) return null;
|
|
58
|
+
const set = [
|
|
59
|
+
levelOwn(think) && `${THINKING.prefix} ${levelOwn(think)}`,
|
|
60
|
+
levelOwn(rh) && `${REASONING.prefix} ${levelOwn(rh)}`,
|
|
61
|
+
contextOwn(ctx) && `ctx ${contextOwn(ctx)}`,
|
|
62
|
+
].filter(Boolean);
|
|
63
|
+
const lines = [think && levelTitle(THINKING, think), rh && levelTitle(REASONING, rh), ctx && contextTitle(ctx)].filter(Boolean);
|
|
64
|
+
return {
|
|
65
|
+
text: set.join(" · "),
|
|
66
|
+
own: set.length > 0,
|
|
67
|
+
title: `${lines.join(". ")}. Click to change them for this session.`,
|
|
68
|
+
label: set.length ? `${PANEL_LABEL}: ${set.join(", ")}` : PANEL_LABEL,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// The session bar's chip (a lit-html template; info_bar.js), nothing
|
|
73
|
+
// without a summary. ⚙ is drawn as the start page's settings icon (the
|
|
74
|
+
// glyph stays the text); the own values are a span of their own: a phone
|
|
75
|
+
// shows ⚙ with a dot instead (index.html's CSS).
|
|
76
|
+
export function chipTemplate(summaries) {
|
|
77
|
+
const chip = summaries && chipModel(summaries);
|
|
78
|
+
if (!chip) return nothing;
|
|
79
|
+
return html`<button type="button" class=${`model settings-chip${chip.own ? " own" : ""}`} title=${chip.title}
|
|
80
|
+
aria-label=${chip.label} aria-haspopup="dialog" data-settings-chip><span class="chip-icon sc-gear" aria-hidden="true">⚙</span>${
|
|
81
|
+
chip.text ? html`<span class="sc-own"> ${chip.text}</span>` : nothing}</button>`;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// The panel's note: when a change applies (after the running turn, while
|
|
85
|
+
// one runs) and what each drawn row's change costs.
|
|
86
|
+
export function noteText({ thinking, reasoningHistory, llmContext, running = false }) {
|
|
87
|
+
return [NOTE, running ? RUNNING_NOTE : null, thinking ? THINKING.cost : null, reasoningHistory ? REASONING.cost : null,
|
|
88
|
+
llmContext ? CONTEXT_COST : null].filter(Boolean).join(" ");
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// The panel (settings_panel.js settingsPanelTemplate) over the session's
|
|
92
|
+
// summaries: the Thinking row, the Reasoning history row (a chat host's
|
|
93
|
+
// only), the Context row with its Apply and budget disclosure; each row's
|
|
94
|
+
// pressed segment the session's own. +more+: the disclosure open; +error+:
|
|
95
|
+
// its Set's.
|
|
96
|
+
export function panelTemplate({ thinking = null, reasoningHistory = null, llmContext = null, running = false,
|
|
97
|
+
more = false, error = null }) {
|
|
98
|
+
const think = segments(THINKING, thinking, levelOwn(thinking));
|
|
99
|
+
const rh = segments(REASONING, reasoningHistory, levelOwn(reasoningHistory));
|
|
100
|
+
const ctx = contextSegments(llmContext, llmContext?.own || null);
|
|
101
|
+
return settingsPanelTemplate({
|
|
102
|
+
heading: PANEL_LABEL,
|
|
103
|
+
kind: "this session",
|
|
104
|
+
rows: [
|
|
105
|
+
think ? { name: "think", label: THINKING.name, segments: think, act: "think" } : null,
|
|
106
|
+
// "Reasoning", the chip's word: "Reasoning history" wraps in the label's column.
|
|
107
|
+
rh ? { name: "rh", label: "Reasoning", segments: rh, act: "reasoning" } : null,
|
|
108
|
+
ctx ? { name: "ctx", label: "Context", segments: ctx, act: "ctx",
|
|
109
|
+
more: { open: more, summary: llmContext, choice: llmContext.own || null, error } } : null,
|
|
110
|
+
],
|
|
111
|
+
note: noteText({ thinking: think, reasoningHistory: rh, llmContext: ctx, running }),
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// What a pick (+act+, a segment's data-act: "think:low", "reasoning:all",
|
|
116
|
+
// "ctx:stale", "ctx:default") runs for +summaries+: the line, or null (the
|
|
117
|
+
// segment pressed already). One field alone (D2): a strategy tap sends the
|
|
118
|
+
// strategy only, the session's own apply and budget stay; the Context
|
|
119
|
+
// row's default is the reset (strategy, apply and budget follow the model
|
|
120
|
+
// again), only when the session has any of its own.
|
|
121
|
+
export function actLine(act, { thinking = null, reasoningHistory = null, llmContext = null } = {}) {
|
|
122
|
+
const [what, value] = act.split(/:(.*)/s);
|
|
123
|
+
if (what === "think") return levelLine(THINKING, value, levelOwn(thinking) || DEFAULT);
|
|
124
|
+
if (what === "reasoning") return levelLine(REASONING, value, levelOwn(reasoningHistory) || DEFAULT);
|
|
125
|
+
if (what !== "ctx") return null;
|
|
126
|
+
if (value === DEFAULT) return llmContext?.own ? contextLine() : null;
|
|
127
|
+
const initial = ownValues(llmContext);
|
|
128
|
+
return contextLine({ ...initial, strategy: value }, initial);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// The disclosure's Set: the apply rule and the budget as typed, against the
|
|
132
|
+
// session's own; only the field that changed goes. {line} (null: nothing
|
|
133
|
+
// changed) or {error} (a budget chi wouldn't take).
|
|
134
|
+
export function moreLine({ apply, budget }, llmContext) {
|
|
135
|
+
const typed = String(budget ?? "").trim();
|
|
136
|
+
if (typed && parseBudget(typed) === null) return { error: BUDGET_ERROR };
|
|
137
|
+
const initial = ownValues(llmContext);
|
|
138
|
+
return { line: contextLine({ ...initial, apply, budget: typed }, initial) };
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// The chip's panel on chip_popover.js' shell (a bottom sheet on a phone).
|
|
142
|
+
// el: where the chip is drawn (#infoText, info_bar.js: clicks are
|
|
143
|
+
// delegated); summaries(): the open session's {thinking, reasoningHistory,
|
|
144
|
+
// llmContext} (null: no session); session(): its id; running(): a turn
|
|
145
|
+
// runs; run(line): sends the command.
|
|
146
|
+
// @return {close, refresh, open}
|
|
147
|
+
export function createSettingsControl({ el, summaries, session = () => null, running = () => false, run,
|
|
148
|
+
id = "settingsPopover", anchor = "[data-settings-chip]",
|
|
149
|
+
doc = document, win = window }) {
|
|
150
|
+
// The summaries the open panel is drawn from (each refresh's newer ones),
|
|
151
|
+
// the disclosure and its Set's error.
|
|
152
|
+
let shown = null;
|
|
153
|
+
let more = false;
|
|
154
|
+
let error = null;
|
|
155
|
+
|
|
156
|
+
function draw() {
|
|
157
|
+
render(panelTemplate({ ...shown, running: running(), more, error }), shell.pop);
|
|
158
|
+
shell.replace();
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function show(data, opening) {
|
|
162
|
+
shown = data;
|
|
163
|
+
if (opening) {
|
|
164
|
+
const own = data.llmContext?.own;
|
|
165
|
+
more = !!(own?.apply || typeof own?.budget_tokens === "number");
|
|
166
|
+
error = null;
|
|
167
|
+
}
|
|
168
|
+
draw();
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function act(name) {
|
|
172
|
+
if (name === "ctx-more") {
|
|
173
|
+
more = !more;
|
|
174
|
+
return draw();
|
|
175
|
+
}
|
|
176
|
+
const line = actLine(name, shown);
|
|
177
|
+
if (line) run(line);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function submit(form) {
|
|
181
|
+
if (!form.classList.contains("sset-ctx-form")) return;
|
|
182
|
+
const result = moreLine({ apply: form.elements.apply.value, budget: form.elements.budget.value }, shown.llmContext);
|
|
183
|
+
error = result.error || null;
|
|
184
|
+
draw();
|
|
185
|
+
if (result.line) run(result.line);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Focus inside on open (the popover sits at the end of <body>): the
|
|
189
|
+
// pressed segment of the first row.
|
|
190
|
+
function focusInside() {
|
|
191
|
+
shell.pop.querySelector(".sset-seg [aria-pressed=true]")?.focus();
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const present = () => {
|
|
195
|
+
const data = summaries();
|
|
196
|
+
return data && chipModel(data) ? data : null;
|
|
197
|
+
};
|
|
198
|
+
const shell = createChipPopover({
|
|
199
|
+
id, className: "ctx-popover start-settings session-settings", width: 440, el, anchor, summary: present, session,
|
|
200
|
+
show, sheet: true, sheetShare: SHEET_TALL, modal: true, label: PANEL_LABEL, onOpen: focusInside,
|
|
201
|
+
onClose: () => { shown = null; error = null; }, onSubmit: submit, onAct: act, doc, win,
|
|
202
|
+
});
|
|
203
|
+
// An edit makes the disclosure Set's error stale.
|
|
204
|
+
shell.pop.addEventListener("input", (e) => {
|
|
205
|
+
if (error && e.target.closest(".sset-ctx-form")) {
|
|
206
|
+
error = null;
|
|
207
|
+
draw();
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
return { close: shell.close, refresh: shell.refresh, get open() { return shell.isOpen(); } };
|
|
212
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
// The settings panel's rows, drawn by the start page's ⚙ panel
|
|
2
|
+
// (start_settings.js) over a new session's choices and by the session bar's
|
|
3
|
+
// (settings_chip.js) over the open session's own: rows of segments
|
|
4
|
+
// (Thinking, Context), the Context row's Apply and budget disclosure, a
|
|
5
|
+
// line for a choice whose row is hidden, the review files' line. The caller
|
|
6
|
+
// passes the rows it draws (settingsPanelTemplate's +rows+) and applies the
|
|
7
|
+
// picks itself; the segments' data-act ("think:low", "ctx:stale",
|
|
8
|
+
// "ctx-more") is the same for every caller.
|
|
9
|
+
|
|
10
|
+
import { APPLIES, choiceWords, ownValues, strategyValue } from "./llm_context_chip.js";
|
|
11
|
+
import { DEFAULT, followSegment, segments } from "./level_chip.js";
|
|
12
|
+
import { reviewChipModel } from "./review_start.js";
|
|
13
|
+
import { CONFIG as THINKING } from "./thinking_chip.js";
|
|
14
|
+
import { html, nothing } from "./vendor/lit-html.js";
|
|
15
|
+
|
|
16
|
+
export { DEFAULT };
|
|
17
|
+
|
|
18
|
+
const STRATEGIES = [
|
|
19
|
+
{ value: "none", label: "none" },
|
|
20
|
+
{ value: "stale", label: "stale" },
|
|
21
|
+
{ value: "stale,forget", label: "stale + forget", title: "stale + forget (experimental)" },
|
|
22
|
+
];
|
|
23
|
+
|
|
24
|
+
// The Thinking row's segments (level_chip.js segments over thinking_chip.js'
|
|
25
|
+
// levels): the model's own level first ("default (high)"; "default" while
|
|
26
|
+
// the session has its own), then the levels;
|
|
27
|
+
// the pressed one is the choice. null without the model's thinking (the row
|
|
28
|
+
// is hidden).
|
|
29
|
+
export function thinkingSegments(summary, choice) {
|
|
30
|
+
return segments(THINKING, summary, choice);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Whether the summary's +field+ (strategy, apply, budget) is the session's
|
|
34
|
+
// own: the value in force is then not the model's, so a default label
|
|
35
|
+
// doesn't name it.
|
|
36
|
+
const ownField = (summary, field) => summary?.[`${field}_source`] === "session";
|
|
37
|
+
|
|
38
|
+
// The Context row's segments over the strategies (a choice that isn't one
|
|
39
|
+
// of them, a newer chi's layer, is added); null without the model's
|
|
40
|
+
// llm_context (the row is hidden).
|
|
41
|
+
export function contextSegments(summary, choice) {
|
|
42
|
+
if (!summary?.strategy) return null;
|
|
43
|
+
const pressed = Array.isArray(choice?.strategy) ? strategyValue(choice.strategy) : DEFAULT;
|
|
44
|
+
const list = STRATEGIES.some((s) => s.value === pressed) || pressed === DEFAULT ? STRATEGIES
|
|
45
|
+
: [...STRATEGIES, { value: pressed, label: pressed }];
|
|
46
|
+
return [followSegment(summary.strategy, ownField(summary, "strategy")), ...list]
|
|
47
|
+
.map((s) => ({ ...s, pressed: s.value === pressed }));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// The context choice in a few words ("stale · budget 64000"), or null.
|
|
51
|
+
export function contextWords(choice) {
|
|
52
|
+
const words = choiceWords(choice);
|
|
53
|
+
if (!words) return null;
|
|
54
|
+
return [words.strategy, words.apply && `apply ${words.apply}`, words.budget && `budget ${words.budget}`]
|
|
55
|
+
.filter(Boolean).join(" · ");
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export const segmentsTemplate = (name, label, segs, act) => html`<div class="sset-row"><span class="sset-label"
|
|
59
|
+
id=${`sset-${name}-label`}>${label}</span><div class="sset-seg" role="group" aria-labelledby=${`sset-${name}-label`}>${
|
|
60
|
+
segs.map((s) => html`<button type="button" data-act=${`${act}:${s.value}`} aria-pressed=${s.pressed ? "true" : "false"}
|
|
61
|
+
title=${s.title || nothing}>${s.label}</button>`)}</div></div>`;
|
|
62
|
+
|
|
63
|
+
const option = (value, label, chosen) => html`<option value=${value} ?selected=${value === chosen}>${label}</option>`;
|
|
64
|
+
|
|
65
|
+
// The disclosure's fields: the apply rule and the budget, with their own
|
|
66
|
+
// Set (a typed budget needs a commit) and mergeChoice's error under them.
|
|
67
|
+
// Each default names the model's value, unless the session set its own
|
|
68
|
+
// (the summary's value is the session's then).
|
|
69
|
+
export function contextMoreTemplate(summary, choice, error) {
|
|
70
|
+
const own = ownValues({ own: choice });
|
|
71
|
+
const budget = summary.budget_tokens ? String(summary.budget_tokens) : "off";
|
|
72
|
+
const apply = ownField(summary, "apply") ? DEFAULT : `${DEFAULT} (${summary.apply})`;
|
|
73
|
+
const budgetDefault = ownField(summary, "budget") ? DEFAULT : `${DEFAULT} (${budget})`;
|
|
74
|
+
return html`<form class="sset-ctx-form"><label class="llmctx-field"><span>Apply</span><span class="select-wrap"><select
|
|
75
|
+
name="apply">${option(DEFAULT, apply, own.apply)}${APPLIES.map((a) => option(a, a, own.apply))}</select
|
|
76
|
+
></span></label><label class="llmctx-field"><span>Budget</span><input name="budget" type="text" inputmode="numeric"
|
|
77
|
+
autocomplete="off" placeholder=${`${budgetDefault}: 64000, 64k or off`} .value=${own.budget}></label
|
|
78
|
+
><div class="ctx-pop-error" role="alert" ?hidden=${!error}>${error || ""}</div
|
|
79
|
+
><div class="ctx-pop-actions"><button type="submit" class="llmctx-set">Set</button></div></form>`;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// A choice whose row is hidden (the picked model has no values for it).
|
|
83
|
+
export const sentAnywayTemplate = (text, act) => html`<div class="sset-anyway ctx-pop-meta">${text}
|
|
84
|
+
<button type="button" class="sset-link" data-act=${act}>Clear</button></div>`;
|
|
85
|
+
|
|
86
|
+
// One of the panel's rows, null drawing nothing:
|
|
87
|
+
// {name, label, segments, act}: a row of segments (segmentsTemplate);
|
|
88
|
+
// with +more+ ({open, summary, choice, error}) the Apply and budget
|
|
89
|
+
// disclosure under it, its fields while open (contextMoreTemplate)
|
|
90
|
+
// {anyway, act}: the line for a choice whose row is hidden, its Clear
|
|
91
|
+
// button's data-act (sentAnywayTemplate)
|
|
92
|
+
function rowTemplate(row) {
|
|
93
|
+
if (!row) return nothing;
|
|
94
|
+
if (row.anyway) return sentAnywayTemplate(row.anyway, row.act);
|
|
95
|
+
const { more } = row;
|
|
96
|
+
return html`${segmentsTemplate(row.name, row.label, row.segments, row.act)}${more ? html`<div class="sset-ctx-more"
|
|
97
|
+
><button type="button" class="sset-link" data-act="ctx-more" aria-expanded=${more.open ? "true" : "false"}>Apply and budget ${
|
|
98
|
+
more.open ? "▾" : "▸"}</button></div>${more.open ? contextMoreTemplate(more.summary, more.choice, more.error) : nothing}`
|
|
99
|
+
: nothing}`;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// The review files' line: the files set (Edit, Remove), else Review files….
|
|
103
|
+
function reviewTemplate(review, ruled) {
|
|
104
|
+
const chip = review.length ? reviewChipModel(review) : null;
|
|
105
|
+
return html`${ruled ? html`<hr class="sset-rule">` : nothing}<div class="sset-review">${chip
|
|
106
|
+
? html`<span class="sset-review-set" title=${chip.title}>Review: ${chip.text.replace(/^review: /, "")}</span
|
|
107
|
+
><button type="button" class="sset-link" data-act="review">Edit</button><button type="button" class="sset-link"
|
|
108
|
+
data-act="review-clear">Remove</button>`
|
|
109
|
+
: html`<button type="button" class="sset-link" data-act="review" title=${reviewChipModel([]).title}>Review files…</button>`}</div>`;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// The panel: its heading (+kind+ beside it, or none) and close button, the
|
|
113
|
+
// +rows+ (rowTemplate's), the review files' line when +review+ is a list
|
|
114
|
+
// (null: no review line; a rule above it when a row of segments is drawn),
|
|
115
|
+
// and the +note+.
|
|
116
|
+
export function settingsPanelTemplate({ heading, kind = null, rows = [], review = null, note }) {
|
|
117
|
+
const segmented = rows.some((row) => row?.segments);
|
|
118
|
+
return html`<div class="ctx-pop-head"><strong>${heading}</strong>${kind ? html`<span class="ctx-pop-kind">${kind}</span>` : nothing
|
|
119
|
+
}<button type="button" class="ctx-pop-close" data-act="close" aria-label="Close">×</button></div
|
|
120
|
+
>${rows.map(rowTemplate)}${review ? reviewTemplate(review, segmented) : nothing}<div class="ctx-pop-meta">${note}</div>`;
|
|
121
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// A `!cmd`'s saved user message: the worker appends "!(<command>)\n<output>"
|
|
2
|
+
// for the next turn (session_commands.rb, no turn id, no time). The history
|
|
3
|
+
// draws it as its command bubble (turn_html.js) and the reload's turns
|
|
4
|
+
// leave it out (timing.js turnGroups: a group of its own, no steps, no
|
|
5
|
+
// timing). Pure, no imports: timing.js and turn_html.js both read it, and
|
|
6
|
+
// turn_html.js imports timing.js. A module of its own: static JS is cached
|
|
7
|
+
// at unversioned URLs, and a new export on an old module would blank the
|
|
8
|
+
// page.
|
|
9
|
+
|
|
10
|
+
// Its command line and its output, or null when the content is any other
|
|
11
|
+
// message. The command sits on the first line inside `!(` `)`; the output
|
|
12
|
+
// is everything after that first newline. A message that merely starts
|
|
13
|
+
// with "!" or "!(" without the newline shape is a plain message.
|
|
14
|
+
export function shellCommandMessage(content) {
|
|
15
|
+
const text = String(content ?? "");
|
|
16
|
+
const match = text.match(/^!\((.*)\)\n([\s\S]*)$/);
|
|
17
|
+
if (!match) return null;
|
|
18
|
+
return { line: `!${match[1]}`, text: match[2] };
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// A history item that is a `!cmd`'s record, not a prompt: a user message of
|
|
22
|
+
// that shape with no turn id (a prompt saves its turn's; session_chain.rb
|
|
23
|
+
// reads the record the same way).
|
|
24
|
+
export function isShellCommandItem(item) {
|
|
25
|
+
return item?.role === "user" && !item.turn_id && shellCommandMessage(item.content) !== null;
|
|
26
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// The stage
|
|
1
|
+
// The stage's pure parts (stage_view.js): what the live slots show
|
|
2
2
|
// for a turn (turn_model.js's), when a finished turn hands off into the
|
|
3
3
|
// history, and in what order its pieces go there. No DOM, so `node --test`
|
|
4
4
|
// covers the rules; stage_view.js draws them.
|
|
@@ -128,7 +128,7 @@ export function miniToolText({ collapsed, tool }) {
|
|
|
128
128
|
}
|
|
129
129
|
|
|
130
130
|
// While the first step has no thinking and no tool rows the turn is plain:
|
|
131
|
-
// its text streams in the stage's answer slot, as the turn
|
|
131
|
+
// its text streams in the stage's answer slot, as the turn block's
|
|
132
132
|
// chrome-less block streams like a bubble.
|
|
133
133
|
export function isPlain(turn) {
|
|
134
134
|
if (!turn || turn.ended || turn.gens.length > 1) return false;
|
|
@@ -137,7 +137,7 @@ export function isPlain(turn) {
|
|
|
137
137
|
}
|
|
138
138
|
|
|
139
139
|
const END_PHASES = { completed: "answered", canceled: "canceled" };
|
|
140
|
-
// A completed turn whose model returned nothing (the turn
|
|
140
|
+
// A completed turn whose model returned nothing (the turn block's notice).
|
|
141
141
|
const NO_ANSWER = "no answer";
|
|
142
142
|
|
|
143
143
|
// What the live slots show for +turn+:
|
|
@@ -224,7 +224,7 @@ export function handOffDue({ ended, pending, inUse, quietSinceMs, nowMs }) {
|
|
|
224
224
|
return { due: nowMs - since >= QUIET_MS, quietSinceMs: since };
|
|
225
225
|
}
|
|
226
226
|
|
|
227
|
-
// A handed-off turn's nodes in the order the turn
|
|
227
|
+
// A handed-off turn's nodes in the order the turn block leaves them: the
|
|
228
228
|
// prompt bubble (none for a reminder turn), the block, its working set's
|
|
229
229
|
// footprint line (web.working_set), the extras in arrival order, the
|
|
230
230
|
// answer's pieces, the timing line, the end line, the kept cards, then the
|
|
@@ -1,21 +1,20 @@
|
|
|
1
|
-
// The stage
|
|
1
|
+
// The stage of a running turn (the page's view of it): the active turn pinned
|
|
2
2
|
// above the composer, its own card floating over the dock's (out of the
|
|
3
3
|
// dock's flow: its height never moves the composer). It shows only
|
|
4
4
|
// what happens now in fixed slots (a status row, the prompt, a two-line
|
|
5
5
|
// headline, the running tool, a one-line trail of the last calls), the
|
|
6
6
|
// cards and rows that arrive meanwhile in their own capped scroller, the
|
|
7
7
|
// answer, and a chip with one tick per tool call, the stage's footer (no
|
|
8
|
-
// content above it can push it away): the "cloud", the turn
|
|
9
|
-
// (
|
|
8
|
+
// content above it can push it away): the "cloud", the turn block
|
|
9
|
+
// (turn_block.js) reused as is, opens above it as the scroller's last slot,
|
|
10
10
|
// newest step first while the turn runs. When the turn has ended and the stage is not
|
|
11
|
-
// in use, everything moves into the history in the turn
|
|
12
|
-
// hand-off), so a handed-off turn reads like
|
|
13
|
-
// reload. Slots are patched in place, never rebuilt from HTML: a selection
|
|
11
|
+
// in use, everything moves into the history in the turn block's order (the
|
|
12
|
+
// hand-off), so a handed-off turn reads like its reload. Slots are patched in place, never rebuilt from HTML: a selection
|
|
14
13
|
// and annotate survive. The rules are pure in stage_model.js; the live
|
|
15
14
|
// slots are drawn by stage_slots.js, the cap and the held height are
|
|
16
15
|
// stage_frame.js's, and the in-use watch that times the hand-off is
|
|
17
16
|
// stage_watch.js's.
|
|
18
|
-
import {
|
|
17
|
+
import { createTurnBlock } from "./turn_block.js";
|
|
19
18
|
import { chipLabelText, flashOf, handOffDue, handOffOrder, headlineOf, isPlain, liveSlots, MARKS, miniToolText, placeFor, placeKind, QUEUED_CHILDREN, stepSlotText } from "./stage_model.js";
|
|
20
19
|
import { createStageSlots, node } from "./stage_slots.js";
|
|
21
20
|
import { createStageFrame, reducedMotion } from "./stage_frame.js";
|
|
@@ -60,7 +59,7 @@ function readCollapsed() {
|
|
|
60
59
|
// page's working set (working_set_view.js, web.working_set; null: off): its
|
|
61
60
|
// rail in the live slots, its footprint at the hand-off.
|
|
62
61
|
export function createStageView({
|
|
63
|
-
historyEl,
|
|
62
|
+
historyEl, isNearBottom: historyNearBottom, sawText, sessionId, dockEl, centerEl, taskStop = null,
|
|
64
63
|
renderMarkdown, linkRefs, workingSet = null,
|
|
65
64
|
}) {
|
|
66
65
|
// The dock becomes a clear column: the stage's card, then the composer's
|
|
@@ -182,9 +181,8 @@ export function createStageView({
|
|
|
182
181
|
let arrived = null;
|
|
183
182
|
let cardInSight = true;
|
|
184
183
|
|
|
185
|
-
const view =
|
|
184
|
+
const view = createTurnBlock({
|
|
186
185
|
historyEl,
|
|
187
|
-
appendToHistory,
|
|
188
186
|
sawText,
|
|
189
187
|
sessionId,
|
|
190
188
|
// The newest step is on top of the cloud: "near the bottom" is near its top.
|
|
@@ -199,8 +197,6 @@ export function createStageView({
|
|
|
199
197
|
answer.append(...pieces);
|
|
200
198
|
showAnswerTop();
|
|
201
199
|
},
|
|
202
|
-
useHold: false,
|
|
203
|
-
newestFirst: true,
|
|
204
200
|
taskStop,
|
|
205
201
|
onChange: () => scheduleRefresh(),
|
|
206
202
|
onThinkingLine: (text) => {
|
|
@@ -305,7 +301,7 @@ export function createStageView({
|
|
|
305
301
|
const host = plain ? answer : cloudBody;
|
|
306
302
|
if (blockEl.parentNode !== host) host.appendChild(blockEl);
|
|
307
303
|
}
|
|
308
|
-
// The turn
|
|
304
|
+
// The turn block closes at the end; the cloud shows its steps.
|
|
309
305
|
if (blockEl && blockEl.parentNode === cloudBody) blockEl.open = true;
|
|
310
306
|
const narration = gen?.text ? headlineOf(gen.text, gen.textDone || gen.closed) : "";
|
|
311
307
|
const thinking = thinkingLine && thinkingLine.step === turn.gens.length ? thinkingLine.text : "";
|
|
@@ -626,7 +622,7 @@ export function createStageView({
|
|
|
626
622
|
}
|
|
627
623
|
|
|
628
624
|
// The hand-off: everything the stage holds goes into the history's end in
|
|
629
|
-
// the turn
|
|
625
|
+
// the turn block's order (prompt, block (chronological; its outline open,
|
|
630
626
|
// else collapsed), extras, answer, timing line, then the end line and
|
|
631
627
|
// what came after the end). The turn is released at once; the stage
|
|
632
628
|
// slides away under the composer still drawn (a transform: nothing on the
|
|
@@ -668,7 +664,7 @@ export function createStageView({
|
|
|
668
664
|
const fold = folding;
|
|
669
665
|
if (!fold) return;
|
|
670
666
|
folding = null;
|
|
671
|
-
// Closed, unless it is an outline (
|
|
667
|
+
// Closed, unless it is an outline (turn_block.js outlineBlock): the
|
|
672
668
|
// history reads its narrated steps without a click.
|
|
673
669
|
if (fold.block) fold.block.open = fold.block.classList.contains("outline");
|
|
674
670
|
historyEl.querySelector(":scope > .hint")?.remove();
|
|
@@ -764,7 +760,6 @@ export function createStageView({
|
|
|
764
760
|
answerReady() {
|
|
765
761
|
settled = true;
|
|
766
762
|
clearTimeout(settleTimer);
|
|
767
|
-
view.answerReady();
|
|
768
763
|
},
|
|
769
764
|
containsLive: (el) => view.containsLive(el),
|
|
770
765
|
askingText: () => view.askingText(),
|
|
@@ -11,57 +11,21 @@
|
|
|
11
11
|
// instead (settingsLabel()).
|
|
12
12
|
|
|
13
13
|
import { createChipPopover, SHEET_TALL } from "./chip_popover.js";
|
|
14
|
-
import {
|
|
14
|
+
import { mergeChoice, ownValues } from "./llm_context_chip.js";
|
|
15
15
|
import { readSubjects, reviewChipModel, reviewFormTemplate } from "./review_start.js";
|
|
16
|
-
import {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
16
|
+
import {
|
|
17
|
+
contextSegments, contextWords, DEFAULT, settingsPanelTemplate, thinkingSegments,
|
|
18
|
+
} from "./settings_panel.js";
|
|
19
|
+
import { render } from "./vendor/lit-html.js";
|
|
20
|
+
|
|
21
|
+
// The panel's rows (settings_panel.js), named here too: the start page and
|
|
22
|
+
// its specs read them from this module.
|
|
23
|
+
export { contextSegments, contextWords, DEFAULT, thinkingSegments };
|
|
20
24
|
export const NOTE = "For this new session only.";
|
|
21
25
|
export const PANEL_LABEL = "Session settings";
|
|
22
26
|
const NO_THINKING = "the picked model has no thinking setting; it is sent anyway";
|
|
23
27
|
const NO_LLM_CONTEXT = "the picked model has no LLM context values; it is sent anyway";
|
|
24
28
|
|
|
25
|
-
const STRATEGIES = [
|
|
26
|
-
{ value: "none", label: "none" },
|
|
27
|
-
{ value: "stale", label: "stale" },
|
|
28
|
-
{ value: "stale,forget", label: "stale + forget", title: "stale + forget (experimental)" },
|
|
29
|
-
];
|
|
30
|
-
|
|
31
|
-
// The first segment: follow the model, its value in brackets ("default
|
|
32
|
-
// (high)"; a model with no level of its own, "default" alone).
|
|
33
|
-
const followSegment = (value) => ({ value: DEFAULT, label: value === DEFAULT ? DEFAULT : `${DEFAULT} (${value})`,
|
|
34
|
-
title: `Follow the model (${value})` });
|
|
35
|
-
|
|
36
|
-
// The Thinking row's segments: the model's own level first ("default
|
|
37
|
-
// (high)"), then the levels; the pressed one is the choice. null without
|
|
38
|
-
// the model's thinking (the row is hidden).
|
|
39
|
-
export function thinkingSegments(summary, choice) {
|
|
40
|
-
if (!summary?.level) return null;
|
|
41
|
-
const pressed = choice || DEFAULT;
|
|
42
|
-
return [followSegment(summary.level),
|
|
43
|
-
...LEVELS.map((level) => ({ value: level, label: level }))].map((s) => ({ ...s, pressed: s.value === pressed }));
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// The Context row's segments over the strategies (a choice that isn't one
|
|
47
|
-
// of them, a newer chi's layer, is added); null without the model's
|
|
48
|
-
// llm_context (the row is hidden).
|
|
49
|
-
export function contextSegments(summary, choice) {
|
|
50
|
-
if (!summary?.strategy) return null;
|
|
51
|
-
const pressed = Array.isArray(choice?.strategy) ? strategyValue(choice.strategy) : DEFAULT;
|
|
52
|
-
const list = STRATEGIES.some((s) => s.value === pressed) || pressed === DEFAULT ? STRATEGIES
|
|
53
|
-
: [...STRATEGIES, { value: pressed, label: pressed }];
|
|
54
|
-
return [followSegment(summary.strategy), ...list].map((s) => ({ ...s, pressed: s.value === pressed }));
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
// The context choice in a few words ("stale · budget 64000"), or null.
|
|
58
|
-
export function contextWords(choice) {
|
|
59
|
-
const words = choiceWords(choice);
|
|
60
|
-
if (!words) return null;
|
|
61
|
-
return [words.strategy, words.apply && `apply ${words.apply}`, words.budget && `budget ${words.budget}`]
|
|
62
|
-
.filter(Boolean).join(" · ");
|
|
63
|
-
}
|
|
64
|
-
|
|
65
29
|
// The row's override chips: what is set for the new session, each
|
|
66
30
|
// {text, title} or null. A choice the picked model shows no row for (its
|
|
67
31
|
// summary is null) stays, its title saying it is sent anyway.
|
|
@@ -92,53 +56,26 @@ export function settingsLabel(over) {
|
|
|
92
56
|
return { set: set.length > 0, label: set.length ? `${PANEL_LABEL}: ${set.map((o) => o.text).join(", ")}` : PANEL_LABEL };
|
|
93
57
|
}
|
|
94
58
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
const option = (value, label, chosen) => html`<option value=${value} ?selected=${value === chosen}>${label}</option>`;
|
|
101
|
-
|
|
102
|
-
// The disclosure's fields: the apply rule and the budget, with their own
|
|
103
|
-
// Set (a typed budget needs a commit) and mergeChoice's error under them.
|
|
104
|
-
function contextMoreTemplate(summary, choice, error) {
|
|
105
|
-
const own = ownValues({ own: choice });
|
|
106
|
-
const budget = summary.budget_tokens ? String(summary.budget_tokens) : "off";
|
|
107
|
-
return html`<form class="sset-ctx-form"><label class="llmctx-field"><span>Apply</span><span class="select-wrap"><select
|
|
108
|
-
name="apply">${option(DEFAULT, `${DEFAULT} (${summary.apply})`, own.apply)}${APPLIES.map((a) => option(a, a, own.apply))}</select
|
|
109
|
-
></span></label><label class="llmctx-field"><span>Budget</span><input name="budget" type="text" inputmode="numeric"
|
|
110
|
-
autocomplete="off" placeholder=${`${DEFAULT} (${budget}): 64000, 64k or off`} .value=${own.budget}></label
|
|
111
|
-
><div class="ctx-pop-error" role="alert" ?hidden=${!error}>${error || ""}</div
|
|
112
|
-
><div class="ctx-pop-actions"><button type="submit" class="llmctx-set">Set</button></div></form>`;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
// A choice whose row is hidden (the picked model has no values for it).
|
|
116
|
-
const sentAnywayTemplate = (text, act) => html`<div class="sset-anyway ctx-pop-meta">${text}
|
|
117
|
-
<button type="button" class="sset-link" data-act=${act}>Clear</button></div>`;
|
|
118
|
-
|
|
119
|
-
// The main view. +state+: {thinking: {summary, choice}, llmContext: {summary,
|
|
120
|
-
// choice}, review: subjects or null (no review row), more: the disclosure
|
|
121
|
-
// open, error: the context Set's}.
|
|
59
|
+
// The main view (settings_panel.js settingsPanelTemplate over the new
|
|
60
|
+
// session's choices). +state+: {thinking: {summary, choice}, llmContext:
|
|
61
|
+
// {summary, choice}, review: subjects or null (no review row), more: the
|
|
62
|
+
// disclosure open, error: the context Set's}.
|
|
122
63
|
export function panelTemplate({ thinking, llmContext, review = null, more = false, error = null }) {
|
|
123
64
|
const think = thinkingSegments(thinking.summary, thinking.choice);
|
|
124
65
|
const ctx = contextSegments(llmContext.summary, llmContext.choice);
|
|
125
66
|
const ctxAnyway = !ctx && contextWords(llmContext.choice);
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
data-act="review-clear">Remove</button>`
|
|
139
|
-
: html`<button type="button" class="sset-link" data-act="review" title=${reviewChipModel([]).title}>Review files…</button>`}</div>`
|
|
140
|
-
: nothing
|
|
141
|
-
}<div class="ctx-pop-meta">${NOTE}</div>`;
|
|
67
|
+
return settingsPanelTemplate({
|
|
68
|
+
heading: "New session",
|
|
69
|
+
rows: [
|
|
70
|
+
think ? { name: "think", label: "Thinking", segments: think, act: "think" } : null,
|
|
71
|
+
!think && thinking.choice ? { anyway: `Thinking ${thinking.choice}: ${NO_THINKING}.`, act: "think-clear" } : null,
|
|
72
|
+
ctx ? { name: "ctx", label: "Context", segments: ctx, act: "ctx",
|
|
73
|
+
more: { open: more, summary: llmContext.summary, choice: llmContext.choice, error } } : null,
|
|
74
|
+
ctxAnyway ? { anyway: `Context ${ctxAnyway}: ${NO_LLM_CONTEXT}.`, act: "ctx-clear" } : null,
|
|
75
|
+
],
|
|
76
|
+
review,
|
|
77
|
+
note: NOTE,
|
|
78
|
+
});
|
|
142
79
|
}
|
|
143
80
|
|
|
144
81
|
// The panel on chip_popover.js' shell, a bottom sheet on a phone. +el+:
|