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

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.
@@ -12,9 +12,13 @@ type __VLS_Props = {
12
12
  };
13
13
  declare var __VLS_39: {
14
14
  message: ChatEngineMessage;
15
+ }, __VLS_61: {
16
+ message: ChatEngineMessage;
15
17
  };
16
18
  type __VLS_Slots = {} & {
17
19
  'message-actions'?: (props: typeof __VLS_39) => any;
20
+ } & {
21
+ 'message-footer'?: (props: typeof __VLS_61) => any;
18
22
  };
19
23
  declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
20
24
  rate: (messageId: string, value: MessageFeedback) => any;
@@ -6,7 +6,7 @@
6
6
  */
7
7
  import { type ChatCopyInput } from '../copy';
8
8
  import type { ChatEngine, ChatEngineMessage } from '../composables/useChatEngine';
9
- import type { AutoScrollMode, CitationType, MessageFeedback, ThinkingVariant } from '../types';
9
+ import type { AutoScrollMode, ChatInputMode, CitationType, MessageFeedback, ThinkingVariant } from '../types';
10
10
  type __VLS_Props = {
11
11
  /**
12
12
  * The conversation to render, from useChatEngine. Required, and not a
@@ -14,7 +14,10 @@ type __VLS_Props = {
14
14
  * kit had before that setting existed.
15
15
  */
16
16
  engine: ChatEngine;
17
- /** All of the app's copy text. Anything missing falls back to its default. */
17
+ /**
18
+ * All of the app's copy text. Anything missing falls back to its default.
19
+ * Read reactively, so an app can change a string while the chat is open.
20
+ */
18
21
  copy?: ChatCopyInput;
19
22
  /** Offers voice dictation, in browsers with speech recognition. */
20
23
  showMic?: boolean;
@@ -48,14 +51,88 @@ type __VLS_Props = {
48
51
  * default, since a client may not want the action at all.
49
52
  */
50
53
  showCopy?: boolean;
54
+ /**
55
+ * Runs before the composer's text reaches the engine. Return false to keep
56
+ * it from being sent: the app has something to settle first (an offer to
57
+ * show, a question to refine) and will call engine.sendMessage itself when
58
+ * it is ready. Anything else lets the send go ahead.
59
+ */
60
+ beforeSend?: (text: string, mode: ChatInputMode) => boolean | void | Promise<boolean | void>;
51
61
  };
52
62
  declare var __VLS_10: {
63
+ message: {
64
+ id: string;
65
+ role: import("../index.ts").ChatEngineRole;
66
+ text: string;
67
+ html: string;
68
+ thinking: string;
69
+ basecampMessageId?: string | undefined;
70
+ sources: {
71
+ entityId: string | null;
72
+ metadata?: {
73
+ [x: string]: unknown;
74
+ title?: string | undefined;
75
+ entityId?: string | undefined;
76
+ page?: number | undefined;
77
+ textPage?: number | undefined;
78
+ link?: string | undefined;
79
+ screenshotUrl?: string | undefined;
80
+ } | undefined;
81
+ screenshotUrl?: string | undefined;
82
+ results: {
83
+ index?: number | undefined;
84
+ content?: string | undefined;
85
+ score?: number | undefined;
86
+ metadata?: {
87
+ [x: string]: unknown;
88
+ title?: string | undefined;
89
+ entityId?: string | undefined;
90
+ page?: number | undefined;
91
+ textPage?: number | undefined;
92
+ link?: string | undefined;
93
+ screenshotUrl?: string | undefined;
94
+ } | undefined;
95
+ }[];
96
+ }[];
97
+ citations: {
98
+ ref: number;
99
+ label: number;
100
+ passage: string;
101
+ entityId?: string | undefined;
102
+ variationId?: string | undefined;
103
+ title?: string | undefined;
104
+ link?: string | undefined;
105
+ canonicalLink?: string | undefined;
106
+ sourceId?: string | undefined;
107
+ screenshotUrl?: string | undefined;
108
+ page?: number | undefined;
109
+ textPage?: number | undefined;
110
+ }[];
111
+ feedback: MessageFeedback | null;
112
+ isOutOfScope: boolean;
113
+ isStreaming: boolean;
114
+ isReceiving: boolean;
115
+ failed: boolean;
116
+ inputMode?: ChatInputMode | undefined;
117
+ data?: Record<string, unknown> | undefined;
118
+ };
119
+ }, __VLS_13: {
53
120
  message: ChatEngineMessage;
54
- }, __VLS_13: {};
121
+ }, __VLS_16: {
122
+ message: ChatEngineMessage;
123
+ }, __VLS_19: {}, __VLS_21: {}, __VLS_33: {};
55
124
  type __VLS_Slots = {} & {
56
- 'message-actions'?: (props: typeof __VLS_10) => any;
125
+ message?: (props: typeof __VLS_10) => any;
126
+ } & {
127
+ 'message-actions'?: (props: typeof __VLS_13) => any;
128
+ } & {
129
+ 'message-footer'?: (props: typeof __VLS_16) => any;
130
+ } & {
131
+ welcome?: (props: typeof __VLS_19) => any;
132
+ } & {
133
+ composer?: (props: typeof __VLS_21) => any;
57
134
  } & {
58
- welcome?: (props: typeof __VLS_13) => any;
135
+ 'composer-footer'?: (props: typeof __VLS_33) => any;
59
136
  };
60
137
  declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
61
138
  rated: (message: ChatEngineMessage, value: MessageFeedback | null) => any;
@@ -5,12 +5,22 @@ type __VLS_Props = {
5
5
  /** Offers voice dictation, in browsers with speech recognition. */
6
6
  showMic?: boolean;
7
7
  };
8
- declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
8
+ declare var __VLS_45: {};
9
+ type __VLS_Slots = {} & {
10
+ footer?: (props: typeof __VLS_45) => any;
11
+ };
12
+ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
9
13
  stop: () => any;
10
14
  send: (text: string, mode: ChatInputMode) => any;
11
15
  }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
12
16
  onStop?: (() => any) | undefined;
13
17
  onSend?: ((text: string, mode: ChatInputMode) => any) | undefined;
14
18
  }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
19
+ declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
15
20
  declare const _default: typeof __VLS_export;
16
21
  export default _default;
22
+ type __VLS_WithSlots<T, S> = T & {
23
+ new (): {
24
+ $slots: S;
25
+ };
26
+ };
@@ -13,13 +13,76 @@ type __VLS_Props = {
13
13
  /** Offers a copy button under each finished answer. */
14
14
  showCopy?: boolean;
15
15
  };
