bunnyquery 1.1.1 → 1.1.3
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 +4 -4
- package/bunnyquery.js +51 -78
- package/package.json +1 -1
package/bunnyquery.css
CHANGED
|
@@ -353,7 +353,7 @@
|
|
|
353
353
|
font-size: 0.9em;
|
|
354
354
|
padding: 0.1em 0.35em;
|
|
355
355
|
background: rgba(0, 0, 0, 0.06);
|
|
356
|
-
border-radius:
|
|
356
|
+
border-radius: 0;
|
|
357
357
|
white-space: pre-wrap;
|
|
358
358
|
word-break: break-word;
|
|
359
359
|
}
|
|
@@ -362,7 +362,7 @@
|
|
|
362
362
|
padding: 0.6em 0.8em;
|
|
363
363
|
background: #1e1e1e;
|
|
364
364
|
color: #f5f5f5;
|
|
365
|
-
border-radius:
|
|
365
|
+
border-radius: 0;
|
|
366
366
|
overflow-x: auto;
|
|
367
367
|
white-space: pre;
|
|
368
368
|
}
|
|
@@ -850,6 +850,7 @@ a.bq-link-button:hover {
|
|
|
850
850
|
.bq-modal-delete-header {
|
|
851
851
|
display: flex;
|
|
852
852
|
align-items: center;
|
|
853
|
+
justify-content: space-between;
|
|
853
854
|
gap: 0.5rem;
|
|
854
855
|
font-size: 0.95rem;
|
|
855
856
|
font-weight: 600;
|
|
@@ -1144,13 +1145,12 @@ a.bq-link-button:hover {
|
|
|
1144
1145
|
|
|
1145
1146
|
@media (max-width: 800px) {
|
|
1146
1147
|
.bq-modal-backdrop {
|
|
1147
|
-
padding: 0;
|
|
1148
|
+
padding: 0.5rem;
|
|
1148
1149
|
}
|
|
1149
1150
|
}
|
|
1150
1151
|
|
|
1151
1152
|
@media (max-width: 430px) {
|
|
1152
1153
|
.bq-modal-delete {
|
|
1153
|
-
max-width: calc(100% - 1rem);
|
|
1154
1154
|
max-height: calc(100% - 1rem);
|
|
1155
1155
|
}
|
|
1156
1156
|
|
package/bunnyquery.js
CHANGED
|
@@ -320,28 +320,16 @@
|
|
|
320
320
|
};
|
|
321
321
|
|
|
322
322
|
// ----- file-fence helpers (mirrors agent.vue) ---------------------------
|
|
323
|
-
//
|
|
324
|
-
//
|
|
325
|
-
//
|
|
326
|
-
const FENCE_LANGUAGE_EXTENSIONS = {
|
|
327
|
-
html: 'html',
|
|
328
|
-
htm: 'html',
|
|
329
|
-
csv: 'csv',
|
|
330
|
-
tsv: 'tsv',
|
|
331
|
-
json: 'json',
|
|
332
|
-
xml: 'xml',
|
|
333
|
-
svg: 'svg',
|
|
334
|
-
md: 'md',
|
|
335
|
-
markdown: 'md',
|
|
336
|
-
yaml: 'yaml',
|
|
337
|
-
yml: 'yml',
|
|
338
|
-
txt: 'txt',
|
|
339
|
-
sql: 'sql',
|
|
340
|
-
css: 'css',
|
|
341
|
-
};
|
|
323
|
+
// Only `\`\`\`filename.ext\n...\n\`\`\`` fences become downloadable file
|
|
324
|
+
// anchors (matches the host system prompt in agent.vue). Bare-language
|
|
325
|
+
// fences like \`\`\`html / \`\`\`csv render as normal code blocks.
|
|
342
326
|
|
|
327
|
+
// Minimal MIME table for the file extensions the host agent is known to
|
|
328
|
+
// emit. Anything else falls back to text/plain so the browser still
|
|
329
|
+
// downloads the blob as a text file.
|
|
343
330
|
const FENCE_MIME_TYPES = {
|
|
344
331
|
html: 'text/html',
|
|
332
|
+
htm: 'text/html',
|
|
345
333
|
csv: 'text/csv',
|
|
346
334
|
tsv: 'text/tab-separated-values',
|
|
347
335
|
json: 'application/json',
|
|
@@ -355,22 +343,9 @@
|
|
|
355
343
|
css: 'text/css',
|
|
356
344
|
};
|
|
357
345
|
|
|
358
|
-
//
|
|
359
|
-
|
|
360
|
-
// Closed fences whose info-string is one of the known language tags
|
|
361
|
-
// above. The model often emits ```html ... ``` or ```csv ... ``` instead
|
|
362
|
-
// of a full filename — surface those as downloadable files too.
|
|
363
|
-
const FENCE_LANG_RE = new RegExp(
|
|
364
|
-
'```(' + Object.keys(FENCE_LANGUAGE_EXTENSIONS).join('|') + ')[ \\t]*\\n([\\s\\S]*?)```',
|
|
365
|
-
'gi'
|
|
366
|
-
);
|
|
367
|
-
// Open (still-streaming) fences for either pattern, used to suppress the
|
|
368
|
-
// raw fence text mid-typewriter.
|
|
346
|
+
// Open (still-streaming) filename fence, used to suppress the raw fence
|
|
347
|
+
// text mid-typewriter.
|
|
369
348
|
const OPEN_FENCE_FILENAME_RE = /```([\w.-]+\.[a-zA-Z0-9]+)\n?/;
|
|
370
|
-
const OPEN_FENCE_LANG_RE = new RegExp(
|
|
371
|
-
'```(' + Object.keys(FENCE_LANGUAGE_EXTENSIONS).join('|') + ')[ \\t]*\\n?',
|
|
372
|
-
'i'
|
|
373
|
-
);
|
|
374
349
|
|
|
375
350
|
// Cache blob URLs keyed by (filename + content) so re-renders don't leak
|
|
376
351
|
// a fresh ObjectURL on every tick of streaming text.
|
|
@@ -387,16 +362,6 @@
|
|
|
387
362
|
return href;
|
|
388
363
|
};
|
|
389
364
|
|
|
390
|
-
// Counter used to mint stable filenames for bare-language fences within
|
|
391
|
-
// a single rendered message ("download.html", "download-2.html", ...).
|
|
392
|
-
// Keyed by message identity so re-renders of the same message reuse the
|
|
393
|
-
// same names (which lets the blob cache hit).
|
|
394
|
-
const _bareFenceNameCounters = new WeakMap();
|
|
395
|
-
const _bareFilenameFor = (msgKey, ext, occurrence) => {
|
|
396
|
-
const base = 'download';
|
|
397
|
-
return occurrence === 0 ? `${base}.${ext}` : `${base}-${occurrence + 1}.${ext}`;
|
|
398
|
-
};
|
|
399
|
-
|
|
400
365
|
// Split message content into renderable parts. When `marked` is loaded,
|
|
401
366
|
// returns a single `{type:'html', html}` part containing the full
|
|
402
367
|
// markdown-rendered bubble (with file/link anchors spliced back in).
|
|
@@ -440,44 +405,19 @@
|
|
|
440
405
|
return PH(idx);
|
|
441
406
|
};
|
|
442
407
|
|
|
443
|
-
//
|
|
444
|
-
// (typewriter ticks) reuse the same synthesized names → blob cache
|
|
445
|
-
// hits instead of leaking ObjectURLs every frame.
|
|
446
|
-
let bareCounters = msgKey ? _bareFenceNameCounters.get(msgKey) : null;
|
|
447
|
-
if (!bareCounters && msgKey) {
|
|
448
|
-
bareCounters = {};
|
|
449
|
-
_bareFenceNameCounters.set(msgKey, bareCounters);
|
|
450
|
-
}
|
|
451
|
-
const bareSeen = {};
|
|
452
|
-
|
|
453
|
-
// 1a. Closed filename fences: ```filename.ext\n...\n```
|
|
408
|
+
// 1. Closed filename fences: ```filename.ext\n...\n```
|
|
454
409
|
let working = content.replace(
|
|
455
410
|
/```([\w.-]+\.[a-zA-Z0-9]+)\n([\s\S]*?)```/g,
|
|
456
411
|
(_full, filename, body) =>
|
|
457
412
|
push(fileToAnchorHtml(filename, _getOrCreateFileHref(filename, body))),
|
|
458
413
|
);
|
|
459
414
|
|
|
460
|
-
// 1b. Closed bare-language fences: ```html ... ``` etc.
|
|
461
|
-
working = working.replace(FENCE_LANG_RE, (_full, lang, body) => {
|
|
462
|
-
const ext = FENCE_LANGUAGE_EXTENSIONS[lang.toLowerCase()] || 'txt';
|
|
463
|
-
const occurrence = bareSeen[ext] || 0;
|
|
464
|
-
bareSeen[ext] = occurrence + 1;
|
|
465
|
-
const filename = _bareFilenameFor(msgKey, ext, occurrence);
|
|
466
|
-
return push(fileToAnchorHtml(filename, _getOrCreateFileHref(filename, body)));
|
|
467
|
-
});
|
|
468
|
-
|
|
469
415
|
// 2. Mid-typewriter unclosed fence → hide raw streaming source.
|
|
470
416
|
if (ctx.isTyping) {
|
|
471
|
-
const
|
|
472
|
-
|
|
473
|
-
const open = fnOpen && (!langOpen || fnOpen.index <= langOpen.index)
|
|
474
|
-
? { match: fnOpen, label: fnOpen[1] }
|
|
475
|
-
: langOpen
|
|
476
|
-
? { match: langOpen, label: 'download.' + (FENCE_LANGUAGE_EXTENSIONS[langOpen[1].toLowerCase()] || 'txt') }
|
|
477
|
-
: null;
|
|
478
|
-
if (open && typeof open.match.index === 'number') {
|
|
417
|
+
const open = working.match(OPEN_FENCE_FILENAME_RE);
|
|
418
|
+
if (open && typeof open.index === 'number') {
|
|
479
419
|
const dots = '.'.repeat(((ctx.dotTick || 0) % 3) + 1);
|
|
480
|
-
working = working.slice(0, open.
|
|
420
|
+
working = working.slice(0, open.index) + `\n[generating ${open[1]}${dots}]`;
|
|
481
421
|
}
|
|
482
422
|
}
|
|
483
423
|
|
|
@@ -1095,6 +1035,7 @@
|
|
|
1095
1035
|
this._loadingOlder = false;
|
|
1096
1036
|
this._loadingFirstHistory = false;
|
|
1097
1037
|
this._initialHistoryLoaded = false;
|
|
1038
|
+
this._isPromptComposing = false;
|
|
1098
1039
|
|
|
1099
1040
|
this.refs = {};
|
|
1100
1041
|
|
|
@@ -1516,12 +1457,11 @@
|
|
|
1516
1457
|
rows: '1',
|
|
1517
1458
|
placeholder: `Ask anything about ${this.projectName || 'the project'}...`,
|
|
1518
1459
|
oninput: (e) => this._autoGrow(e.target),
|
|
1519
|
-
onkeydown: (e) =>
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
this._sendMessage();
|
|
1523
|
-
}
|
|
1460
|
+
onkeydown: (e) => this._handlePromptEnter(e),
|
|
1461
|
+
oncompositionstart: () => {
|
|
1462
|
+
this._isPromptComposing = true;
|
|
1524
1463
|
},
|
|
1464
|
+
oncompositionend: () => this._handlePromptCompositionEnd(),
|
|
1525
1465
|
});
|
|
1526
1466
|
const sendBtn = $('button', { type: 'submit', class: 'btn' }, 'Send');
|
|
1527
1467
|
|
|
@@ -1669,6 +1609,23 @@
|
|
|
1669
1609
|
el.style.height = Math.min(el.scrollHeight, 200) + 'px';
|
|
1670
1610
|
}
|
|
1671
1611
|
|
|
1612
|
+
_handlePromptCompositionEnd() {
|
|
1613
|
+
this._isPromptComposing = false;
|
|
1614
|
+
if (this.refs.textarea) {
|
|
1615
|
+
this._autoGrow(this.refs.textarea);
|
|
1616
|
+
}
|
|
1617
|
+
}
|
|
1618
|
+
|
|
1619
|
+
_handlePromptEnter(e) {
|
|
1620
|
+
if (e.key !== 'Enter' || e.shiftKey) return;
|
|
1621
|
+
if (e.isComposing || this._isPromptComposing || e.keyCode === 229 || e.which === 229) {
|
|
1622
|
+
return;
|
|
1623
|
+
}
|
|
1624
|
+
|
|
1625
|
+
e.preventDefault();
|
|
1626
|
+
Promise.resolve().then(() => this._sendMessage());
|
|
1627
|
+
}
|
|
1628
|
+
|
|
1672
1629
|
_anyPending() {
|
|
1673
1630
|
return this.messages.some((m) => m.isPending);
|
|
1674
1631
|
}
|
|
@@ -1909,6 +1866,13 @@
|
|
|
1909
1866
|
},
|
|
1910
1867
|
}, 'Clear');
|
|
1911
1868
|
|
|
1869
|
+
const closeBtn = $('button', {
|
|
1870
|
+
type: 'button',
|
|
1871
|
+
class: 'bq-modal-close',
|
|
1872
|
+
'aria-label': 'Close',
|
|
1873
|
+
onclick: close,
|
|
1874
|
+
}, '×');
|
|
1875
|
+
|
|
1912
1876
|
const modal = $('div', {
|
|
1913
1877
|
class: 'bq-modal-backdrop',
|
|
1914
1878
|
onclick: (e) => { if (e.target === modal) close(); },
|
|
@@ -1916,6 +1880,7 @@
|
|
|
1916
1880
|
$('div', { class: 'bq-modal-delete' }, [
|
|
1917
1881
|
$('div', { class: 'bq-modal-delete-header' }, [
|
|
1918
1882
|
$('span', null, 'Clear chat history'),
|
|
1883
|
+
closeBtn,
|
|
1919
1884
|
]),
|
|
1920
1885
|
$('div', { class: 'bq-modal-delete-body' }, [
|
|
1921
1886
|
$('p', null, 'Hide all previous messages from this chat?'),
|
|
@@ -1965,6 +1930,13 @@
|
|
|
1965
1930
|
},
|
|
1966
1931
|
}, 'Logout');
|
|
1967
1932
|
|
|
1933
|
+
const closeBtn = $('button', {
|
|
1934
|
+
type: 'button',
|
|
1935
|
+
class: 'bq-modal-close',
|
|
1936
|
+
'aria-label': 'Close',
|
|
1937
|
+
onclick: close,
|
|
1938
|
+
}, '×');
|
|
1939
|
+
|
|
1968
1940
|
const modal = $('div', {
|
|
1969
1941
|
class: 'bq-modal-backdrop',
|
|
1970
1942
|
onclick: (e) => { if (e.target === modal) close(); },
|
|
@@ -1972,6 +1944,7 @@
|
|
|
1972
1944
|
$('div', { class: 'bq-modal-delete' }, [
|
|
1973
1945
|
$('div', { class: 'bq-modal-delete-header' }, [
|
|
1974
1946
|
$('span', null, 'Logout'),
|
|
1947
|
+
closeBtn,
|
|
1975
1948
|
]),
|
|
1976
1949
|
$('div', { class: 'bq-modal-delete-body' }, [
|
|
1977
1950
|
$('p', null, 'Log out of this chat?'),
|