@twentyfourg/chat-kit 1.0.0-beta.2 → 1.0.0-beta.4

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 CHANGED
@@ -114,8 +114,21 @@ that changes once the conversation has started, say) can be a computed.
114
114
  | `message-actions` | Scoped, gives you `{ message }`. Sits on the left of the row that holds the thumbs, on finished replies. This is where an app's own button goes, an export for example, and it comes before the kit's copy button so your actions stay together. |
115
115
  | `message-footer` | Scoped, gives you `{ message }`. Whatever your app wants under a reply, below its sources and actions: follow-up prompts, a link back to something above. Rendered for every reply, streaming included, so check `message.isStreaming` if it should wait. |
116
116
  | `composer` | Replaces the whole composer. For a state the kit's field has no shape for, such as a locked bar with an upgrade prompt. Your content calls `engine.sendMessage` itself if it needs to send. |
117
+ | `send` | Scoped, gives you `{ send, canSend }`. Replaces the send button and nothing else, so the field keeps its own typing, dictation, and Enter handling. For a client whose send button is a finished asset that draws its own shape and disabled state. Not rendered while an answer streams, when the stop button has the spot. |
117
118
  | `composer-footer` | Sits inside the composer, under the field. An option that changes how the next question is handled, a hint, a counter. |
118
119
 
120
+ Your own send button, keeping everything else the kit does:
121
+
122
+ ```vue
123
+ <Chat :engine="engine">
124
+ <template #send="{ send, canSend }">
125
+ <button :disabled="!canSend" @click="send">
126
+ <img :src="canSend ? sendIcon : sendDisabledIcon" alt="Send" />
127
+ </button>
128
+ </template>
129
+ </Chat>
130
+ ```
131
+
119
132
  ### Events
120
133
 
121
134
  `Chat` emits `rated` with the message and the rating now on it, or `null`
@@ -152,6 +165,24 @@ The heading, label, and interface fonts follow `--font-body` unless you set
152
165
  them. The citation and button accents follow `--accent`. An app that already
153
166
  defines the whole set itself can skip the import.
154
167
 
168
+ ### Room around the chat
169
+
170
+ Two properties control how much space the chat takes, separately from the rest
171
+ of the theme:
172
+
173
+ | Property | Default | What it does |
174
+ | --- | --- | --- |
175
+ | `--chat-inset` | `24px` | The gutter between the chat's edge and its contents. The conversation and the composer both read it, so they stay lined up with each other. |
176
+ | `--composer-padding` | `8px` | Space between the composer's border and the field inside it. The 40px buttons set the height, not the text, so this is added to those: the default makes a 56px composer plus its border. |
177
+
178
+ A roomier chat, for a design with space to spend:
179
+
180
+ ```css
181
+ :root {
182
+ --composer-padding: 24px;
183
+ }
184
+ ```
185
+
155
186
  One thing to watch. If your own tokens live inside a CSS cascade layer, layer
156
187
  order decides the winner before anything else does. Import `theme.css` before
157
188
  your layered styles, or state the order yourself:
package/dist/chat-kit.js CHANGED
@@ -717,7 +717,7 @@ function Qe(e) {
717
717
  inputMode: i
718
718
  });
719
719
  o.value.push(u);