16
- declare var __VLS_1: {}, __VLS_19: {
16
+ declare var __VLS_1: {}, __VLS_8: {
17
+ message: {
18
+ id: string;
19
+ role: import("../index.ts").ChatEngineRole;
20
+ text: string;
21
+ html: string;
22
+ thinking: string;
23
+ basecampMessageId?: string | undefined;
24
+ sources: {
25
+ entityId: string | null;
26
+ metadata?: {
27
+ [x: string]: unknown;
28
+ title?: string | undefined;
29
+ entityId?: string | undefined;
30
+ page?: number | undefined;
31
+ textPage?: number | undefined;
32
+ link?: string | undefined;
33
+ screenshotUrl?: string | undefined;
34
+ } | undefined;
35
+ screenshotUrl?: string | undefined;
36
+ results: {
37
+ index?: number | undefined;
38
+ content?: string | undefined;
39
+ score?: number | undefined;
40
+ metadata?: {
41
+ [x: string]: unknown;
42
+ title?: string | undefined;
43
+ entityId?: string | undefined;
44
+ page?: number | undefined;
45
+ textPage?: number | undefined;
46
+ link?: string | undefined;
47
+ screenshotUrl?: string | undefined;
48
+ } | undefined;
49
+ }[];
50
+ }[];
51
+ citations: {
52
+ ref: number;
53
+ label: number;
54
+ passage: string;
55
+ entityId?: string | undefined;
56
+ variationId?: string | undefined;
57
+ title?: string | undefined;
58
+ link?: string | undefined;
59
+ canonicalLink?: string | undefined;
60
+ sourceId?: string | undefined;
61
+ screenshotUrl?: string | undefined;
62
+ page?: number | undefined;
63
+ textPage?: number | undefined;
64
+ }[];
65
+ feedback: MessageFeedback | null;
66
+ isOutOfScope: boolean;
67
+ isStreaming: boolean;
68
+ isReceiving: boolean;
69
+ failed: boolean;
70
+ inputMode?: import("../types").ChatInputMode | undefined;
71
+ data?: Record<string, unknown> | undefined;
72
+ };
73
+ }, __VLS_21: {
74
+ message: ChatEngineMessage;
75
+ }, __VLS_24: {
17
76
  message: ChatEngineMessage;
18
77
  };
19
78
  type __VLS_Slots = {} & {
20
79
  welcome?: (props: typeof __VLS_1) => any;
21
80
  } & {
22
- 'message-actions'?: (props: typeof __VLS_19) => any;
81
+ message?: (props: typeof __VLS_8) => any;
82
+ } & {
83
+ 'message-actions'?: (props: typeof __VLS_21) => any;
84
+ } & {
85
+ 'message-footer'?: (props: typeof __VLS_24) => any;
23
86
  };
24
87
  declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
25
88
  rated: (message: ChatEngineMessage, value: MessageFeedback | null) => any;
@@ -31,11 +31,18 @@ export interface ChatTransport {
31
31
  */
32
32
  getQuota?(agentId: string): Promise<QuotaResponse>;
33
33
  }
34
+ /**
35
+ * Who a message belongs to. 'user' and 'assistant' are the conversation with
36
+ * the agent. 'app' is a message the app put there itself (see insertMessage):
37
+ * it renders through Chat's `message` slot, is never sent anywhere, and the
38
+ * kit's own bookkeeping passes over it.
39
+ */
40
+ export type ChatEngineRole = 'user' | 'assistant' | 'app';
34
41
  /** One message as the components render it. */
35
42
  export interface ChatEngineMessage {
36
43
  /** Local id, stable for the life of the conversation. */
37
44
  id: string;
38
- role: 'user' | 'assistant';
45
+ role: ChatEngineRole;
39
46
  /** The raw text. For assistant messages it grows while streaming. */
40
47
  text: string;
41
48
  /** Rendered, sanitized HTML for assistant messages. */
@@ -61,6 +68,21 @@ export interface ChatEngineMessage {
61
68
  failed: boolean;
62
69
  /** How the user entered this message (typed or voice); only user messages set it. */
63
70
  inputMode?: ChatInputMode;
71
+ /**
72
+ * Whatever the app attached to a message it inserted. The kit never reads
73
+ * it; it exists so the app's `message` slot has what it needs to render.
74
+ */
75
+ data?: Record<string, unknown>;
76
+ }
77
+ /** What insertMessage takes: the message's text, and anything else the app's slot needs. */
78
+ export interface InsertMessageInput {
79
+ /**
80
+ * 'app' unless said otherwise. 'user' is for a question the app is holding
81
+ * back and wants shown as the user's own bubble in the meantime.
82
+ */
83
+ role?: 'app' | 'user';
84
+ text?: string;
85
+ data?: Record<string, unknown>;
64
86
  }
65
87
  /**
66
88
  * What the engine needs to exist at all: which agent, reached how. Not
@@ -167,7 +189,7 @@ export declare function useChatEngine(options: ChatEngineOptions): {
167
189
  threadId: Readonly<import("vue").Ref<string | null, string | null>>;
168
190
  messages: import("vue").Ref<{
169
191
  id: string;
170
- role: "user" | "assistant";
192
+ role: ChatEngineRole;
171
193
  text: string;
172
194
  html: string;
173
195
  thinking: string;
@@ -219,9 +241,10 @@ export declare function useChatEngine(options: ChatEngineOptions): {
219
241
  isReceiving: boolean;
220
242
  failed: boolean;
221
243
  inputMode?: ChatInputMode | undefined;
244
+ data?: Record<string, unknown> | undefined;
222
245
  }[], ChatEngineMessage[] | {
223
246
  id: string;
224
- role: "user" | "assistant";
247
+ role: ChatEngineRole;
225
248
  text: string;
226
249
  html: string;
227
250
  thinking: string;
@@ -273,6 +296,7 @@ export declare function useChatEngine(options: ChatEngineOptions): {
273
296
  isReceiving: boolean;
274
297
  failed: boolean;
275
298
  inputMode?: ChatInputMode | undefined;
299
+ data?: Record<string, unknown> | undefined;
276
300
  }[]>;
277
301
  status: Readonly<import("vue").Ref<ChatStatus, ChatStatus>>;
278
302
  error: Readonly<import("vue").Ref<Error | null, Error | null>>;
@@ -313,6 +337,11 @@ export declare function useChatEngine(options: ChatEngineOptions): {
313
337
  readonly contextLimit: number;
314
338
  readonly cumulativeLimit: number;
315
339
  } | null>>;
340
+ customData: Readonly<import("vue").Ref<{
341
+ readonly [x: string]: Readonly<unknown>;
342
+ } | null, {
343
+ readonly [x: string]: Readonly<unknown>;
344
+ } | null>>;
316
345
  quota: Readonly<import("vue").Ref<{
317
346
  readonly limit: number | "unlimited";
318
347
  readonly remaining: number | "unlimited";
@@ -330,6 +359,8 @@ export declare function useChatEngine(options: ChatEngineOptions): {
330
359
  loadThread: (id: string) => Promise<void>;
331
360
  setFeedback: (messageId: string, value: MessageFeedback) => Promise<void>;
332
361
  noteCitationClick: (messageId: string, citation: ChatCitation) => void;
362
+ insertMessage: (input?: InsertMessageInput, index?: number) => ChatEngineMessage;
363
+ removeMessage: (id: string) => boolean;
333
364
  retry: () => Promise<void>;
334
365
  };
335
366
  export type ChatEngine = ReturnType<typeof useChatEngine>;
package/dist/copy.d.ts CHANGED
@@ -135,10 +135,18 @@ export declare function formatCopy(template: string, params: Record<string, stri
135
135
  /**
136
136
  * Merges provided copy with the default values. Any missing strings fall back to the default and gets listed in a warning
137
137
  * during development. That way an incomplete set of text is easy to spot but never leaves blank labels in the interface.
138
+ *
139
+ * `warnMissing` is for callers that resolve the same map again after a change;
140
+ * the first resolve has already said what was missing.
141
+ */
142
+ export declare function resolveChatCopy(input?: ChatCopyInput, warnMissing?: boolean): ResolvedChatCopy;
143
+ /**
144
+ * Called once by the chat root component with its `copy` prop. Given a getter,
145
+ * the provided copy follows the prop: an app can change a string while the
146
+ * chat is open (a placeholder that depends on state, say) and the components
147
+ * pick it up, since they read the one reactive object this provides.
138
148
  */
139
- export declare function resolveChatCopy(input?: ChatCopyInput): ResolvedChatCopy;
140
- /** Called once by the chat root component with its `copy` prop value. */
141
- export declare function provideChatCopy(input?: ChatCopyInput): ResolvedChatCopy;
149
+ export declare function provideChatCopy(input?: ChatCopyInput | (() => ChatCopyInput | undefined)): ResolvedChatCopy;
142
150
  /** Read the resolved copy anywhere inside the chat component tree. */
143
151
  export declare function useChatCopy(): ResolvedChatCopy;
144
152
  export {};
package/dist/index.css CHANGED
@@ -1 +1 @@
1
- .icon-button[data-v-ce6d8ce0]{border-radius:var(--r-pill);background:var(--icon-button-state-bg,var(--icon-button-bg));width:40px;height:40px;color:var(--icon-button-state-fg,var(--text-primary));cursor:pointer;border:none;place-items:center;padding:0;display:grid;position:relative}.icon-button[data-v-ce6d8ce0]:disabled{color:var(--text-secondary);cursor:default}.icon-button[data-v-ce6d8ce0]:not(:disabled):hover{background:var(--icon-button-state-bg,var(--icon-button-bg-hover))}.icon-button__label[data-v-ce6d8ce0]{bottom:calc(100% + var(--spacing-1));border-radius:var(--card-radius);background:var(--surface-card);width:max-content;box-shadow:var(--card-shadow);color:var(--text-primary);font-family:var(--font-label);font-size:var(--t-xs);font-weight:var(--fw-bold);text-transform:uppercase;white-space:nowrap;opacity:0;visibility:hidden;pointer-events:none;padding:15px;line-height:1;transition:opacity .15s;position:absolute;left:50%;transform:translate(-50%)}@media (hover:hover){.icon-button:not(:disabled):hover .icon-button__label[data-v-ce6d8ce0]{opacity:1;visibility:visible}}.input-bar[data-v-752f72a0]{gap:var(--spacing-3);padding:var(--spacing-6);flex-direction:column;padding-top:0;display:flex}.input-bar__field[data-v-752f72a0]{align-items:center;gap:var(--spacing-5);padding:var(--spacing-6);border:1px solid var(--card-stroke);border-radius:var(--card-radius);background:var(--composer-bg);display:flex}.input-bar__field[data-v-752f72a0]:focus-within{border-color:var(--accent)}.input-bar__send[data-v-752f72a0]:enabled{--icon-button-state-bg:var(--icon-button-bg-active);--icon-button-state-fg:var(--icon-button-fg-active)}.input-bar__send[data-v-752f72a0]:enabled:hover{--icon-button-state-bg:var(--icon-button-bg-active-hover)}.input-bar__textarea[data-v-752f72a0]{--composer-line:32px;min-width:0;max-height:100px;min-height:var(--composer-line);resize:none;color:var(--composer-fg);font-family:var(--font-body);font-size:var(--t-base);font-weight:var(--fw-regular);line-height:var(--composer-line);background:0 0;border:none;flex:1;padding:0}.input-bar__textarea[data-v-752f72a0]::placeholder{color:var(--composer-placeholder)}.input-bar__textarea[data-v-752f72a0]:focus{outline:none}.input-bar__buttons[data-v-752f72a0]{align-items:center;gap:var(--spacing-2);flex-shrink:0;display:flex}.input-bar__mic-unsupported[data-v-752f72a0]{width:40px;height:40px;color:var(--text-secondary);cursor:help;place-items:center;display:grid;position:relative}.input-bar__mic-unsupported[data-v-752f72a0]:after{content:attr(data-tooltip);bottom:calc(100% + var(--spacing-2));width:max-content;max-width:240px;padding:var(--spacing-2) var(--spacing-3);border:1px solid var(--card-stroke);border-radius:var(--r-md);background:var(--surface-card);box-shadow:var(--card-shadow);color:var(--text-primary);font-size:var(--t-sm);line-height:var(--lh-sm);opacity:0;visibility:hidden;pointer-events:none;transition:opacity .15s;position:absolute;right:0}.input-bar__mic-unsupported[data-v-752f72a0]:hover:after{opacity:1;visibility:visible}.input-bar__button--listening[data-v-752f72a0]{--icon-button-state-bg:var(--icon-button-bg-active);--icon-button-state-fg:var(--icon-button-fg-active);animation:1.6s ease-in-out infinite mic-pulse-752f72a0}@keyframes mic-pulse-752f72a0{50%{opacity:.65}}.citation-popover[data-v-c713f6b3]{gap:var(--spacing-5);width:min(350px,100%);padding:var(--spacing-6);border:1px solid var(--card-stroke);border-radius:var(--card-radius);background:var(--surface-card);box-shadow:var(--card-shadow);overscroll-behavior:contain;scrollbar-width:thin;scrollbar-color:var(--scrollbar-thumb) transparent;flex-direction:column;display:flex;overflow-y:auto}.citation-popover__source[data-v-c713f6b3]{align-items:flex-start;gap:var(--spacing-2);flex-shrink:0;display:flex}.citation-popover__thumb[data-v-c713f6b3]{width:64px;height:72px;margin-right:var(--spacing-2);border:1px solid var(--card-stroke);border-radius:var(--r-lg);object-fit:cover;object-position:top center;flex-shrink:0;display:block}.citation-popover__badge[data-v-c713f6b3]{border-radius:var(--r-md);background:var(--citation-bg);min-width:20px;height:20px;color:var(--accent-fg);font-family:var(--font-body);font-size:var(--t-xs);font-weight:var(--fw-bold);flex-shrink:0;place-items:center;padding:0 5px;display:grid}.citation-popover__source-text[data-v-c713f6b3]{gap:var(--spacing-1);flex-direction:column;min-width:0;display:flex}.citation-popover__title[data-v-c713f6b3]{color:var(--text-primary);font-family:var(--font-body);font-size:var(--t-sm);font-weight:var(--fw-bold);overflow-wrap:break-word}.citation-popover__page[data-v-c713f6b3]{color:var(--text-helper);font-family:var(--font-body);font-size:var(--t-sm)}.citation-popover__passage-block[data-v-c713f6b3]{gap:var(--spacing-2);flex-direction:column;flex:1;min-height:0;display:flex}.citation-popover__passage-label[data-v-c713f6b3]{color:var(--text-helper);font-family:var(--font-label);font-size:var(--t-xs);font-weight:var(--fw-bold);text-transform:uppercase}.citation-popover__passage[data-v-c713f6b3]{padding-left:var(--spacing-6);border-left:2px solid var(--accent);color:var(--text-primary);font-family:var(--font-body);font-size:var(--t-sm);line-height:var(--lh-body);overscroll-behavior:contain;scrollbar-width:thin;scrollbar-color:var(--scrollbar-thumb) transparent;min-height:0;margin:0;overflow-y:auto}.citation-popover__notice[data-v-c713f6b3]{color:var(--text-helper);font-family:var(--font-body);font-size:var(--t-xs);line-height:var(--lh-body);white-space:pre-wrap}.citation-popover__footer[data-v-c713f6b3]{gap:var(--spacing-4);flex-shrink:0;display:flex}.citation-popover__copy[data-v-c713f6b3],.citation-popover__open[data-v-c713f6b3]{justify-content:center;align-items:center;gap:var(--spacing-2);height:35px;padding:0 var(--spacing-4);border-radius:var(--r-md);font-family:var(--font-ui);font-size:var(--t-sm);font-weight:var(--fw-bold);cursor:pointer;flex:1;display:flex}.citation-popover__copy[data-v-c713f6b3]{border:1px solid var(--card-stroke);color:var(--text-primary);background:0 0}.citation-popover__open[data-v-c713f6b3]{background:var(--accent);color:var(--accent-fg);border:none;text-decoration:none}.feedback[data-v-6aa14982]{justify-content:flex-end;align-items:center;gap:var(--spacing-4);width:100%;display:flex}.feedback__question[data-v-6aa14982]{color:var(--text-helper);font-family:var(--font-label);font-size:var(--t-xs);font-weight:var(--fw-bold);text-transform:uppercase}.feedback__thumbs[data-v-6aa14982]{gap:var(--spacing-4);display:flex}.feedback__thumb[data-v-6aa14982]{color:var(--text-helper);cursor:pointer;background:0 0;border:none;place-items:center;padding:2px;display:grid}.feedback__thumb[data-v-6aa14982]:hover{color:var(--text-primary)}.feedback__thumb.is-active[data-v-6aa14982]{color:var(--accent)}.page-image-modal[data-v-33820e23]{z-index:200;padding:var(--spacing-6);background:var(--overlay-scrim);place-items:center;display:grid;position:fixed;inset:0}.page-image-modal__card[data-v-33820e23]{gap:var(--spacing-4);max-width:min(720px,100%);max-height:100%;padding:var(--spacing-6);border:1px solid var(--card-stroke);border-radius:var(--card-radius);background:var(--surface-card);box-shadow:var(--card-shadow);flex-direction:column;display:flex}.page-image-modal__header[data-v-33820e23]{justify-content:space-between;align-items:flex-start;gap:var(--spacing-4);display:flex}.page-image-modal__title[data-v-33820e23]{color:var(--text-primary);font-family:var(--font-body);font-size:var(--t-sm);font-weight:var(--fw-bold);overflow-wrap:break-word}.page-image-modal__detail[data-v-33820e23]{color:var(--text-helper);font-family:var(--font-body);font-size:var(--t-sm)}.page-image-modal__close[data-v-33820e23]{padding:var(--spacing-1) var(--spacing-3);border:1px solid var(--card-stroke);border-radius:var(--r-md);color:var(--text-primary);font-family:var(--font-ui);font-size:var(--t-sm);font-weight:var(--fw-bold);cursor:pointer;background:0 0}.page-image-modal__close[data-v-33820e23]:hover{background:var(--surface-hover)}.page-image-modal__image[data-v-33820e23]{border:1px solid var(--card-stroke);border-radius:var(--r-lg);object-fit:contain;min-height:0;overflow-y:auto}.source-card[data-v-49b414a6]{gap:var(--spacing-4);padding:var(--spacing-6);border:1px solid var(--card-stroke);border-radius:var(--r-md);background:var(--surface-raised);flex-direction:column;display:flex;position:relative;container:source-card/inline-size}.source-card__front[data-v-49b414a6]{gap:var(--spacing-4);flex-direction:column;width:100%;display:flex}.source-card__details[data-v-49b414a6]{gap:var(--spacing-5);flex-direction:column;display:flex}@container source-card (width>=420px){.source-card__details[data-v-49b414a6]{flex-direction:row;align-items:center}}.source-card__toggle[data-v-49b414a6]{top:var(--spacing-6);right:var(--spacing-6);border-radius:var(--r-sm);width:32px;height:32px;color:var(--text-secondary);cursor:pointer;background:0 0;border:none;place-items:center;padding:0;transition:transform .3s;display:grid;position:absolute}.source-card__toggle[data-v-49b414a6]:hover{background:var(--surface-hover);color:var(--text-primary)}.source-card__toggle--open[data-v-49b414a6]{transform:rotate(180deg)}.source-card__cover[data-v-49b414a6]{object-fit:contain;flex-shrink:0;width:150px;height:150px}.source-card__naming[data-v-49b414a6]{align-items:flex-start;gap:var(--spacing-5);flex-direction:column;min-width:0;display:flex}.source-card__heading[data-v-49b414a6]{color:var(--text-helper);font-family:var(--font-label);font-size:var(--t-xs);font-weight:var(--fw-bold);text-transform:uppercase}.source-card__title[data-v-49b414a6]{color:var(--text-primary);font-family:var(--font-display);font-size:var(--t-2xl);font-weight:var(--fw-regular);text-align:start;overflow-wrap:break-word}.source-card__link[data-v-49b414a6]{align-items:center;gap:var(--spacing-1);color:var(--accent);font-family:var(--font-ui);font-size:var(--t-xs);font-weight:var(--fw-bold);align-self:flex-start;display:inline-flex}.source-card__body[data-v-49b414a6]{gap:var(--spacing-4);padding-top:var(--spacing-4);border-top:1px solid var(--card-stroke);flex-direction:column;display:flex}.source-card__reference-heading[data-v-49b414a6]{color:var(--text-helper);font-family:var(--font-label);font-size:var(--t-xs);font-weight:var(--fw-bold);text-transform:uppercase}.source-card__reference[data-v-49b414a6]{gap:var(--spacing-5);text-align:left;flex-direction:column;width:100%;display:flex}@container source-card (width>=420px){.source-card__reference[data-v-49b414a6]{flex-direction:row;align-items:flex-start}}.source-card__page-thumb[data-v-49b414a6]{object-fit:contain;cursor:pointer;flex-shrink:0;width:150px;height:150px}.source-card__reference-text[data-v-49b414a6]{gap:var(--spacing-4);flex-direction:column;width:100%;min-width:0;display:flex}.source-card__page-line[data-v-49b414a6]{color:var(--text-primary);font-family:var(--font-label);font-size:var(--t-xs);font-weight:var(--fw-bold);text-transform:uppercase;letter-spacing:.12px}.source-card__passage[data-v-49b414a6]{color:var(--text-secondary);font-family:var(--font-body);font-size:var(--t-sm);line-height:var(--lh-body);overflow-wrap:break-word}.source-list[data-v-124ee2ae]{gap:var(--spacing-3);flex-direction:column;display:flex}.source-list__intro[data-v-124ee2ae]{color:var(--text-primary);font-family:var(--font-body);font-size:var(--t-sm);font-weight:var(--fw-bold)}.source-list__notice[data-v-124ee2ae]{color:var(--text-helper);font-family:var(--font-body);font-size:var(--t-xs);line-height:var(--lh-body);white-space:pre-wrap}@property --thinking-arc{syntax:"<angle>";inherits:false;initial-value:121deg}.thinking__spinner[data-v-a73d8186]{border-radius:var(--r-pill);background:var(--accent);width:26px;height:26px;-webkit-mask:conic-gradient(from 90deg, transparent 0deg, #000 var(--thinking-arc), transparent calc(var(--thinking-arc) + 1deg)), radial-gradient(farthest-side, transparent calc(100% - 6px), #000 calc(100% - 6px));mask:conic-gradient(from 90deg, transparent 0deg, #000 var(--thinking-arc), transparent calc(var(--thinking-arc) + 1deg)), radial-gradient(farthest-side, transparent calc(100% - 6px), #000 calc(100% - 6px));flex:none;animation:1s linear infinite thinking-spin-a73d8186,1.8s ease-in-out infinite thinking-chase-a73d8186;-webkit-mask-composite:source-in;mask-composite:intersect}@keyframes thinking-spin-a73d8186{to{transform:rotate(1turn)}}@keyframes thinking-chase-a73d8186{0%{--thinking-arc:40deg;animation-timing-function:ease-in-out}40%{--thinking-arc:247deg;animation-timing-function:linear}to{--thinking-arc:40deg}}.thinking__sparkles[data-v-a73d8186]{width:32px;height:32px;color:var(--accent);flex:none;position:relative}.thinking__dots[data-v-a73d8186]{align-items:center;gap:var(--spacing-1);flex:none;display:flex}.thinking__dot[data-v-a73d8186]{border-radius:var(--r-pill);background:var(--accent);width:6px;height:6px;animation:1.2s ease-in-out infinite both thinking-dot-a73d8186}.thinking__dot[data-v-a73d8186]:nth-child(2){animation-delay:.15s}.thinking__dot[data-v-a73d8186]:nth-child(3){animation-delay:.3s}@keyframes thinking-dot-a73d8186{0%,60%,to{opacity:.4;transform:translateY(0)}30%{opacity:1;transform:translateY(-4px)}}.thinking__sparkle[data-v-a73d8186]{animation:1.5s linear infinite both thinking-sparkle-a73d8186;position:absolute}.thinking__sparkle[data-v-a73d8186]:first-child{width:18px;height:18px;top:7px;left:6px}.thinking__sparkle[data-v-a73d8186]:nth-child(2){width:11px;height:11px;animation-delay:-.5s;top:1.7px;left:19.3px}.thinking__sparkle[data-v-a73d8186]:nth-child(3){width:8px;height:8px;animation-delay:-1s;top:20.3px;left:3.7px}@keyframes thinking-sparkle-a73d8186{0%{opacity:0;transform:translate(-9px,-9px)scale(0)}35%{opacity:1;transform:translate(-2.7px,-2.7px)scale(1)}65%{opacity:1;transform:translate(2.7px,2.7px)scale(1)}to{opacity:0;transform:translate(9px,9px)scale(0)}}.thinking-process[data-v-3dcefa4f]{gap:var(--spacing-2);flex-direction:column;width:100%;display:flex}.thinking-process__toggle[data-v-3dcefa4f]{color:var(--text-helper);font-family:var(--font-label);font-size:var(--t-xs);font-weight:var(--fw-bold);text-transform:uppercase;cursor:pointer;background:0 0;border:none;align-self:flex-start;padding:0}.thinking-process__toggle[data-v-3dcefa4f]:hover{color:var(--text-primary)}.thinking-process__body[data-v-3dcefa4f]{max-height:220px;padding:var(--spacing-3) var(--spacing-4);border-radius:var(--r-lg);background:var(--surface-raised);color:var(--text-secondary);font-family:var(--font-body);font-size:var(--t-sm);line-height:var(--lh-body);white-space:pre-wrap;overflow-wrap:break-word;scrollbar-width:thin;scrollbar-color:var(--scrollbar-thumb) transparent;overflow-y:auto}.assistant-message[data-v-81f04579]{gap:var(--spacing-2);flex-direction:column;width:100%;display:flex}.assistant-message__footer[data-v-81f04579]{justify-content:space-between;align-items:center;gap:var(--spacing-4);display:flex}.assistant-message__actions[data-v-81f04579]{align-items:center;gap:var(--spacing-2);flex:none;display:flex}.assistant-message__header[data-v-81f04579]{align-items:center;gap:var(--spacing-4);display:flex}.assistant-message__avatar[data-v-81f04579]{border-radius:var(--r-pill);background:var(--accent);width:32px;height:32px;color:var(--accent-fg);flex-shrink:0;place-items:center;display:grid}.assistant-message__name[data-v-81f04579]{color:var(--text-primary);font-family:var(--font-body);font-size:var(--t-base);font-weight:var(--fw-bold)}.assistant-message__body-wrap[data-v-81f04579]{width:100%}.assistant-message__popover[data-v-81f04579]{z-index:100;position:fixed}.assistant-message__body[data-v-81f04579] .citation-chip{min-width:20px;height:20px;margin-left:var(--spacing-1);border-radius:var(--r-md);background:var(--citation-bg);color:var(--accent-fg);font-size:var(--t-xs);font-weight:var(--fw-bold);vertical-align:text-bottom;cursor:pointer;-webkit-user-select:none;user-select:none;place-items:center;padding:0 5px;display:inline-grid}.assistant-message__body[data-v-81f04579] .citation-chip:not(.citation-chip--pending):hover{opacity:.7}.assistant-message__body[data-v-81f04579] .citation-chip--pending{cursor:default;opacity:.55}.assistant-message__body[data-v-81f04579]{color:var(--text-primary);font-family:var(--font-body);font-size:var(--t-base);font-weight:var(--fw-regular);line-height:var(--lh-body);overflow-wrap:break-word}.assistant-message__body[data-v-81f04579] p,.assistant-message__body[data-v-81f04579] ul,.assistant-message__body[data-v-81f04579] ol,.assistant-message__body[data-v-81f04579] pre,.assistant-message__body[data-v-81f04579] table,.assistant-message__body[data-v-81f04579] blockquote{margin-block:0 var(--spacing-4)}.assistant-message__body[data-v-81f04579] ul,.assistant-message__body[data-v-81f04579] ol{padding-inline-start:var(--spacing-6)}.assistant-message__body[data-v-81f04579] a{color:var(--accent)}.assistant-message__body[data-v-81f04579] .chat-math{max-width:100%;overflow-x:auto}.assistant-message__body[data-v-81f04579] .chat-math--display{margin-block:var(--spacing-4);text-align:center;display:block}.assistant-message__body[data-v-81f04579] code{font-family:var(--font-mono);font-size:var(--t-sm);background:var(--surface-raised);border-radius:var(--r-sm);padding:1px 5px}.assistant-message__body[data-v-81f04579] pre{background:var(--surface-raised);padding:var(--spacing-4);border-radius:var(--r-lg);overflow-x:auto}.assistant-message__body[data-v-81f04579] blockquote{padding:var(--spacing-3) var(--spacing-4);border-left:3px solid var(--accent);border-radius:0 var(--r-md) var(--r-md) 0;background:var(--surface-raised)}.assistant-message__body[data-v-81f04579] blockquote>:last-child{margin-bottom:0}.assistant-message__body[data-v-81f04579] table{border-collapse:collapse;max-width:100%;display:block;overflow-x:auto}.assistant-message__body[data-v-81f04579] th,.assistant-message__body[data-v-81f04579] td{padding:var(--spacing-2) var(--spacing-3);border:1px solid var(--card-stroke);text-align:left;vertical-align:top}.assistant-message__body[data-v-81f04579] th{background:var(--surface-raised);font-weight:var(--fw-bold)}.assistant-message__out-of-scope[data-v-81f04579]{align-self:flex-start;align-items:center;gap:var(--spacing-2);padding:var(--spacing-3);border:1px solid var(--accent);border-radius:var(--r-sm);background:color-mix(in srgb, var(--accent) 15%, transparent);color:var(--accent);font-family:var(--font-body);font-size:var(--t-xs);font-weight:var(--fw-bold);letter-spacing:.6px;text-transform:uppercase;display:flex}.assistant-message__out-of-scope+.assistant-message__body[data-v-81f04579]{margin-top:var(--spacing-2)}.assistant-message__error[data-v-81f04579]{align-items:center;gap:var(--spacing-4);color:var(--warning);font-family:var(--font-body);font-size:var(--t-sm);display:flex}.assistant-message__retry[data-v-81f04579]{padding:var(--spacing-1) var(--spacing-3);border:1px solid var(--card-stroke);border-radius:var(--r-md);color:var(--text-primary);font-family:var(--font-ui);font-size:var(--t-sm);font-weight:var(--fw-bold);cursor:pointer;background:0 0}.assistant-message__retry[data-v-81f04579]:hover{background:var(--surface-hover)}.thinking[data-v-c804834e]{align-items:center;gap:var(--spacing-4);color:var(--text-secondary);font-family:var(--font-body);font-size:var(--t-base);display:flex}.user-message[data-v-2eb946f2]{justify-content:flex-end;width:100%;display:flex}.user-message__bubble[data-v-2eb946f2]{max-width:70%;padding:var(--spacing-4);border-radius:var(--card-radius);background:var(--user-bubble-bg);color:var(--user-bubble-fg);font-family:var(--font-body);font-size:var(--t-base);font-weight:var(--fw-regular);white-space:pre-wrap;overflow-wrap:break-word;line-height:normal}.message-list[data-v-1e679b5e]{gap:var(--spacing-5);min-height:0;padding:var(--spacing-6);scrollbar-width:thin;scrollbar-color:var(--scrollbar-thumb) transparent;flex-direction:column;flex:1;display:flex;overflow-y:auto}.message-list[data-v-1e679b5e]::-webkit-scrollbar{width:10px}.message-list[data-v-1e679b5e]::-webkit-scrollbar-track{background:0 0}.message-list[data-v-1e679b5e]::-webkit-scrollbar-thumb{background:var(--scrollbar-thumb);border-radius:var(--r-pill);background-clip:padding-box;border:3px solid #0000}.message-list[data-v-1e679b5e]::-webkit-scrollbar-thumb:hover{background:var(--scrollbar-thumb-hover);background-clip:padding-box}.message-list__welcome-eyebrow[data-v-1e679b5e]{color:var(--accent);font-family:var(--font-label);font-size:var(--t-xs);font-weight:var(--fw-bold);text-transform:uppercase;margin-bottom:var(--spacing-3)}.message-list__welcome-heading[data-v-1e679b5e]{color:var(--text-primary);font-family:var(--font-display);font-size:var(--t-2xl);font-weight:var(--fw-bold);letter-spacing:var(--tr-tight);margin-bottom:var(--spacing-3)}.message-list__welcome-body[data-v-1e679b5e]{color:var(--text-primary);font-family:var(--font-body);font-size:var(--t-base);line-height:var(--lh-body)}.message-list__error[data-v-1e679b5e]{align-items:center;gap:var(--spacing-4);color:var(--warning);font-family:var(--font-body);font-size:var(--t-sm);display:flex}.message-list__retry[data-v-1e679b5e]{padding:var(--spacing-1) var(--spacing-3);border:1px solid var(--card-stroke);border-radius:var(--r-md);color:var(--text-primary);font-family:var(--font-ui);font-size:var(--t-sm);font-weight:var(--fw-bold);cursor:pointer;background:0 0}.message-list__retry[data-v-1e679b5e]:hover{background:var(--surface-hover)}.chat[data-v-f63ed144]{flex-direction:column;flex:1;min-height:0;display:flex}
1
+ .icon-button[data-v-ce6d8ce0]{border-radius:var(--r-pill);background:var(--icon-button-state-bg,var(--icon-button-bg));width:40px;height:40px;color:var(--icon-button-state-fg,var(--text-primary));cursor:pointer;border:none;place-items:center;padding:0;display:grid;position:relative}.icon-button[data-v-ce6d8ce0]:disabled{color:var(--text-secondary);cursor:default}.icon-button[data-v-ce6d8ce0]:not(:disabled):hover{background:var(--icon-button-state-bg,var(--icon-button-bg-hover))}.icon-button__label[data-v-ce6d8ce0]{bottom:calc(100% + var(--spacing-1));border-radius:var(--card-radius);background:var(--surface-card);width:max-content;box-shadow:var(--card-shadow);color:var(--text-primary);font-family:var(--font-label);font-size:var(--t-xs);font-weight:var(--fw-bold);text-transform:uppercase;white-space:nowrap;opacity:0;visibility:hidden;pointer-events:none;padding:15px;line-height:1;transition:opacity .15s;position:absolute;left:50%;transform:translate(-50%)}@media (hover:hover){.icon-button:not(:disabled):hover .icon-button__label[data-v-ce6d8ce0]{opacity:1;visibility:visible}}.input-bar[data-v-2a67b2cf]{gap:var(--spacing-3);padding:var(--spacing-6);flex-direction:column;padding-top:0;display:flex}.input-bar__field[data-v-2a67b2cf]{align-items:center;gap:var(--spacing-5);padding:var(--spacing-6);border:1px solid var(--card-stroke);border-radius:var(--card-radius);background:var(--composer-bg);display:flex}.input-bar__field[data-v-2a67b2cf]:focus-within{border-color:var(--accent)}.input-bar__send[data-v-2a67b2cf]:enabled{--icon-button-state-bg:var(--icon-button-bg-active);--icon-button-state-fg:var(--icon-button-fg-active)}.input-bar__send[data-v-2a67b2cf]:enabled:hover{--icon-button-state-bg:var(--icon-button-bg-active-hover)}.input-bar__textarea[data-v-2a67b2cf]{--composer-line:32px;min-width:0;max-height:100px;min-height:var(--composer-line);resize:none;color:var(--composer-fg);font-family:var(--font-body);font-size:var(--t-base);font-weight:var(--fw-regular);line-height:var(--composer-line);background:0 0;border:none;flex:1;padding:0}.input-bar__textarea[data-v-2a67b2cf]::placeholder{color:var(--composer-placeholder)}.input-bar__textarea[data-v-2a67b2cf]:focus{outline:none}.input-bar__buttons[data-v-2a67b2cf]{align-items:center;gap:var(--spacing-2);flex-shrink:0;display:flex}.input-bar__mic-unsupported[data-v-2a67b2cf]{width:40px;height:40px;color:var(--text-secondary);cursor:help;place-items:center;display:grid;position:relative}.input-bar__mic-unsupported[data-v-2a67b2cf]:after{content:attr(data-tooltip);bottom:calc(100% + var(--spacing-2));width:max-content;max-width:240px;padding:var(--spacing-2) var(--spacing-3);border:1px solid var(--card-stroke);border-radius:var(--r-md);background:var(--surface-card);box-shadow:var(--card-shadow);color:var(--text-primary);font-size:var(--t-sm);line-height:var(--lh-sm);opacity:0;visibility:hidden;pointer-events:none;transition:opacity .15s;position:absolute;right:0}.input-bar__mic-unsupported[data-v-2a67b2cf]:hover:after{opacity:1;visibility:visible}.input-bar__button--listening[data-v-2a67b2cf]{--icon-button-state-bg:var(--icon-button-bg-active);--icon-button-state-fg:var(--icon-button-fg-active);animation:1.6s ease-in-out infinite mic-pulse-2a67b2cf}@keyframes mic-pulse-2a67b2cf{50%{opacity:.65}}.citation-popover[data-v-c713f6b3]{gap:var(--spacing-5);width:min(350px,100%);padding:var(--spacing-6);border:1px solid var(--card-stroke);border-radius:var(--card-radius);background:var(--surface-card);box-shadow:var(--card-shadow);overscroll-behavior:contain;scrollbar-width:thin;scrollbar-color:var(--scrollbar-thumb) transparent;flex-direction:column;display:flex;overflow-y:auto}.citation-popover__source[data-v-c713f6b3]{align-items:flex-start;gap:var(--spacing-2);flex-shrink:0;display:flex}.citation-popover__thumb[data-v-c713f6b3]{width:64px;height:72px;margin-right:var(--spacing-2);border:1px solid var(--card-stroke);border-radius:var(--r-lg);object-fit:cover;object-position:top center;flex-shrink:0;display:block}.citation-popover__badge[data-v-c713f6b3]{border-radius:var(--r-md);background:var(--citation-bg);min-width:20px;height:20px;color:var(--accent-fg);font-family:var(--font-body);font-size:var(--t-xs);font-weight:var(--fw-bold);flex-shrink:0;place-items:center;padding:0 5px;display:grid}.citation-popover__source-text[data-v-c713f6b3]{gap:var(--spacing-1);flex-direction:column;min-width:0;display:flex}.citation-popover__title[data-v-c713f6b3]{color:var(--text-primary);font-family:var(--font-body);font-size:var(--t-sm);font-weight:var(--fw-bold);overflow-wrap:break-word}.citation-popover__page[data-v-c713f6b3]{color:var(--text-helper);font-family:var(--font-body);font-size:var(--t-sm)}.citation-popover__passage-block[data-v-c713f6b3]{gap:var(--spacing-2);flex-direction:column;flex:1;min-height:0;display:flex}.citation-popover__passage-label[data-v-c713f6b3]{color:var(--text-helper);font-family:var(--font-label);font-size:var(--t-xs);font-weight:var(--fw-bold);text-transform:uppercase}.citation-popover__passage[data-v-c713f6b3]{padding-left:var(--spacing-6);border-left:2px solid var(--accent);color:var(--text-primary);font-family:var(--font-body);font-size:var(--t-sm);line-height:var(--lh-body);overscroll-behavior:contain;scrollbar-width:thin;scrollbar-color:var(--scrollbar-thumb) transparent;min-height:0;margin:0;overflow-y:auto}.citation-popover__notice[data-v-c713f6b3]{color:var(--text-helper);font-family:var(--font-body);font-size:var(--t-xs);line-height:var(--lh-body);white-space:pre-wrap}.citation-popover__footer[data-v-c713f6b3]{gap:var(--spacing-4);flex-shrink:0;display:flex}.citation-popover__copy[data-v-c713f6b3],.citation-popover__open[data-v-c713f6b3]{justify-content:center;align-items:center;gap:var(--spacing-2);height:35px;padding:0 var(--spacing-4);border-radius:var(--r-md);font-family:var(--font-ui);font-size:var(--t-sm);font-weight:var(--fw-bold);cursor:pointer;flex:1;display:flex}.citation-popover__copy[data-v-c713f6b3]{border:1px solid var(--card-stroke);color:var(--text-primary);background:0 0}.citation-popover__open[data-v-c713f6b3]{background:var(--accent);color:var(--accent-fg);border:none;text-decoration:none}.feedback[data-v-6aa14982]{justify-content:flex-end;align-items:center;gap:var(--spacing-4);width:100%;display:flex}.feedback__question[data-v-6aa14982]{color:var(--text-helper);font-family:var(--font-label);font-size:var(--t-xs);font-weight:var(--fw-bold);text-transform:uppercase}.feedback__thumbs[data-v-6aa14982]{gap:var(--spacing-4);display:flex}.feedback__thumb[data-v-6aa14982]{color:var(--text-helper);cursor:pointer;background:0 0;border:none;place-items:center;padding:2px;display:grid}.feedback__thumb[data-v-6aa14982]:hover{color:var(--text-primary)}.feedback__thumb.is-active[data-v-6aa14982]{color:var(--accent)}.page-image-modal[data-v-33820e23]{z-index:200;padding:var(--spacing-6);background:var(--overlay-scrim);place-items:center;display:grid;position:fixed;inset:0}.page-image-modal__card[data-v-33820e23]{gap:var(--spacing-4);max-width:min(720px,100%);max-height:100%;padding:var(--spacing-6);border:1px solid var(--card-stroke);border-radius:var(--card-radius);background:var(--surface-card);box-shadow:var(--card-shadow);flex-direction:column;display:flex}.page-image-modal__header[data-v-33820e23]{justify-content:space-between;align-items:flex-start;gap:var(--spacing-4);display:flex}.page-image-modal__title[data-v-33820e23]{color:var(--text-primary);font-family:var(--font-body);font-size:var(--t-sm);font-weight:var(--fw-bold);overflow-wrap:break-word}.page-image-modal__detail[data-v-33820e23]{color:var(--text-helper);font-family:var(--font-body);font-size:var(--t-sm)}.page-image-modal__close[data-v-33820e23]{padding:var(--spacing-1) var(--spacing-3);border:1px solid var(--card-stroke);border-radius:var(--r-md);color:var(--text-primary);font-family:var(--font-ui);font-size:var(--t-sm);font-weight:var(--fw-bold);cursor:pointer;background:0 0}.page-image-modal__close[data-v-33820e23]:hover{background:var(--surface-hover)}.page-image-modal__image[data-v-33820e23]{border:1px solid var(--card-stroke);border-radius:var(--r-lg);object-fit:contain;min-height:0;overflow-y:auto}.source-card[data-v-49b414a6]{gap:var(--spacing-4);padding:var(--spacing-6);border:1px solid var(--card-stroke);border-radius:var(--r-md);background:var(--surface-raised);flex-direction:column;display:flex;position:relative;container:source-card/inline-size}.source-card__front[data-v-49b414a6]{gap:var(--spacing-4);flex-direction:column;width:100%;display:flex}.source-card__details[data-v-49b414a6]{gap:var(--spacing-5);flex-direction:column;display:flex}@container source-card (width>=420px){.source-card__details[data-v-49b414a6]{flex-direction:row;align-items:center}}.source-card__toggle[data-v-49b414a6]{top:var(--spacing-6);right:var(--spacing-6);border-radius:var(--r-sm);width:32px;height:32px;color:var(--text-secondary);cursor:pointer;background:0 0;border:none;place-items:center;padding:0;transition:transform .3s;display:grid;position:absolute}.source-card__toggle[data-v-49b414a6]:hover{background:var(--surface-hover);color:var(--text-primary)}.source-card__toggle--open[data-v-49b414a6]{transform:rotate(180deg)}.source-card__cover[data-v-49b414a6]{object-fit:contain;flex-shrink:0;width:150px;height:150px}.source-card__naming[data-v-49b414a6]{align-items:flex-start;gap:var(--spacing-5);flex-direction:column;min-width:0;display:flex}.source-card__heading[data-v-49b414a6]{color:var(--text-helper);font-family:var(--font-label);font-size:var(--t-xs);font-weight:var(--fw-bold);text-transform:uppercase}.source-card__title[data-v-49b414a6]{color:var(--text-primary);font-family:var(--font-display);font-size:var(--t-2xl);font-weight:var(--fw-regular);text-align:start;overflow-wrap:break-word}.source-card__link[data-v-49b414a6]{align-items:center;gap:var(--spacing-1);color:var(--accent);font-family:var(--font-ui);font-size:var(--t-xs);font-weight:var(--fw-bold);align-self:flex-start;display:inline-flex}.source-card__body[data-v-49b414a6]{gap:var(--spacing-4);padding-top:var(--spacing-4);border-top:1px solid var(--card-stroke);flex-direction:column;display:flex}.source-card__reference-heading[data-v-49b414a6]{color:var(--text-helper);font-family:var(--font-label);font-size:var(--t-xs);font-weight:var(--fw-bold);text-transform:uppercase}.source-card__reference[data-v-49b414a6]{gap:var(--spacing-5);text-align:left;flex-direction:column;width:100%;display:flex}@container source-card (width>=420px){.source-card__reference[data-v-49b414a6]{flex-direction:row;align-items:flex-start}}.source-card__page-thumb[data-v-49b414a6]{object-fit:contain;cursor:pointer;flex-shrink:0;width:150px;height:150px}.source-card__reference-text[data-v-49b414a6]{gap:var(--spacing-4);flex-direction:column;width:100%;min-width:0;display:flex}.source-card__page-line[data-v-49b414a6]{color:var(--text-primary);font-family:var(--font-label);font-size:var(--t-xs);font-weight:var(--fw-bold);text-transform:uppercase;letter-spacing:.12px}.source-card__passage[data-v-49b414a6]{color:var(--text-secondary);font-family:var(--font-body);font-size:var(--t-sm);line-height:var(--lh-body);overflow-wrap:break-word}.source-list[data-v-124ee2ae]{gap:var(--spacing-3);flex-direction:column;display:flex}.source-list__intro[data-v-124ee2ae]{color:var(--text-primary);font-family:var(--font-body);font-size:var(--t-sm);font-weight:var(--fw-bold)}.source-list__notice[data-v-124ee2ae]{color:var(--text-helper);font-family:var(--font-body);font-size:var(--t-xs);line-height:var(--lh-body);white-space:pre-wrap}@property --thinking-arc{syntax:"<angle>";inherits:false;initial-value:121deg}.thinking__spinner[data-v-a73d8186]{border-radius:var(--r-pill);background:var(--accent);width:26px;height:26px;-webkit-mask:conic-gradient(from 90deg, transparent 0deg, #000 var(--thinking-arc), transparent calc(var(--thinking-arc) + 1deg)), radial-gradient(farthest-side, transparent calc(100% - 6px), #000 calc(100% - 6px));mask:conic-gradient(from 90deg, transparent 0deg, #000 var(--thinking-arc), transparent calc(var(--thinking-arc) + 1deg)), radial-gradient(farthest-side, transparent calc(100% - 6px), #000 calc(100% - 6px));flex:none;animation:1s linear infinite thinking-spin-a73d8186,1.8s ease-in-out infinite thinking-chase-a73d8186;-webkit-mask-composite:source-in;mask-composite:intersect}@keyframes thinking-spin-a73d8186{to{transform:rotate(1turn)}}@keyframes thinking-chase-a73d8186{0%{--thinking-arc:40deg;animation-timing-function:ease-in-out}40%{--thinking-arc:247deg;animation-timing-function:linear}to{--thinking-arc:40deg}}.thinking__sparkles[data-v-a73d8186]{width:32px;height:32px;color:var(--accent);flex:none;position:relative}.thinking__dots[data-v-a73d8186]{align-items:center;gap:var(--spacing-1);flex:none;display:flex}.thinking__dot[data-v-a73d8186]{border-radius:var(--r-pill);background:var(--accent);width:6px;height:6px;animation:1.2s ease-in-out infinite both thinking-dot-a73d8186}.thinking__dot[data-v-a73d8186]:nth-child(2){animation-delay:.15s}.thinking__dot[data-v-a73d8186]:nth-child(3){animation-delay:.3s}@keyframes thinking-dot-a73d8186{0%,60%,to{opacity:.4;transform:translateY(0)}30%{opacity:1;transform:translateY(-4px)}}.thinking__sparkle[data-v-a73d8186]{animation:1.5s linear infinite both thinking-sparkle-a73d8186;position:absolute}.thinking__sparkle[data-v-a73d8186]:first-child{width:18px;height:18px;top:7px;left:6px}.thinking__sparkle[data-v-a73d8186]:nth-child(2){width:11px;height:11px;animation-delay:-.5s;top:1.7px;left:19.3px}.thinking__sparkle[data-v-a73d8186]:nth-child(3){width:8px;height:8px;animation-delay:-1s;top:20.3px;left:3.7px}@keyframes thinking-sparkle-a73d8186{0%{opacity:0;transform:translate(-9px,-9px)scale(0)}35%{opacity:1;transform:translate(-2.7px,-2.7px)scale(1)}65%{opacity:1;transform:translate(2.7px,2.7px)scale(1)}to{opacity:0;transform:translate(9px,9px)scale(0)}}.thinking-process[data-v-3dcefa4f]{gap:var(--spacing-2);flex-direction:column;width:100%;display:flex}.thinking-process__toggle[data-v-3dcefa4f]{color:var(--text-helper);font-family:var(--font-label);font-size:var(--t-xs);font-weight:var(--fw-bold);text-transform:uppercase;cursor:pointer;background:0 0;border:none;align-self:flex-start;padding:0}.thinking-process__toggle[data-v-3dcefa4f]:hover{color:var(--text-primary)}.thinking-process__body[data-v-3dcefa4f]{max-height:220px;padding:var(--spacing-3) var(--spacing-4);border-radius:var(--r-lg);background:var(--surface-raised);color:var(--text-secondary);font-family:var(--font-body);font-size:var(--t-sm);line-height:var(--lh-body);white-space:pre-wrap;overflow-wrap:break-word;scrollbar-width:thin;scrollbar-color:var(--scrollbar-thumb) transparent;overflow-y:auto}.assistant-message[data-v-466b7413]{gap:var(--spacing-2);flex-direction:column;width:100%;display:flex}.assistant-message__footer[data-v-466b7413]{justify-content:space-between;align-items:center;gap:var(--spacing-4);display:flex}.assistant-message__actions[data-v-466b7413]{align-items:center;gap:var(--spacing-2);flex:none;display:flex}.assistant-message__header[data-v-466b7413]{align-items:center;gap:var(--spacing-4);display:flex}.assistant-message__avatar[data-v-466b7413]{border-radius:var(--r-pill);background:var(--accent);width:32px;height:32px;color:var(--accent-fg);flex-shrink:0;place-items:center;display:grid}.assistant-message__name[data-v-466b7413]{color:var(--text-primary);font-family:var(--font-body);font-size:var(--t-base);font-weight:var(--fw-bold)}.assistant-message__body-wrap[data-v-466b7413]{width:100%}.assistant-message__popover[data-v-466b7413]{z-index:100;position:fixed}.assistant-message__body[data-v-466b7413] .citation-chip{min-width:20px;height:20px;margin-left:var(--spacing-1);border-radius:var(--r-md);background:var(--citation-bg);color:var(--accent-fg);font-size:var(--t-xs);font-weight:var(--fw-bold);vertical-align:text-bottom;cursor:pointer;-webkit-user-select:none;user-select:none;place-items:center;padding:0 5px;display:inline-grid}.assistant-message__body[data-v-466b7413] .citation-chip:not(.citation-chip--pending):hover{opacity:.7}.assistant-message__body[data-v-466b7413] .citation-chip--pending{cursor:default;opacity:.55}.assistant-message__body[data-v-466b7413]{color:var(--text-primary);font-family:var(--font-body);font-size:var(--t-base);font-weight:var(--fw-regular);line-height:var(--lh-body);overflow-wrap:break-word}.assistant-message__body[data-v-466b7413] p,.assistant-message__body[data-v-466b7413] ul,.assistant-message__body[data-v-466b7413] ol,.assistant-message__body[data-v-466b7413] pre,.assistant-message__body[data-v-466b7413] table,.assistant-message__body[data-v-466b7413] blockquote{margin-block:0 var(--spacing-4)}.assistant-message__body[data-v-466b7413] ul,.assistant-message__body[data-v-466b7413] ol{padding-inline-start:var(--spacing-6)}.assistant-message__body[data-v-466b7413] a{color:var(--accent)}.assistant-message__body[data-v-466b7413] .chat-math{max-width:100%;overflow-x:auto}.assistant-message__body[data-v-466b7413] .chat-math--display{margin-block:var(--spacing-4);text-align:center;display:block}.assistant-message__body[data-v-466b7413] code{font-family:var(--font-mono);font-size:var(--t-sm);background:var(--surface-raised);border-radius:var(--r-sm);padding:1px 5px}.assistant-message__body[data-v-466b7413] pre{background:var(--surface-raised);padding:var(--spacing-4);border-radius:var(--r-lg);overflow-x:auto}.assistant-message__body[data-v-466b7413] blockquote{padding:var(--spacing-3) var(--spacing-4);border-left:3px solid var(--accent);border-radius:0 var(--r-md) var(--r-md) 0;background:var(--surface-raised)}.assistant-message__body[data-v-466b7413] blockquote>:last-child{margin-bottom:0}.assistant-message__body[data-v-466b7413] table{border-collapse:collapse;max-width:100%;display:block;overflow-x:auto}.assistant-message__body[data-v-466b7413] th,.assistant-message__body[data-v-466b7413] td{padding:var(--spacing-2) var(--spacing-3);border:1px solid var(--card-stroke);text-align:left;vertical-align:top}.assistant-message__body[data-v-466b7413] th{background:var(--surface-raised);font-weight:var(--fw-bold)}.assistant-message__out-of-scope[data-v-466b7413]{align-self:flex-start;align-items:center;gap:var(--spacing-2);padding:var(--spacing-3);border:1px solid var(--accent);border-radius:var(--r-sm);background:color-mix(in srgb, var(--accent) 15%, transparent);color:var(--accent);font-family:var(--font-body);font-size:var(--t-xs);font-weight:var(--fw-bold);letter-spacing:.6px;text-transform:uppercase;display:flex}.assistant-message__out-of-scope+.assistant-message__body[data-v-466b7413]{margin-top:var(--spacing-2)}.assistant-message__error[data-v-466b7413]{align-items:center;gap:var(--spacing-4);color:var(--warning);font-family:var(--font-body);font-size:var(--t-sm);display:flex}.assistant-message__retry[data-v-466b7413]{padding:var(--spacing-1) var(--spacing-3);border:1px solid var(--card-stroke);border-radius:var(--r-md);color:var(--text-primary);font-family:var(--font-ui);font-size:var(--t-sm);font-weight:var(--fw-bold);cursor:pointer;background:0 0}.assistant-message__retry[data-v-466b7413]:hover{background:var(--surface-hover)}.thinking[data-v-c804834e]{align-items:center;gap:var(--spacing-4);color:var(--text-secondary);font-family:var(--font-body);font-size:var(--t-base);display:flex}.user-message[data-v-2eb946f2]{justify-content:flex-end;width:100%;display:flex}.user-message__bubble[data-v-2eb946f2]{max-width:70%;padding:var(--spacing-4);border-radius:var(--card-radius);background:var(--user-bubble-bg);color:var(--user-bubble-fg);font-family:var(--font-body);font-size:var(--t-base);font-weight:var(--fw-regular);white-space:pre-wrap;overflow-wrap:break-word;line-height:normal}.message-list[data-v-2f494fbc]{gap:var(--spacing-5);min-height:0;padding:var(--spacing-6);scrollbar-width:thin;scrollbar-color:var(--scrollbar-thumb) transparent;flex-direction:column;flex:1;display:flex;overflow-y:auto}.message-list[data-v-2f494fbc]::-webkit-scrollbar{width:10px}.message-list[data-v-2f494fbc]::-webkit-scrollbar-track{background:0 0}.message-list[data-v-2f494fbc]::-webkit-scrollbar-thumb{background:var(--scrollbar-thumb);border-radius:var(--r-pill);background-clip:padding-box;border:3px solid #0000}.message-list[data-v-2f494fbc]::-webkit-scrollbar-thumb:hover{background:var(--scrollbar-thumb-hover);background-clip:padding-box}.message-list__welcome-eyebrow[data-v-2f494fbc]{color:var(--accent);font-family:var(--font-label);font-size:var(--t-xs);font-weight:var(--fw-bold);text-transform:uppercase;margin-bottom:var(--spacing-3)}.message-list__welcome-heading[data-v-2f494fbc]{color:var(--text-primary);font-family:var(--font-display);font-size:var(--t-2xl);font-weight:var(--fw-bold);letter-spacing:var(--tr-tight);margin-bottom:var(--spacing-3)}.message-list__welcome-body[data-v-2f494fbc]{color:var(--text-primary);font-family:var(--font-body);font-size:var(--t-base);line-height:var(--lh-body)}.message-list__error[data-v-2f494fbc]{align-items:center;gap:var(--spacing-4);color:var(--warning);font-family:var(--font-body);font-size:var(--t-sm);display:flex}.message-list__retry[data-v-2f494fbc]{padding:var(--spacing-1) var(--spacing-3);border:1px solid var(--card-stroke);border-radius:var(--r-md);color:var(--text-primary);font-family:var(--font-ui);font-size:var(--t-sm);font-weight:var(--fw-bold);cursor:pointer;background:0 0}.message-list__retry[data-v-2f494fbc]:hover{background:var(--surface-hover)}.chat[data-v-7ad4b6f4]{flex-direction:column;flex:1;min-height:0;display:flex}
package/dist/index.d.ts CHANGED
@@ -12,6 +12,7 @@ export * from './services/markdown';
12
12
  export * from './services/entities';
13
13
  export type { MathOptions, MathSetting } from './services/math';
14
14
  export { default as StreamingMessage } from './services/streaming-message';
15
+ export { default as Typewriter, type TypewriterOptions, type TypewriterSetting } from './services/typewriter';
15
16
  export * from './composables/useChatEngine';
16
17
  export * from './testing/mock-transport';
17
18
  export { default as Chat } from './components/Chat.vue';
@@ -3,7 +3,7 @@
3
3
  * Base Camp's Zodios API definitions (the authoritative OpenAPI contract).
4
4
  * No retries and no error swallowing; callers decide how to recover.
5
5
  */
6
- import type { AgentDetails, CreateThreadBody, CreateThreadResponse, FeedbackResponse, MessageListResponse, QuotaResponse, SendMessageBody, StopMessageResponse, Thread, ThreadDetail, TrackEventBody, TrackEventResponse } from '../types';
6
+ import type { AgentDetails, CreateThreadBody, CreateThreadResponse, FeedbackResponse, MessageListResponse, QuotaResponse, SendMessageBody, StopMessageResponse, Thread, ThreadDetail, TrackEventBody, TrackEventResponse, UpdateThreadBody } from '../types';
7
7
  export interface BaseCampClientOptions {
8
8
  /** Base Camp REST host, without a trailing slash; request paths start with /v1. */
9
9
  baseUrl: string;
@@ -36,6 +36,12 @@ export declare class BaseCampClient {
36
36
  * server only reads customData on thread update.
37
37
  */
38
38
  createThread(agentId: string, body?: CreateThreadBody): Promise<CreateThreadResponse>;
39
+ /**
40
+ * PATCH /v1/agents/:agentId/threads/:threadId. The way to store an app's own
41
+ * data on a thread after it exists; Base Camp keeps customData verbatim and
42
+ * hands it back with the thread's messages.
43
+ */
44
+ updateThread(agentId: string, threadId: string, body: UpdateThreadBody): Promise<Thread>;
39
45
  /** GET /v1/agents/:agentId/threads (current user's threads for the agent). */
40
46
  /** GET /v1/agents/:agentId/threads?limit=N. Every status comes back; callers filter. */
41
47
  getThreads(agentId: string, limit: number): Promise<Thread[]>;
@@ -4,15 +4,27 @@ export interface InjectCitationChipsOptions {
4
4
  * Chips markers whose citations have not arrived yet, as inert placeholders.
5
5
  * The citations payload only arrives once the text finishes, so without this
6
6
  * a streaming answer shows raw `[^N]` text that snaps into chips at the very
7
- * end. Placeholders upgrade to real chips when their citation lands; on by
8
- * the caller only while streaming, so a truly unresolved marker still ends
9
- * up as literal text.
7
+ * end. Placeholders upgrade to real chips when their citation lands.
8
+ *
9
+ * The caller turns this on while streaming. It is not needed for a stopped
10
+ * answer, whose markers are recognised from the payload never arriving; see
11
+ * below.
10
12
  */
11
13
  pendingMarkers?: boolean;
12
14
  }
13
15
  /**
14
16
  * Replaces resolved `[^N]` markers in `html` with citation chips. A marker
15
- * with no matching citation is left as literal text, so a hallucinated ref
16
- * never becomes a dead chip.
17
+ * with no matching citation never becomes a dead chip, and never stays as raw
18
+ * markdown either, since that reads as the page having failed to render. What
19
+ * it becomes depends on why it is unresolved:
20
+ *
21
+ * - The payload has not arrived, so a source really was being cited and is
22
+ * still coming or never will. The marker holds a placeholder, which is what
23
+ * a reader who stopped an answer partway should see.
24
+ * - The payload arrived without this ref, so the model cited something that
25
+ * does not exist. The marker is dropped rather than promise a source.
26
+ *
27
+ * This reads `html` and returns a new string without changing it, so a
28
+ * citation that lands late still upgrades its marker on the next pass.
17
29
  */
18
30
  export declare function injectCitationChips(html: string, citations: ChatCitation[], options?: InjectCitationChipsOptions): string;
package/dist/types.d.ts CHANGED
@@ -89,6 +89,10 @@ export interface CreateThreadBody {
89
89
  variables?: ThreadVariables;
90
90
  customData?: ThreadCustomData;
91
91
  }
92
+ /** PATCH /v1/agents/:agentId/threads/:threadId body. Base Camp merges customData into what is stored. */
93
+ export interface UpdateThreadBody {
94
+ customData?: ThreadCustomData;
95
+ }
92
96
  export interface CreateThreadResponse {
93
97
  id: string;
94
98
  agentId: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@twentyfourg/chat-kit",
3
- "version": "1.0.0-beta.1",
3
+ "version": "1.0.0-beta.2",
4
4
  "description": "Base Camp chat frontend for Vue 3: the conversation and composer components, a typed client, SSE streaming, sanitized markdown, and a copy and theme contract.",
5
5
  "license": "MIT",
6
6
  "type": "module",