dsh-nextsteps 1.1.0 → 1.2.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.
- package/client.js +91 -4
- package/package.json +1 -1
package/client.js
CHANGED
|
@@ -144,6 +144,57 @@ window.__ModuleLoader__.load({
|
|
|
144
144
|
|
|
145
145
|
const captures = new Map();
|
|
146
146
|
const captureListeners = new Set();
|
|
147
|
+
const CAPTURES_KEY = 'dsh.nextsteps.captures.v1';
|
|
148
|
+
const CAPTURE_LIMIT = 400;
|
|
149
|
+
|
|
150
|
+
function readStoredCaptures() {
|
|
151
|
+
try {
|
|
152
|
+
const raw = window.localStorage.getItem(CAPTURES_KEY);
|
|
153
|
+
const parsed = raw === null ? {} : JSON.parse(raw);
|
|
154
|
+
return parsed !== null && typeof parsed === 'object' ? parsed : {};
|
|
155
|
+
} catch (error) {
|
|
156
|
+
console.warn('dsh-nextsteps: could not read stored captures:', error);
|
|
157
|
+
return {};
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function writeStoredCaptures(store) {
|
|
162
|
+
try {
|
|
163
|
+
window.localStorage.setItem(CAPTURES_KEY, JSON.stringify(store));
|
|
164
|
+
} catch (error) {
|
|
165
|
+
console.warn('dsh-nextsteps: could not persist captures:', error);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function rehydrateCaptures() {
|
|
170
|
+
const store = readStoredCaptures();
|
|
171
|
+
for (const [sessionId, sessionEntries] of Object.entries(store)) {
|
|
172
|
+
if (sessionEntries === null || typeof sessionEntries !== 'object') continue;
|
|
173
|
+
const entries = sessionCaptures(sessionId);
|
|
174
|
+
for (const [callId, entry] of Object.entries(sessionEntries)) {
|
|
175
|
+
if (entry === null || typeof entry !== 'object' || !Array.isArray(entry.followups) || entry.followups.length === 0) continue;
|
|
176
|
+
if (entries.has(callId)) continue;
|
|
177
|
+
entries.set(callId, { turn: entry.turn, followups: entry.followups, clickedId: entry.clickedId });
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function pruneStoredCaptures(store) {
|
|
183
|
+
let total = 0;
|
|
184
|
+
for (const sessionEntries of Object.values(store)) total += Object.keys(sessionEntries ?? {}).length;
|
|
185
|
+
if (total <= CAPTURE_LIMIT) return;
|
|
186
|
+
const sessionIds = Object.keys(store);
|
|
187
|
+
for (const sessionId of sessionIds) {
|
|
188
|
+
if (total <= CAPTURE_LIMIT) break;
|
|
189
|
+
const keys = Object.keys(store[sessionId]);
|
|
190
|
+
for (const callId of keys) {
|
|
191
|
+
if (total <= CAPTURE_LIMIT) break;
|
|
192
|
+
delete store[sessionId][callId];
|
|
193
|
+
total -= 1;
|
|
194
|
+
}
|
|
195
|
+
if (Object.keys(store[sessionId]).length === 0) delete store[sessionId];
|
|
196
|
+
}
|
|
197
|
+
}
|
|
147
198
|
|
|
148
199
|
function sessionCaptures(sessionId) {
|
|
149
200
|
let entries = captures.get(sessionId);
|
|
@@ -162,6 +213,12 @@ window.__ModuleLoader__.load({
|
|
|
162
213
|
const clickedId = previous?.clickedId
|
|
163
214
|
?? `sf-${contentHash(`${sessionId}|${mergedTurn ?? callId}|${followups.map((entry) => entry.prompt).join('|')}`)}`;
|
|
164
215
|
entries.set(callId, { turn: mergedTurn, followups, clickedId });
|
|
216
|
+
const store = readStoredCaptures();
|
|
217
|
+
const sessionEntries = store[sessionId] !== null && typeof store[sessionId] === 'object' ? store[sessionId] : {};
|
|
218
|
+
sessionEntries[callId] = { turn: mergedTurn, followups, clickedId };
|
|
219
|
+
store[sessionId] = sessionEntries;
|
|
220
|
+
pruneStoredCaptures(store);
|
|
221
|
+
writeStoredCaptures(store);
|
|
165
222
|
for (const listener of captureListeners) listener(sessionId);
|
|
166
223
|
}
|
|
167
224
|
|
|
@@ -180,9 +237,34 @@ window.__ModuleLoader__.load({
|
|
|
180
237
|
|
|
181
238
|
function newestCapture(sessionId) {
|
|
182
239
|
const entries = captures.get(sessionId);
|
|
183
|
-
if (entries === undefined || entries.size
|
|
184
|
-
|
|
185
|
-
|
|
240
|
+
if (entries === undefined || entries.size === 0) return null;
|
|
241
|
+
let best = null;
|
|
242
|
+
for (const entry of entries.values()) {
|
|
243
|
+
if (best === null || (entry.turn ?? -1) > (best.turn ?? -1)) best = entry;
|
|
244
|
+
}
|
|
245
|
+
return best;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const adoptedCallIds = new Set();
|
|
249
|
+
|
|
250
|
+
function unclaimedCaptures(sessionId, turn) {
|
|
251
|
+
const entries = captures.get(sessionId);
|
|
252
|
+
if (entries === undefined) return null;
|
|
253
|
+
const candidates = [];
|
|
254
|
+
for (const [callId, entry] of entries.entries()) {
|
|
255
|
+
if (entry.turn === undefined && !adoptedCallIds.has(callId)) candidates.push([callId, entry]);
|
|
256
|
+
}
|
|
257
|
+
if (candidates.length === 0) return null;
|
|
258
|
+
candidates.sort((a, b) => String(a[0]).localeCompare(String(b[0])));
|
|
259
|
+
const [callId, entry] = candidates[0];
|
|
260
|
+
adoptedCallIds.add(callId);
|
|
261
|
+
entries.set(callId, { ...entry, turn });
|
|
262
|
+
const store = readStoredCaptures();
|
|
263
|
+
const sessionEntries = store[sessionId] !== null && typeof store[sessionId] === 'object' ? store[sessionId] : {};
|
|
264
|
+
sessionEntries[callId] = { turn, followups: entry.followups, clickedId: entry.clickedId };
|
|
265
|
+
store[sessionId] = sessionEntries;
|
|
266
|
+
writeStoredCaptures(store);
|
|
267
|
+
return entry;
|
|
186
268
|
}
|
|
187
269
|
|
|
188
270
|
function displayLabel(followup) {
|
|
@@ -384,7 +466,11 @@ window.__ModuleLoader__.load({
|
|
|
384
466
|
|
|
385
467
|
|
|
386
468
|
|
|
387
|
-
|
|
469
|
+
let entry = turn !== undefined ? captureForTurn(sessionId, turn) : newestCapture(sessionId);
|
|
470
|
+
if (entry === null) {
|
|
471
|
+
const unclaimed = unclaimedCaptures(sessionId, turn);
|
|
472
|
+
if (unclaimed !== null) entry = unclaimed;
|
|
473
|
+
}
|
|
388
474
|
return h(FollowupsCard, { entry, sessionId, ctx, t: props.t });
|
|
389
475
|
};
|
|
390
476
|
}
|
|
@@ -501,6 +587,7 @@ window.__ModuleLoader__.load({
|
|
|
501
587
|
inject: ['slots', 'locale', 'remote'],
|
|
502
588
|
apply(ctx) {
|
|
503
589
|
installStyles(ctx);
|
|
590
|
+
rehydrateCaptures();
|
|
504
591
|
refreshPluginSettings();
|
|
505
592
|
for (const [code, dictionary] of Object.entries(DICTIONARIES)) {
|
|
506
593
|
ctx.effect(() => {
|