720
- let d = b({
720
+ let m = b({
721
721
  id: K(),
722
722
  role: "assistant",
723
723
  text: "",
@@ -731,64 +731,71 @@ function Qe(e) {
731
731
  isReceiving: !1,
732
732
  failed: !1
733
733
  });
734
- p.value = d;
735
- let m = new AbortController();
736
- g = m;
737
- let h = new Ke((e) => {
738
- d.html = e;
734
+ p.value = m;
735
+ let h = new AbortController();
736
+ g = h;
737
+ let _ = new Ke((e) => {
738
+ m.html = e;
739
739
  }, 25, e.showMath);
740
- function _() {
741
- s.value !== "streaming" && (s.value = "streaming", d.isStreaming = !0, d.isReceiving = !0, o.value.push(d));
740
+ function v() {
741
+ s.value !== "streaming" && (s.value = "streaming", m.isStreaming = !0, m.isReceiving = !0, o.value.push(m));
742
742
  }
743
- let v = new Ye((e) => {
744
- _(), d.text += e, h.addChunk(e);
745
- }, e.typewriter ?? !0), y = null;
743
+ let y = new Ye((e) => {
744
+ v(), m.text += e, _.addChunk(e);
745
+ }, e.typewriter ?? !0), x = null, S = null;
746
746
  try {
747
747
  let o = await D();
748
748
  if (await U(await t.sendMessage(r, o, {
749
749
  message: n,
750
750
  stream: !0,
751
751
  ...i === "voice" && { inputMode: "dictation" }
752
- }, { signal: m.signal }), {
752
+ }, { signal: h.signal }), {
753
753
  onChunk: (e) => {
754
- v.push(e);
754
+ y.push(e);
755
755
  },
756
756
  onComplete: () => {},
757
757
  onError: (e) => {
758
- y = e;
758
+ x = e;
759
759
  },
760
760
  onMetadata: (e) => {
761
- e.sources && (d.sources = e.sources);
761
+ e.sources && (m.sources = e.sources);
762
+ },
763
+ onRateLimit: ({ limit: e, remaining: t, resetSeconds: n }) => {
764
+ S = {
765
+ limit: e,
766
+ remaining: t,
767
+ resetAt: Number.isFinite(n) ? (/* @__PURE__ */ new Date(n * 1e3)).toISOString() : ""
768
+ };
762
769
  },
763
770
  onThinkingChunk: (e) => {
764
- d.thinking += e;
771
+ m.thinking += e;
765
772
  }
766
773
  }, (e, t) => {
767
- if (e === "citations" && Array.isArray(t)) return d.citations = t, !0;
774
+ if (e === "citations" && Array.isArray(t)) return m.citations = t, !0;
768
775
  let n = t ?? {};
769
- return e === "message_id" && typeof n.messageId == "string" ? (u.basecampMessageId = n.messageId, !0) : e === "assistant_message_id" && typeof n.messageId == "string" ? (d.basecampMessageId = n.messageId, !0) : e === "used_sources" && Array.isArray(t) ? (d.sources = t, !0) : e === "token_usage" && n.thread ? (l.value = n.thread, !0) : !1;
770
- }), y) throw y;
771
- if (m.signal.aborted) {
772
- v.flush(), await h.complete(), d.isReceiving = !1, d.isStreaming = !1;
776
+ return e === "message_id" && typeof n.messageId == "string" ? (u.basecampMessageId = n.messageId, !0) : e === "assistant_message_id" && typeof n.messageId == "string" ? (m.basecampMessageId = n.messageId, !0) : e === "used_sources" && Array.isArray(t) ? (m.sources = t, !0) : e === "token_usage" && n.thread ? (l.value = n.thread, !0) : !1;
777
+ }), x) throw x;
778
+ if (h.signal.aborted) {
779
+ y.flush(), await _.complete(), m.isReceiving = !1, m.isStreaming = !1;
773
780
  return;
774
781
  }
775
- if (d.isReceiving = !1, await v.finish(), !d.text) throw Error("The response ended without any content.");
776
- await h.complete(), d.isOutOfScope = e.isOutOfScope?.(d.text) ?? !1, d.isStreaming = !1, w({
782
+ if (m.isReceiving = !1, await y.finish(), !m.text) throw Error("The response ended without any content.");
783
+ await _.complete(), m.isOutOfScope = e.isOutOfScope?.(m.text) ?? !1, m.isStreaming = !1, w({
777
784
  type: "turn",
778
785
  query: n,
779
786
  inputMode: i,
780
- response: d.text,
781
- isOutOfScope: d.isOutOfScope,
782
- citations: d.citations,
783
- sources: d.sources,
787
+ response: m.text,
788
+ isOutOfScope: m.isOutOfScope,
789
+ citations: m.citations,
790
+ sources: m.sources,
784
791
  threadId: a.value,
785
792
  userMessageId: u.basecampMessageId,
786
- assistantMessageId: d.basecampMessageId
793
+ assistantMessageId: m.basecampMessageId
787
794
  });
788
795
  } catch (t) {
789
- v.flush(), t?.name !== "AbortError" && (d.text ? d.failed = !0 : c.value = t instanceof Error ? t : Error(String(t))), h.stop(), d.isReceiving = !1, d.isStreaming = !1, d.text && (d.html = await W(d.text, { showMath: e.showMath }));
796
+ y.flush(), t?.name !== "AbortError" && (m.text ? m.failed = !0 : c.value = t instanceof Error ? t : Error(String(t))), _.stop(), m.isReceiving = !1, m.isStreaming = !1, m.text && (m.html = await W(m.text, { showMath: e.showMath }));
790
797
  } finally {
791
- v.destroy(), g = null, p.value = null, s.value = "idle", f();
798
+ y.destroy(), g = null, p.value = null, s.value = "idle", S ? d.value = S : f();
792
799
  }
793
800
  }
794
801
  async function A() {
@@ -1034,9 +1041,22 @@ var J = (e) => new Promise((t) => setTimeout(t, e)), et = class {
1034
1041
  } });
1035
1042
  return new Response(m, {
1036
1043
  status: 200,
1037
- headers: { "Content-Type": "text/event-stream" }
1044
+ headers: {
1045
+ "Content-Type": "text/event-stream",
1046
+ ...this.rateLimitHeaders()
1047
+ }
1038
1048
  });
1039
1049
  }
1050
+ rateLimitHeaders() {
1051
+ let e = this.options.messageLimit;
1052
+ if (e === void 0) return { "X-RateLimit-Limit": "unlimited" };
1053
+ let t = Math.ceil(this.messageCount / 2);
1054
+ return {
1055
+ "X-RateLimit-Limit": String(e),
1056
+ "X-RateLimit-Remaining": String(Math.max(0, e - t)),
1057
+ "X-RateLimit-Reset": String(Math.floor(new Date(this.quotaResetAt).getTime() / 1e3))
1058
+ };
1059
+ }
1040
1060
  async stopMessage() {
1041
1061
  return {
1042
1062
  success: !0,
@@ -1271,8 +1291,10 @@ var it = {
1271
1291
  }, {
1272
1292
  default: j(() => [l(Z, { name: "stop" })]),
1273
1293
  _: 1
1274
- }, 8, ["label"])) : (v(), r(X, {
1275
- key: 3,
1294
+ }, 8, ["label"])) : w(t.$slots, "send", {
1295
+ send: O,
1296
+ canSend: x.value
1297
+ }, () => [l(X, {
1276
1298
  class: "input-bar__send",
1277
1299
  label: D(u).composer.sendLabel,
1278
1300
  disabled: !x.value,
@@ -1283,9 +1305,9 @@ var it = {
1283
1305
  size: 20
1284
1306
  })]),
1285
1307
  _: 1
1286
- }, 8, ["label", "disabled"]))])]), w(t.$slots, "footer", {}, void 0, !0)]));
1308
+ }, 8, ["label", "disabled"])], !0, 3)])]), w(t.$slots, "footer", {}, void 0, !0)]));
1287
1309
  }
