@aleph-alpha/chat-kit 5.12.0 → 6.0.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.
Files changed (69) hide show
  1. package/dist/{CkHistory.vue_vue_type_script_setup_true_lang-CImSzLYs.js → CkHistory.vue_vue_type_script_setup_true_lang-D9Ph9eYp.js} +115 -31
  2. package/dist/adapters/responses-api.d.ts +1 -1
  3. package/dist/adapters/responses-api.d.ts.map +1 -1
  4. package/dist/adapters/responses-api.js +9 -7
  5. package/dist/chat-kit.css +12 -12
  6. package/dist/components/base/CkCitation/CkCitation.stories.d.ts +1 -4
  7. package/dist/components/base/CkCitation/CkCitation.stories.d.ts.map +1 -1
  8. package/dist/components/base/CkCitation/CkCitation.vue.d.ts.map +1 -1
  9. package/dist/components/base/CkCitation/types.d.ts +6 -1
  10. package/dist/components/base/CkCitation/types.d.ts.map +1 -1
  11. package/dist/components/base/CkCitationCard/CkCitationCard.stories.d.ts +13 -2
  12. package/dist/components/base/CkCitationCard/CkCitationCard.stories.d.ts.map +1 -1
  13. package/dist/components/base/CkCitationCard/CkCitationCard.vue.d.ts +1 -0
  14. package/dist/components/base/CkCitationCard/CkCitationCard.vue.d.ts.map +1 -1
  15. package/dist/components/base/CkCitationCard/types.d.ts +6 -1
  16. package/dist/components/base/CkCitationCard/types.d.ts.map +1 -1
  17. package/dist/components/base/CkCitationLabel/CkCitationLabel.vue.d.ts.map +1 -1
  18. package/dist/components/base/CkHistoryItem/CkHistoryItem.vue.d.ts.map +1 -1
  19. package/dist/components/base/CkHistoryWrapper/CkHistoryWrapper.vue.d.ts.map +1 -1
  20. package/dist/components/base/CkMarkdownRenderer/CkMarkdownRenderer.stories.d.ts.map +1 -1
  21. package/dist/components/index.js +1 -1
  22. package/dist/composables/fileUpload/useFileUpload.d.ts.map +1 -1
  23. package/dist/composables/index.js +1 -1
  24. package/dist/composables/useChat.d.ts.map +1 -1
  25. package/dist/composables/useConversations.d.ts.map +1 -1
  26. package/dist/composables/useIsTruncated.d.ts +22 -0
  27. package/dist/composables/useIsTruncated.d.ts.map +1 -0
  28. package/dist/handlers/index.js +1 -1
  29. package/dist/handlers/useChatKitLabels.d.ts +35 -1
  30. package/dist/handlers/useChatKitLabels.d.ts.map +1 -1
  31. package/dist/{index-Dn4ctxk8.js → index-C72e57-5.js} +48 -24
  32. package/dist/index.js +3 -3
  33. package/dist/test-utils/createTextPlugin.d.ts +7 -1
  34. package/dist/test-utils/createTextPlugin.d.ts.map +1 -1
  35. package/dist/{useChatKitLabels-CMOr3Kjq.js → useChatKitLabels-368lodS5.js} +1 -1
  36. package/package.json +4 -4
  37. package/src/adapters/responses-api.spec.ts +86 -0
  38. package/src/adapters/responses-api.ts +13 -9
  39. package/src/components/base/CkCitation/CkCitation.spec.ts +3 -3
  40. package/src/components/base/CkCitation/CkCitation.stories.ts +85 -3
  41. package/src/components/base/CkCitation/CkCitation.vue +5 -1
  42. package/src/components/base/CkCitation/types.ts +6 -1
  43. package/src/components/base/CkCitationCard/CkCitationCard.spec.ts +37 -0
  44. package/src/components/base/CkCitationCard/CkCitationCard.stories.ts +30 -3
  45. package/src/components/base/CkCitationCard/CkCitationCard.vue +54 -12
  46. package/src/components/base/CkCitationCard/types.ts +6 -1
  47. package/src/components/base/CkCitationLabel/CkCitationLabel.spec.ts +3 -3
  48. package/src/components/base/CkCitationLabel/CkCitationLabel.vue +7 -6
  49. package/src/components/base/CkConversationLayout/CkConversationLayout.spec.ts +1 -5
  50. package/src/components/base/CkCopyMessageButton/CkCopyMessageButton.spec.ts +1 -1
  51. package/src/components/base/CkFeedbackButton/CkFeedbackButton.vue +3 -3
  52. package/src/components/base/CkFeedbackDialog/CkFeedbackDialog.spec.ts +1 -1
  53. package/src/components/base/CkFeedbackDialog/CkFeedbackDialog.vue +9 -9
  54. package/src/components/base/CkHistoryItem/CkHistoryItem.vue +5 -7
  55. package/src/components/base/CkHistoryWrapper/CkHistoryWrapper.vue +2 -1
  56. package/src/components/base/CkMarkdownRenderer/CkMarkdownRenderer.stories.ts +9 -4
  57. package/src/components/base/CkMarkdownRenderer/CkMarkdownRenderer.vue +1 -1
  58. package/src/components/base/CkModelSelector/CkModelSelector.vue +3 -3
  59. package/src/composables/fileUpload/useFileUpload.ts +45 -30
  60. package/src/composables/fileUpload/useFileUploadWrapper.spec.ts +1 -1
  61. package/src/composables/fileUpload/useFileUploadWrapper.ts +3 -3
  62. package/src/composables/useChat.ts +3 -1
  63. package/src/composables/useConversations.ts +8 -5
  64. package/src/composables/useIsTruncated.ts +82 -0
  65. package/src/handlers/useChatKitLabels.spec.ts +0 -1
  66. package/src/handlers/useChatKitLabels.ts +2 -2
  67. package/src/handlers/useMarkdownParser.ts +1 -1
  68. package/src/test-setup.ts +3 -3
  69. package/src/test-utils/createTextPlugin.ts +11 -3
