samagotchi 0.76.0 → 0.77.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 +26 -1
- data/docs/internals/engine.md +1 -1
- data/docs/plugins.md +42 -0
- data/lib/samagotchi/answer_command.rb +5 -5
- data/lib/samagotchi/bridge.rb +3 -3
- data/lib/samagotchi/bundles/source-links/hooks/source_links.rb +12 -10
- data/lib/samagotchi/bundles/source-links/manifest.yml +2 -2
- data/lib/samagotchi/bundles/system/manifest.yml +1 -1
- data/lib/samagotchi/child_reports.rb +1 -1
- data/lib/samagotchi/engine.rb +21 -12
- data/lib/samagotchi/guardrails/parent_approvals.rb +4 -4
- data/lib/samagotchi/guardrails/parent_continue.rb +2 -2
- data/lib/samagotchi/memory_bundle/builder.rb +2 -0
- data/lib/samagotchi/message_parts.rb +272 -0
- data/lib/samagotchi/parent_report.rb +30 -22
- data/lib/samagotchi/pending_question.rb +49 -0
- data/lib/samagotchi/question_desk.rb +39 -32
- data/lib/samagotchi/relay.rb +8 -9
- data/lib/samagotchi/relay_verifier.rb +2 -2
- data/lib/samagotchi/relay_watcher.rb +2 -2
- data/lib/samagotchi/reply_wait.rb +4 -4
- data/lib/samagotchi/row_refs.rb +1 -1
- data/lib/samagotchi/session.rb +22 -14
- data/lib/samagotchi/session_commands.rb +4 -1
- data/lib/samagotchi/terminal_ui/attach_launcher.rb +3 -2
- data/lib/samagotchi/terminal_ui/attached_loop.rb +2 -2
- data/lib/samagotchi/terminal_ui/formatting.rb +3 -106
- data/lib/samagotchi/terminal_ui/model_segments_text.rb +118 -0
- data/lib/samagotchi/terminal_ui.rb +5 -3
- data/lib/samagotchi/tool_output_cut.rb +1 -1
- data/lib/samagotchi/tool_response.rb +13 -13
- data/lib/samagotchi/tool_row_fields.rb +1 -1
- data/lib/samagotchi/tool_runner.rb +16 -9
- data/lib/samagotchi/tools/delegate_relay.rb +7 -7
- data/lib/samagotchi/tools/delegate_wait.rb +1 -1
- data/lib/samagotchi/tools/web_fetch.rb +3 -1
- data/lib/samagotchi/version.rb +1 -1
- data/lib/samagotchi/web/app.rb +2 -2
- data/lib/samagotchi/web/public/app.js +17 -55
- data/lib/samagotchi/web/public/ctx.js +12 -1
- data/lib/samagotchi/web/public/index.html +2 -1
- data/lib/samagotchi/web/public/markdown_place.js +53 -0
- data/lib/samagotchi/web/public/stream_handlers.js +7 -3
- data/lib/samagotchi/web/public/toasts.js +69 -0
- metadata +6 -2
- data/lib/samagotchi/web/message_parts.rb +0 -274
|
@@ -37,7 +37,7 @@ module Samagotchi
|
|
|
37
37
|
# @param relay [#open_question, #relay_desk] Peers#relay
|
|
38
38
|
# @return [Boolean] a relay can take this question
|
|
39
39
|
def relayable?(relay, question)
|
|
40
|
-
!relay.nil? && question.is_a?(
|
|
40
|
+
!relay.nil? && question.is_a?(PendingQuestion) && question.kind.to_s == Guardrails::Approval::KIND
|
|
41
41
|
end
|
|
42
42
|
|
|
43
43
|
# Relay the child's pending approval and wait until it settles.
|
|
@@ -45,7 +45,7 @@ module Samagotchi
|
|
|
45
45
|
# @return [Outcome]
|
|
46
46
|
def call(child_id, question, relay:, state_dir:, more: 0)
|
|
47
47
|
child = Session.load(child_id, state_dir: state_dir)
|
|
48
|
-
qid = question
|
|
48
|
+
qid = question.id.to_s
|
|
49
49
|
desk = relay.relay_desk
|
|
50
50
|
relay_id = desk.open(child_id: child_id, child_question_id: qid)
|
|
51
51
|
client = BridgeClient.discover(child_id, session_dir: Session.session_dir(child_id, state_dir: state_dir))
|
|
@@ -70,7 +70,7 @@ module Samagotchi
|
|
|
70
70
|
end
|
|
71
71
|
|
|
72
72
|
pending = Session.load(child_id, state_dir: state_dir).pending_question
|
|
73
|
-
pending && pending
|
|
73
|
+
pending && pending.id.to_s == qid ? nil : "answered_on_child"
|
|
74
74
|
rescue ArgumentError
|
|
75
75
|
"child_gone"
|
|
76
76
|
end
|
|
@@ -175,7 +175,7 @@ module Samagotchi
|
|
|
175
175
|
return if waiting.empty?
|
|
176
176
|
|
|
177
177
|
child_id, question = waiting.first
|
|
178
|
-
@done[question
|
|
178
|
+
@done[question.id.to_s] = true
|
|
179
179
|
outcome = DelegateRelay.call(child_id, question, relay: @relay, state_dir: @state_dir, more: waiting.size - 1)
|
|
180
180
|
@on_outcome.call(child_id, outcome)
|
|
181
181
|
# The next one, if any, right after.
|
|
@@ -186,7 +186,7 @@ module Samagotchi
|
|
|
186
186
|
# @return [Integer]
|
|
187
187
|
def count = waiting.size
|
|
188
188
|
|
|
189
|
-
# @return [Array<Array(String,
|
|
189
|
+
# @return [Array<Array(String, PendingQuestion)>] [child id, pending question],
|
|
190
190
|
# oldest question first
|
|
191
191
|
def waiting
|
|
192
192
|
rows = SessionManager.children_of(@parent_id, state_dir: @state_dir).select do |row|
|
|
@@ -194,10 +194,10 @@ module Samagotchi
|
|
|
194
194
|
end
|
|
195
195
|
rows.filter_map do |row|
|
|
196
196
|
pending = Session.load(row[:id], state_dir: @state_dir).pending_question
|
|
197
|
-
[row[:id], pending] if pending && pending
|
|
197
|
+
[row[:id], pending] if pending && pending.id.to_s == row[:waiting_id].to_s
|
|
198
198
|
rescue ArgumentError
|
|
199
199
|
nil
|
|
200
|
-
end.sort_by { |_, pending| pending
|
|
200
|
+
end.sort_by { |_, pending| pending.created_at.to_s }
|
|
201
201
|
end
|
|
202
202
|
|
|
203
203
|
private
|
|
@@ -91,7 +91,7 @@ module Samagotchi
|
|
|
91
91
|
# asked), the relayed question no longer reported: a turn that
|
|
92
92
|
# ends with no reply during the relay's POST or after it still
|
|
93
93
|
# ends the wait.
|
|
94
|
-
baseline = ReplyWait.baseline_of(wait.session, question_id: wait.question
|
|
94
|
+
baseline = ReplyWait.baseline_of(wait.session, question_id: wait.question.id)
|
|
95
95
|
end
|
|
96
96
|
rescue ArgumentError => e
|
|
97
97
|
"Error: #{e.message}"
|
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
require "net/http"
|
|
4
4
|
require "uri"
|
|
5
5
|
require "ipaddr"
|
|
6
|
-
require "nokogiri"
|
|
7
6
|
require_relative "../version"
|
|
8
7
|
|
|
9
8
|
module Samagotchi
|
|
@@ -128,6 +127,9 @@ module Samagotchi
|
|
|
128
127
|
def self.extract_text_from_html(html)
|
|
129
128
|
return "Error: empty HTML response" if html.nil? || html.empty?
|
|
130
129
|
|
|
130
|
+
# Load lazily so workers don't pay for Nokogiri on every start.
|
|
131
|
+
require "nokogiri"
|
|
132
|
+
|
|
131
133
|
doc = Nokogiri::HTML(html)
|
|
132
134
|
|
|
133
135
|
# Extract noscript text before removing it
|
data/lib/samagotchi/version.rb
CHANGED
data/lib/samagotchi/web/app.rb
CHANGED
|
@@ -43,7 +43,7 @@ require_relative "../recap_store"
|
|
|
43
43
|
require_relative "markdown_renderer"
|
|
44
44
|
require_relative "capabilities"
|
|
45
45
|
require_relative "editor"
|
|
46
|
-
require_relative "message_parts"
|
|
46
|
+
require_relative "../message_parts"
|
|
47
47
|
require_relative "session_summary"
|
|
48
48
|
require_relative "../log"
|
|
49
49
|
require_relative "../paths"
|
|
@@ -855,7 +855,7 @@ module Samagotchi
|
|
|
855
855
|
current_turn = turn_snapshot["current_turn"]
|
|
856
856
|
current_turn ? current_turn["pending_question"] : turn_snapshot["pending_question"]
|
|
857
857
|
elsif owner&.worker?
|
|
858
|
-
session.pending_question
|
|
858
|
+
session.pending_question&.to_h
|
|
859
859
|
end
|
|
860
860
|
end
|
|
861
861
|
|
|
@@ -45,6 +45,7 @@ import { elementOf, emptyAnswerTemplate } from "./turn_html.js";
|
|
|
45
45
|
import { createBubbles, hintEl, outputBubbleTemplate, setBubbleDelivery } from "./bubbles.js";
|
|
46
46
|
import { createCardsPlace } from "./cards_place.js";
|
|
47
47
|
import { createStageView, FOLD_MS } from "./stage_view.js";
|
|
48
|
+
import { createMarkdownPlace } from "./markdown_place.js";
|
|
48
49
|
import { reducedMotion, STAGE_GAP } from "./stage_frame.js";
|
|
49
50
|
import { chainNeighbours, continueRefusal, continuedText, freshLink, openChildrenTemplate, openerText, openTitle } from "./chains.js";
|
|
50
51
|
import { createTaskStopper } from "./task_stop.js";
|
|
@@ -86,6 +87,7 @@ import { liveSlots } from "./stage_model.js";
|
|
|
86
87
|
import { currentGen } from "./turn_model.js";
|
|
87
88
|
import { toolName } from "./activity.js";
|
|
88
89
|
import { toastBottom } from "./toast_place.js";
|
|
90
|
+
import { createToasts } from "./toasts.js";
|
|
89
91
|
|
|
90
92
|
const $ = (s) => document.querySelector(s);
|
|
91
93
|
const topStripEl = $("#topStrip");
|
|
@@ -231,26 +233,25 @@ const viewArgs = {
|
|
|
231
233
|
// the route (its sidecar features).
|
|
232
234
|
taskStop: createTaskStopper({ sessionId: () => selected, features: () => current.workerFeatures }),
|
|
233
235
|
};
|
|
236
|
+
const markdownPlace = createMarkdownPlace({
|
|
237
|
+
capabilities: () => pageCapabilities,
|
|
238
|
+
renderText,
|
|
239
|
+
roots: () => turnRoots(),
|
|
240
|
+
touchedFiles,
|
|
241
|
+
currentDir: () => current.dir,
|
|
242
|
+
linkRefs,
|
|
243
|
+
applyFinalAnswer,
|
|
244
|
+
findInTurnRoots: (selector) => findInTurnRoots(selector),
|
|
245
|
+
normalize,
|
|
246
|
+
pairAnswerRefs,
|
|
247
|
+
});
|
|
248
|
+
const { renderMarkdown, linkRefsIn, applyFinalMarkdown, linkTextRefs } = markdownPlace;
|
|
234
249
|
const turnDom = view === "stage"
|
|
235
250
|
? createStageView({
|
|
236
251
|
...viewArgs, isNearBottom: () => atHistoryEnd(), dockEl: $("#dock"), centerEl: $("#center"), composerEl,
|
|
237
252
|
renderMarkdown, linkRefs: linkRefsIn,
|
|
238
253
|
})
|
|
239
254
|
: createTurnView(viewArgs);
|
|
240
|
-
// A text the page already shows as its markdown HTML (POST /api/render,
|
|
241
|
-
// sanitised there), or null: markdown off here (no request), or a failed
|
|
242
|
-
// read (the raw text stays).
|
|
243
|
-
function renderMarkdown(text) {
|
|
244
|
-
if (pageCapabilities.display !== true) return Promise.resolve(null);
|
|
245
|
-
return renderText(text)
|
|
246
|
-
.then((d) => (typeof d?.html === "string" && d.html ? d.html : null))
|
|
247
|
-
.catch(() => null);
|
|
248
|
-
}
|
|
249
|
-
// The `path:line` refs in one rendered text (the stage's asking slot, a
|
|
250
|
-
// kept reply), as linkTextRefs links the answers'.
|
|
251
|
-
function linkRefsIn(el) {
|
|
252
|
-
linkRefs(el, touchedFiles(turnRoots()), current.dir);
|
|
253
|
-
}
|
|
254
255
|
// Where a running turn's rows can be: the history, and the stage while it
|
|
255
256
|
// holds a turn (lookups by id, copy, annotate, card actions).
|
|
256
257
|
function turnRoots() {
|
|
@@ -978,32 +979,6 @@ async function readTurnEnd() {
|
|
|
978
979
|
} catch(_){}
|
|
979
980
|
}
|
|
980
981
|
|
|
981
|
-
// The turn's answer as its rendered markdown and with its plugin refs,
|
|
982
|
-
// once it is final (the turn-end read, an answer_display re-read:
|
|
983
|
-
// answer_refs.js applyFinalAnswer); then the refs.
|
|
984
|
-
function applyFinalMarkdown(messages) {
|
|
985
|
-
applyFinalAnswer(messages, findInTurnRoots(".bubble.output"), normalize);
|
|
986
|
-
linkTextRefs();
|
|
987
|
-
}
|
|
988
|
-
|
|
989
|
-
// The refs on the answers' and closed steps' texts, on this page: first
|
|
990
|
-
// each answer's own (a plugin's, answer_refs.js), then the `path:line`
|
|
991
|
-
// refs that name a file the session's rows (and cards) touched
|
|
992
|
-
// (ref_scan.js), which leave a plugin's alone. Not mid-stream: at a
|
|
993
|
-
// turn's end and after a reload's render. A ref already made stays as it
|
|
994
|
-
// is.
|
|
995
|
-
function linkTextRefs() {
|
|
996
|
-
const roots = turnRoots();
|
|
997
|
-
const touched = touchedFiles(roots);
|
|
998
|
-
for (const root of roots) {
|
|
999
|
-
for (const el of root.querySelectorAll(".bubble.output")) pairAnswerRefs(el);
|
|
1000
|
-
}
|
|
1001
|
-
if (!touched.size) return;
|
|
1002
|
-
for (const root of roots) {
|
|
1003
|
-
for (const el of root.querySelectorAll(".bubble.output, .gen:not(.live) > .gen-text")) linkRefs(el, touched, current.dir);
|
|
1004
|
-
}
|
|
1005
|
-
}
|
|
1006
|
-
|
|
1007
982
|
function seedSeenFromDom() {
|
|
1008
983
|
shownTexts.seed(findInTurnRoots(".bubble").map((b) => normalize(b.textContent)));
|
|
1009
984
|
}
|
|
@@ -2053,24 +2028,11 @@ function placeToast() {
|
|
|
2053
2028
|
else root.removeProperty("--toast-bottom");
|
|
2054
2029
|
}
|
|
2055
2030
|
}
|
|
2031
|
+
const toasts = createToasts({ el: $("#toast"), onChange: placeToast });
|
|
2056
2032
|
// The model panel opening, closing or changing height (its list filters).
|
|
2057
2033
|
new ResizeObserver(() => placeToast()).observe(document.getElementById("modelPanel"));
|
|
2058
2034
|
|
|
2059
|
-
|
|
2060
|
-
// words, or a template (chains.js openChildrenTemplate: a link in it, a
|
|
2061
|
-
// delegate a continue waits for, opens that session and hides the toast).
|
|
2062
|
-
let toastTimer = null;
|
|
2063
|
-
function showToast(text, action = null, { sticky = false } = {}) {
|
|
2064
|
-
const el = $("#toast");
|
|
2065
|
-
const hide = () => { el.hidden = true; };
|
|
2066
|
-
render(html`<span @click=${(e) => { if (e.target.closest?.("a[href]")) hide(); }}>${text}</span>${action
|
|
2067
|
-
? html`<button type="button" class="toast-action" @click=${() => { hide(); action.run(); }}>${action.label}</button>`
|
|
2068
|
-
: nothing}${sticky ? html`<button type="button" class="toast-close" title="Dismiss" aria-label="Dismiss" @click=${hide}>✕</button>` : nothing}`, el);
|
|
2069
|
-
el.hidden = false;
|
|
2070
|
-
placeToast();
|
|
2071
|
-
clearTimeout(toastTimer);
|
|
2072
|
-
if (!sticky) toastTimer = setTimeout(hide, 6000);
|
|
2073
|
-
}
|
|
2035
|
+
function showToast(text, action = null, options = {}) { toasts.show(text, action, options); }
|
|
2074
2036
|
|
|
2075
2037
|
// Annotate (annotate_bar.js): a selection in the conversation offers
|
|
2076
2038
|
// "Annotate" and the presets, which append a quote to the composer, and the
|
|
@@ -208,6 +208,17 @@ export function modelSegmentPrefillText(row) {
|
|
|
208
208
|
return `switch prefill ${countK(tokens)} uncached prompt${cost ? ` ≈${cost}` : ""}`;
|
|
209
209
|
}
|
|
210
210
|
|
|
211
|
+
// A ref past +max+ characters with its middle cut to one "…":
|
|
212
|
+
// the tooltip is a native title, which grows as wide as its longest line.
|
|
213
|
+
// The host's start and the model's end stay; the model chip has the full ref.
|
|
214
|
+
const TIP_REF_MAX = 40;
|
|
215
|
+
export function shortRef(text, max = TIP_REF_MAX) {
|
|
216
|
+
const chars = [...text];
|
|
217
|
+
if (chars.length <= max) return text;
|
|
218
|
+
const head = Math.floor((max - 1) / 3);
|
|
219
|
+
return `${chars.slice(0, head).join("")}…${chars.slice(chars.length - (max - 1 - head)).join("")}`;
|
|
220
|
+
}
|
|
221
|
+
|
|
211
222
|
// The tooltip's rows per model, with ≥ 2 (one model: none): "model ·
|
|
212
223
|
// turns · in / out · cost", a switch's prefill under its row.
|
|
213
224
|
function modelSegmentLines(segments) {
|
|
@@ -215,7 +226,7 @@ function modelSegmentLines(segments) {
|
|
|
215
226
|
const lines = ["per model:"];
|
|
216
227
|
for (const row of segments) {
|
|
217
228
|
const turns = num(row.turns) || 0;
|
|
218
|
-
const parts = [modelSegmentLabel(row), `${turns} ${turns === 1 ? "turn" : "turns"}`, modelSegmentTokensText(row)];
|
|
229
|
+
const parts = [shortRef(modelSegmentLabel(row)), `${turns} ${turns === 1 ? "turn" : "turns"}`, modelSegmentTokensText(row)];
|
|
219
230
|
const cost = modelSegmentCostText(row);
|
|
220
231
|
if (cost) parts.push(cost);
|
|
221
232
|
lines.push(` ${parts.join(" · ")}`);
|
|
@@ -1121,8 +1121,9 @@ body.all-view #center, body.all-view #topStrip{display:none}
|
|
|
1121
1121
|
.card.archived:hover{opacity:.85}
|
|
1122
1122
|
.card .archived-badge,.family-row .archived-badge{font-size:10px;padding:1px 6px;border-radius:6px;border:1px solid var(--border-strong);color:var(--text-muted)}
|
|
1123
1123
|
/* bottom: above the session's composer card when one shows (--toast-bottom, app.js placeToast). */
|
|
1124
|
-
#toast{position:fixed;left:50%;bottom:var(--toast-bottom,24px);transform:translateX(-50%);width:max-content;max-width:calc(100vw - 32px);box-sizing:border-box;z-index:50;display:flex;
|
|
1124
|
+
#toast{position:fixed;left:50%;bottom:var(--toast-bottom,24px);transform:translateX(-50%);width:max-content;max-width:calc(100vw - 32px);box-sizing:border-box;z-index:50;display:flex;flex-direction:column;align-items:center;gap:8px}
|
|
1125
1125
|
#toast[hidden]{display:none}
|
|
1126
|
+
#toast .toast-item{display:flex;gap:12px;align-items:center;width:max-content;max-width:100%;box-sizing:border-box;padding:8px 14px;border-radius:10px;background:var(--bg-raised);color:var(--text);border:1px solid var(--border-strong);box-shadow:var(--toast-shadow);font-size:13px}
|
|
1126
1127
|
#toast .toast-action{background:transparent;border:0;color:var(--accent);font-weight:600;cursor:pointer;padding:2px 4px}
|
|
1127
1128
|
#toast .toast-close{background:transparent;border:0;color:var(--text-muted);cursor:pointer;padding:2px 4px;margin-left:-6px}
|
|
1128
1129
|
#toast .toast-close:hover{color:var(--text)}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
// Where the page's rendered markdown and its refs go (moved out of app.js):
|
|
2
|
+
// a text as its markdown HTML, the `path:line` refs in one rendered text,
|
|
3
|
+
// a final answer's rendering and refs, and the refs across the page. The
|
|
4
|
+
// page's state comes in as getters (capabilities, roots, currentDir), read
|
|
5
|
+
// at each call.
|
|
6
|
+
export function createMarkdownPlace({
|
|
7
|
+
capabilities, renderText, roots, touchedFiles, currentDir, linkRefs, applyFinalAnswer, findInTurnRoots, normalize,
|
|
8
|
+
pairAnswerRefs,
|
|
9
|
+
}) {
|
|
10
|
+
// A text the page already shows as its markdown HTML (POST /api/render,
|
|
11
|
+
// sanitised there), or null: markdown off here (no request), or a failed
|
|
12
|
+
// read (the raw text stays).
|
|
13
|
+
function renderMarkdown(text) {
|
|
14
|
+
if (capabilities().display !== true) return Promise.resolve(null);
|
|
15
|
+
return renderText(text)
|
|
16
|
+
.then((d) => (typeof d?.html === "string" && d.html ? d.html : null))
|
|
17
|
+
.catch(() => null);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// The `path:line` refs in one rendered text (the stage's asking slot, a
|
|
21
|
+
// kept reply), as linkTextRefs links the answers'.
|
|
22
|
+
function linkRefsIn(el) {
|
|
23
|
+
linkRefs(el, touchedFiles(roots()), currentDir());
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// The turn's answer as its rendered markdown and with its plugin refs,
|
|
27
|
+
// once it is final (the turn-end read, an answer_display re-read:
|
|
28
|
+
// answer_refs.js applyFinalAnswer); then the refs.
|
|
29
|
+
function applyFinalMarkdown(messages) {
|
|
30
|
+
applyFinalAnswer(messages, findInTurnRoots(".bubble.output"), normalize);
|
|
31
|
+
linkTextRefs();
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// The refs on the answers' and closed steps' texts, on this page: first
|
|
35
|
+
// each answer's own (a plugin's, answer_refs.js), then the `path:line`
|
|
36
|
+
// refs that name a file the session's rows (and cards) touched
|
|
37
|
+
// (ref_scan.js), which leave a plugin's alone. Not mid-stream: at a
|
|
38
|
+
// turn's end and after a reload's render. A ref already made stays as it
|
|
39
|
+
// is.
|
|
40
|
+
function linkTextRefs() {
|
|
41
|
+
const pageRoots = roots();
|
|
42
|
+
const touched = touchedFiles(pageRoots);
|
|
43
|
+
for (const root of pageRoots) {
|
|
44
|
+
for (const el of root.querySelectorAll(".bubble.output")) pairAnswerRefs(el);
|
|
45
|
+
}
|
|
46
|
+
if (!touched.size) return;
|
|
47
|
+
for (const root of pageRoots) {
|
|
48
|
+
for (const el of root.querySelectorAll(".bubble.output, .gen:not(.live) > .gen-text")) linkRefs(el, touched, currentDir());
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return { renderMarkdown, linkRefsIn, applyFinalMarkdown, linkTextRefs };
|
|
53
|
+
}
|
|
@@ -75,12 +75,16 @@ export function createStreamHandlers(deps) {
|
|
|
75
75
|
generation_started: (data) => {
|
|
76
76
|
// The request after a /model's unresolved window started: whatever it
|
|
77
77
|
// brings, the ctx tooltip no longer waits for it (ctxBarTip).
|
|
78
|
-
state()
|
|
78
|
+
const current = state();
|
|
79
|
+
let contextChanged = current.ctxUnknownAfterSwitch !== false;
|
|
80
|
+
current.ctxUnknownAfterSwitch = false;
|
|
79
81
|
if (data?.context_window_tokens) {
|
|
80
|
-
const
|
|
82
|
+
const source = data.context_window_source || null;
|
|
83
|
+
contextChanged ||= current.ctxWindow !== data.context_window_tokens || current.ctxWindowSource !== source;
|
|
81
84
|
current.ctxWindow = data.context_window_tokens;
|
|
82
|
-
current.ctxWindowSource =
|
|
85
|
+
current.ctxWindowSource = source;
|
|
83
86
|
}
|
|
87
|
+
if (contextChanged) updateInfoBar();
|
|
84
88
|
liveTurn.timing.setNote("");
|
|
85
89
|
// The step's clock (the composer's delivery line): the page's own at a
|
|
86
90
|
// live start; a replay's comes from the snapshot's generation age,
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { html, nothing, render } from "./vendor/lit-html.js";
|
|
2
|
+
|
|
3
|
+
// The page's toasts (app.js showToast): a stack in #toast, newest last, at
|
|
4
|
+
// most MAX_TOASTS. Each item has its own 6 s timer, or none when sticky: a
|
|
5
|
+
// sticky one stays until its action, its ✕ or a link in it is clicked, so a
|
|
6
|
+
// later notice can't hide an error. +text+: words, or a template
|
|
7
|
+
// (chains.js openChildrenTemplate: a link in it opens that session and hides
|
|
8
|
+
// the toast). The same text again while it shows reuses its item, moved to
|
|
9
|
+
// the end with a fresh timer. When a 4th comes, the oldest non-sticky goes
|
|
10
|
+
// (the oldest of all if every one is sticky).
|
|
11
|
+
const TOAST_MS = 6000;
|
|
12
|
+
const MAX_TOASTS = 3;
|
|
13
|
+
|
|
14
|
+
export function createToasts({ el, onChange = () => {}, doc = globalThis.document }) {
|
|
15
|
+
const entries = [];
|
|
16
|
+
|
|
17
|
+
function sync() {
|
|
18
|
+
el.hidden = entries.length === 0;
|
|
19
|
+
onChange();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function remove(entry, notify = true) {
|
|
23
|
+
const index = entries.indexOf(entry);
|
|
24
|
+
if (index < 0) return;
|
|
25
|
+
if (entry.timer !== null) clearTimeout(entry.timer);
|
|
26
|
+
entry.timer = null;
|
|
27
|
+
entries.splice(index, 1);
|
|
28
|
+
entry.el.remove();
|
|
29
|
+
if (notify) sync();
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function draw(entry, text, action, sticky) {
|
|
33
|
+
render(html`<span class="toast-message" @click=${(e) => { if (e.target.closest?.("a[href]")) remove(entry); }}>${text}</span>${action
|
|
34
|
+
? html`<button type="button" class="toast-action" @click=${() => { remove(entry); action.run(); }}>${action.label}</button>`
|
|
35
|
+
: nothing}${sticky ? html`<button type="button" class="toast-close" title="Dismiss" aria-label="Dismiss" @click=${() => remove(entry)}>✕</button>` : nothing}`,
|
|
36
|
+
entry.el);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function show(text, action = null, { sticky = false } = {}) {
|
|
40
|
+
const candidate = { el: doc.createElement("div"), timer: null, sticky, key: null };
|
|
41
|
+
candidate.el.className = "toast-item";
|
|
42
|
+
draw(candidate, text, action, sticky);
|
|
43
|
+
candidate.key = candidate.el.querySelector(".toast-message").textContent;
|
|
44
|
+
|
|
45
|
+
const duplicate = entries.find((entry) => entry.key === candidate.key);
|
|
46
|
+
if (duplicate) {
|
|
47
|
+
if (duplicate.timer !== null) clearTimeout(duplicate.timer);
|
|
48
|
+
duplicate.timer = null;
|
|
49
|
+
duplicate.sticky = sticky;
|
|
50
|
+
duplicate.key = candidate.key;
|
|
51
|
+
draw(duplicate, text, action, sticky);
|
|
52
|
+
entries.push(...entries.splice(entries.indexOf(duplicate), 1));
|
|
53
|
+
el.appendChild(duplicate.el);
|
|
54
|
+
if (!sticky) duplicate.timer = setTimeout(() => remove(duplicate), TOAST_MS);
|
|
55
|
+
sync();
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (entries.length === MAX_TOASTS) {
|
|
60
|
+
remove(entries.find((entry) => !entry.sticky) || entries[0], false);
|
|
61
|
+
}
|
|
62
|
+
entries.push(candidate);
|
|
63
|
+
el.appendChild(candidate.el);
|
|
64
|
+
if (!sticky) candidate.timer = setTimeout(() => remove(candidate), TOAST_MS);
|
|
65
|
+
sync();
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return { show };
|
|
69
|
+
}
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: samagotchi
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.77.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Dmitry Dedov
|
|
@@ -393,6 +393,7 @@ files:
|
|
|
393
393
|
- lib/samagotchi/memory_bundle/trash.rb
|
|
394
394
|
- lib/samagotchi/memory_bundle/uninstaller.rb
|
|
395
395
|
- lib/samagotchi/memory_paths.rb
|
|
396
|
+
- lib/samagotchi/message_parts.rb
|
|
396
397
|
- lib/samagotchi/model_catalog.rb
|
|
397
398
|
- lib/samagotchi/model_list_store.rb
|
|
398
399
|
- lib/samagotchi/model_match.rb
|
|
@@ -412,6 +413,7 @@ files:
|
|
|
412
413
|
- lib/samagotchi/paths.rb
|
|
413
414
|
- lib/samagotchi/peer_sources.rb
|
|
414
415
|
- lib/samagotchi/pending_input_queue.rb
|
|
416
|
+
- lib/samagotchi/pending_question.rb
|
|
415
417
|
- lib/samagotchi/plugin/api.rb
|
|
416
418
|
- lib/samagotchi/plugin/attached_context.rb
|
|
417
419
|
- lib/samagotchi/plugin/context.rb
|
|
@@ -485,6 +487,7 @@ files:
|
|
|
485
487
|
- lib/samagotchi/terminal_ui/input_support.rb
|
|
486
488
|
- lib/samagotchi/terminal_ui/line_reader.rb
|
|
487
489
|
- lib/samagotchi/terminal_ui/live_region.rb
|
|
490
|
+
- lib/samagotchi/terminal_ui/model_segments_text.rb
|
|
488
491
|
- lib/samagotchi/terminal_ui/one_shot_sink.rb
|
|
489
492
|
- lib/samagotchi/terminal_ui/plain_surface.rb
|
|
490
493
|
- lib/samagotchi/terminal_ui/question_prompt.rb
|
|
@@ -573,7 +576,6 @@ files:
|
|
|
573
576
|
- lib/samagotchi/web/info_probe.rb
|
|
574
577
|
- lib/samagotchi/web/lan.rb
|
|
575
578
|
- lib/samagotchi/web/markdown_renderer.rb
|
|
576
|
-
- lib/samagotchi/web/message_parts.rb
|
|
577
579
|
- lib/samagotchi/web/public/activity.js
|
|
578
580
|
- lib/samagotchi/web/public/annotate_actions.js
|
|
579
581
|
- lib/samagotchi/web/public/annotate_bar.js
|
|
@@ -615,6 +617,7 @@ files:
|
|
|
615
617
|
- lib/samagotchi/web/public/live_timing.js
|
|
616
618
|
- lib/samagotchi/web/public/live_turn.js
|
|
617
619
|
- lib/samagotchi/web/public/llm_context_chip.js
|
|
620
|
+
- lib/samagotchi/web/public/markdown_place.js
|
|
618
621
|
- lib/samagotchi/web/public/model_pick.js
|
|
619
622
|
- lib/samagotchi/web/public/model_picker.js
|
|
620
623
|
- lib/samagotchi/web/public/notice_links.js
|
|
@@ -651,6 +654,7 @@ files:
|
|
|
651
654
|
- lib/samagotchi/web/public/thinking_ticker.js
|
|
652
655
|
- lib/samagotchi/web/public/timing.js
|
|
653
656
|
- lib/samagotchi/web/public/toast_place.js
|
|
657
|
+
- lib/samagotchi/web/public/toasts.js
|
|
654
658
|
- lib/samagotchi/web/public/toolkit_chip.js
|
|
655
659
|
- lib/samagotchi/web/public/turn_events.js
|
|
656
660
|
- lib/samagotchi/web/public/turn_html.js
|