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 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: 3px;
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: 4px;
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
- // Bare language tags we treat as downloadable files. Maps to the
324
- // extension used in the synthesized filename. Mirrors the file types the
325
- // host agent (agent.vue) is known to emit as ```filename.ext fences.
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
- // Closed fences with `filename.ext` after the opening backticks.
359
- const FENCE_FILENAME_RE = /```([\w.-]+\.[a-zA-Z0-9]+)\n([\s\S]*?)```/g;
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
- // Bare-fence filename minting state, keyed per-message so re-renders
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 fnOpen = working.match(OPEN_FENCE_FILENAME_RE);
472
- const langOpen = working.match(OPEN_FENCE_LANG_RE);
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.match.index) + `\n[generating ${open.label}${dots}]`;
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
- if (e.key === 'Enter' && !e.shiftKey) {
1521
- e.preventDefault();
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?'),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bunnyquery",
3
- "version": "1.1.1",
3
+ "version": "1.1.3",
4
4
  "description": "BunnyQuery AI chat client",
5
5
  "main": "bunnyquery.js",
6
6
  "files": [