1288
- }), [["__scopeId", "data-v-2a67b2cf"]]), St = 20, Ct = 300, wt = .3, Tt = 12, Et = 8;
1310
+ }), [["__scopeId", "data-v-4e9c096d"]]), St = 20, Ct = 300, wt = .3, Tt = 12, Et = 8;
1289
1311
  function Dt(e, t = {}) {
1290
1312
  let n = S("following"), r = !1, i = 0, a = !1, o = null, s = !1, c = () => t.mode?.() ?? "reply";
1291
1313
  function l(e) {
@@ -1728,6 +1750,7 @@ var Mt = { class: "citation-popover__source" }, Nt = ["src"], Pt = { class: "cit
1728
1750
  e.message.isOutOfScope ? (v(), a("div", Nn, [l(Z, { name: "warning" }), o("span", null, E(D(d).outOfScope.label), 1)])) : i("", !0),
1729
1751
  e.showThinking && e.message.thinking ? (v(), r(On, {
1730
1752
  key: 1,
1753
+ class: "assistant-message__reasoning",
1731
1754
  text: e.message.thinking
1732
1755
  }, null, 8, ["text"])) : i("", !0),
1733
1756
  o("div", Pn, [o("div", {
@@ -1769,7 +1792,7 @@ var Mt = { class: "citation-popover__source" }, Nt = ["src"], Pt = { class: "cit
1769
1792
  w(n.$slots, "message-footer", { message: e.message }, void 0, !0)
1770
1793
  ]));
1771
1794
  }
1772
- }), [["__scopeId", "data-v-466b7413"]]), Un = { class: "thinking" }, Wn = {
1795
+ }), [["__scopeId", "data-v-0cff2e6c"]]), Un = { class: "thinking" }, Wn = {
1773
1796
  key: 0,
1774
1797
  class: "thinking__label"
1775
1798
  }, Gn = 2e3, Kn = /*#__PURE__*/ Y(/* @__PURE__ */ u({
@@ -1893,7 +1916,7 @@ var Mt = { class: "citation-popover__source" }, Nt = ["src"], Pt = { class: "cit
1893
1916
  }, E(D(u).status.retryLabel), 1)])) : i("", !0)
