bunnyquery 1.3.4 → 1.4.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/README.md +76 -7
- package/bunnyquery.js +177 -44
- package/dist/engine.cjs +175 -40
- package/dist/engine.cjs.map +1 -1
- package/dist/engine.d.mts +72 -1
- package/dist/engine.d.ts +72 -1
- package/dist/engine.mjs +170 -41
- package/dist/engine.mjs.map +1 -1
- package/package.json +1 -1
- package/src/engine/attachment_parsers.ts +112 -0
- package/src/engine/config.ts +11 -0
- package/src/engine/index.ts +12 -0
- package/src/engine/office.ts +26 -4
- package/src/engine/prompts/indexing_user_message.ts +20 -0
- package/src/engine/requests.ts +17 -5
- package/src/engine/session.ts +63 -0
package/README.md
CHANGED
|
@@ -24,6 +24,9 @@ you can build your own chat UI on top of it. See
|
|
|
24
24
|
first load and on scroll-up.
|
|
25
25
|
- **Attachments** — drag-and-drop files and folders, per-file upload status
|
|
26
26
|
(uploading / failed / indexed), and overflow collapsing for large batches.
|
|
27
|
+
- **Attachment parser plugins** — register a client-side parser so the widget
|
|
28
|
+
extracts text in the browser from formats the model can't otherwise read, and
|
|
29
|
+
indexes it directly. See [Attachment parser plugins](#attachment-parser-plugins).
|
|
27
30
|
- **Settings panel** — in-place inside the chat: light/dark theme, account details,
|
|
28
31
|
newsletter subscription, clear history, and remove account.
|
|
29
32
|
- **Theming** — light and dark modes via CSS custom properties; the choice is
|
|
@@ -110,17 +113,19 @@ Mounts the widget. Returns the `BunnyQuery` object.
|
|
|
110
113
|
| `dev` | `boolean` | `false` | Use the development MCP host and `skapi.app` db-CDN host instead of production. |
|
|
111
114
|
| `mcpBaseUrl` | `string` | `null` | Override the MCP OAuth server base URL entirely (advanced). |
|
|
112
115
|
| `hostDomain` | `string` | `null` | db-CDN host for temporary file URLs. Defaults to `skapi.app` (dev) / `skapi.com` (prod). |
|
|
116
|
+
| `attachmentParsers` | `array` | `null` | Client-side attachment parsers. See [Attachment parser plugins](#attachment-parser-plugins). |
|
|
113
117
|
|
|
114
118
|
### Methods
|
|
115
119
|
|
|
116
120
|
The `BunnyQuery` global also exposes:
|
|
117
121
|
|
|
118
|
-
| Method
|
|
119
|
-
|
|
|
120
|
-
| `setTheme(theme)`
|
|
121
|
-
| `toggleTheme()`
|
|
122
|
-
| `logout()`
|
|
123
|
-
| `
|
|
122
|
+
| Method | Description |
|
|
123
|
+
| ---------------------------------- | ----------------------------------------------------------------------------------- |
|
|
124
|
+
| `setTheme(theme)` | Apply `"light"` or `"dark"` and persist it. |
|
|
125
|
+
| `toggleTheme()` | Switch between light and dark. |
|
|
126
|
+
| `logout()` | Sign the current user out and return to the login view. |
|
|
127
|
+
| `registerAttachmentParser(parser)` | Register a client-side attachment parser. May be called before or after `init()`. See [Attachment parser plugins](#attachment-parser-plugins). |
|
|
128
|
+
| `version` | The widget's package version string. Also logged to the console on `init()`. |
|
|
124
129
|
|
|
125
130
|
```js
|
|
126
131
|
BunnyQuery.setTheme("dark");
|
|
@@ -129,7 +134,71 @@ BunnyQuery.logout();
|
|
|
129
134
|
```
|
|
130
135
|
|
|
131
136
|
> `init()` is idempotent — calling it twice logs a warning and returns the existing
|
|
132
|
-
> instance rather than re-mounting.
|
|
137
|
+
> instance rather than re-mounting. On a successful mount it logs its version, e.g.
|
|
138
|
+
> `[bunnyquery] v1.3.5`.
|
|
139
|
+
|
|
140
|
+
## Attachment parser plugins
|
|
141
|
+
|
|
142
|
+
By default the chat agent reads most uploads via the model's `web_fetch` tool
|
|
143
|
+
(text, CSV, PDF, …), and Office/OpenDocument/EPUB files are extracted on the
|
|
144
|
+
server. For any format that can be read by **neither** (e.g. a proprietary
|
|
145
|
+
binary format), register a **parser plugin**: it runs in the browser, turns the
|
|
146
|
+
uploaded file into text (or an HTML string), and the widget sends that content
|
|
147
|
+
**inline** for indexing — no `web_fetch`, no server extraction for that file.
|
|
148
|
+
|
|
149
|
+
BunnyQuery ships only the **mechanism**. You bring the parsing library (so the
|
|
150
|
+
widget stays lean and you choose which formats and which library).
|
|
151
|
+
|
|
152
|
+
A parser is a plain object:
|
|
153
|
+
|
|
154
|
+
```ts
|
|
155
|
+
interface AttachmentParser {
|
|
156
|
+
name?: string; // label, used in logs
|
|
157
|
+
match: (file: { name: string; mime?: string }) => boolean; // handle this file?
|
|
158
|
+
parse: (file: File) => string | null | undefined | Promise<string | null | undefined>; // text or HTML; falsy = skip
|
|
159
|
+
}
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
The first parser whose `match` returns `true` wins. A parser that throws or
|
|
163
|
+
returns nothing is ignored — the file falls back to its normal path. Output is
|
|
164
|
+
capped (~200k chars) before it is inlined.
|
|
165
|
+
|
|
166
|
+
### Example
|
|
167
|
+
|
|
168
|
+
Load whatever parsing library reads your format, then register a parser that
|
|
169
|
+
turns a `File` into text:
|
|
170
|
+
|
|
171
|
+
```html
|
|
172
|
+
<!-- bring your own parsing library, e.g. from a CDN -->
|
|
173
|
+
<script src="https://cdn.example.com/my-format-parser.js"></script>
|
|
174
|
+
<script>
|
|
175
|
+
BunnyQuery.registerAttachmentParser({
|
|
176
|
+
name: "my-format",
|
|
177
|
+
match: (file) => /\.myext$/i.test(file.name),
|
|
178
|
+
parse: async (file) => {
|
|
179
|
+
const bytes = new Uint8Array(await file.arrayBuffer());
|
|
180
|
+
return window.myFormatParser.toText(bytes); // return plain text OR an HTML string
|
|
181
|
+
},
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
BunnyQuery.init(skapi, "chatbox", { theme: "light" });
|
|
185
|
+
</script>
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
Equivalent one-shot form via init options:
|
|
189
|
+
|
|
190
|
+
```js
|
|
191
|
+
BunnyQuery.init(skapi, "chatbox", {
|
|
192
|
+
attachmentParsers: [ myParser ],
|
|
193
|
+
});
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
Bundler consumers can import the same registry from the engine:
|
|
197
|
+
|
|
198
|
+
```js
|
|
199
|
+
import { registerAttachmentParser } from "bunnyquery/engine";
|
|
200
|
+
registerAttachmentParser(myParser);
|
|
201
|
+
```
|
|
133
202
|
|
|
134
203
|
## Theming
|
|
135
204
|
|
package/bunnyquery.js
CHANGED
|
@@ -1,10 +1,52 @@
|
|
|
1
1
|
(function () {
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
|
+
// src/engine/attachment_parsers.ts
|
|
5
|
+
var MAX_PARSED_CONTENT_CHARS = 2e5;
|
|
6
|
+
var _parsers = [];
|
|
7
|
+
function registerAttachmentParser(parser) {
|
|
8
|
+
if (parser && typeof parser.match === "function" && typeof parser.parse === "function" && _parsers.indexOf(parser) === -1) {
|
|
9
|
+
_parsers.push(parser);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
function findAttachmentParser(name, mime) {
|
|
13
|
+
for (let i = 0; i < _parsers.length; i++) {
|
|
14
|
+
try {
|
|
15
|
+
if (_parsers[i].match({ name, mime })) return _parsers[i];
|
|
16
|
+
} catch {
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return void 0;
|
|
20
|
+
}
|
|
21
|
+
async function parseAttachmentContent(file, name, mime) {
|
|
22
|
+
const parser = findAttachmentParser(name, mime);
|
|
23
|
+
if (!parser) return null;
|
|
24
|
+
let raw;
|
|
25
|
+
try {
|
|
26
|
+
raw = await parser.parse(file);
|
|
27
|
+
} catch (err) {
|
|
28
|
+
console.error(
|
|
29
|
+
`[chat-engine] attachment parser ${parser.name || "(unnamed)"} failed for ${name}:`,
|
|
30
|
+
err
|
|
31
|
+
);
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
let text = (raw == null ? "" : String(raw)).trim();
|
|
35
|
+
if (!text) return null;
|
|
36
|
+
if (text.length > MAX_PARSED_CONTENT_CHARS) {
|
|
37
|
+
text = text.slice(0, MAX_PARSED_CONTENT_CHARS) + `
|
|
38
|
+
...[truncated for length; original ${text.length} characters]`;
|
|
39
|
+
}
|
|
40
|
+
return text;
|
|
41
|
+
}
|
|
42
|
+
|
|
4
43
|
// src/engine/config.ts
|
|
5
44
|
var _config = null;
|
|
6
45
|
function configureChatEngine(config) {
|
|
7
46
|
_config = config;
|
|
47
|
+
if (config.attachmentParsers) {
|
|
48
|
+
for (const parser of config.attachmentParsers) registerAttachmentParser(parser);
|
|
49
|
+
}
|
|
8
50
|
}
|
|
9
51
|
function chatEngineConfig() {
|
|
10
52
|
if (!_config) {
|
|
@@ -31,13 +73,41 @@
|
|
|
31
73
|
"pptx",
|
|
32
74
|
"pptm",
|
|
33
75
|
"hwp",
|
|
34
|
-
"hwpx"
|
|
76
|
+
"hwpx",
|
|
77
|
+
"ods",
|
|
78
|
+
"odt",
|
|
79
|
+
"odp",
|
|
80
|
+
"epub"
|
|
81
|
+
]);
|
|
82
|
+
var WEB_FETCHABLE_TEXT_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
83
|
+
"csv",
|
|
84
|
+
"tsv",
|
|
85
|
+
"tab",
|
|
86
|
+
"txt",
|
|
87
|
+
"text",
|
|
88
|
+
"md",
|
|
89
|
+
"markdown",
|
|
90
|
+
"json",
|
|
91
|
+
"ndjson",
|
|
92
|
+
"jsonl",
|
|
93
|
+
"xml",
|
|
94
|
+
"yaml",
|
|
95
|
+
"yml",
|
|
96
|
+
"log",
|
|
97
|
+
// RTF is a TEXT format (not a binary zip), so web_fetch can read it and the
|
|
98
|
+
// model decodes its control words. Pin it here so a `.rtf` reported as
|
|
99
|
+
// `application/msword` isn't misrouted to server-side extraction (which has no
|
|
100
|
+
// .rtf extractor → "unsupported format" note).
|
|
101
|
+
"rtf",
|
|
102
|
+
"htm",
|
|
103
|
+
"html"
|
|
35
104
|
]);
|
|
36
105
|
function isOfficeFile(name, mime) {
|
|
37
106
|
const ext = (name || "").split(".").pop()?.toLowerCase() || "";
|
|
38
107
|
if (OFFICE_FILE_EXTENSIONS.has(ext)) return true;
|
|
108
|
+
if (WEB_FETCHABLE_TEXT_EXTENSIONS.has(ext)) return false;
|
|
39
109
|
const m = (mime || "").toLowerCase();
|
|
40
|
-
return m.includes("officedocument") || m.includes("hwp") || m === "application/msword" || m === "application/vnd.ms-excel" || m === "application/vnd.ms-powerpoint";
|
|
110
|
+
return m.includes("officedocument") || m.includes("opendocument") || m.includes("hwp") || m.includes("epub") || m === "application/msword" || m === "application/vnd.ms-excel" || m === "application/vnd.ms-powerpoint";
|
|
41
111
|
}
|
|
42
112
|
var _extractPlaceholderSeq = 0;
|
|
43
113
|
function makeExtractPlaceholder(seed) {
|
|
@@ -158,6 +228,14 @@ File metadata:
|
|
|
158
228
|
` + (attachment.mime ? `- mime type: ${attachment.mime}
|
|
159
229
|
` : "") + (typeof attachment.size === "number" ? `- size (bytes): ${attachment.size}
|
|
160
230
|
` : "");
|
|
231
|
+
if (options?.inlineContent) {
|
|
232
|
+
return head + `
|
|
233
|
+
The file's content was parsed by the client and is provided inline below. Read it directly \u2014 do NOT fetch any URL for this file. Use the storage path above (not this content) for the "src::" unique_id.
|
|
234
|
+
|
|
235
|
+
----- BEGIN FILE CONTENT -----
|
|
236
|
+
${options.inlineContent}
|
|
237
|
+
----- END FILE CONTENT -----`;
|
|
238
|
+
}
|
|
161
239
|
if (options?.inlineContentPlaceholder) {
|
|
162
240
|
return head + `
|
|
163
241
|
The file's text content was extracted on the server and is provided inline below. Read it directly \u2014 do NOT fetch any URL for this file. Use the storage path above (not this content) for the "src::" unique_id.
|
|
@@ -653,14 +731,14 @@ ${options.inlineContentPlaceholder}
|
|
|
653
731
|
});
|
|
654
732
|
}
|
|
655
733
|
async function notifyAgentSaveAttachment(info) {
|
|
656
|
-
const { platform, service, owner, attachment } = info;
|
|
657
|
-
const office = isOfficeFile(attachment.name, attachment.mime);
|
|
734
|
+
const { platform, service, owner, attachment, parsedContent } = info;
|
|
735
|
+
const office = !parsedContent && isOfficeFile(attachment.name, attachment.mime);
|
|
658
736
|
const placeholder = office ? makeExtractPlaceholder(attachment.storagePath) : void 0;
|
|
659
737
|
const extractContent = office && placeholder ? [{ path: attachment.storagePath, placeholder, name: attachment.name, mime: attachment.mime }] : void 0;
|
|
660
738
|
const skapiExtract = extractContent && extractContent.length ? { _skapi_extract: extractContent } : {};
|
|
661
739
|
const userMessage = buildIndexingUserMessage(
|
|
662
740
|
attachment,
|
|
663
|
-
placeholder ? { inlineContentPlaceholder: placeholder } : void 0
|
|
741
|
+
parsedContent ? { inlineContent: parsedContent } : placeholder ? { inlineContentPlaceholder: placeholder } : void 0
|
|
664
742
|
);
|
|
665
743
|
const systemPrompt = buildIndexingSystemPrompt({
|
|
666
744
|
service,
|
|
@@ -1221,6 +1299,7 @@ ${options.inlineContentPlaceholder}
|
|
|
1221
1299
|
this.enqueueTypewrite(aiIdx, answer, lid);
|
|
1222
1300
|
}
|
|
1223
1301
|
}
|
|
1302
|
+
this._removeStrayPendingAssistants();
|
|
1224
1303
|
this.promoteNextQueuedToRunning();
|
|
1225
1304
|
this.updateHistoryCache();
|
|
1226
1305
|
this.host.notify();
|
|
@@ -1263,6 +1342,7 @@ ${options.inlineContentPlaceholder}
|
|
|
1263
1342
|
if (thPos2 !== -1) this.state.messages.splice(thPos2, 1);
|
|
1264
1343
|
}
|
|
1265
1344
|
if (serverId) this.cancelledServerIds.delete(serverId);
|
|
1345
|
+
this._removeStrayPendingAssistants();
|
|
1266
1346
|
this.promoteNextQueuedToRunning();
|
|
1267
1347
|
this.updateHistoryCache();
|
|
1268
1348
|
this.host.notify();
|
|
@@ -1276,6 +1356,7 @@ ${options.inlineContentPlaceholder}
|
|
|
1276
1356
|
return;
|
|
1277
1357
|
}
|
|
1278
1358
|
this.insertAtTarget({ role: "assistant", content: getErrorMessage(err), isError: true }, targetIdx);
|
|
1359
|
+
this._removeStrayPendingAssistants();
|
|
1279
1360
|
this.promoteNextQueuedToRunning();
|
|
1280
1361
|
this.updateHistoryCache();
|
|
1281
1362
|
this.host.notify();
|
|
@@ -1437,16 +1518,48 @@ ${options.inlineContentPlaceholder}
|
|
|
1437
1518
|
if (pendingIdx === -1) return Promise.resolve();
|
|
1438
1519
|
if (latest.isError || !latest.content) {
|
|
1439
1520
|
this.state.messages[pendingIdx] = { role: "assistant", content: latest.content || "", isError: !!latest.isError };
|
|
1521
|
+
this._removeStrayPendingAssistants();
|
|
1440
1522
|
this.host.notify();
|
|
1441
1523
|
this.promoteNextQueuedToRunning();
|
|
1442
1524
|
return Promise.resolve();
|
|
1443
1525
|
}
|
|
1444
1526
|
var lid = this._newLocalId();
|
|
1445
1527
|
this.state.messages[pendingIdx] = { role: "assistant", content: "", isPending: false, _localId: lid };
|
|
1528
|
+
this._removeStrayPendingAssistants();
|
|
1446
1529
|
this.host.notify();
|
|
1447
1530
|
this.promoteNextQueuedToRunning();
|
|
1448
1531
|
return this.enqueueTypewrite(pendingIdx, latest.content, lid);
|
|
1449
1532
|
}
|
|
1533
|
+
// Remove any leftover non-background pending ("Thinking…") assistant bubbles.
|
|
1534
|
+
// There is normally at most ONE such bubble at a time (promoteNext* refuses to
|
|
1535
|
+
// add a second), so any extra is a duplicate — it appears when a concurrent
|
|
1536
|
+
// history refetch re-maps the still-"running" turn into a pending placeholder
|
|
1537
|
+
// (with a real _serverItemId) while the local pending bubble (no _serverItemId)
|
|
1538
|
+
// is rescued and re-appended (see loadHistory rescue below). Each resolve path
|
|
1539
|
+
// only replaces the FIRST pending bubble, so without this a stray "Thinking…"
|
|
1540
|
+
// survives next to the reply/error. MUST run AFTER the resolved bubble has been
|
|
1541
|
+
// made non-pending and BEFORE promoteNext*() (so a freshly-promoted Thinking,
|
|
1542
|
+
// which is added only once no pending assistant remains, is preserved).
|
|
1543
|
+
_removeStrayPendingAssistants() {
|
|
1544
|
+
for (var k = this.state.messages.length - 1; k >= 0; k--) {
|
|
1545
|
+
var m = this.state.messages[k];
|
|
1546
|
+
if (m.isPending && m.role === "assistant" && !m.isBackgroundTask) this.state.messages.splice(k, 1);
|
|
1547
|
+
}
|
|
1548
|
+
}
|
|
1549
|
+
// Drop the pending flags on the resolved turn's USER bubble (preserving its
|
|
1550
|
+
// content + background-task marker). Needed because a bg "Indexing:" turn's user
|
|
1551
|
+
// bubble carries isPendingInProcess; leaving it set keeps the bubble visually
|
|
1552
|
+
// stuck and keeps its bgTaskQueue entry alive forever.
|
|
1553
|
+
_clearPendingUserBubble(itemId) {
|
|
1554
|
+
var uIdx = this.state.messages.findIndex(function(m) {
|
|
1555
|
+
return m.role === "user" && m._serverItemId === itemId && (m.isPendingInProcess || m.isPendingQueued || m.isSendingToServer);
|
|
1556
|
+
});
|
|
1557
|
+
if (uIdx === -1) return;
|
|
1558
|
+
var u = this.state.messages[uIdx];
|
|
1559
|
+
var cleaned = { role: "user", content: u.content, _serverItemId: itemId };
|
|
1560
|
+
if (u.isBackgroundTask) cleaned.isBackgroundTask = true;
|
|
1561
|
+
this.state.messages[uIdx] = cleaned;
|
|
1562
|
+
}
|
|
1450
1563
|
// If an immediate-send request for the current cache key is still in flight
|
|
1451
1564
|
// (e.g. the view unmounted then remounted mid-request), show the sending
|
|
1452
1565
|
// state, await it, then render the reply from the cache. Skipped when the
|
|
@@ -1485,6 +1598,7 @@ ${options.inlineContentPlaceholder}
|
|
|
1485
1598
|
return m.isPending && m._serverItemId === itemId;
|
|
1486
1599
|
});
|
|
1487
1600
|
if (idx !== -1) {
|
|
1601
|
+
this._clearPendingUserBubble(itemId);
|
|
1488
1602
|
if (isErr) {
|
|
1489
1603
|
this.state.messages[idx] = { role: "assistant", content: answer, isError: true, _serverItemId: itemId };
|
|
1490
1604
|
this.host.notify();
|
|
@@ -1634,12 +1748,16 @@ ${options.inlineContentPlaceholder}
|
|
|
1634
1748
|
self.state.messages.forEach(function(m) {
|
|
1635
1749
|
if (m.isCancelled && m._serverItemId) locallyCancelled[m._serverItemId] = 1;
|
|
1636
1750
|
});
|
|
1751
|
+
var mappedHasPendingAssistant = mapped.some(function(m) {
|
|
1752
|
+
return m.isPending && m.role === "assistant" && !m.isBackgroundTask;
|
|
1753
|
+
});
|
|
1637
1754
|
var rescued = [];
|
|
1638
1755
|
for (var ri = 0; ri < self.state.messages.length; ri++) {
|
|
1639
1756
|
var mm = self.state.messages[ri];
|
|
1640
1757
|
if (mm.isBackgroundTask) continue;
|
|
1641
1758
|
if (mm._serverItemId && serverIds[mm._serverItemId]) continue;
|
|
1642
1759
|
if (!mm._serverItemId) {
|
|
1760
|
+
if (mappedHasPendingAssistant) continue;
|
|
1643
1761
|
if (mm.isSendingToServer || mm.isPendingQueued || mm.isPendingInProcess || mm.isPending) rescued.push(mm);
|
|
1644
1762
|
else if (self.state.sending && mm.role === "user") {
|
|
1645
1763
|
var next = self.state.messages[ri + 1];
|
|
@@ -1797,44 +1915,49 @@ ${options.inlineContentPlaceholder}
|
|
|
1797
1915
|
att.storagePath = member.storagePath;
|
|
1798
1916
|
}
|
|
1799
1917
|
var mime = member.file.type || self.host.getMimeType(member.file.name);
|
|
1800
|
-
return
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
url
|
|
1814
|
-
}
|
|
1815
|
-
}).then(function(ack) {
|
|
1816
|
-
if (ack && typeof ack.id === "string") {
|
|
1817
|
-
self.bgTaskQueue.push({
|
|
1818
|
-
serviceId: id.serviceId,
|
|
1819
|
-
platform: id.platform,
|
|
1820
|
-
id: ack.id,
|
|
1821
|
-
filename: member.file.name,
|
|
1918
|
+
return Promise.resolve(
|
|
1919
|
+
parseAttachmentContent(member.file, member.file.name, mime || void 0)
|
|
1920
|
+
).then(function(parsedContent) {
|
|
1921
|
+
return notifyAgentSaveAttachment({
|
|
1922
|
+
platform: id.platform,
|
|
1923
|
+
model: id.model,
|
|
1924
|
+
service: id.serviceId,
|
|
1925
|
+
owner: id.owner,
|
|
1926
|
+
userId: id.userId || id.serviceId,
|
|
1927
|
+
serviceName: id.serviceName,
|
|
1928
|
+
serviceDescription: id.serviceDescription,
|
|
1929
|
+
attachment: {
|
|
1930
|
+
name: member.file.name,
|
|
1822
1931
|
storagePath: member.storagePath,
|
|
1823
|
-
isReindex: hadExists,
|
|
1824
1932
|
mime: mime || void 0,
|
|
1825
1933
|
size: member.file.size,
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1934
|
+
url
|
|
1935
|
+
},
|
|
1936
|
+
parsedContent: parsedContent || void 0
|
|
1937
|
+
}).then(function(ack) {
|
|
1938
|
+
if (ack && typeof ack.id === "string") {
|
|
1939
|
+
self.bgTaskQueue.push({
|
|
1940
|
+
serviceId: id.serviceId,
|
|
1941
|
+
platform: id.platform,
|
|
1942
|
+
id: ack.id,
|
|
1943
|
+
filename: member.file.name,
|
|
1944
|
+
storagePath: member.storagePath,
|
|
1945
|
+
isReindex: hadExists,
|
|
1946
|
+
mime: mime || void 0,
|
|
1947
|
+
size: member.file.size,
|
|
1948
|
+
status: ack.status === "running" ? "running" : "pending",
|
|
1949
|
+
poll: ack.poll
|
|
1950
|
+
});
|
|
1951
|
+
self.drainBgTaskQueue();
|
|
1952
|
+
}
|
|
1953
|
+
}, function(e) {
|
|
1954
|
+
console.error("[chat-engine] indexing request failed", e);
|
|
1955
|
+
anyIndexFailed = true;
|
|
1956
|
+
if (!att.errorCode && !att.errorDetail) {
|
|
1957
|
+
att.errorCode = e && (e.code || e.body && e.body.code) || "";
|
|
1958
|
+
att.errorDetail = e && (e.message || e.body && e.body.message) || (typeof e === "string" ? e : "");
|
|
1959
|
+
}
|
|
1960
|
+
});
|
|
1838
1961
|
});
|
|
1839
1962
|
});
|
|
1840
1963
|
});
|
|
@@ -1918,6 +2041,7 @@ ${options.inlineContentPlaceholder}
|
|
|
1918
2041
|
(function() {
|
|
1919
2042
|
var MCP_PROD = "https://mcp.broadwayinc.computer";
|
|
1920
2043
|
var MCP_DEV = "https://mcp-dev.broadwayinc.computer";
|
|
2044
|
+
var BQ_VERSION = "1.4.0" ;
|
|
1921
2045
|
var ATTACHMENT_URL_EXPIRES_SECONDS = 600;
|
|
1922
2046
|
var GOOGLE_AUTH_URL = "https://accounts.google.com/o/oauth2/v2/auth";
|
|
1923
2047
|
var GOOGLE_TOKEN_URL = "https://oauth2.googleapis.com/token";
|
|
@@ -2139,7 +2263,7 @@ ${options.inlineContentPlaceholder}
|
|
|
2139
2263
|
if (typeof S.skapi.getConnectionInfo === "function") return S.skapi.getConnectionInfo();
|
|
2140
2264
|
return S.skapi.connection || null;
|
|
2141
2265
|
}).then(function(conn) {
|
|
2142
|
-
console.log("[bunnyquery] loadServiceInfo", conn);
|
|
2266
|
+
if (S.opts && S.opts.dev) console.log("[bunnyquery] loadServiceInfo", conn);
|
|
2143
2267
|
if (conn) {
|
|
2144
2268
|
S.serviceId = conn.service || S.serviceId;
|
|
2145
2269
|
S.owner = conn.owner || S.owner;
|
|
@@ -4961,8 +5085,10 @@ ${options.inlineContentPlaceholder}
|
|
|
4961
5085
|
googleClientSecretName: "ggl",
|
|
4962
5086
|
signupConfirmationUrl: null,
|
|
4963
5087
|
// defaults to current host page
|
|
4964
|
-
hostDomain: null
|
|
5088
|
+
hostDomain: null,
|
|
4965
5089
|
// db-CDN host; null → skapi.app (dev) / skapi.com (prod)
|
|
5090
|
+
attachmentParsers: null
|
|
5091
|
+
// client-side attachment parsers, e.g. [createHwpParser()]
|
|
4966
5092
|
}, opts || {});
|
|
4967
5093
|
S.mountEl = mountEl;
|
|
4968
5094
|
clear(mountEl);
|
|
@@ -4970,6 +5096,7 @@ ${options.inlineContentPlaceholder}
|
|
|
4970
5096
|
mountEl.appendChild(S.root);
|
|
4971
5097
|
applyTheme(loadTheme());
|
|
4972
5098
|
S.booted = true;
|
|
5099
|
+
console.log("[bunnyquery] v" + BQ_VERSION);
|
|
4973
5100
|
configureChatEngine({
|
|
4974
5101
|
clientSecretRequest: function(o) {
|
|
4975
5102
|
return S.skapi.clientSecretRequest(o);
|
|
@@ -4978,7 +5105,9 @@ ${options.inlineContentPlaceholder}
|
|
|
4978
5105
|
return S.skapi.clientSecretRequestHistory(p, f);
|
|
4979
5106
|
},
|
|
4980
5107
|
mcpBaseUrl: mcpBaseUrl(),
|
|
4981
|
-
poll: 0
|
|
5108
|
+
poll: 0,
|
|
5109
|
+
// Client-side attachment parsers (e.g. an .hwp parser) passed via init opts.
|
|
5110
|
+
attachmentParsers: S.opts.attachmentParsers || void 0
|
|
4982
5111
|
});
|
|
4983
5112
|
if (!S._resizeBound && typeof window !== "undefined" && window.addEventListener) {
|
|
4984
5113
|
S._resizeBound = true;
|
|
@@ -4991,12 +5120,16 @@ ${options.inlineContentPlaceholder}
|
|
|
4991
5120
|
}
|
|
4992
5121
|
var PUBLIC = {
|
|
4993
5122
|
init,
|
|
5123
|
+
// Register a client-side attachment parser (e.g. createHwpParser()) so the
|
|
5124
|
+
// widget parses matching uploads in-browser and sends the text for indexing.
|
|
5125
|
+
// Can be called before or after init(); also settable via init opts.attachmentParsers.
|
|
5126
|
+
registerAttachmentParser,
|
|
4994
5127
|
setTheme: function(t) {
|
|
4995
5128
|
applyTheme(t);
|
|
4996
5129
|
},
|
|
4997
5130
|
toggleTheme,
|
|
4998
5131
|
logout,
|
|
4999
|
-
version:
|
|
5132
|
+
version: BQ_VERSION,
|
|
5000
5133
|
_state: S
|
|
5001
5134
|
// exposed for later-phase modules / debugging
|
|
5002
5135
|
};
|