@@ -24,7 +24,8 @@ const rootClass = computed(() => {
24
24
  });
25
25
 
26
26
  const passthroughAttrs = computed(() => {
27
- const { class: _class, ...rest } = attrs as Record<string, unknown>;
27
+ const rest = { ...(attrs as Record<string, unknown>) };
28
+ delete rest.class;
28
29
  return rest;
29
30
  });
30
31
  </script>
@@ -394,16 +394,21 @@ export const MathExpressions: Story = {
394
394
  parameters: { docs: { source: { code: mathTemplateSource } } },
395
395
  };
396
396
 
397
- function visitTextNodes(node: any, transform: (value: string) => string): void {
398
- if (node.type === 'text') {
397
+ type MdTextNode = { type: string; value?: string; children?: MdTextNode[] };
398
+
399
+ function visitTextNodes(
400
+ node: MdTextNode,
401
+ transform: (value: string) => string,
402
+ ): void {
403
+ if (node.type === 'text' && node.value !== undefined) {
399
404
  node.value = transform(node.value);
400
405
  }
401
406
  if (node.children) {
402
- node.children.forEach((child: any) => visitTextNodes(child, transform));
407
+ node.children.forEach((child) => visitTextNodes(child, transform));
403
408
  }
404
409
  }
405
410
 
406
- const uppercasePlugin = () => (tree: any) => {
411
+ const uppercasePlugin = () => (tree: MdTextNode) => {
407
412
  visitTextNodes(tree, (value) => value.toUpperCase());
408
413
  };
409
414
 
@@ -146,7 +146,7 @@ export default defineComponent({
146
146
  classes: mergedClasses.value,
147
147
  tableLabel:
148
148
  labels.value.markdownTableLabel ??
149
- defaultChatKitLabels.markdownTableLabel!,
149
+ defaultChatKitLabels.markdownTableLabel,
150
150
  renderCitation: (el) => renderCitationNode(el, slots.citation),
151
151
  };
152
152
  const children = parsedNodes.value
@@ -32,17 +32,17 @@ const labels = useChatKitLabels();
32
32
  const triggerAriaLabel = computed(
33
33
  () =>
34
34
  labels.value.modelSelectorAriaLabel ??
35
- defaultChatKitLabels.modelSelectorAriaLabel!,
35
+ defaultChatKitLabels.modelSelectorAriaLabel,
36
36
  );
37
37
  const listAriaLabel = computed(
38
38
  () =>
39
39
  labels.value.modelSelectorListAriaLabel ??
40
- defaultChatKitLabels.modelSelectorListAriaLabel!,
40
+ defaultChatKitLabels.modelSelectorListAriaLabel,
41
41
  );
42
42
  const placeholderLabel = computed(
43
43
  () =>
44
44
  labels.value.modelSelectorPlaceholder ??
45
- defaultChatKitLabels.modelSelectorPlaceholder!,
45
+ defaultChatKitLabels.modelSelectorPlaceholder,
46
46
  );
47
47
 
48
48
  const open = ref(false);
@@ -137,8 +137,9 @@ class Semaphore {
137
137
  }
138
138
 
139
139
  release(): void {
140
- if (this.queue.length > 0) {
141
- this.queue.shift()!();
140
+ const next = this.queue.shift();
141
+ if (next) {
142
+ next();
142
143
  } else {
143
144
  this.permits++;
144
145
  }
@@ -184,7 +185,12 @@ function generateCanvasThumbnail(
184
185
  const canvas = document.createElement('canvas');
185
186
  canvas.width = w;
186
187
  canvas.height = h;
187
- const ctx = canvas.getContext('2d')!;
188
+ const ctx = canvas.getContext('2d');
189
+ if (!ctx) {
190
+ URL.revokeObjectURL(url);
191
+ reject(new Error('Canvas 2D context is not available'));
192
+ return;
193
+ }
188
194
  ctx.drawImage(img, 0, 0, w, h);
189
195
 
190
196
  const dataUrl = canvas.toDataURL('image/png');
@@ -284,12 +290,18 @@ async function runCustomValidators(
284
290
  // Retry
285
291
  // ---------------------------------------------------------------------------
286
292
 
293
+ function extractHttpStatus(err: unknown): number | undefined {
294
+ const e = err as {
295
+ httpStatus?: number;
296
+ status?: number;
297
+ response?: { status?: number };
298
+ } | null;
299
+ return e?.httpStatus ?? e?.status ?? e?.response?.status;
300
+ }
301
+
287
302
  function isRetryable(err: unknown, policy: RetryPolicy): boolean {
288
303
  if (!policy.retryableStatusCodes?.length) return true;
289
- const status =
290
- (err as any)?.httpStatus ??
291
- (err as any)?.status ??
292
- (err as any)?.response?.status;
304
+ const status = extractHttpStatus(err);
293
305
  return status != null && policy.retryableStatusCodes.includes(status);
294
306
  }
295
307
 
@@ -608,10 +620,11 @@ export function useFileUpload(
608
620
 
609
621
  for (let i = 0; i < uploadReady.length; i++) {
610
622
  const { stateKey } = uploadReady[i] as { file: File; stateKey: string };
611
- const entry = files.value.get(stateKey)!;
623
+ const entry = files.value.get(stateKey);
624
+ if (!entry) continue;
625
+ const startedAt = entry.uploadStartedAt ?? completedAt;
612
626
  const durationMs =
613
- new Date(completedAt).getTime() -
614
- new Date(entry.uploadStartedAt!).getTime();
627
+ new Date(completedAt).getTime() - new Date(startedAt).getTime();
615
628
 
616
629
  const remoteResult = results[i] as
617
630
  | { id?: string; documentId?: string }
@@ -682,12 +695,13 @@ export function useFileUpload(
682
695
  const uploadErr: FileUploadError = {
683
696
  code: 'UPLOAD_FAILED',
684
697
  message: (err as Error).message ?? 'Upload failed.',
685
- httpStatus: (err as any)?.httpStatus ?? (err as any)?.status,
698
+ httpStatus: extractHttpStatus(err),
686
699
  occurredAt: completedAt,
687
700
  };
688
701
 
689
702
  for (const { stateKey } of uploadReady) {
690
- const entry = files.value.get(stateKey)!;
703
+ const entry = files.value.get(stateKey);
704
+ if (!entry) continue;
691
705
  const durationMs = entry.uploadStartedAt
692
706
  ? new Date(completedAt).getTime() -
693
707
  new Date(entry.uploadStartedAt).getTime()
@@ -764,16 +778,17 @@ export function useFileUpload(
764
778
 
765
779
  const uploadStarted = now();
766
780
  for (const { stateKey } of uploadReady) {
781
+ const attemptCount = (files.value.get(stateKey)?.attemptCount ?? 0) + 1;
767
782
  updateEntry(stateKey, {
768
783
  status: 'uploading',
769
784
  uploadStartedAt: uploadStarted,
770
- attemptCount: (files.value.get(stateKey)?.attemptCount ?? 0) + 1,
785
+ attemptCount,
771
786
  lastAttemptAt: uploadStarted,
772
787
  });
773
788
  emit({
774
789
  type: 'file:uploading',
775
790
  fileId: stateKey,
776
- attemptCount: files.value.get(stateKey)!.attemptCount,
791
+ attemptCount,
777
792
  });
778
793
  }
779
794
 
@@ -863,17 +878,20 @@ export function useFileUpload(
863
878
  ): UploadResult {
864
879
  return {
865
880
  id: groupId,
866
- results: batch.map(({ stateKey }) => {
867
- const entry = files.value.get(stateKey)!;
868
- return {
869
- fileId: stateKey,
870
- success: entry.status === 'success',
871
- status: entry.status,
872
- remoteFileId: entry.remoteFileId ?? undefined,
873
- error: entry.error ?? undefined,
874
- uploadDate: entry.uploadCompletedAt ?? entry.queuedAt,
875
- metadata: entry.metadata,
876
- } satisfies UploadFileResult;
881
+ results: batch.flatMap(({ stateKey }) => {
882
+ const entry = files.value.get(stateKey);
883
+ if (!entry) return [];
884
+ return [
885
+ {
886
+ fileId: stateKey,
887
+ success: entry.status === 'success',
888
+ status: entry.status,
889
+ remoteFileId: entry.remoteFileId ?? undefined,
890
+ error: entry.error ?? undefined,
891
+ uploadDate: entry.uploadCompletedAt ?? entry.queuedAt,
892
+ metadata: entry.metadata,
893
+ } satisfies UploadFileResult,
894
+ ];
877
895
  }),
878
896
  };
879
897
  }
@@ -927,10 +945,7 @@ export function useFileUpload(
927
945
  try {
928
946
  await client.delete(endpoints.file(storeId, entry.remoteFileId));
929
947
  } catch (err) {
930
- const status =
931
- (err as any)?.httpStatus ??
932
- (err as any)?.status ??
933
- (err as any)?.response?.status;
948
+ const status = extractHttpStatus(err);
934
949
 
935
950
  if (status === 404) {
936
951
  // File no longer exists on the server — proceed with local removal.
@@ -1022,7 +1037,7 @@ export function useFileUpload(
1022
1037
  files.value.delete(entry.id);
1023
1038
  if (db) {
1024
1039
  deletePersistedEntry(db, persistStoreName, entry.id).catch(
1025
- () => {},
1040
+ () => undefined,
1026
1041
  );
1027
1042
  }
1028
1043
  }
@@ -206,7 +206,7 @@ describe('useFileUploadWrapper handleFilesSelected', () => {
206
206
  const { handleFilesSelected } = provider.useDocumentUpload('conv-1');
207
207
 
208
208
  const event = makeInputEvent([createFile('a.pdf')]);
209
- const input = (event as any).target as HTMLInputElement;
209
+ const input = event.target as unknown as HTMLInputElement;
210
210
 
211
211
  const valueSetter = vi.spyOn(input, 'value', 'set');
212
212
 
@@ -401,9 +401,9 @@ export function createDocumentUploadProvider(
401
401
  }
402
402
  }
403
403
 
404
- const successFileIds = result.results
405
- .filter((r) => r.success && r.remoteFileId)
406
- .map((r) => r.remoteFileId!);
404
+ const successFileIds = result.results.flatMap((r) =>
405
+ r.success && r.remoteFileId ? [r.remoteFileId] : [],
406
+ );
407
407
 
408
408
  if (successFileIds.length > 0) {
409
409
  await upload.runWorkflow(successFileIds);
@@ -15,4 +15,6 @@
15
15
  * await chat.sendMessage({ role: 'user', content: 'Hello' });
16
16
  * ```
17
17
  */
18
- export function useChat() {}
18
+ export function useChat() {
19
+ throw new Error('useChat is not implemented yet.');
20
+ }
@@ -216,11 +216,14 @@ export function useConversations(
216
216
  const raw = await http.post<RawConversation>(path, body);
217
217
  const created = mapConversation(raw);
218
218
 
219
- conversations.value = placeholder
220
- ? conversations.value.map((c) =>
221
- c.id === placeholder!.id ? created : c,
222
- )
223
- : [created, ...conversations.value];
219
+ if (placeholder) {
220
+ const placeholderId = placeholder.id;
221
+ conversations.value = conversations.value.map((c) =>
222
+ c.id === placeholderId ? created : c,
223
+ );
224
+ } else {
225
+ conversations.value = [created, ...conversations.value];
226
+ }
224
227
 
225
228
  return created;
226
229
  } catch (e) {
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Local copy of ui-library's internal probe of the same name. chat-kit's
3
+ * supported ui-library range (^5.3.0) does not export it, and it is too small
4
+ * to justify widening that package's public API. Drop this file once the range
5
+ * moves up to a version that exports it.
6
+ */
7
+
8
+ import { onScopeDispose, ref, watch, type Ref, type ShallowRef } from 'vue';
9
+
10
+ /** An element ref as produced by `useTemplateRef`. */
11
+ type ElementRef = Readonly<ShallowRef<HTMLElement | null>>;
12
+
13
+ /**
14
+ * One observer for every caller. `ResizeObserver` is built to watch many
15
+ * targets and delivers them in a single batched callback, so a list of rows
16
+ * costs one callback and one layout pass rather than one per row. Created
17
+ * lazily so importing this module never touches the DOM.
18
+ */
19
+ const measurers = new WeakMap<Element, () => void>();
20
+ let sharedObserver: ResizeObserver | undefined;
21
+
22
+ function getSharedObserver(): ResizeObserver | undefined {
23
+ if (typeof ResizeObserver === 'undefined') return undefined;
24
+ sharedObserver ??= new ResizeObserver((entries) => {
25
+ for (const entry of entries) measurers.get(entry.target)?.();
26
+ });
27
+ return sharedObserver;
28
+ }
29
+
30
+ function watchElement(el: Element, measure: () => void): void {
31
+ measurers.set(el, measure);
32
+ getSharedObserver()?.observe(el);
33
+ }
34
+
35
+ function unwatchElement(el: Element): void {
36
+ measurers.delete(el);
37
+ getSharedObserver()?.unobserve(el);
38
+ }
39
+
40
+ /**
41
+ * Whether an element's text is currently clipped by `text-overflow: ellipsis`.
42
+ *
43
+ * Re-measures when the element resizes and whenever `watchSource` changes, so
44
+ * it stays correct as the container narrows or the text is swapped out. Use it
45
+ * to show a tooltip only for names that are actually cut off.
46
+ * @param elementRef - The element whose text may be clipped
47
+ * @param watchSource - Value to re-measure on, e.g. the rendered text
48
+ * @returns Reactive truncation state
49
+ */
50
+ export function useIsTruncated(
51
+ elementRef: ElementRef,
52
+ watchSource?: () => unknown,
53
+ ): Ref<boolean> {
54
+ const isTruncated = ref(false);
55
+
56
+ function measure(): void {
57
+ const el = elementRef.value;
58
+ // Sub-pixel widths round differently per browser, so require a whole
59
+ // pixel of overflow before calling it truncated.
60
+ isTruncated.value = el ? el.scrollWidth - el.clientWidth >= 1 : false;
61
+ }
62
+
63
+ // A resize alone doesn't cover a text swap that keeps the box the same size.
64
+ if (watchSource) watch(watchSource, measure, { flush: 'post' });
65
+
66
+ // Runs on mount too, when the template ref goes null -> element.
67
+ watch(
68
+ elementRef,
69
+ (el, previous) => {
70
+ if (previous) unwatchElement(previous);
71
+ if (el) watchElement(el, measure);
72
+ measure();
73
+ },
74
+ { immediate: true, flush: 'post' },
75
+ );
76
+
77
+ onScopeDispose(() => {
78
+ if (elementRef.value) unwatchElement(elementRef.value);
79
+ });
80
+
81
+ return isTruncated;
82
+ }
@@ -1,4 +1,3 @@
1
- /* eslint-disable vue/one-component-per-file */
2
1
  import type { ChatKitLabels } from './useChatKitLabels';
3
2
  import {
4
3
  CHAT_KIT_LABELS,
@@ -114,7 +114,7 @@ export type ChatKitLabels = {
114
114
  };
115
115
 
116
116
  /** English defaults used when no labels are provided by the consumer. */
117
- export const defaultChatKitLabels: ChatKitLabels = {
117
+ export const defaultChatKitLabels = {
118
118
  agentAvatarAlt: 'Agent avatar',
119
119
  agentLoadingAvatarAlt: 'Agent loading',
120
120
  inputPlaceholder: 'Type a message...',
@@ -150,7 +150,7 @@ export const defaultChatKitLabels: ChatKitLabels = {
150
150
  historyItemEditAction: 'Edit',
151
151
  historyItemDeleteAction: 'Delete',
152
152
  historyShowAll: 'Show all',
153
- };
153
+ } satisfies Required<ChatKitLabels>;
154
154
 
155
155
  /** Vue injection key for ChatKit labels. */
156
156
  export const CHAT_KIT_LABELS: InjectionKey<ComputedRef<ChatKitLabels>> =
@@ -100,7 +100,7 @@ export function useMarkdownParser(
100
100
  let cachedProcessor: SyncProcessor | undefined;
101
101
 
102
102
  function getProcessor(list: PluggableList): SyncProcessor {
103
- if (list === cachedPlugins) return cachedProcessor!;
103
+ if (list === cachedPlugins && cachedProcessor) return cachedProcessor;
104
104
  cachedPlugins = list;
105
105
  cachedProcessor = buildProcessor(list);
106
106
  return cachedProcessor;
package/src/test-setup.ts CHANGED
@@ -6,7 +6,7 @@ import '@testing-library/jest-dom/vitest';
6
6
 
7
7
  // ResizeObserver is not available in JSDOM
8
8
  global.ResizeObserver = class ResizeObserver {
9
- observe(): void {}
10
- unobserve(): void {}
11
- disconnect(): void {}
9
+ observe = (): void => undefined;
10
+ unobserve = (): void => undefined;
11
+ disconnect = (): void => undefined;
12
12
  };
@@ -1,7 +1,15 @@
1
+ type TextPluginNode = {
2
+ type: string;
3
+ value?: string;
4
+ children?: TextPluginNode[];
5
+ };
6
+
1
7
  export function createTextPlugin(transform: (text: string) => string) {
2
- return () => (tree: any) => {
3
- const visit = (node: any) => {
4
- if (node.type === 'text') node.value = transform(node.value);
8
+ return () => (tree: TextPluginNode) => {
9
+ const visit = (node: TextPluginNode) => {
10
+ if (node.type === 'text' && node.value !== undefined) {
11
+ node.value = transform(node.value);
12
+ }
5
13
  if (node.children) node.children.forEach(visit);
6
14
  };
7
15
  visit(tree);