bunnyquery 1.10.0 → 1.10.2
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/bunnyquery.css +20 -0
- package/bunnyquery.js +40 -7
- package/dist/engine.cjs +38 -4
- package/dist/engine.cjs.map +1 -1
- package/dist/engine.d.mts +18 -12
- package/dist/engine.d.ts +18 -12
- package/dist/engine.mjs +38 -4
- package/dist/engine.mjs.map +1 -1
- package/package.json +1 -1
- package/src/engine/indexing_groups.ts +86 -7
- package/src/engine/link_markup.ts +11 -4
- package/styles/chat.css +20 -0
package/package.json
CHANGED
|
@@ -36,6 +36,17 @@
|
|
|
36
36
|
* loaded (`mayHaveOlder`), and it is the same event that already re-derives
|
|
37
37
|
* `runKey` — so the view treats it as a new row either way.
|
|
38
38
|
*
|
|
39
|
+
* One row per FILE, not per run. A file indexed and later indexed again (a
|
|
40
|
+
* re-upload, an explicit Reindex, a retry after a failure) has several RUNS in
|
|
41
|
+
* the transcript, each opened by its own "A new file has just been uploaded"
|
|
42
|
+
* pass. They are attributed as separate groups (mixing their passes would let an
|
|
43
|
+
* old failure be painted over by a new success, or a new run inherit an old
|
|
44
|
+
* stop), but only the NEWEST run of a file is rendered once the older ones have
|
|
45
|
+
* settled. Two green "Indexed" rows for one file read as a duplicate, not as
|
|
46
|
+
* history, and a superseded run's verdict is not the file's state. An older run
|
|
47
|
+
* that is still working stays visible until it settles, so a double dispatch is
|
|
48
|
+
* never hidden while both chains are live and its Stop button stays reachable.
|
|
49
|
+
*
|
|
39
50
|
* The group deliberately reports no authoritative pass TOTAL. History is paged
|
|
40
51
|
* newest-first, so any total computed from loaded messages is a lower bound that
|
|
41
52
|
* a later scroll-up would contradict. It reports STATE (indexing / indexed /
|
|
@@ -75,12 +86,13 @@ export type IndexingGroup = {
|
|
|
75
86
|
* surprised out of. */
|
|
76
87
|
key: string;
|
|
77
88
|
/** Identity of this ROW: one indexing RUN of that file. A file indexed on
|
|
78
|
-
* Monday and re-indexed on Wednesday is two runs, and
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
*
|
|
83
|
-
*
|
|
89
|
+
* Monday and re-indexed on Wednesday is two runs, and merging them into one
|
|
90
|
+
* group claimed Monday's passes for Wednesday and let Monday's failure be
|
|
91
|
+
* overwritten by Wednesday's success. They stay separate groups; what the
|
|
92
|
+
* view gets is only the newest of them once the rest have settled (see the
|
|
93
|
+
* file docstring). Named after the run's FIRST loaded pass (see where it is
|
|
94
|
+
* assigned below), so passes appended to the run and other runs appearing on
|
|
95
|
+
* either side of it never rename a row already on screen.
|
|
84
96
|
*
|
|
85
97
|
* This is the RENDER key, and only that. It is renamed when the run's true
|
|
86
98
|
* first pass finally loads — routine while a worker-driven chain is running,
|
|
@@ -395,6 +407,48 @@ function isHiddenPass(m: ChatMessage): boolean {
|
|
|
395
407
|
* Messages that are not background-indexing pass through untouched, at their
|
|
396
408
|
* original positions and with their original indices.
|
|
397
409
|
*/
|
|
410
|
+
/**
|
|
411
|
+
* A loaded group reads its state off its newest loaded pass, and for a run the WORKER
|
|
412
|
+
* ended (three failed retries, a stale grant, a cap-out) that pass looks settled: the
|
|
413
|
+
* model answered normally, the failure happened after. The run:: record is the only
|
|
414
|
+
* thing that knows, and until now it was consulted for record-only stubs alone, so
|
|
415
|
+
* the chat row said "Indexed", the files page took that as a verdict and painted the
|
|
416
|
+
* badge green over the record's "error". The record's terminal verdict is laid over
|
|
417
|
+
* the group when it describes THIS run (started no later than the newest loaded pass)
|
|
418
|
+
* and is newer than that pass; visible work and a live queue hit still outrank it.
|
|
419
|
+
*/
|
|
420
|
+
function overlayRunRecordVerdict(
|
|
421
|
+
grp: IndexingGroup,
|
|
422
|
+
rec: RunStubInfo,
|
|
423
|
+
liveIndexKeys: { [key: string]: boolean },
|
|
424
|
+
): void {
|
|
425
|
+
if (!grp.members.length || grp.status === 'active' || grp.cancelling) return;
|
|
426
|
+
if (rec.status === 'working' || typeof rec.started !== 'number') return;
|
|
427
|
+
if (liveIndexKeys[grp.key] || liveIndexKeys[canonIndexKey(grp.key)]) return;
|
|
428
|
+
var firstTs = grp.members[0].msg._ts;
|
|
429
|
+
var lastTs = grp.members[grp.members.length - 1].msg._ts;
|
|
430
|
+
if (typeof lastTs !== 'number' || typeof firstTs !== 'number') return;
|
|
431
|
+
// a record that started after the newest loaded pass is a LATER run, not this one
|
|
432
|
+
if (rec.started > lastTs) return;
|
|
433
|
+
var when = typeof rec.finished === 'number' ? rec.finished : undefined;
|
|
434
|
+
if (when === undefined) return;
|
|
435
|
+
// a record that ENDED before this run began describes a previous run
|
|
436
|
+
if (when < firstTs) return;
|
|
437
|
+
// the record's verdict must be at least as new as the pass it would override
|
|
438
|
+
// (a few seconds of slack: the worker closes the record right after the pass lands)
|
|
439
|
+
if (when < lastTs - 5000) return;
|
|
440
|
+
if (rec.status === 'error' || rec.status === 'cancelled') {
|
|
441
|
+
grp.status = rec.status;
|
|
442
|
+
} else if (rec.status === 'done') {
|
|
443
|
+
grp.status = 'done';
|
|
444
|
+
} else {
|
|
445
|
+
return;
|
|
446
|
+
}
|
|
447
|
+
grp.finished = true;
|
|
448
|
+
grp.resolving = false;
|
|
449
|
+
grp.resolvingReason = undefined;
|
|
450
|
+
}
|
|
451
|
+
|
|
398
452
|
export function buildChatDisplayList(
|
|
399
453
|
messages: ChatMessage[],
|
|
400
454
|
opts?: BuildDisplayListOptions,
|
|
@@ -812,6 +866,28 @@ export function buildChatDisplayList(
|
|
|
812
866
|
}
|
|
813
867
|
}
|
|
814
868
|
|
|
869
|
+
// --- superseded runs -------------------------------------------------------
|
|
870
|
+
// Every run of a file but its newest is history the newest run replaced: the
|
|
871
|
+
// file's state is the newest run's, and a second "Indexed" row for the same
|
|
872
|
+
// file reads as a duplicate (reported as exactly that, for files re-indexed
|
|
873
|
+
// during testing). Such a run keeps its group (its passes stay attributed to
|
|
874
|
+
// it, never demoted to loose "Indexing: ..." bubbles) but draws no row.
|
|
875
|
+
//
|
|
876
|
+
// ONLY once it has settled. An older run that is still working, or still
|
|
877
|
+
// stopping, is live work the user can see the effects of and may want to stop,
|
|
878
|
+
// and hiding it would also hide a double dispatch (two chains reading one file)
|
|
879
|
+
// for as long as both ran. Runs open in walk order, and the walk is request
|
|
880
|
+
// creation order, so the last run of a key is its newest.
|
|
881
|
+
var superseded: { [runId: string]: boolean } = {};
|
|
882
|
+
for (var sk in runsOfKey) {
|
|
883
|
+
var srs = runsOfKey[sk];
|
|
884
|
+
for (var sri = 0; sri < srs.length - 1; sri++) {
|
|
885
|
+
var sgp = groups[srs[sri]];
|
|
886
|
+
if (!sgp || sgp.status === 'active' || sgp.cancelling) continue;
|
|
887
|
+
superseded[srs[sri]] = true;
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
|
|
815
891
|
// --- durable run:: stubs ---------------------------------------------------
|
|
816
892
|
// A run record whose passes are not among the loaded messages still deserves
|
|
817
893
|
// a row: on a fresh open the bg history is deferred, and a run older than the
|
|
@@ -991,6 +1067,7 @@ export function buildChatDisplayList(
|
|
|
991
1067
|
// record-only to loaded-passes keeps its DOM node and its position.
|
|
992
1068
|
tg.runKey = 'run:' + (tg.path || tg.key) + '#' + trec.started;
|
|
993
1069
|
stubList.push({ started: trec.started, group: tg });
|
|
1070
|
+
overlayRunRecordVerdict(tg, trec, liveIndexKeys);
|
|
994
1071
|
}
|
|
995
1072
|
}
|
|
996
1073
|
stubList.sort(function (a, b) { return a.started - b.started; });
|
|
@@ -1015,7 +1092,9 @@ export function buildChatDisplayList(
|
|
|
1015
1092
|
// Every other member of the run is represented by the row at the anchor —
|
|
1016
1093
|
// unless the row is timestamp-anchored (incomplete run with a run::
|
|
1017
1094
|
// record), in which case the flush above already emitted it.
|
|
1018
|
-
|
|
1095
|
+
// ...or the run is superseded, in which case nothing is emitted for it at
|
|
1096
|
+
// all: its members are represented by the newer run's row.
|
|
1097
|
+
if (groups[r].anchorIndex === j && !suppressAnchor[r] && !superseded[r]) {
|
|
1019
1098
|
out.push({ kind: 'indexing', group: groups[r], index: j });
|
|
1020
1099
|
}
|
|
1021
1100
|
}
|
|
@@ -77,8 +77,15 @@ export function renderInlineLinkHtml(link: RenderableInlineLink, opts?: InlineLi
|
|
|
77
77
|
if (unavailable) cls.push('is-unavailable');
|
|
78
78
|
if (preview) cls.push('is-image-preview');
|
|
79
79
|
|
|
80
|
-
|
|
81
|
-
|
|
80
|
+
// The glyph is its own element so chat.css can make it an atomic inline: an
|
|
81
|
+
// underline propagates into every in-flow inline descendant of the anchor and
|
|
82
|
+
// nothing inside can cancel it, so the ↗ was underlined along with the label
|
|
83
|
+
// whenever the chip was. The spacing that used to be a literal space is a
|
|
84
|
+
// margin on the span for the same reason (a space is decorated too).
|
|
85
|
+
var glyphHtml = '<span class="bq-link-glyph" translate="no">'
|
|
86
|
+
+ (unavailable ? INLINE_LINK_UNAVAILABLE_GLYPH : INLINE_LINK_GLYPH) + '</span>';
|
|
87
|
+
var labelHtml = glyphHtml + escapeInlineHtml(link.label
|
|
88
|
+
+ (unavailable ? INLINE_LINK_UNAVAILABLE_SUFFIX : refreshing ? ' (fetching...)' : ''));
|
|
82
89
|
var attrs = ['class="' + cls.join(' ') + '"'];
|
|
83
90
|
// NO href when the file is unavailable. That is what disables the click:
|
|
84
91
|
// an anchor with no href does not navigate, is not a tab stop and takes the
|
|
@@ -99,7 +106,7 @@ export function renderInlineLinkHtml(link: RenderableInlineLink, opts?: InlineLi
|
|
|
99
106
|
if (link.remotePath) attrs.push('data-bq-remote-path="' + escapeInlineHtml(link.remotePath) + '"');
|
|
100
107
|
if (link.fullLabel) attrs.push('data-bq-full-label="' + escapeInlineHtml(link.fullLabel) + '"');
|
|
101
108
|
|
|
102
|
-
if (!preview) return '<a ' + attrs.join(' ') + '>' +
|
|
109
|
+
if (!preview) return '<a ' + attrs.join(' ') + '>' + labelHtml + '</a>';
|
|
103
110
|
|
|
104
111
|
// NO src attribute. A stored file always classifies as the _expired_.url
|
|
105
112
|
// placeholder until something mints a real url, so a src written here would be
|
|
@@ -127,6 +134,6 @@ export function renderInlineLinkHtml(link: RenderableInlineLink, opts?: InlineLi
|
|
|
127
134
|
// dot trail, never the jumping bunny. CSS hides it the moment the <img>
|
|
128
135
|
// gains a src, so no JS removes DOM and nothing re-parses.
|
|
129
136
|
'<span class="bq-loader" data-bq-img-loader="1"></span>' +
|
|
130
|
-
'<span class="bq-img-preview-caption" translate="no">' +
|
|
137
|
+
'<span class="bq-img-preview-caption" translate="no">' + labelHtml + '</span>' +
|
|
131
138
|
'</a>';
|
|
132
139
|
}
|
package/styles/chat.css
CHANGED
|
@@ -419,6 +419,26 @@
|
|
|
419
419
|
text-decoration: none;
|
|
420
420
|
cursor: default;
|
|
421
421
|
}
|
|
422
|
+
/* An assistant bubble's chips are emitted by renderInlineLinkHtml INSIDE the
|
|
423
|
+
v-html `.bq-md` block, where `.bq-md a` below (underline at rest, none on
|
|
424
|
+
hover -- the prose-link convention) is (0,1,1) and outranks .bq-link-button
|
|
425
|
+
at (0,1,0). So those chips rendered the exact inverse of the rule above:
|
|
426
|
+
underlined at rest, underline gone under the pointer, while the same chip on
|
|
427
|
+
a user bubble (outside .bq-md) behaved. Restate the chip rule at a
|
|
428
|
+
specificity that wins inside .bq-md, the dead chip included. */
|
|
429
|
+
.bq-md a.bq-link-button { text-decoration: none; }
|
|
430
|
+
.bq-md a.bq-link-button:hover { text-decoration: underline; }
|
|
431
|
+
.bq-md a.bq-link-button.is-unavailable:hover { text-decoration: none; }
|
|
432
|
+
/* The ↗ / ✕ glyph is never underlined, at rest or on hover. Underlines propagate
|
|
433
|
+
into every in-flow inline descendant and a descendant cannot cancel them, so
|
|
434
|
+
`text-decoration: none` on the glyph alone does nothing; an atomic inline
|
|
435
|
+
(inline-block) is exempt, and the spacing is a margin so no decorated space
|
|
436
|
+
character carries the line up to the glyph. */
|
|
437
|
+
.bq-link-glyph {
|
|
438
|
+
display: inline-block;
|
|
439
|
+
text-decoration: none;
|
|
440
|
+
margin-right: 0.3em;
|
|
441
|
+
}
|
|
422
442
|
|
|
423
443
|
/* ---- inline image previews ----------------------------------------------*/
|
|
424
444
|
/* The anchor IS the preview: picture on top, the ordinary chip underneath as a
|