bunnyquery 1.8.10 → 1.8.12

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
@@ -1088,7 +1088,11 @@
1088
1088
  cancelled bubbles, indexing passes (the collapsed rows' members), and the
1089
1089
  empty-chat greeting. Inlined data URI so both consumers get it without
1090
1090
  markup or asset-hosting changes. */
1091
- .bq-message.is-assistant:not(.is-error):not(.is-cancelled):not(.bq-index-pass):not(.bq-empty-greeting) .bq-bubble::before {
1091
+ /* The greeting is NOT excluded here any more. It used to be
1092
+ (:not(.bq-empty-greeting)), because it was a one-off notice shown only to an
1093
+ empty chat. It is now a permanent first message in every conversation, so it
1094
+ is an assistant bubble like any other and wears the same face. */
1095
+ .bq-message.is-assistant:not(.is-error):not(.is-cancelled):not(.bq-index-pass) .bq-bubble::before {
1092
1096
  content: "";
1093
1097
  /* block, not float: the bunny takes the first line by itself and the
1094
1098
  message starts on the next line below it */
@@ -1103,12 +1107,12 @@
1103
1107
  /* While the reply is still "Thinking..." the bubble holds only the dot-trail
1104
1108
  loader: put the bunny INLINE with the dots instead of stacking above them.
1105
1109
  Settled replies keep the block bunny with the message on the next line. */
1106
- .bq-message.is-assistant:not(.is-error):not(.is-cancelled):not(.bq-index-pass):not(.bq-empty-greeting) .bq-bubble:has(> .bq-loader)::before {
1110
+ .bq-message.is-assistant:not(.is-error):not(.is-cancelled):not(.bq-index-pass) .bq-bubble:has(> .bq-loader)::before {
1107
1111
  display: inline-block;
1108
1112
  vertical-align: middle;
1109
1113
  margin-right: 0.4rem;
1110
1114
  }
1111
- .bq-message.is-assistant:not(.is-error):not(.is-cancelled):not(.bq-index-pass):not(.bq-empty-greeting) .bq-bubble:has(> .bq-loader) > .bq-loader {
1115
+ .bq-message.is-assistant:not(.is-error):not(.is-cancelled):not(.bq-index-pass) .bq-bubble:has(> .bq-loader) > .bq-loader {
1112
1116
  vertical-align: middle;
1113
1117
  }
1114
1118
 
@@ -1494,7 +1498,7 @@
1494
1498
  background: rgba(127, 127, 127, 0.08);
1495
1499
  }
1496
1500
  .bq-md hr { border: none; border-top: 1px solid var(--bq-line); margin: 0.8em 0; }
1497
- .bq-md a { color: var(--bq-main); text-decoration: underline; }
1501
+ .bq-md a { color: var(--bq-main); text-decoration: underline;vertical-align: text-bottom; }
1498
1502
  .bq-md a:hover { text-decoration: none; }
1499
1503
  .bq-md code {
1500
1504
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
package/bunnyquery.js CHANGED
@@ -276,10 +276,10 @@ Extracted content of attached office files (read inline below; do NOT fetch thei
276
276
 
277
277
  // src/engine/prompts/chat_system_prompt.ts
278
278
  function buildChatSystemPrompt(params) {
279
- const { projectId, serviceName, serviceDescription } = params;
279
+ const { projectId, serviceName, serviceDescription, greeting, canUpload} = params;
280
280
  let systemPrompt = `
281
281
  You are a dedicated assistant for the project ID: "${projectId}".
282
- Scope: Only answer questions about this project and its data. Do not answer questions about other projects or topics unrelated to this project. When the user refers to "my database", "my data", or "my files", treat those as references to this project's database and file storage.
282
+ Scope: Only answer questions about this project and its data. Do not answer questions about other projects or topics unrelated to this project. When the user refers to "my database", "my data", or "my files", treat those as references to this project's database and file storage. The ONE exception is BunnyQuery itself - what this app is, what it can do, and how to use it - which is always in scope: answer it from the "About BunnyQuery" section at the end of this prompt.
283
283
  Knowledge lookup: Before saying you don't know or that something isn't in the chat history, ALWAYS query this project's database through the available MCP tools to look for the answer. The user's data is the source of truth - the chat transcript is not. Only respond with "I don't know" or "I couldn't find that" after you have actually searched the project's data and come back empty.
284
284
  Complete answers over stored data: The database holds one record per spreadsheet row, and each uploaded file becomes many records. ONE file is routinely SPLIT ACROSS SEVERAL TABLES - a summary row in one table, its page or row content in another, its extracted photos and other media in "__MEDIA__", and the indexer often invents a differently-named table on each pass. An index or tag filter matches inside ONE table only and requires table_name: on getRecords, an index or tag sent with table_name but no access_group is auto-filled with access_group "authorized" (where the indexer writes; pass access_group explicitly, including 0, to search another group), while an index or tag WITHOUT table_name FAILS with an error instead of answering, so read the error rather than guessing. Reference is the exception: reference ALONE spans EVERY table and EVERY access group, so getRecords with reference "src::<the file's storage path>" is the one call that returns a whole file's records wherever the indexer put them. Adding table_name narrows it to that table; access_group WITHOUT table_name fails with '"table" is required'; table_name on its own returns that whole table across all access groups. For anything NOT scoped to a single file, call getTables FIRST, run the query once per table that could hold the answer, and combine the results. For any request that counts, sums, totals, lists every match, compares across records, finds which one, or asks whether something is present or ABSENT (for example "how many", "total spent", "which card", "is there any", "\uC5C6\uC5B4?", "\uD558\uB098\uB3C4 \uC5C6\uB098?"), you MUST read the COMPLETE matching set before answering. Query with fetch_all set to true, or page through getToolResponsePage until pagination.complete is true, across EVERY table and EVERY relevant file. A single default query returns only the first page (about 50 records). That is a SAMPLE. Never treat it as the whole dataset. If you already answered from one table and then realise another table holds more, do not simply apologise: re-run the sweep and give the complete answer.
285
285
  Never assert absence from a partial read. Do not say "there is no X", "none", "not found", or "\uC544\uB2C8\uC694, \uC5C6\uC2B5\uB2C8\uB2E4" until a complete scan has come back empty. If you have not finished scanning every relevant table and file, keep querying instead of guessing. A confident "no" that later turns out wrong is worse than telling the user you are still checking.
@@ -309,6 +309,16 @@ Mushrooms,41,$73.80
309
309
  Zucchini,29,$43.50
310
310
  \`\`\`
311
311
  The same pattern applies to any format - name the block after the file you intend: \`\`\`my-data.json, \`\`\`index.html, \`\`\`sample.txt, and so on.`;
312
+ systemPrompt += `
313
+ About BunnyQuery (this app - questions about it are in scope):
314
+ You are the assistant inside BunnyQuery, an AI assistant for the user's own business data. Instead of digging through folders, dashboards and files, the user uploads their documents, spreadsheets, images, notes and records, BunnyQuery indexes them into this project's database, and you answer questions, write reports and summarize from THAT data rather than from the open internet. Each project has its own data, its own AI platform (ChatGPT or Claude, powered by the project owner's own API key) and its own base prompt. BunnyQuery is built on Skapi (www.skapi.com), so the same project database is also reachable over MCP from any MCP-compatible AI client (mcp.broadwayinc.computer), and this chat can be embedded in a website as a widget with one script tag. Answer product questions from the facts in this section. If you are asked something about BunnyQuery that is NOT stated here - pricing, plan limits, a roadmap, a feature you cannot see - say you are not certain and point the user at the project owner or the BunnyQuery site, rather than inventing it.
315
+ How data gets in: ${canUpload === false ? `this user CANNOT upload in this session (they are not signed in, or the project's database is frozen for non-admins), and the attach affordances are hidden from them. Never instruct them to attach, drag in or upload a file, and never blame a missing answer on them not having uploaded it. Answer from what is already indexed, and when something genuinely is not in the project, say so and suggest asking the project's owner to add it.` : `the user attaches files to a chat message with the paperclip button in the composer, or drags and drops them onto the chat (whole folders work; up to 20 files per message). Uploaded files land in this project's file storage and are indexed automatically: read end to end and turned into database records. "Indexed" means exactly that, and it is why you can only answer from a file once its indexing has finished. While a file indexes, the chat shows a status row for it: yellow while it is working, green when it is indexed, red if it failed. A large file is indexed in windows over several passes, which takes longer; indexing runs on the server, so it keeps going if the user closes the page and the row is still there when they come back. The user can also paste plain text straight into the chat and ask you to save it - store it with the postRecords tool. BunnyQuery reads over 50 formats: office documents (.docx, .xlsx, .pptx, .hwp, .hwpx, .odt, .ods, .odp, .epub), PDFs, images, .csv/.tsv, .json, .xml, .html, .txt/.md and source code. Images and scanned PDFs are read with vision at index time.`}
316
+ Getting answers out: the user asks in plain language, in any language, and you answer from this project's data. You can also produce reports and downloadable files (CSV and the rest) as described in the File generation rules above, and any stored file can be handed back as a link, with images rendering inline in the chat.${""}${`
317
+ This chat is the BunnyQuery widget embedded in a website, so the user may have no access to the project console: keep any instructions to what can be done here in the chat.` }`;
318
+ if (greeting) {
319
+ systemPrompt += `
320
+ Your opening message: this chat always opens with a fixed line from you, reading """${greeting}""". It is rendered by the client and is NOT part of the message history you receive, so the user can reply to it ("which files?", "what do you mean by indexed?", "what can you do?") with nothing in the transcript to refer back to. Treat that line as something you said, and answer the follow-up from this section.`;
321
+ }
312
322
  if (serviceDescription) {
313
323
  systemPrompt += `
314
324
  Project name: "${serviceName ?? ""}"
@@ -455,6 +465,14 @@ Records for the earlier windows/pages of this file are ALREADY saved (they refer
455
465
  Index the REMAINING windows - one record per row/item, looking at any page images or embedded photos - saving as you go until readFileContent reports END OF FILE. A \xABPHOTO <cell>\xBB marker in a window marks an embedded picture whose extracted file already has a record in table "__MEDIA__": find it with getRecords reference "src::" + the storage path above and match the cell against data.anchor or tags (a repeated picture is stored under its first anchor only), then enrich it with updateRecords. Never create a photo record of your own and never construct a path for one. Do NOT re-save windows that are already saved. Set every record's reference to exactly "src::" + the storage path above (no sheet, window or summary suffix added). That file record already exists, so do NOT post it; enrich it with updateRecords. When the ENTIRE file is finally indexed, end your message with the token INDEXING_COMPLETE.`;
456
466
  }
457
467
 
468
+ // src/engine/greeting.ts
469
+ function buildChatGreeting(params) {
470
+ const name = params.projectName ? '"' + params.projectName + '"' : "";
471
+ const lead = params.canUpload === false ? "Hi! Ask me anything about the data in your project" : "Hi! Start by attaching the files related to your project";
472
+ const tail = params.canUpload === false ? "." : ", or pasting plain text into the chat. Once they are indexed, ask me anything about that data.";
473
+ return { lead, name, tail, text: lead + (name ? " " + name : "") + tail };
474
+ }
475
+
458
476
  // src/engine/errors.ts
459
477
  var STATUS_MESSAGE = {
460
478
  "408": "The AI provider timed out before it started.",
@@ -5980,7 +5998,7 @@ Index the REMAINING windows - one record per row/item, looking at any page image
5980
5998
  (function() {
5981
5999
  var MCP_PROD = "https://mcp.broadwayinc.computer";
5982
6000
  var MCP_DEV = "https://mcp-dev.broadwayinc.computer";
5983
- var BQ_VERSION = "1.8.10" ;
6001
+ var BQ_VERSION = "1.8.11" ;
5984
6002
  var ATTACHMENT_URL_EXPIRES_SECONDS = 600;
5985
6003
  var GOOGLE_AUTH_URL = "https://accounts.google.com/o/oauth2/v2/auth";
5986
6004
  var GOOGLE_TOKEN_URL = "https://oauth2.googleapis.com/token";
@@ -7816,8 +7834,12 @@ Index the REMAINING windows - one record per row/item, looking at any page image
7816
7834
  return buildChatSystemPrompt({
7817
7835
  projectId: promptProjectId,
7818
7836
  serviceName: S.serviceName,
7819
- serviceDescription: S.serviceDescription
7820
- });
7837
+ serviceDescription: S.serviceDescription,
7838
+ // The opening bubble never enters the history (it is DOM the client
7839
+ // paints), so the model is told what it opened with. Same call as
7840
+ // buildGreetingEl, so the two can never disagree.
7841
+ greeting: greetingParts().text,
7842
+ canUpload: !uploadsFrozenForUser()});
7821
7843
  }
7822
7844
  function refreshSkapiSession() {
7823
7845
  return S.skapi.getProfile({ refreshToken: true }).then(function() {
@@ -9218,7 +9240,6 @@ Index the REMAINING windows - one record per row/item, looking at any page image
9218
9240
  var stubRecheckTimer = null;
9219
9241
  var stubRecheckSig = "";
9220
9242
  var stubRecheckRounds = 0;
9221
- var markerSweepSettled = false;
9222
9243
  function armStubRecheck() {
9223
9244
  if (stubRecheckTimer !== null) return;
9224
9245
  stubRecheckTimer = setTimeout(function() {
@@ -9262,14 +9283,12 @@ Index the REMAINING windows - one record per row/item, looking at any page image
9262
9283
  function refreshIndexMarkers(invalidate) {
9263
9284
  if (invalidate) invalidateIndexMarkerSweep();
9264
9285
  return sweepIndexMarkersDb().then(function(res) {
9265
- markerSweepSettled = true;
9266
9286
  if (res) {
9267
9287
  maybeArmStubRecheck();
9268
9288
  renderMessages();
9269
9289
  }
9270
9290
  return res;
9271
9291
  }).catch(function() {
9272
- markerSweepSettled = true;
9273
9292
  renderMessages();
9274
9293
  return null;
9275
9294
  });
@@ -9554,6 +9573,18 @@ Index the REMAINING windows - one record per row/item, looking at any page image
9554
9573
  }
9555
9574
  return el;
9556
9575
  }
9576
+ function greetingParts() {
9577
+ return buildChatGreeting({ projectName: S.serviceName, canUpload: !uploadsFrozenForUser() });
9578
+ }
9579
+ function buildGreetingEl() {
9580
+ var parts = greetingParts();
9581
+ var name = parts.name ? [document.createTextNode(" "), h("strong", { translate: "no", text: parts.name })] : null;
9582
+ var bubble = h("div", { class: "bq-bubble" });
9583
+ append(bubble, parts.lead);
9584
+ append(bubble, name);
9585
+ append(bubble, parts.tail);
9586
+ return h("div", { class: "bq-message is-assistant bq-empty-greeting" }, bubble);
9587
+ }
9557
9588
  function historyLoadingEl(initial) {
9558
9589
  if (initial) {
9559
9590
  return h(
@@ -9648,6 +9679,7 @@ Index the REMAINING windows - one record per row/item, looking at any page image
9648
9679
  h("span", { class: "bq-loader" })
9649
9680
  ));
9650
9681
  }
9682
+ CS.messagesBox.appendChild(buildGreetingEl());
9651
9683
  if (!CS.messages.length) {
9652
9684
  if (CS.loadingHistory && !CS.loadingOlderHistory) {
9653
9685
  CS.messagesBox.appendChild(historyLoadingEl(true));
@@ -9664,17 +9696,6 @@ Index the REMAINING windows - one record per row/item, looking at any page image
9664
9696
  }
9665
9697
  } catch (e) {
9666
9698
  }
9667
- if (!emptyStubEls.length && !session.state.bgHistoryLoading && markerSweepSettled) {
9668
- CS.messagesBox.appendChild(h(
9669
- "div",
9670
- { class: "bq-message is-assistant bq-empty-greeting" },
9671
- h(
9672
- "div",
9673
- { class: "bq-bubble" },
9674
- document.createTextNode("Hi! Ask me anything about " + (S.serviceName ? '"' + S.serviceName + '"' : "your project") + ".")
9675
- )
9676
- ));
9677
- }
9678
9699
  for (var gse = 0; gse < emptyStubEls.length; gse++) CS.messagesBox.appendChild(emptyStubEls[gse]);
9679
9700
  syncDraftingIndicator();
9680
9701
  return;
package/dist/engine.cjs CHANGED
@@ -283,10 +283,10 @@ function groupAttachmentFailures(attachments) {
283
283
 
284
284
  // src/engine/prompts/chat_system_prompt.ts
285
285
  function buildChatSystemPrompt(params) {
286
- const { projectId, serviceName, serviceDescription } = params;
286
+ const { projectId, serviceName, serviceDescription, greeting, canUpload, client } = params;
287
287
  let systemPrompt = `
288
288
  You are a dedicated assistant for the project ID: "${projectId}".
289
- Scope: Only answer questions about this project and its data. Do not answer questions about other projects or topics unrelated to this project. When the user refers to "my database", "my data", or "my files", treat those as references to this project's database and file storage.
289
+ Scope: Only answer questions about this project and its data. Do not answer questions about other projects or topics unrelated to this project. When the user refers to "my database", "my data", or "my files", treat those as references to this project's database and file storage. The ONE exception is BunnyQuery itself - what this app is, what it can do, and how to use it - which is always in scope: answer it from the "About BunnyQuery" section at the end of this prompt.
290
290
  Knowledge lookup: Before saying you don't know or that something isn't in the chat history, ALWAYS query this project's database through the available MCP tools to look for the answer. The user's data is the source of truth - the chat transcript is not. Only respond with "I don't know" or "I couldn't find that" after you have actually searched the project's data and come back empty.
291
291
  Complete answers over stored data: The database holds one record per spreadsheet row, and each uploaded file becomes many records. ONE file is routinely SPLIT ACROSS SEVERAL TABLES - a summary row in one table, its page or row content in another, its extracted photos and other media in "__MEDIA__", and the indexer often invents a differently-named table on each pass. An index or tag filter matches inside ONE table only and requires table_name: on getRecords, an index or tag sent with table_name but no access_group is auto-filled with access_group "authorized" (where the indexer writes; pass access_group explicitly, including 0, to search another group), while an index or tag WITHOUT table_name FAILS with an error instead of answering, so read the error rather than guessing. Reference is the exception: reference ALONE spans EVERY table and EVERY access group, so getRecords with reference "src::<the file's storage path>" is the one call that returns a whole file's records wherever the indexer put them. Adding table_name narrows it to that table; access_group WITHOUT table_name fails with '"table" is required'; table_name on its own returns that whole table across all access groups. For anything NOT scoped to a single file, call getTables FIRST, run the query once per table that could hold the answer, and combine the results. For any request that counts, sums, totals, lists every match, compares across records, finds which one, or asks whether something is present or ABSENT (for example "how many", "total spent", "which card", "is there any", "\uC5C6\uC5B4?", "\uD558\uB098\uB3C4 \uC5C6\uB098?"), you MUST read the COMPLETE matching set before answering. Query with fetch_all set to true, or page through getToolResponsePage until pagination.complete is true, across EVERY table and EVERY relevant file. A single default query returns only the first page (about 50 records). That is a SAMPLE. Never treat it as the whole dataset. If you already answered from one table and then realise another table holds more, do not simply apologise: re-run the sweep and give the complete answer.
292
292
  Never assert absence from a partial read. Do not say "there is no X", "none", "not found", or "\uC544\uB2C8\uC694, \uC5C6\uC2B5\uB2C8\uB2E4" until a complete scan has come back empty. If you have not finished scanning every relevant table and file, keep querying instead of guessing. A confident "no" that later turns out wrong is worse than telling the user you are still checking.
@@ -316,6 +316,17 @@ Mushrooms,41,$73.80
316
316
  Zucchini,29,$43.50
317
317
  \`\`\`
318
318
  The same pattern applies to any format - name the block after the file you intend: \`\`\`my-data.json, \`\`\`index.html, \`\`\`sample.txt, and so on.`;
319
+ systemPrompt += `
320
+ About BunnyQuery (this app - questions about it are in scope):
321
+ You are the assistant inside BunnyQuery, an AI assistant for the user's own business data. Instead of digging through folders, dashboards and files, the user uploads their documents, spreadsheets, images, notes and records, BunnyQuery indexes them into this project's database, and you answer questions, write reports and summarize from THAT data rather than from the open internet. Each project has its own data, its own AI platform (ChatGPT or Claude, powered by the project owner's own API key) and its own base prompt. BunnyQuery is built on Skapi (www.skapi.com), so the same project database is also reachable over MCP from any MCP-compatible AI client (mcp.broadwayinc.computer), and this chat can be embedded in a website as a widget with one script tag. Answer product questions from the facts in this section. If you are asked something about BunnyQuery that is NOT stated here - pricing, plan limits, a roadmap, a feature you cannot see - say you are not certain and point the user at the project owner or the BunnyQuery site, rather than inventing it.
322
+ How data gets in: ${canUpload === false ? `this user CANNOT upload in this session (they are not signed in, or the project's database is frozen for non-admins), and the attach affordances are hidden from them. Never instruct them to attach, drag in or upload a file, and never blame a missing answer on them not having uploaded it. Answer from what is already indexed, and when something genuinely is not in the project, say so and suggest asking the project's owner to add it.` : `the user attaches files to a chat message with the paperclip button in the composer, or drags and drops them onto the chat (whole folders work; up to 20 files per message). Uploaded files land in this project's file storage and are indexed automatically: read end to end and turned into database records. "Indexed" means exactly that, and it is why you can only answer from a file once its indexing has finished. While a file indexes, the chat shows a status row for it: yellow while it is working, green when it is indexed, red if it failed. A large file is indexed in windows over several passes, which takes longer; indexing runs on the server, so it keeps going if the user closes the page and the row is still there when they come back. The user can also paste plain text straight into the chat and ask you to save it - store it with the postRecords tool. BunnyQuery reads over 50 formats: office documents (.docx, .xlsx, .pptx, .hwp, .hwpx, .odt, .ods, .odp, .epub), PDFs, images, .csv/.tsv, .json, .xml, .html, .txt/.md and source code. Images and scanned PDFs are read with vision at index time.`}
323
+ Getting answers out: the user asks in plain language, in any language, and you answer from this project's data. You can also produce reports and downloadable files (CSV and the rest) as described in the File generation rules above, and any stored file can be handed back as a link, with images rendering inline in the chat.${client === "console" ? `
324
+ Where things are in the BunnyQuery console (this user is in it, at bunnyquery.com): the left nav has "Query" (this chat), "Files" (browse this project's stored files, upload more, and see which are indexed), "Collaborators" (invite teammates or clients so they can ask questions themselves) and "Settings" (the AI platform, model and API key, the project's description / base prompt, which is added to your instructions, and the Freeze Database switch that blocks writes). Plans and billing live on the project's Subscription page - send the user there rather than quoting prices, which you do not know.` : ""}${client === "widget" ? `
325
+ This chat is the BunnyQuery widget embedded in a website, so the user may have no access to the project console: keep any instructions to what can be done here in the chat.` : ""}`;
326
+ if (greeting) {
327
+ systemPrompt += `
328
+ Your opening message: this chat always opens with a fixed line from you, reading """${greeting}""". It is rendered by the client and is NOT part of the message history you receive, so the user can reply to it ("which files?", "what do you mean by indexed?", "what can you do?") with nothing in the transcript to refer back to. Treat that line as something you said, and answer the follow-up from this section.`;
329
+ }
319
330
  if (serviceDescription) {
320
331
  systemPrompt += `
321
332
  Project name: "${serviceName ?? ""}"
@@ -462,6 +473,14 @@ Records for the earlier windows/pages of this file are ALREADY saved (they refer
462
473
  Index the REMAINING windows - one record per row/item, looking at any page images or embedded photos - saving as you go until readFileContent reports END OF FILE. A \xABPHOTO <cell>\xBB marker in a window marks an embedded picture whose extracted file already has a record in table "__MEDIA__": find it with getRecords reference "src::" + the storage path above and match the cell against data.anchor or tags (a repeated picture is stored under its first anchor only), then enrich it with updateRecords. Never create a photo record of your own and never construct a path for one. Do NOT re-save windows that are already saved. Set every record's reference to exactly "src::" + the storage path above (no sheet, window or summary suffix added). That file record already exists, so do NOT post it; enrich it with updateRecords. When the ENTIRE file is finally indexed, end your message with the token INDEXING_COMPLETE.`;
463
474
  }
464
475
 
476
+ // src/engine/greeting.ts
477
+ function buildChatGreeting(params) {
478
+ const name = params.projectName ? '"' + params.projectName + '"' : "";
479
+ const lead = params.canUpload === false ? "Hi! Ask me anything about the data in your project" : "Hi! Start by attaching the files related to your project";
480
+ const tail = params.canUpload === false ? "." : ", or pasting plain text into the chat. Once they are indexed, ask me anything about that data.";
481
+ return { lead, name, tail, text: lead + (name ? " " + name : "") + tail };
482
+ }
483
+
465
484
  // src/engine/errors.ts
466
485
  var STATUS_MESSAGE = {
467
486
  "408": "The AI provider timed out before it started.",
@@ -6173,6 +6192,7 @@ exports.bgIndexingQueueName = bgIndexingQueueName;
6173
6192
  exports.buildAiAgentValue = buildAiAgentValue;
6174
6193
  exports.buildBoundedChatMessages = buildBoundedChatMessages;
6175
6194
  exports.buildChatDisplayList = buildChatDisplayList;
6195
+ exports.buildChatGreeting = buildChatGreeting;
6176
6196
  exports.buildChatSystemPrompt = buildChatSystemPrompt;
6177
6197
  exports.buildDisplayExpiredAttachmentHref = buildDisplayExpiredAttachmentHref;
6178
6198
  exports.buildHistoryItemFullId = buildHistoryItemFullId;