samagotchi 0.131.0 → 0.133.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 +88 -1
- data/bin/chi +19 -1
- data/docs/architecture.md +59 -1
- data/docs/cli.md +29 -8
- data/docs/configuration.md +115 -16
- data/docs/desktop.md +16 -6
- data/docs/guardrails.md +10 -4
- data/docs/internals/anthropic.md +3 -2
- data/docs/internals/engine.md +4 -4
- data/docs/internals/prompt-caching.md +26 -6
- data/docs/memory.md +16 -2
- data/docs/plugins.md +68 -1
- data/docs/sessions.md +60 -6
- data/docs/testing.md +1 -1
- data/lib/samagotchi/bootstrap_command.rb +35 -6
- data/lib/samagotchi/bundles/mcp/manifest.yml +2 -2
- data/lib/samagotchi/bundles/mcp/plugin.rb +10 -3
- data/lib/samagotchi/bundles/model-notes/manifest.yml +4 -1
- data/lib/samagotchi/bundles/model-notes/model_notes_tone.md +3 -0
- data/lib/samagotchi/bundles/model-notes/notes/README.md +14 -0
- data/lib/samagotchi/bundles/system/manifest.yml +1 -1
- data/lib/samagotchi/cache_confirm.rb +18 -0
- data/lib/samagotchi/chi_home.rb +311 -0
- data/lib/samagotchi/chi_shim.rb +77 -0
- data/lib/samagotchi/child_env.rb +5 -3
- data/lib/samagotchi/config.rb +2 -0
- data/lib/samagotchi/context_fetch.rb +4 -2
- data/lib/samagotchi/desktop/macos.rb +100 -18
- data/lib/samagotchi/engine.rb +35 -4
- data/lib/samagotchi/engine_snapshots.rb +5 -0
- data/lib/samagotchi/gem_update.rb +19 -10
- data/lib/samagotchi/guardrail_wiring.rb +3 -1
- data/lib/samagotchi/guardrails/pair_boundary.rb +21 -2
- data/lib/samagotchi/guardrails/pair_command.rb +57 -1
- data/lib/samagotchi/guardrails/protected_paths.rb +6 -3
- data/lib/samagotchi/guardrails/review_paths.rb +7 -4
- data/lib/samagotchi/guardrails/verdict.rb +1 -1
- data/lib/samagotchi/home_command.rb +145 -0
- data/lib/samagotchi/home_doctor.rb +177 -0
- data/lib/samagotchi/home_notice.rb +80 -0
- data/lib/samagotchi/installed_versions.rb +5 -3
- data/lib/samagotchi/llm_context_command.rb +53 -7
- data/lib/samagotchi/llm_context_override.rb +3 -2
- data/lib/samagotchi/plugin/api.rb +45 -1
- data/lib/samagotchi/plugin/context.rb +34 -1
- data/lib/samagotchi/plugin/loader.rb +5 -2
- data/lib/samagotchi/plugin/prompt_sections.rb +124 -0
- data/lib/samagotchi/project_env.rb +360 -0
- data/lib/samagotchi/run_options.rb +1 -0
- data/lib/samagotchi/self_report.rb +33 -0
- data/lib/samagotchi/session_access/contract.rb +1 -1
- data/lib/samagotchi/session_commands.rb +68 -19
- data/lib/samagotchi/session_manager.rb +54 -1
- data/lib/samagotchi/session_model_settings.rb +5 -2
- data/lib/samagotchi/system_prompt.rb +34 -3
- data/lib/samagotchi/terminal_ui/attach_launcher.rb +3 -2
- data/lib/samagotchi/terminal_ui/formatting.rb +3 -0
- data/lib/samagotchi/testing/boundary.rb +3 -1
- data/lib/samagotchi/testing/bundle.rb +22 -3
- data/lib/samagotchi/testing/host.rb +22 -1
- data/lib/samagotchi/testing/recorder.rb +6 -1
- data/lib/samagotchi/testing/recording_api.rb +1 -1
- data/lib/samagotchi/tools/builtins.rb +13 -2
- data/lib/samagotchi/update_command.rb +65 -7
- data/lib/samagotchi/version.rb +1 -1
- data/lib/samagotchi/web/app.rb +61 -1
- data/lib/samagotchi/web/public/app.js +4 -1
- data/lib/samagotchi/web/public/chip_popover.js +5 -2
- data/lib/samagotchi/web/public/composer.css +11 -1
- data/lib/samagotchi/web/public/data.js +8 -4
- data/lib/samagotchi/web/public/index.html +52 -10
- data/lib/samagotchi/web/public/level_chip.js +22 -38
- data/lib/samagotchi/web/public/llm_context_chip.js +30 -73
- data/lib/samagotchi/web/public/model_pick.js +26 -0
- data/lib/samagotchi/web/public/model_picker.js +3 -1
- data/lib/samagotchi/web/public/review_start.js +13 -57
- data/lib/samagotchi/web/public/send_turn.js +3 -2
- data/lib/samagotchi/web/public/start_page.js +131 -99
- data/lib/samagotchi/web/public/start_settings.js +298 -0
- data/lib/samagotchi/web/public/start_toolkit.js +157 -0
- data/lib/samagotchi/web/public/thinking_chip.js +9 -22
- data/lib/samagotchi/web/server.rb +27 -8
- data/lib/samagotchi/web/update_check.rb +3 -2
- metadata +11 -1
|
@@ -7,10 +7,9 @@
|
|
|
7
7
|
// worker's snapshot, a command_ran, or the server's own reading of the
|
|
8
8
|
// session file).
|
|
9
9
|
//
|
|
10
|
-
// The start page
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
// remembered: nothing is sent unless the user sets one.
|
|
10
|
+
// The start page's new session sets its strategy in the ⚙ panel instead
|
|
11
|
+
// (start_settings.js): mergeChoice and choiceWords are its, a choice in the
|
|
12
|
+
// session file's shape sent as POST /api/sessions' llm_context words.
|
|
14
13
|
|
|
15
14
|
import { createChipPopover } from "./chip_popover.js";
|
|
16
15
|
import { html, nothing, render } from "./vendor/lit-html.js";
|
|
@@ -22,7 +21,6 @@ const STRATEGIES = [
|
|
|
22
21
|
];
|
|
23
22
|
export const APPLIES = ["payoff", "next_request", "turn_end"];
|
|
24
23
|
export const SESSION = "session";
|
|
25
|
-
export const NEW_SESSION = "new session";
|
|
26
24
|
|
|
27
25
|
const DEFAULT = "default";
|
|
28
26
|
const LAYER_ORDER = ["stale", "forget"];
|
|
@@ -43,34 +41,15 @@ export function ownValues(summary) {
|
|
|
43
41
|
return { strategy, apply: own.apply || DEFAULT, budget };
|
|
44
42
|
}
|
|
45
43
|
|
|
46
|
-
// The
|
|
47
|
-
//
|
|
48
|
-
|
|
49
|
-
// "session" ("the new session"). The model's own summary without a choice.
|
|
50
|
-
export function withChoice(summary, choice) {
|
|
51
|
-
if (!summary || !choice) return summary;
|
|
52
|
-
const merged = { ...summary, own: choice };
|
|
53
|
-
// (the budget's source and where are budget_source / budget_where)
|
|
54
|
-
const set = (field, key, value) => Object.assign(merged, { [key]: value, [`${field}_source`]: SESSION,
|
|
55
|
-
[`${field}_where`]: "the new session" });
|
|
56
|
-
if (Array.isArray(choice.strategy)) set("strategy", "strategy", strategyValue(choice.strategy));
|
|
57
|
-
if (choice.apply) set("apply", "apply", choice.apply);
|
|
58
|
-
if (typeof choice.budget_tokens === "number") {
|
|
59
|
-
set("budget", "budget_tokens", choice.budget_tokens > 0 ? choice.budget_tokens : null);
|
|
60
|
-
}
|
|
61
|
-
return merged;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// The chip: its text, tooltip and whether the session (or the new session)
|
|
65
|
-
// set its own; null without a summary (an older worker).
|
|
66
|
-
export function chipModel(summary, { kind = SESSION } = {}) {
|
|
44
|
+
// The chip: its text, tooltip and whether the session set its own; null
|
|
45
|
+
// without a summary (an older worker).
|
|
46
|
+
export function chipModel(summary) {
|
|
67
47
|
if (!summary || !summary.strategy) return null;
|
|
68
48
|
const own = summary.strategy_source === "session" || !!summary.own;
|
|
69
49
|
const budget = summary.budget_tokens ? `${summary.budget_tokens} tokens` : "off";
|
|
70
|
-
const what = kind === NEW_SESSION ? "the new session" : "this session";
|
|
71
50
|
const title = `LLM context strategy: ${summary.strategy} (${summary.strategy_where}); apply ${summary.apply} ` +
|
|
72
|
-
`(${summary.apply_where}); budget ${budget} (${summary.budget_where}). Click to change it for
|
|
73
|
-
return { text: `llm ctx ${summary.strategy}${own ? ` · ${
|
|
51
|
+
`(${summary.apply_where}); budget ${budget} (${summary.budget_where}). Click to change it for this session.`;
|
|
52
|
+
return { text: `llm ctx ${summary.strategy}${own ? ` · ${SESSION}` : ""}`, title, own };
|
|
74
53
|
}
|
|
75
54
|
|
|
76
55
|
// The session bar's chip (a lit-html template; info_bar.js), nothing
|
|
@@ -85,14 +64,15 @@ export function chipTemplate(summary) {
|
|
|
85
64
|
// the form opened with) only the fields the user changed go, so a value the
|
|
86
65
|
// form can't show is never unset by a Set; null when nothing changed.
|
|
87
66
|
// Without it every field goes, "default" for one left to the model, and all
|
|
88
|
-
// of them default is a reset.
|
|
67
|
+
// of them default is a reset. With --yes: the form states what a change
|
|
68
|
+
// costs, so a Set is the choice (no confirm step), as level_chip.js's.
|
|
89
69
|
export function commandLine({ strategy = DEFAULT, apply = DEFAULT, budget = "" } = {}, initial = null) {
|
|
90
70
|
const words = { strategy, apply, budget: String(budget).trim() || DEFAULT };
|
|
91
71
|
const was = initial && { strategy: initial.strategy, apply: initial.apply, budget: String(initial.budget).trim() || DEFAULT };
|
|
92
72
|
const fields = Object.keys(words).filter((field) => !was || words[field] !== was[field]);
|
|
93
73
|
if (!fields.length) return null;
|
|
94
|
-
if (!was && fields.every((field) => words[field] === DEFAULT)) return "/llm-context reset";
|
|
95
|
-
return `/llm-context ${fields.map((field) => `${field} ${words[field]}`).join(" ")}`;
|
|
74
|
+
if (!was && fields.every((field) => words[field] === DEFAULT)) return "/llm-context reset --yes";
|
|
75
|
+
return `/llm-context ${fields.map((field) => `${field} ${words[field]}`).join(" ")} --yes`;
|
|
96
76
|
}
|
|
97
77
|
|
|
98
78
|
// A budget as typed ("64000", "64k", "off", "0") in tokens, 0 for off;
|
|
@@ -107,10 +87,10 @@ export function parseBudget(text) {
|
|
|
107
87
|
return tokens >= 4000 && tokens <= 10_000_000 ? tokens : null;
|
|
108
88
|
}
|
|
109
89
|
|
|
110
|
-
// The new session's choice after a
|
|
111
|
-
// changed (vs +initial+, the values
|
|
112
|
-
// set back to default dropped; null when nothing
|
|
113
|
-
// budget that isn't one.
|
|
90
|
+
// The new session's choice (start_settings.js) after a pick or a Set:
|
|
91
|
+
// +choice+ with the fields the user changed (vs +initial+, the values
|
|
92
|
+
// before) applied, a field set back to default dropped; null when nothing
|
|
93
|
+
// is left. Throws on a budget that isn't one.
|
|
114
94
|
export function mergeChoice(choice, { strategy = DEFAULT, apply = DEFAULT, budget = "" } = {}, initial = null) {
|
|
115
95
|
const next = { ...(choice || {}) };
|
|
116
96
|
const words = { strategy, apply, budget: String(budget).trim() || DEFAULT };
|
|
@@ -160,23 +140,18 @@ export function nowText(summary) {
|
|
|
160
140
|
`budget ${budget} (${summary.budget_where})`;
|
|
161
141
|
}
|
|
162
142
|
|
|
163
|
-
// The popover's form (a lit-html template).
|
|
164
|
-
//
|
|
165
|
-
//
|
|
166
|
-
//
|
|
167
|
-
//
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
export function formTemplate(summary, { running = false, kind = SESSION, choice = null, now = summary, error = null,
|
|
171
|
-
unset = false } = {}) {
|
|
172
|
-
const fresh = kind === NEW_SESSION;
|
|
173
|
-
const own = fresh ? ownValues({ own: choice }) : ownValues(summary);
|
|
143
|
+
// The popover's form (a lit-html template). +now+ is the summary its "Now:"
|
|
144
|
+
// line reads (refresh passes a newer one, the rest stays as the form opened,
|
|
145
|
+
// so lit leaves what the user typed alone); +error+ a failed Set's message.
|
|
146
|
+
// The selects take their value as a property (.value), set after their
|
|
147
|
+
// options: +unset+ draws the options alone, the first pass renderForm makes.
|
|
148
|
+
export function formTemplate(summary, { running = false, now = summary, error = null, unset = false } = {}) {
|
|
149
|
+
const own = ownValues(summary);
|
|
174
150
|
const follow = (field) => (summary[`${field}_source`] === "session" ? "follow the model" : `follow the model (${summary[field]})`);
|
|
175
|
-
const when =
|
|
176
|
-
: "From the next turn's start. Turning forget on or off re-reads the whole prompt once.";
|
|
151
|
+
const when = "From the next turn's start. Turning forget on or off re-reads the whole prompt once.";
|
|
177
152
|
const chosen = (value) => (unset ? nothing : value);
|
|
178
153
|
return html`<form class="llmctx-form"><div class="ctx-pop-head"><strong>LLM context</strong
|
|
179
|
-
><span class="ctx-pop-kind"
|
|
154
|
+
><span class="ctx-pop-kind">this session</span
|
|
180
155
|
><button type="button" class="ctx-pop-close" data-act="close" aria-label="Close">×</button></div
|
|
181
156
|
><div class="ctx-pop-meta llmctx-now">${nowText(now)}</div
|
|
182
157
|
><label class="llmctx-field"><span>Strategy</span><span class="select-wrap"><select name="strategy" .value=${chosen(own.strategy)}>${
|
|
@@ -205,16 +180,11 @@ export function renderForm(target, summary, opts = {}) {
|
|
|
205
180
|
// The chip's popover (chip_popover.js' shell). el: where the chip is drawn
|
|
206
181
|
// (#infoText, info_bar.js: clicks are delegated); summary(): the open
|
|
207
182
|
// session's llm_context; session(): the open session's id; running(): a
|
|
208
|
-
// turn runs; run(line): sends the command.
|
|
209
|
-
//
|
|
210
|
-
// choice() the pending choice, and choose(next) takes a Set's (null for
|
|
211
|
-
// "follow the model") instead of a command running. id and anchor (the
|
|
212
|
-
// chip's selector) keep the two controls apart.
|
|
183
|
+
// turn runs; run(line): sends the command. id and anchor: the popover's id
|
|
184
|
+
// and the chip's selector.
|
|
213
185
|
export function createLlmContextControl({ el, summary, session = () => null, running = () => false, run,
|
|
214
|
-
kind = SESSION, choice = () => null, choose = null,
|
|
215
186
|
id = "llmContextPopover", anchor = ".llmctx-chip",
|
|
216
187
|
doc = document, win = window }) {
|
|
217
|
-
const fresh = kind === NEW_SESSION;
|
|
218
188
|
// The values the form opened with.
|
|
219
189
|
let initial = null;
|
|
220
190
|
// What the open form was drawn from: the summary, the form's options and
|
|
@@ -230,26 +200,14 @@ export function createLlmContextControl({ el, summary, session = () => null, run
|
|
|
230
200
|
// line alone).
|
|
231
201
|
function show(data, opening) {
|
|
232
202
|
if (!opening) return draw({ now: data });
|
|
233
|
-
initial =
|
|
234
|
-
shown = { data, opts: { running: running()
|
|
203
|
+
initial = ownValues(data);
|
|
204
|
+
shown = { data, opts: { running: running() } };
|
|
235
205
|
draw({ now: data, error: null });
|
|
236
206
|
}
|
|
237
207
|
|
|
238
208
|
function submit(form) {
|
|
239
209
|
const values = { strategy: form.elements.strategy.value, apply: form.elements.apply.value,
|
|
240
210
|
budget: form.elements.budget.value };
|
|
241
|
-
if (fresh) {
|
|
242
|
-
let next;
|
|
243
|
-
try {
|
|
244
|
-
next = mergeChoice(choice(), values, initial);
|
|
245
|
-
} catch (err) {
|
|
246
|
-
draw({ error: err.message });
|
|
247
|
-
return;
|
|
248
|
-
}
|
|
249
|
-
shell.close();
|
|
250
|
-
choose(next);
|
|
251
|
-
return;
|
|
252
|
-
}
|
|
253
211
|
const line = commandLine(values, initial);
|
|
254
212
|
shell.close();
|
|
255
213
|
if (line) run(line);
|
|
@@ -258,8 +216,7 @@ export function createLlmContextControl({ el, summary, session = () => null, run
|
|
|
258
216
|
function act(name) {
|
|
259
217
|
if (name !== "reset") return;
|
|
260
218
|
shell.close();
|
|
261
|
-
|
|
262
|
-
else run(commandLine());
|
|
219
|
+
run(commandLine());
|
|
263
220
|
}
|
|
264
221
|
|
|
265
222
|
const shell = createChipPopover({
|
|
@@ -58,6 +58,32 @@ export function modelRows(payload) {
|
|
|
58
58
|
});
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
+
// A host-less server fallback may still carry "host:org/id".
|
|
62
|
+
function shortNameCandidate(row) {
|
|
63
|
+
const name = String(row?.name || row?.id || "");
|
|
64
|
+
let host = row?.host ? String(row.host) : "";
|
|
65
|
+
let id = String(row?.id || name);
|
|
66
|
+
if (!host) {
|
|
67
|
+
const colon = name.indexOf(":");
|
|
68
|
+
const slash = name.indexOf("/");
|
|
69
|
+
if (colon > 0 && slash > colon) {
|
|
70
|
+
host = name.slice(0, colon);
|
|
71
|
+
id = name.slice(colon + 1);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
const model = id.slice(id.lastIndexOf("/") + 1) || name;
|
|
75
|
+
const labelHost = host && (!row?.isDefaultHost || !row?.host) ? `${host} · ` : "";
|
|
76
|
+
return { name, label: `${labelHost}${model}` };
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// A compact label for the chosen model; list rows and all stored/API names
|
|
80
|
+
// remain full.
|
|
81
|
+
export function shortName(row, rows = []) {
|
|
82
|
+
const candidate = shortNameCandidate(row);
|
|
83
|
+
const clashes = rows.filter((other) => shortNameCandidate(other).label === candidate.label).length;
|
|
84
|
+
return clashes > 1 ? candidate.name : candidate.label;
|
|
85
|
+
}
|
|
86
|
+
|
|
61
87
|
const byId = (a, b) => (b.configured === true) - (a.configured === true) ||
|
|
62
88
|
a.id.localeCompare(b.id, "en", { sensitivity: "base" });
|
|
63
89
|
|
|
@@ -13,6 +13,7 @@ import { moveSelection } from "./command_complete.js";
|
|
|
13
13
|
import { readStored, writeStored } from "./storage.js";
|
|
14
14
|
import {
|
|
15
15
|
MODEL_KEY, RECENT_KEY, groupRows, matchModels, modelRows, pickModel, pushRecent, recentRows, rowNote, rowTitle,
|
|
16
|
+
shortName,
|
|
16
17
|
} from "./model_pick.js";
|
|
17
18
|
import { html, nothing, render as draw } from "./vendor/lit-html.js";
|
|
18
19
|
|
|
@@ -63,7 +64,8 @@ export function createModelPicker({ button, panel, input, list, composer, center
|
|
|
63
64
|
// app.js', carries the host's warning).
|
|
64
65
|
function setChosen(name) {
|
|
65
66
|
chosen = name;
|
|
66
|
-
|
|
67
|
+
const row = rows.find((item) => item.name === name);
|
|
68
|
+
button.querySelector(".model-pick-name").textContent = row ? shortName(row, rows) : name;
|
|
67
69
|
button.classList.toggle("unavailable", rows.some((r) => r.name === name && r.unavailable === true));
|
|
68
70
|
}
|
|
69
71
|
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
// Start review (the web's entry points to a review session, docs/sessions.md
|
|
2
|
-
// Review sessions): the start page's review
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
// ({review: {from}}): its recap,
|
|
6
|
-
|
|
7
|
-
import {
|
|
2
|
+
// Review sessions): the start page's review files (the ⚙ panel's review
|
|
3
|
+
// view, start_settings.js), which make the new session a review session
|
|
4
|
+
// (POST /api/sessions {review: {subjects}}), and the session bar's review
|
|
5
|
+
// button, a review fork of the open session ({review: {from}}): its recap,
|
|
6
|
+
// not its transcript; it keeps running.
|
|
7
|
+
import { html, nothing } from "./vendor/lit-html.js";
|
|
8
8
|
|
|
9
9
|
// The files typed: split on commas, spaces and new lines, no repeats; a
|
|
10
10
|
// path with a space (or a comma) in double quotes ("docs/my plan.md").
|
|
@@ -25,7 +25,7 @@ export function formatSubjects(subjects) {
|
|
|
25
25
|
return (subjects || []).map((s) => (/[\s,"]/.test(s) ? `"${s}"` : s)).join(", ");
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
// The start page's chip: "review…" with none, else the files' names.
|
|
28
|
+
// The start page's override chip: "review…" with none, else the files' names.
|
|
29
29
|
export function reviewChipModel(subjects) {
|
|
30
30
|
const list = subjects || [];
|
|
31
31
|
if (!list.length) {
|
|
@@ -37,9 +37,12 @@ export function reviewChipModel(subjects) {
|
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
// +error+: why the files weren't taken (a quote left open, the server's
|
|
40
|
-
// refusal at Start), shown above Set; +text+ the form's text as typed
|
|
41
|
-
|
|
42
|
-
|
|
40
|
+
// refusal at Start), shown above Set; +text+ the form's text as typed;
|
|
41
|
+
// +back+: a ‹ before the heading (data-act="back": the start page's ⚙
|
|
42
|
+
// panel, start_settings.js, whose view this is).
|
|
43
|
+
export function reviewFormTemplate(subjects, { error = null, text = null, back = false } = {}) {
|
|
44
|
+
return html`<div class="ctx-pop-head">${back ? html`<button type="button" class="ss-back" data-act="back"
|
|
45
|
+
aria-label="Back to the settings" title="Back to the settings">‹</button>` : nothing}<strong>Review</strong><span class="ctx-pop-kind">new session</span
|
|
43
46
|
><button type="button" class="ctx-pop-close" data-act="close" aria-label="Close">×</button></div>
|
|
44
47
|
<div class="ctx-pop-meta" id="reviewNewHelp">The files participants read beside the thread: project paths, relative to this folder,
|
|
45
48
|
a path with a space in quotes. Secrets (.env, keys) and files outside the project are refused.</div>
|
|
@@ -50,50 +53,3 @@ export function reviewFormTemplate(subjects, { error = null, text = null } = {})
|
|
|
50
53
|
<div class="ctx-pop-actions">${subjects?.length ? html`<button type="button" data-act="clear">No review</button>` : nothing}<button
|
|
51
54
|
type="submit">Set</button></div></form>`;
|
|
52
55
|
}
|
|
53
|
-
|
|
54
|
-
// The start page's chip and its form. +el+ the chip; +choose(subjects)+.
|
|
55
|
-
// refuse(message): Start's refusal of the files (400 invalid_review), said
|
|
56
|
-
// in the form, opened again at the files chosen.
|
|
57
|
-
export function createReviewStartControl({ el, choice, choose, doc = document, win = window }) {
|
|
58
|
-
let error = null;
|
|
59
|
-
let text = null;
|
|
60
|
-
const draw = () => {
|
|
61
|
-
render(reviewFormTemplate(choice(), { error, text }), shell.pop);
|
|
62
|
-
shell.replace();
|
|
63
|
-
};
|
|
64
|
-
const shell = createChipPopover({
|
|
65
|
-
id: "reviewNewPopover", className: "ctx-popover review-popover", width: 360, el, anchor: `#${el.id}`,
|
|
66
|
-
summary: () => ({}), sheet: true, label: "Review files", doc, win,
|
|
67
|
-
show: () => {
|
|
68
|
-
draw();
|
|
69
|
-
shell.pop.querySelector("textarea")?.focus();
|
|
70
|
-
},
|
|
71
|
-
onClose: () => { error = null; text = null; },
|
|
72
|
-
onSubmit: (form) => {
|
|
73
|
-
const typed = form.elements.subjects?.value || "";
|
|
74
|
-
const read = readSubjects(typed);
|
|
75
|
-
if (read.error) {
|
|
76
|
-
error = read.error;
|
|
77
|
-
text = typed;
|
|
78
|
-
return draw();
|
|
79
|
-
}
|
|
80
|
-
choose(read.subjects);
|
|
81
|
-
shell.close();
|
|
82
|
-
},
|
|
83
|
-
onAct: (name) => {
|
|
84
|
-
if (name === "clear") {
|
|
85
|
-
choose([]);
|
|
86
|
-
shell.close();
|
|
87
|
-
}
|
|
88
|
-
},
|
|
89
|
-
});
|
|
90
|
-
return {
|
|
91
|
-
close: () => shell.close(),
|
|
92
|
-
refuse: (message) => {
|
|
93
|
-
error = message;
|
|
94
|
-
text = null;
|
|
95
|
-
if (shell.isOpen()) draw();
|
|
96
|
-
else shell.open(el);
|
|
97
|
-
},
|
|
98
|
-
};
|
|
99
|
-
}
|
|
@@ -54,10 +54,11 @@ export function createSendTurn({
|
|
|
54
54
|
}
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
-
// A create's refusal: the review files' in the review
|
|
58
|
-
//
|
|
57
|
+
// A create's refusal: the review files' in the ⚙ panel's review view, a
|
|
58
|
+
// picked kit's in the 🧰 list, else an alert.
|
|
59
59
|
function refused(e) {
|
|
60
60
|
if (e?.code === "invalid_review" && startPage.reviewRefused?.(e.message)) return;
|
|
61
|
+
if (e?.code === "toolkit_refused" && startPage.toolkitRefused?.(e.message)) return;
|
|
61
62
|
alert(e.message);
|
|
62
63
|
}
|
|
63
64
|
|