1894
1917
  ], 512));
1895
1918
  }
1896
- }), [["__scopeId", "data-v-2f494fbc"]]), rr = { class: "chat" }, ir = /*#__PURE__*/ Y(/* @__PURE__ */ u({
1919
+ }), [["__scopeId", "data-v-7817c1d9"]]), rr = { class: "chat" }, ir = /*#__PURE__*/ Y(/* @__PURE__ */ u({
1897
1920
  __name: "Chat",
1898
1921
  props: {
1899
1922
  engine: {},
@@ -1957,13 +1980,20 @@ var Mt = { class: "citation-popover__source" }, Nt = ["src"], Pt = { class: "cit
1957
1980
  "show-mic": e.showMic,
1958
1981
  onSend: i,
1959
1982
  onStop: n[1] ||= () => e.engine.stopGeneration()
1960
- }, s({ _: 2 }, [t.$slots["composer-footer"] ? {
1983
+ }, s({ _: 2 }, [t.$slots.send ? {
1984
+ name: "send",
1985
+ fn: j(({ send: e, canSend: n }) => [w(t.$slots, "send", {
1986
+ send: e,
1987
+ canSend: n
1988
+ }, void 0, !0)]),
1989
+ key: "0"
1990
+ } : void 0, t.$slots["composer-footer"] ? {
1961
1991
  name: "footer",
1962
1992
  fn: j(() => [w(t.$slots, "composer-footer", {}, void 0, !0)]),
1963
- key: "0"
1993
+ key: "1"
1964
1994
  } : void 0]), 1032, ["busy", "show-mic"])], !0)]));
1965
1995
  }
1966
- }), [["__scopeId", "data-v-7ad4b6f4"]]);
1996
+ }), [["__scopeId", "data-v-7ae994a7"]]);
1967
1997
  //#endregion
1968
1998
  export { H as AnonymousAuthError, ue as BaseCampClient, le as BaseCampError, ir as Chat, X as IconButton, et as MockTransport, fe as RateLimitError, Ke as StreamingMessage, Ye as Typewriter, U as consumeSSEResponse, ce as createAnonymousTokenProvider, G as decodeHtmlEntities, L as defaultChatCopy, ie as fetchAuthConfig, R as formatCopy, re as provideChatCopy, W as renderMarkdownSafe, Ve as renderMarkdownStreaming, te as resolveChatCopy, se as signInAnonymously, Se as streamSSE, Ge as stripHtmlTags, B as useChatCopy, Qe as useChatEngine };
1969
1999