@twentyfourg/chat-kit 1.0.0-beta.1
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/LICENSE +21 -0
- package/README.md +222 -0
- package/dist/chat-kit.js +1914 -0
- package/dist/chat-kit.js.map +1 -0
- package/dist/components/AssistantMessage.vue.d.ts +35 -0
- package/dist/components/Chat.vue.d.ts +72 -0
- package/dist/components/CitationPopover.vue.d.ts +11 -0
- package/dist/components/FeedbackRow.vue.d.ts +11 -0
- package/dist/components/IconButton.vue.d.ts +17 -0
- package/dist/components/InputBar.vue.d.ts +16 -0
- package/dist/components/KitIcon.vue.d.ts +12 -0
- package/dist/components/MessageList.vue.d.ts +36 -0
- package/dist/components/PageImageModal.vue.d.ts +14 -0
- package/dist/components/SourceCard.vue.d.ts +21 -0
- package/dist/components/SourceList.vue.d.ts +7 -0
- package/dist/components/ThinkingAnimation.vue.d.ts +15 -0
- package/dist/components/ThinkingIndicator.vue.d.ts +13 -0
- package/dist/components/ThinkingProcess.vue.d.ts +8 -0
- package/dist/components/UserMessage.vue.d.ts +7 -0
- package/dist/composables/useAutoScroll.d.ts +36 -0
- package/dist/composables/useChatEngine.d.ts +335 -0
- package/dist/composables/useDictation.d.ts +29 -0
- package/dist/copy.d.ts +144 -0
- package/dist/index.css +1 -0
- package/dist/index.d.ts +18 -0
- package/dist/services/anonymous-auth.d.ts +63 -0
- package/dist/services/base-camp-client.d.ts +66 -0
- package/dist/services/citations.d.ts +18 -0
- package/dist/services/entities.d.ts +29 -0
- package/dist/services/markdown.d.ts +47 -0
- package/dist/services/math.d.ts +36 -0
- package/dist/services/streaming-message.d.ts +32 -0
- package/dist/services/streaming.service.d.ts +84 -0
- package/dist/services/typewriter.d.ts +54 -0
- package/dist/testing/mock-transport.d.ts +52 -0
- package/dist/testing/setup-tests.d.ts +8 -0
- package/dist/theme.css +122 -0
- package/dist/types.d.ts +389 -0
- package/package.json +76 -0
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
import { type TypewriterSetting } from '../services/typewriter';
|
|
2
|
+
import type { MathSetting } from '../services/math';
|
|
3
|
+
import type { AgentDetails, ChatCitation, ChatEvent, ChatInputMode, CreateThreadBody, CreateThreadResponse, FeedbackResponse, MessageFeedback, MessageListResponse, QuotaResponse, SendMessageBody, StopMessageResponse, Thread, UsedSource } from '../types';
|
|
4
|
+
/**
|
|
5
|
+
* What the engine needs from a chat backend. The real Base Camp client
|
|
6
|
+
* satisfies this, and so does the mock used for tests and local development.
|
|
7
|
+
*/
|
|
8
|
+
export interface ChatTransport {
|
|
9
|
+
getAgent(agentId: string): Promise<AgentDetails>;
|
|
10
|
+
createThread(agentId: string, body?: CreateThreadBody): Promise<CreateThreadResponse>;
|
|
11
|
+
sendMessage(agentId: string, threadId: string, body: SendMessageBody, options?: {
|
|
12
|
+
signal?: AbortSignal;
|
|
13
|
+
}): Promise<Response>;
|
|
14
|
+
stopMessage(agentId: string, threadId: string, assistantMessageId: string): Promise<StopMessageResponse>;
|
|
15
|
+
submitFeedback(agentId: string, threadId: string, messageId: string, feedback: MessageFeedback): Promise<FeedbackResponse>;
|
|
16
|
+
clearFeedback(agentId: string, threadId: string, messageId: string): Promise<FeedbackResponse>;
|
|
17
|
+
/**
|
|
18
|
+
* The two history methods are optional so a minimal transport stays easy to
|
|
19
|
+
* write. The real Base Camp client implements both; the engine's listThreads
|
|
20
|
+
* and loadThread refuse clearly when a transport leaves them out.
|
|
21
|
+
*
|
|
22
|
+
* getThreads returns at most `limit` threads, newest first, in whatever
|
|
23
|
+
* status they are in; the engine does the filtering.
|
|
24
|
+
*/
|
|
25
|
+
getThreads?(agentId: string, limit: number): Promise<Thread[]>;
|
|
26
|
+
getMessages?(agentId: string, threadId: string): Promise<MessageListResponse>;
|
|
27
|
+
/**
|
|
28
|
+
* The sender's remaining message allowance. Optional like the history
|
|
29
|
+
* methods, and read on the engine's own initiative rather than on request,
|
|
30
|
+
* so a transport without it leaves `quota` null instead of refusing.
|
|
31
|
+
*/
|
|
32
|
+
getQuota?(agentId: string): Promise<QuotaResponse>;
|
|
33
|
+
}
|
|
34
|
+
/** One message as the components render it. */
|
|
35
|
+
export interface ChatEngineMessage {
|
|
36
|
+
/** Local id, stable for the life of the conversation. */
|
|
37
|
+
id: string;
|
|
38
|
+
role: 'user' | 'assistant';
|
|
39
|
+
/** The raw text. For assistant messages it grows while streaming. */
|
|
40
|
+
text: string;
|
|
41
|
+
/** Rendered, sanitized HTML for assistant messages. */
|
|
42
|
+
html: string;
|
|
43
|
+
/** The model's reasoning for this reply; grows while it streams. Empty for user messages. */
|
|
44
|
+
thinking: string;
|
|
45
|
+
basecampMessageId?: string;
|
|
46
|
+
sources: UsedSource[];
|
|
47
|
+
/** Passage-level inline citations; stays empty for prose-mode agents. */
|
|
48
|
+
citations: ChatCitation[];
|
|
49
|
+
feedback: MessageFeedback | null;
|
|
50
|
+
isOutOfScope: boolean;
|
|
51
|
+
isStreaming: boolean;
|
|
52
|
+
/**
|
|
53
|
+
* True while the server is still sending this reply.
|
|
54
|
+
*
|
|
55
|
+
* Goes false when the stream closes, which is when citations land and their
|
|
56
|
+
* popovers start working. That is earlier than `isStreaming`, because the
|
|
57
|
+
* reveal carries on typing out text that has already arrived, so this is
|
|
58
|
+
* the one to watch for "is the server still working".
|
|
59
|
+
*/
|
|
60
|
+
isReceiving: boolean;
|
|
61
|
+
failed: boolean;
|
|
62
|
+
/** How the user entered this message (typed or voice); only user messages set it. */
|
|
63
|
+
inputMode?: ChatInputMode;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* What the engine needs to exist at all: which agent, reached how. Not
|
|
67
|
+
* settings, and there are no defaults to fall back on.
|
|
68
|
+
*/
|
|
69
|
+
export interface ChatEngineConnection {
|
|
70
|
+
transport: ChatTransport;
|
|
71
|
+
agentId: string;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Everything an app may adjust. All optional, and every default is the
|
|
75
|
+
* behaviour the kit had before the setting existed, so an engine built from
|
|
76
|
+
* the connection alone is a working chat.
|
|
77
|
+
*/
|
|
78
|
+
export interface ChatEngineSettings {
|
|
79
|
+
/**
|
|
80
|
+
* Reveals answers a few characters at a time instead of in raw network
|
|
81
|
+
* bursts. On unless explicitly turned off. Pass { speed } to retune the
|
|
82
|
+
* pace for an app: 2 types twice as fast as the default, 0.5 at half.
|
|
83
|
+
*/
|
|
84
|
+
typewriter?: TypewriterSetting;
|
|
85
|
+
/**
|
|
86
|
+
* Decides whether a finished answer is the assistant declining a question it
|
|
87
|
+
* cannot help with. Those replies get the out-of-scope marking and no thumbs
|
|
88
|
+
* rating. Apps that recognise a set decline message match against it here.
|
|
89
|
+
*/
|
|
90
|
+
isOutOfScope?: (text: string) => boolean;
|
|
91
|
+
/**
|
|
92
|
+
* Renders `$...$` and `$$...$$` in answers as formulas. Needs the optional
|
|
93
|
+
* `mathlive` peer dependency (`pnpm add mathlive`), which is loaded on the
|
|
94
|
+
* first answer that contains any. Leave off unless the corpus has math: `$`
|
|
95
|
+
* is also a currency symbol, and prices should stay prices.
|
|
96
|
+
*
|
|
97
|
+
* `true` uses MathLive's defaults; pass `{ fontsDirectory }` to say where
|
|
98
|
+
* its font files are served from.
|
|
99
|
+
*/
|
|
100
|
+
showMath?: MathSetting;
|
|
101
|
+
/**
|
|
102
|
+
* Called when something the app may want to record happens: a turn
|
|
103
|
+
* finishes, a thumbs rating is saved, a citation is opened. Listening
|
|
104
|
+
* never changes how the chat behaves, even if the listener throws.
|
|
105
|
+
*/
|
|
106
|
+
onEvent?: (event: ChatEvent) => void;
|
|
107
|
+
}
|
|
108
|
+
/** The two halves together, as useChatEngine takes them. */
|
|
109
|
+
export type ChatEngineOptions = ChatEngineConnection & ChatEngineSettings;
|
|
110
|
+
/** One page of a history rail, from listThreads. */
|
|
111
|
+
export interface ThreadPage {
|
|
112
|
+
/** Open threads only, newest first. */
|
|
113
|
+
threads: Thread[];
|
|
114
|
+
/** True when the page came back full, so asking for more may find more. */
|
|
115
|
+
mightHaveMore: boolean;
|
|
116
|
+
}
|
|
117
|
+
export type ChatStatus = 'idle' | 'thinking' | 'streaming';
|
|
118
|
+
export declare function useChatEngine(options: ChatEngineOptions): {
|
|
119
|
+
agent: Readonly<import("vue").Ref<{
|
|
120
|
+
readonly id: string;
|
|
121
|
+
readonly name: string;
|
|
122
|
+
readonly description: string;
|
|
123
|
+
readonly firstMessage: string;
|
|
124
|
+
readonly config?: {
|
|
125
|
+
readonly showThinkingProcess: boolean;
|
|
126
|
+
readonly showThinkingProcessButton: boolean;
|
|
127
|
+
readonly enableVoiceInteraction?: boolean | undefined;
|
|
128
|
+
readonly showExpertLocker: boolean;
|
|
129
|
+
readonly showKnowledgeSources: boolean;
|
|
130
|
+
readonly showSourcesButton: boolean;
|
|
131
|
+
readonly defaultLockerViewThisExpertOnly: boolean;
|
|
132
|
+
readonly showTokenUsageIndicator: boolean;
|
|
133
|
+
readonly chatTokenLimit: number;
|
|
134
|
+
readonly conversationType: import("..").ConversationType;
|
|
135
|
+
} | undefined;
|
|
136
|
+
readonly variableNames?: readonly string[] | undefined;
|
|
137
|
+
readonly knowledgeSource: readonly {
|
|
138
|
+
readonly type: import("..").KnowledgeSourceKind;
|
|
139
|
+
readonly uniqueExternalId: string | null;
|
|
140
|
+
readonly baseCampId: string | null;
|
|
141
|
+
}[];
|
|
142
|
+
} | null, {
|
|
143
|
+
readonly id: string;
|
|
144
|
+
readonly name: string;
|
|
145
|
+
readonly description: string;
|
|
146
|
+
readonly firstMessage: string;
|
|
147
|
+
readonly config?: {
|
|
148
|
+
readonly showThinkingProcess: boolean;
|
|
149
|
+
readonly showThinkingProcessButton: boolean;
|
|
150
|
+
readonly enableVoiceInteraction?: boolean | undefined;
|
|
151
|
+
readonly showExpertLocker: boolean;
|
|
152
|
+
readonly showKnowledgeSources: boolean;
|
|
153
|
+
readonly showSourcesButton: boolean;
|
|
154
|
+
readonly defaultLockerViewThisExpertOnly: boolean;
|
|
155
|
+
readonly showTokenUsageIndicator: boolean;
|
|
156
|
+
readonly chatTokenLimit: number;
|
|
157
|
+
readonly conversationType: import("..").ConversationType;
|
|
158
|
+
} | undefined;
|
|
159
|
+
readonly variableNames?: readonly string[] | undefined;
|
|
160
|
+
readonly knowledgeSource: readonly {
|
|
161
|
+
readonly type: import("..").KnowledgeSourceKind;
|
|
162
|
+
readonly uniqueExternalId: string | null;
|
|
163
|
+
readonly baseCampId: string | null;
|
|
164
|
+
}[];
|
|
165
|
+
} | null>>;
|
|
166
|
+
agentReady: Promise<AgentDetails | null>;
|
|
167
|
+
threadId: Readonly<import("vue").Ref<string | null, string | null>>;
|
|
168
|
+
messages: import("vue").Ref<{
|
|
169
|
+
id: string;
|
|
170
|
+
role: "user" | "assistant";
|
|
171
|
+
text: string;
|
|
172
|
+
html: string;
|
|
173
|
+
thinking: string;
|
|
174
|
+
basecampMessageId?: string | undefined;
|
|
175
|
+
sources: {
|
|
176
|
+
entityId: string | null;
|
|
177
|
+
metadata?: {
|
|
178
|
+
[x: string]: unknown;
|
|
179
|
+
title?: string | undefined;
|
|
180
|
+
entityId?: string | undefined;
|
|
181
|
+
page?: number | undefined;
|
|
182
|
+
textPage?: number | undefined;
|
|
183
|
+
link?: string | undefined;
|
|
184
|
+
screenshotUrl?: string | undefined;
|
|
185
|
+
} | undefined;
|
|
186
|
+
screenshotUrl?: string | undefined;
|
|
187
|
+
results: {
|
|
188
|
+
index?: number | undefined;
|
|
189
|
+
content?: string | undefined;
|
|
190
|
+
score?: number | undefined;
|
|
191
|
+
metadata?: {
|
|
192
|
+
[x: string]: unknown;
|
|
193
|
+
title?: string | undefined;
|
|
194
|
+
entityId?: string | undefined;
|
|
195
|
+
page?: number | undefined;
|
|
196
|
+
textPage?: number | undefined;
|
|
197
|
+
link?: string | undefined;
|
|
198
|
+
screenshotUrl?: string | undefined;
|
|
199
|
+
} | undefined;
|
|
200
|
+
}[];
|
|
201
|
+
}[];
|
|
202
|
+
citations: {
|
|
203
|
+
ref: number;
|
|
204
|
+
label: number;
|
|
205
|
+
passage: string;
|
|
206
|
+
entityId?: string | undefined;
|
|
207
|
+
variationId?: string | undefined;
|
|
208
|
+
title?: string | undefined;
|
|
209
|
+
link?: string | undefined;
|
|
210
|
+
canonicalLink?: string | undefined;
|
|
211
|
+
sourceId?: string | undefined;
|
|
212
|
+
screenshotUrl?: string | undefined;
|
|
213
|
+
page?: number | undefined;
|
|
214
|
+
textPage?: number | undefined;
|
|
215
|
+
}[];
|
|
216
|
+
feedback: MessageFeedback | null;
|
|
217
|
+
isOutOfScope: boolean;
|
|
218
|
+
isStreaming: boolean;
|
|
219
|
+
isReceiving: boolean;
|
|
220
|
+
failed: boolean;
|
|
221
|
+
inputMode?: ChatInputMode | undefined;
|
|
222
|
+
}[], ChatEngineMessage[] | {
|
|
223
|
+
id: string;
|
|
224
|
+
role: "user" | "assistant";
|
|
225
|
+
text: string;
|
|
226
|
+
html: string;
|
|
227
|
+
thinking: string;
|
|
228
|
+
basecampMessageId?: string | undefined;
|
|
229
|
+
sources: {
|
|
230
|
+
entityId: string | null;
|
|
231
|
+
metadata?: {
|
|
232
|
+
[x: string]: unknown;
|
|
233
|
+
title?: string | undefined;
|
|
234
|
+
entityId?: string | undefined;
|
|
235
|
+
page?: number | undefined;
|
|
236
|
+
textPage?: number | undefined;
|
|
237
|
+
link?: string | undefined;
|
|
238
|
+
screenshotUrl?: string | undefined;
|
|
239
|
+
} | undefined;
|
|
240
|
+
screenshotUrl?: string | undefined;
|
|
241
|
+
results: {
|
|
242
|
+
index?: number | undefined;
|
|
243
|
+
content?: string | undefined;
|
|
244
|
+
score?: number | undefined;
|
|
245
|
+
metadata?: {
|
|
246
|
+
[x: string]: unknown;
|
|
247
|
+
title?: string | undefined;
|
|
248
|
+
entityId?: string | undefined;
|
|
249
|
+
page?: number | undefined;
|
|
250
|
+
textPage?: number | undefined;
|
|
251
|
+
link?: string | undefined;
|
|
252
|
+
screenshotUrl?: string | undefined;
|
|
253
|
+
} | undefined;
|
|
254
|
+
}[];
|
|
255
|
+
}[];
|
|
256
|
+
citations: {
|
|
257
|
+
ref: number;
|
|
258
|
+
label: number;
|
|
259
|
+
passage: string;
|
|
260
|
+
entityId?: string | undefined;
|
|
261
|
+
variationId?: string | undefined;
|
|
262
|
+
title?: string | undefined;
|
|
263
|
+
link?: string | undefined;
|
|
264
|
+
canonicalLink?: string | undefined;
|
|
265
|
+
sourceId?: string | undefined;
|
|
266
|
+
screenshotUrl?: string | undefined;
|
|
267
|
+
page?: number | undefined;
|
|
268
|
+
textPage?: number | undefined;
|
|
269
|
+
}[];
|
|
270
|
+
feedback: MessageFeedback | null;
|
|
271
|
+
isOutOfScope: boolean;
|
|
272
|
+
isStreaming: boolean;
|
|
273
|
+
isReceiving: boolean;
|
|
274
|
+
failed: boolean;
|
|
275
|
+
inputMode?: ChatInputMode | undefined;
|
|
276
|
+
}[]>;
|
|
277
|
+
status: Readonly<import("vue").Ref<ChatStatus, ChatStatus>>;
|
|
278
|
+
error: Readonly<import("vue").Ref<Error | null, Error | null>>;
|
|
279
|
+
errorKind: import("vue").ComputedRef<"error" | "rate-limited" | null>;
|
|
280
|
+
busy: import("vue").ComputedRef<boolean>;
|
|
281
|
+
tokenUsage: Readonly<import("vue").Ref<{
|
|
282
|
+
readonly cumulative: {
|
|
283
|
+
readonly totalInputTokens: number;
|
|
284
|
+
readonly totalOutputTokens: number;
|
|
285
|
+
readonly totalTokens: number;
|
|
286
|
+
readonly percentOfContextLimit: number;
|
|
287
|
+
readonly percentOfCumulativeLimit: number;
|
|
288
|
+
};
|
|
289
|
+
readonly active: {
|
|
290
|
+
readonly inputTokens: number;
|
|
291
|
+
readonly outputTokens: number;
|
|
292
|
+
readonly totalTokens: number;
|
|
293
|
+
readonly messageCount: number;
|
|
294
|
+
readonly percentOfContextLimit: number;
|
|
295
|
+
};
|
|
296
|
+
readonly contextLimit: number;
|
|
297
|
+
readonly cumulativeLimit: number;
|
|
298
|
+
} | null, {
|
|
299
|
+
readonly cumulative: {
|
|
300
|
+
readonly totalInputTokens: number;
|
|
301
|
+
readonly totalOutputTokens: number;
|
|
302
|
+
readonly totalTokens: number;
|
|
303
|
+
readonly percentOfContextLimit: number;
|
|
304
|
+
readonly percentOfCumulativeLimit: number;
|
|
305
|
+
};
|
|
306
|
+
readonly active: {
|
|
307
|
+
readonly inputTokens: number;
|
|
308
|
+
readonly outputTokens: number;
|
|
309
|
+
readonly totalTokens: number;
|
|
310
|
+
readonly messageCount: number;
|
|
311
|
+
readonly percentOfContextLimit: number;
|
|
312
|
+
};
|
|
313
|
+
readonly contextLimit: number;
|
|
314
|
+
readonly cumulativeLimit: number;
|
|
315
|
+
} | null>>;
|
|
316
|
+
quota: Readonly<import("vue").Ref<{
|
|
317
|
+
readonly limit: number | "unlimited";
|
|
318
|
+
readonly remaining: number | "unlimited";
|
|
319
|
+
readonly resetAt: string;
|
|
320
|
+
} | null, {
|
|
321
|
+
readonly limit: number | "unlimited";
|
|
322
|
+
readonly remaining: number | "unlimited";
|
|
323
|
+
readonly resetAt: string;
|
|
324
|
+
} | null>>;
|
|
325
|
+
liveThinking: import("vue").ComputedRef<string>;
|
|
326
|
+
sendMessage: (text: string, inputMode?: ChatInputMode) => Promise<void>;
|
|
327
|
+
stopGeneration: () => Promise<void>;
|
|
328
|
+
newConversation: () => Promise<void>;
|
|
329
|
+
listThreads: (limit?: number) => Promise<ThreadPage>;
|
|
330
|
+
loadThread: (id: string) => Promise<void>;
|
|
331
|
+
setFeedback: (messageId: string, value: MessageFeedback) => Promise<void>;
|
|
332
|
+
noteCitationClick: (messageId: string, citation: ChatCitation) => void;
|
|
333
|
+
retry: () => Promise<void>;
|
|
334
|
+
};
|
|
335
|
+
export type ChatEngine = ReturnType<typeof useChatEngine>;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Voice dictation through the browser's built-in speech recognition.
|
|
3
|
+
*
|
|
4
|
+
* Chrome, Edge and Safari expose the Web Speech API (Safari under the webkit
|
|
5
|
+
* prefix); Firefox has no implementation, so callers should hide their mic
|
|
6
|
+
* button when `isSupported` is false. Chrome and Edge run recognition on
|
|
7
|
+
* Google's servers rather than on the device, which matters to
|
|
8
|
+
* privacy-sensitive products.
|
|
9
|
+
*
|
|
10
|
+
* The transcript is written into the caller's text ref as the user speaks,
|
|
11
|
+
* after whatever was already typed. Nothing is submitted from here; the user
|
|
12
|
+
* reviews the dictated text and sends it like a typed message. Dictation
|
|
13
|
+
* stops itself after a stretch of silence, so the mic is not left hot while
|
|
14
|
+
* the user reads their question back.
|
|
15
|
+
*/
|
|
16
|
+
import { type Ref } from 'vue';
|
|
17
|
+
export interface DictationOptions {
|
|
18
|
+
/** Recognition language, as a BCP 47 tag. */
|
|
19
|
+
lang?: string;
|
|
20
|
+
/** Milliseconds of silence before dictation stops itself. 0 turns this off. */
|
|
21
|
+
idleMs?: number;
|
|
22
|
+
}
|
|
23
|
+
export default function useDictation(text: Ref<string>, options?: DictationOptions): {
|
|
24
|
+
/** False where the browser has no speech recognition, notably Firefox. */
|
|
25
|
+
isSupported: boolean;
|
|
26
|
+
isListening: Ref<boolean, boolean>;
|
|
27
|
+
toggle: () => void;
|
|
28
|
+
stop: () => void;
|
|
29
|
+
};
|
package/dist/copy.d.ts
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
export interface ChatCopy {
|
|
2
|
+
/**
|
|
3
|
+
* 'welcome' is shown before the first message is sent. Apps that want more than text here,
|
|
4
|
+
* such as a logo or buttons, use the chat's welcome slot instead.
|
|
5
|
+
*/
|
|
6
|
+
welcome: {
|
|
7
|
+
eyebrow: string;
|
|
8
|
+
heading: string;
|
|
9
|
+
body: string;
|
|
10
|
+
};
|
|
11
|
+
/** The names shown above each message. */
|
|
12
|
+
roles: {
|
|
13
|
+
assistant: string;
|
|
14
|
+
user: string;
|
|
15
|
+
};
|
|
16
|
+
composer: {
|
|
17
|
+
placeholder: string;
|
|
18
|
+
/** Tooltip on the mic spot when the browser has no speech recognition. */
|
|
19
|
+
micUnsupported: string;
|
|
20
|
+
/** Names of the composer's icon-only buttons, shown on hover. */
|
|
21
|
+
dictateLabel: string;
|
|
22
|
+
sendLabel: string;
|
|
23
|
+
stopLabel: string;
|
|
24
|
+
};
|
|
25
|
+
status: {
|
|
26
|
+
/** Held beside the waiting animation. Leave empty to show the animation alone. */
|
|
27
|
+
thinking: string;
|
|
28
|
+
error: string;
|
|
29
|
+
retryLabel: string;
|
|
30
|
+
/**
|
|
31
|
+
* Shown when the server refuses a message because the sender is over
|
|
32
|
+
* their message limit. Optional: an app that does not set it shows its
|
|
33
|
+
* own `error` string for these too, exactly as before this key existed.
|
|
34
|
+
*/
|
|
35
|
+
rateLimited?: string;
|
|
36
|
+
/**
|
|
37
|
+
* Lines the thinking indicator steps through while an answer is being
|
|
38
|
+
* retrieved, in place of the single `thinking` label. Only read when the
|
|
39
|
+
* chat's `rotateThinkingLabel` prop is on. An empty array falls back to
|
|
40
|
+
* `thinking`, which is also how to switch the sequence off per-app.
|
|
41
|
+
*/
|
|
42
|
+
thinkingSequence?: string[];
|
|
43
|
+
};
|
|
44
|
+
feedback: {
|
|
45
|
+
question: string;
|
|
46
|
+
};
|
|
47
|
+
sources: {
|
|
48
|
+
openSourceLabel: string;
|
|
49
|
+
/** Heading over the quoted passage in a citation popover. */
|
|
50
|
+
citedPassageLabel: string;
|
|
51
|
+
/** Caveat shown with a source, such as a login the reader may need. Leave empty to hide it. */
|
|
52
|
+
noticeLine: string;
|
|
53
|
+
/** The copy-link button in a citation popover, and what it says right after copying. */
|
|
54
|
+
copyUrlLabel: string;
|
|
55
|
+
copiedLabel: string;
|
|
56
|
+
/**
|
|
57
|
+
* The rest of this section belongs to the source cards that list mode
|
|
58
|
+
* shows under an answer. All optional; see ResolvedChatCopy.
|
|
59
|
+
*/
|
|
60
|
+
/** Line above the cards. Leave empty to hide it. */
|
|
61
|
+
introLine?: string;
|
|
62
|
+
/** Numbered heading on each card; {n} is the card's position. */
|
|
63
|
+
sourceLabel?: string;
|
|
64
|
+
/** Heading over a card's cited passages. */
|
|
65
|
+
referenceLabel?: string;
|
|
66
|
+
/** Where a passage sits in the document; {page} is the page number. */
|
|
67
|
+
pageLabel?: string;
|
|
68
|
+
/** Appended to pageLabel when the printed page differs; {textPage}. */
|
|
69
|
+
textPageLabel?: string;
|
|
70
|
+
/** Names of the card's fold toggle, and of the page-image dialog's close button. */
|
|
71
|
+
expandLabel?: string;
|
|
72
|
+
collapseLabel?: string;
|
|
73
|
+
closeLabel?: string;
|
|
74
|
+
};
|
|
75
|
+
/** Shown when the assistant declines a question it cannot answer. */
|
|
76
|
+
outOfScope: {
|
|
77
|
+
label: string;
|
|
78
|
+
};
|
|
79
|
+
/**
|
|
80
|
+
* The collapsible panel showing the model's reasoning. Only rendered when
|
|
81
|
+
* the app turns on the chat's `showThinking` prop, so the section is
|
|
82
|
+
* optional here too.
|
|
83
|
+
*/
|
|
84
|
+
reasoning?: {
|
|
85
|
+
showLabel: string;
|
|
86
|
+
hideLabel: string;
|
|
87
|
+
};
|
|
88
|
+
/**
|
|
89
|
+
* The row of actions under a finished answer. Only the copy button uses
|
|
90
|
+
* these, and only when the app turns on `showCopy`, so the section is
|
|
91
|
+
* optional here too.
|
|
92
|
+
*/
|
|
93
|
+
actions?: {
|
|
94
|
+
/** Names the button that copies the answer. */
|
|
95
|
+
copyLabel: string;
|
|
96
|
+
/** Replaces it for a moment afterwards, since nothing else visibly happens. */
|
|
97
|
+
copiedLabel: string;
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Copy with every later-added optional part guaranteed present. This is what
|
|
102
|
+
* resolveChatCopy returns and what components read through useChatCopy, so a
|
|
103
|
+
* component never carries a fallback for a string the defaults already have.
|
|
104
|
+
*
|
|
105
|
+
* The optional keys exist because apps were already shipping complete maps
|
|
106
|
+
* with `satisfies ChatCopy` when these strings were added; making them
|
|
107
|
+
* required would have broken those builds.
|
|
108
|
+
*/
|
|
109
|
+
export type ResolvedChatCopy = ChatCopy & {
|
|
110
|
+
status: ChatCopy['status'] & {
|
|
111
|
+
rateLimited: string;
|
|
112
|
+
thinkingSequence: string[];
|
|
113
|
+
};
|
|
114
|
+
sources: ChatCopy['sources'] & {
|
|
115
|
+
introLine: string;
|
|
116
|
+
sourceLabel: string;
|
|
117
|
+
referenceLabel: string;
|
|
118
|
+
pageLabel: string;
|
|
119
|
+
textPageLabel: string;
|
|
120
|
+
expandLabel: string;
|
|
121
|
+
collapseLabel: string;
|
|
122
|
+
closeLabel: string;
|
|
123
|
+
};
|
|
124
|
+
reasoning: NonNullable<ChatCopy['reasoning']>;
|
|
125
|
+
actions: NonNullable<ChatCopy['actions']>;
|
|
126
|
+
};
|
|
127
|
+
type DeepPartial<T> = {
|
|
128
|
+
[K in keyof T]?: DeepPartial<T[K]>;
|
|
129
|
+
};
|
|
130
|
+
/** What an app passes to the chat's `copy` prop. Any part can be left out. */
|
|
131
|
+
export type ChatCopyInput = DeepPartial<ChatCopy>;
|
|
132
|
+
export declare const defaultChatCopy: ResolvedChatCopy;
|
|
133
|
+
/** Drops values into a string, so formatCopy('{n} sources', { n: 3 }) gives '3 sources'. */
|
|
134
|
+
export declare function formatCopy(template: string, params: Record<string, string | number>): string;
|
|
135
|
+
/**
|
|
136
|
+
* Merges provided copy with the default values. Any missing strings fall back to the default and gets listed in a warning
|
|
137
|
+
* during development. That way an incomplete set of text is easy to spot but never leaves blank labels in the interface.
|
|
138
|
+
*/
|
|
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;
|
|
142
|
+
/** Read the resolved copy anywhere inside the chat component tree. */
|
|
143
|
+
export declare function useChatCopy(): ResolvedChatCopy;
|
|
144
|
+
export {};
|
package/dist/index.css
ADDED
|
@@ -0,0 +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}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Everything the chat kit makes available. Import from '@twentyfourg/chat-kit'
|
|
3
|
+
* rather than reaching into files inside src, since only what is listed here
|
|
4
|
+
* is meant to stay stable.
|
|
5
|
+
*/
|
|
6
|
+
export * from './types';
|
|
7
|
+
export * from './copy';
|
|
8
|
+
export * from './services/anonymous-auth';
|
|
9
|
+
export * from './services/base-camp-client';
|
|
10
|
+
export * from './services/streaming.service';
|
|
11
|
+
export * from './services/markdown';
|
|
12
|
+
export * from './services/entities';
|
|
13
|
+
export type { MathOptions, MathSetting } from './services/math';
|
|
14
|
+
export { default as StreamingMessage } from './services/streaming-message';
|
|
15
|
+
export * from './composables/useChatEngine';
|
|
16
|
+
export * from './testing/mock-transport';
|
|
17
|
+
export { default as Chat } from './components/Chat.vue';
|
|
18
|
+
export { default as IconButton } from './components/IconButton.vue';
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Signs the visitor in without asking them for anything.
|
|
3
|
+
*
|
|
4
|
+
* Base Camp will not answer without an identity, but this product is open to
|
|
5
|
+
* the public. LXS provides an "anonymous" login for exactly this: the browser
|
|
6
|
+
* asks for a throwaway session, then trades it for a token Base Camp accepts.
|
|
7
|
+
* Nothing here is secret, so it runs in the browser with no server of ours in
|
|
8
|
+
* the middle.
|
|
9
|
+
*
|
|
10
|
+
* The exchange happens in two steps, and the second proves the first came from
|
|
11
|
+
* this same page:
|
|
12
|
+
* 1. Ask for a session, sending a fingerprint of a random string.
|
|
13
|
+
* 2. Hand back the original random string to collect the token.
|
|
14
|
+
*/
|
|
15
|
+
export interface AnonymousAuthOptions {
|
|
16
|
+
/** Base URL of the LXS API. */
|
|
17
|
+
lxsApiUrl: string;
|
|
18
|
+
/**
|
|
19
|
+
* Which login configuration to use. Optional: when left out, the tenant's
|
|
20
|
+
* default one is looked up instead, which is what the LXP frontend does.
|
|
21
|
+
*/
|
|
22
|
+
authConfigId?: string;
|
|
23
|
+
/** Identifies the tenant these sessions belong to. */
|
|
24
|
+
tenantNumber: string;
|
|
25
|
+
deployEnv?: string;
|
|
26
|
+
jobNumber?: string;
|
|
27
|
+
}
|
|
28
|
+
interface AuthConfig {
|
|
29
|
+
PK: string;
|
|
30
|
+
authenticationStrategies?: {
|
|
31
|
+
id: string;
|
|
32
|
+
type: string;
|
|
33
|
+
enabled: boolean;
|
|
34
|
+
}[];
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Asks LXS which login configuration applies to this tenant. The endpoint is
|
|
38
|
+
* public, so it works before anyone has signed in. Saves having to hardcode an
|
|
39
|
+
* id per tenant, and lets us check that guest sign-in is actually switched on.
|
|
40
|
+
*/
|
|
41
|
+
export declare function fetchAuthConfig(options: AnonymousAuthOptions): Promise<AuthConfig>;
|
|
42
|
+
export declare class AnonymousAuthError extends Error {
|
|
43
|
+
constructor(message: string);
|
|
44
|
+
}
|
|
45
|
+
/** Runs the two-step exchange and returns a token Base Camp will accept. */
|
|
46
|
+
export declare function signInAnonymously(options: AnonymousAuthOptions): Promise<string>;
|
|
47
|
+
/** What the Base Camp client asks for, with a way to start it early. */
|
|
48
|
+
export interface AnonymousTokenProvider {
|
|
49
|
+
(): Promise<string>;
|
|
50
|
+
/**
|
|
51
|
+
* Signs in now rather than waiting for the first request. Call it once the
|
|
52
|
+
* page has loaded so the exchange finishes while the visitor is still
|
|
53
|
+
* reading, instead of delaying their first question. Failures are ignored
|
|
54
|
+
* here: the next real request tries again and reports properly.
|
|
55
|
+
*/
|
|
56
|
+
prefetch(): void;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Wraps the sign-in so the token is fetched once and shared. Hand the returned
|
|
60
|
+
* function to the Base Camp client, which calls it before every request.
|
|
61
|
+
*/
|
|
62
|
+
export declare function createAnonymousTokenProvider(options: AnonymousAuthOptions): AnonymousTokenProvider;
|
|
63
|
+
export {};
|