@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.
Files changed (39) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +222 -0
  3. package/dist/chat-kit.js +1914 -0
  4. package/dist/chat-kit.js.map +1 -0
  5. package/dist/components/AssistantMessage.vue.d.ts +35 -0
  6. package/dist/components/Chat.vue.d.ts +72 -0
  7. package/dist/components/CitationPopover.vue.d.ts +11 -0
  8. package/dist/components/FeedbackRow.vue.d.ts +11 -0
  9. package/dist/components/IconButton.vue.d.ts +17 -0
  10. package/dist/components/InputBar.vue.d.ts +16 -0
  11. package/dist/components/KitIcon.vue.d.ts +12 -0
  12. package/dist/components/MessageList.vue.d.ts +36 -0
  13. package/dist/components/PageImageModal.vue.d.ts +14 -0
  14. package/dist/components/SourceCard.vue.d.ts +21 -0
  15. package/dist/components/SourceList.vue.d.ts +7 -0
  16. package/dist/components/ThinkingAnimation.vue.d.ts +15 -0
  17. package/dist/components/ThinkingIndicator.vue.d.ts +13 -0
  18. package/dist/components/ThinkingProcess.vue.d.ts +8 -0
  19. package/dist/components/UserMessage.vue.d.ts +7 -0
  20. package/dist/composables/useAutoScroll.d.ts +36 -0
  21. package/dist/composables/useChatEngine.d.ts +335 -0
  22. package/dist/composables/useDictation.d.ts +29 -0
  23. package/dist/copy.d.ts +144 -0
  24. package/dist/index.css +1 -0
  25. package/dist/index.d.ts +18 -0
  26. package/dist/services/anonymous-auth.d.ts +63 -0
  27. package/dist/services/base-camp-client.d.ts +66 -0
  28. package/dist/services/citations.d.ts +18 -0
  29. package/dist/services/entities.d.ts +29 -0
  30. package/dist/services/markdown.d.ts +47 -0
  31. package/dist/services/math.d.ts +36 -0
  32. package/dist/services/streaming-message.d.ts +32 -0
  33. package/dist/services/streaming.service.d.ts +84 -0
  34. package/dist/services/typewriter.d.ts +54 -0
  35. package/dist/testing/mock-transport.d.ts +52 -0
  36. package/dist/testing/setup-tests.d.ts +8 -0
  37. package/dist/theme.css +122 -0
  38. package/dist/types.d.ts +389 -0
  39. package/package.json +76 -0
@@ -0,0 +1,389 @@
1
+ /**
2
+ * Shared Base Camp + chat domain types.
3
+ *
4
+ * Shapes are derived from Base Camp's Zodios API definitions
5
+ * (packages/api/src/api/*.api.ts and packages/lib/src/schemas/* in the
6
+ * Base Camp repo), which generate the authoritative OpenAPI contract.
7
+ * SSE event names and the parsing loop live in services/streaming.service.ts;
8
+ * this file only defines the payload shapes those events carry.
9
+ */
10
+ export type ConversationType = 'SINGLE_SHOT' | 'CONTINUOUS';
11
+ /**
12
+ * Per-agent feature toggles the frontend should honor. Several are also
13
+ * enforced server-side (sources, knowledge-source visibility, single-shot locking).
14
+ */
15
+ export interface AgentConfig {
16
+ /** Render the model's thinking inline as it streams. Display hint only; thinking events always arrive. */
17
+ showThinkingProcess: boolean;
18
+ /** Show an affordance to reveal the thinking process. Display hint only. */
19
+ showThinkingProcessButton: boolean;
20
+ /** Enable voice interaction UI (mic / audio controls). */
21
+ enableVoiceInteraction?: boolean;
22
+ /** Show the export locker UI. */
23
+ showExpertLocker: boolean;
24
+ /** Show knowledge sources in an information panel. */
25
+ showKnowledgeSources: boolean;
26
+ /** Show the sources button on assistant replies; also gates the used-sources stream event server-side. */
27
+ showSourcesButton: boolean;
28
+ /** Default the locker view to exports from this agent only. */
29
+ defaultLockerViewThisExpertOnly: boolean;
30
+ /** Display the token usage indicator in the chat interface. */
31
+ showTokenUsageIndicator: boolean;
32
+ /** Cumulative token budget for a thread's lifetime; server rejects messages past it. */
33
+ chatTokenLimit: number;
34
+ conversationType: ConversationType;
35
+ }
36
+ export type KnowledgeSourceKind = 'INTERNAL' | 'FILE' | 'DOWNLOAD';
37
+ /** Public-safe knowledge source reference returned on agent detail (types and ids only). */
38
+ export interface AgentKnowledgeSourceRef {
39
+ type: KnowledgeSourceKind;
40
+ uniqueExternalId: string | null;
41
+ baseCampId: string | null;
42
+ }
43
+ /** Public agent detail from GET /v1/agents/:agentId. */
44
+ export interface AgentDetails {
45
+ id: string;
46
+ name: string;
47
+ description: string;
48
+ firstMessage: string;
49
+ config?: AgentConfig;
50
+ /** Names usable as {{placeholder}} variables in agent prompts, filled per thread. */
51
+ variableNames?: string[];
52
+ knowledgeSource: AgentKnowledgeSourceRef[];
53
+ }
54
+ export type ThreadStatus = 'ACTIVE' | 'ARCHIVED' | 'DELETED';
55
+ /** User's resolved rate-limit tier, recorded on the thread at creation. */
56
+ export type RateLimitTier = 'guest' | 'basic' | 'premium';
57
+ /** Per-thread values for the agent's declared variables; keys must be in variableNames. */
58
+ export type ThreadVariables = Record<string, string>;
59
+ /** Opaque product-owned data bag; Base Camp stores it verbatim and never interprets it. */
60
+ export type ThreadCustomData = Record<string, unknown>;
61
+ export interface Thread {
62
+ id: string;
63
+ agentId: string;
64
+ status: ThreadStatus;
65
+ title?: string;
66
+ lastMessageAt?: string;
67
+ messageCount?: number;
68
+ createdAt?: string;
69
+ updatedAt?: string;
70
+ tier?: RateLimitTier;
71
+ /** True when a SINGLE_SHOT thread already has a completed turn and rejects new messages. */
72
+ locked?: boolean;
73
+ variables?: ThreadVariables;
74
+ customData?: ThreadCustomData;
75
+ }
76
+ /** Agent summary embedded in thread detail; firstMessage arrives pre-substituted with thread variables. */
77
+ export interface ThreadAgentSummary {
78
+ id: string;
79
+ name: string;
80
+ description: string;
81
+ firstMessage: string;
82
+ config?: AgentConfig;
83
+ }
84
+ /** GET /v1/agents/:agentId/threads/:threadId response. */
85
+ export interface ThreadDetail extends Thread {
86
+ agent: ThreadAgentSummary;
87
+ }
88
+ export interface CreateThreadBody {
89
+ variables?: ThreadVariables;
90
+ customData?: ThreadCustomData;
91
+ }
92
+ export interface CreateThreadResponse {
93
+ id: string;
94
+ agentId: string;
95
+ status: ThreadStatus;
96
+ createdAt: string;
97
+ variables?: ThreadVariables;
98
+ tier?: RateLimitTier;
99
+ }
100
+ export type ChatRole = 'user' | 'assistant';
101
+ export type MessageFeedback = 'POSITIVE' | 'NEGATIVE';
102
+ /** Per-message token counts as stored in message metadata. */
103
+ export interface MessageTokens {
104
+ input: number;
105
+ output: number;
106
+ total: number;
107
+ }
108
+ export interface ChatMessageMetadata {
109
+ /** Reasoning text captured for this message. */
110
+ thinking?: string;
111
+ tokens?: MessageTokens;
112
+ /** True while the assistant reply is still being generated server-side (reconnect marker). */
113
+ streaming?: boolean;
114
+ /** Server marks the thread-opening welcome message with these. */
115
+ isWelcomeMessage?: boolean;
116
+ messageType?: string;
117
+ [key: string]: unknown;
118
+ }
119
+ /** Client-friendly attachment reference on a history message. */
120
+ export interface MessageAttachment {
121
+ format: string;
122
+ name?: string;
123
+ url: string;
124
+ }
125
+ /** A message as returned by GET .../messages (chat history). */
126
+ export interface ChatMessage {
127
+ id: string;
128
+ role: ChatRole;
129
+ text: string;
130
+ attachments?: MessageAttachment[];
131
+ createdAt: string;
132
+ stopReason?: string;
133
+ /** Grouped source citations persisted with the assistant message. */
134
+ sourceContext?: UsedSource[];
135
+ metadata?: ChatMessageMetadata;
136
+ /** Current user's explicit feedback on this message, not an aggregate. */
137
+ feedback?: MessageFeedback;
138
+ feedbackUpdatedAt?: string;
139
+ }
140
+ /** GET /v1/agents/:agentId/threads/:threadId/messages response. */
141
+ export interface MessageListResponse {
142
+ threadId: string;
143
+ messages: ChatMessage[];
144
+ tokenUsage?: TokenUsage;
145
+ customData?: ThreadCustomData;
146
+ }
147
+ export interface SendMessageBody {
148
+ message: string;
149
+ stream?: boolean;
150
+ /**
151
+ * Tells Base Camp the text was dictated rather than typed, so the two can
152
+ * be told apart in reporting.
153
+ *
154
+ * Only ever 'dictation'. A typed message leaves the field out and takes the
155
+ * server's default of 'keyboard', and the kit has no voice-to-voice mode,
156
+ * which is the 'realtime' value the API also accepts.
157
+ */
158
+ inputMode?: 'dictation';
159
+ }
160
+ export interface StopMessageResponse {
161
+ success: boolean;
162
+ message: string;
163
+ }
164
+ /** POST .../messages/:messageId/feedback response; feedback is absent after a clear. */
165
+ export interface FeedbackResponse {
166
+ messageId: string;
167
+ feedback?: MessageFeedback;
168
+ feedbackUpdatedAt?: string;
169
+ }
170
+ export interface SourceResultMetadata {
171
+ title?: string;
172
+ entityId?: string;
173
+ page?: number;
174
+ /** Prefer over `page` for a "text page N" display, when present. */
175
+ textPage?: number;
176
+ /** External source URL, when the document has one. */
177
+ link?: string;
178
+ /** Short-lived signed page-image URL; valid when displayed, not cacheable. */
179
+ screenshotUrl?: string;
180
+ [key: string]: unknown;
181
+ }
182
+ /** One retrieval hit inside a grouped source. */
183
+ export interface SourceResult {
184
+ index?: number;
185
+ content?: string;
186
+ score?: number;
187
+ metadata?: SourceResultMetadata;
188
+ }
189
+ /**
190
+ * Source citations grouped by knowledge entity, as emitted by the used-sources
191
+ * stream event and persisted on messages as sourceContext. entityId is null
192
+ * for hits the server could not attribute to an entity.
193
+ */
194
+ export interface UsedSource {
195
+ entityId: string | null;
196
+ metadata?: SourceResultMetadata;
197
+ /**
198
+ * The document's cover image as older threads persisted it. It moved into
199
+ * metadata since; readers should prefer metadata.screenshotUrl and fall
200
+ * back to this, so reopened old threads keep their covers.
201
+ */
202
+ screenshotUrl?: string;
203
+ results: SourceResult[];
204
+ }
205
+ /**
206
+ * How the chat presents its sources. 'inline' swaps [^N] markers in the answer
207
+ * for numbered chips that open a passage popover; 'list' shows the documents
208
+ * the answer drew on underneath it instead.
209
+ */
210
+ export type CitationType = 'inline' | 'list';
211
+ /**
212
+ * How the thinking indicator animates while an answer is being retrieved:
213
+ * 'ring' is a spinning arc, 'sparkles' is a cluster of pulsing stars, 'dots'
214
+ * is three dots rising in turn.
215
+ */
216
+ export type ThinkingVariant = 'ring' | 'sparkles' | 'dots';
217
+ /**
218
+ * How far the conversation follows an answer as it streams.
219
+ *
220
+ * 'reply' stops once the answer's own header reaches the top, so a long answer
221
+ * settles with its beginning in view and grows out of sight below. 'bottom'
222
+ * follows the newest text instead, keeping the last line in view. 'off' leaves
223
+ * the view entirely to the reader.
224
+ *
225
+ * Under 'reply' and 'bottom' alike, scrolling up stops following until the
226
+ * reader returns to the bottom.
227
+ */
228
+ export type AutoScrollMode = 'off' | 'reply' | 'bottom';
229
+ /**
230
+ * One passage-level citation, emitted by the citations stream event for agents
231
+ * configured with `citationMode: 'inline'`. The answer text carries matching
232
+ * `[^N]` markers where N is `ref`.
233
+ */
234
+ export interface ChatCitation {
235
+ /** N from the `[^N]` marker in the answer text. Unique within the turn. */
236
+ ref: number;
237
+ /** Dense display number (1..N). Always show this, never `ref`. */
238
+ label: number;
239
+ /** The cited passage. */
240
+ passage: string;
241
+ entityId?: string;
242
+ variationId?: string;
243
+ title?: string;
244
+ /**
245
+ * Where to send a reader who opens this source. Once Base Camp's citation
246
+ * tracking is deployed this is a signed redirect that records the click,
247
+ * not the document's own address, so navigate with it but never share it.
248
+ */
249
+ link?: string;
250
+ /**
251
+ * The document's own URL, for sharing and for showing someone where a
252
+ * passage came from. Absent on replies from before citation tracking, where
253
+ * `link` was the plain URL and is the thing to fall back to.
254
+ */
255
+ canonicalLink?: string;
256
+ /**
257
+ * Base Camp's id for this citation within its message. Needed to report
258
+ * that the passage was opened, since the server resolves the document,
259
+ * page and thread from it rather than trusting the browser. Absent until
260
+ * Base Camp's citation tracking is deployed.
261
+ */
262
+ sourceId?: string;
263
+ /** Short-lived signed page-image URL. */
264
+ screenshotUrl?: string;
265
+ page?: number;
266
+ /** Prefer over `page` for a "p. X" display. */
267
+ textPage?: number;
268
+ }
269
+ /** How the user entered their question: typing, or voice dictation. */
270
+ export type ChatInputMode = 'typed' | 'voice';
271
+ /**
272
+ * A moment in the conversation the app may want to record, announced through
273
+ * the engine's onEvent option. The kit only describes what happened; whether
274
+ * and where it gets reported is the app's business.
275
+ */
276
+ export type ChatEvent = {
277
+ type: 'turn';
278
+ /** The user's question as sent. */
279
+ query: string;
280
+ inputMode: ChatInputMode;
281
+ /** The assistant's full answer text. */
282
+ response: string;
283
+ isOutOfScope: boolean;
284
+ citations: ChatCitation[];
285
+ sources: UsedSource[];
286
+ threadId: string | null;
287
+ userMessageId?: string;
288
+ assistantMessageId?: string;
289
+ } | {
290
+ type: 'citation-click';
291
+ citation: ChatCitation;
292
+ assistantMessageId?: string;
293
+ threadId: string | null;
294
+ /** The question and answer the citation belongs to. */
295
+ query: string;
296
+ response: string;
297
+ } | {
298
+ type: 'feedback';
299
+ /** The rating now in effect; null means the user cleared their thumb. */
300
+ value: MessageFeedback | null;
301
+ assistantMessageId?: string;
302
+ threadId: string | null;
303
+ /** The question and answer the rating is about. */
304
+ query: string;
305
+ response: string;
306
+ };
307
+ /**
308
+ * POST /v1/analytics/events body. Base Camp stores the event for the
309
+ * project's analytics reports; eventType says which report it belongs to.
310
+ */
311
+ export interface TrackEventBody {
312
+ /**
313
+ * Random visitor id the frontend may generate and store in the browser, to
314
+ * group one visitor's rows in the sheet. Optional, and better left out when
315
+ * Base Camp can identify the caller from the request itself, which a
316
+ * browser cannot forge.
317
+ */
318
+ sessionId?: string;
319
+ eventType: string;
320
+ /** The assistant message the event belongs to. */
321
+ messageId?: string;
322
+ /** Which citation within that message, from `ChatCitation.sourceId`. */
323
+ sourceId?: string;
324
+ /**
325
+ * Extra detail for events that need it. Base Camp resolves what it can
326
+ * from the ids above and ignores anything here it already knows.
327
+ */
328
+ payload?: Record<string, unknown>;
329
+ }
330
+ export interface TrackEventResponse {
331
+ success: boolean;
332
+ }
333
+ /** Thread-level token accounting returned with history and the token-usage stream event. */
334
+ export interface TokenUsage {
335
+ /** Running totals across the thread lifecycle. */
336
+ cumulative: {
337
+ totalInputTokens: number;
338
+ totalOutputTokens: number;
339
+ totalTokens: number;
340
+ percentOfContextLimit: number;
341
+ percentOfCumulativeLimit: number;
342
+ };
343
+ /** Tokens sent to the model for the current context window. */
344
+ active: {
345
+ inputTokens: number;
346
+ outputTokens: number;
347
+ totalTokens: number;
348
+ messageCount: number;
349
+ percentOfContextLimit: number;
350
+ };
351
+ /** Context window limit for the AI model. */
352
+ contextLimit: number;
353
+ /** Total token cap across the thread lifecycle. */
354
+ cumulativeLimit: number;
355
+ }
356
+ /** GET /v1/agents/:agentId/quota response; reads without consuming quota. */
357
+ export interface QuotaResponse {
358
+ limit: number | 'unlimited';
359
+ remaining: number | 'unlimited';
360
+ resetAt: string;
361
+ }
362
+ /** Payload of the message_id and assistant_message_id events. */
363
+ export interface StreamMessageIdPayload {
364
+ messageId: string;
365
+ }
366
+ /** Payload of thinking_delta and response_delta events. */
367
+ export interface StreamTextPayload {
368
+ text: string;
369
+ }
370
+ /** Payload of the text_complete event. */
371
+ export interface StreamTextCompletePayload {
372
+ hasSourceContext: boolean;
373
+ }
374
+ /** Payload of the token_usage event. */
375
+ export interface StreamTokenUsagePayload {
376
+ message: {
377
+ inputTokens: number;
378
+ outputTokens: number;
379
+ totalTokens: number;
380
+ };
381
+ thread: TokenUsage;
382
+ }
383
+ /** Payload of the used_sources event. */
384
+ export type StreamUsedSourcesPayload = UsedSource[];
385
+ /** Payload of the error event; message is always server-sanitized. */
386
+ export interface StreamErrorPayload {
387
+ error: string;
388
+ message?: string;
389
+ }
package/package.json ADDED
@@ -0,0 +1,76 @@
1
+ {
2
+ "name": "@twentyfourg/chat-kit",
3
+ "version": "1.0.0-beta.1",
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
+ "license": "MIT",
6
+ "type": "module",
7
+ "packageManager": "pnpm@10.22.0",
8
+ "main": "./dist/chat-kit.js",
9
+ "module": "./dist/chat-kit.js",
10
+ "types": "./dist/index.d.ts",
11
+ "exports": {
12
+ ".": {
13
+ "types": "./dist/index.d.ts",
14
+ "import": "./dist/chat-kit.js"
15
+ },
16
+ "./theme.css": "./dist/theme.css"
17
+ },
18
+ "files": [
19
+ "dist"
20
+ ],
21
+ "sideEffects": [
22
+ "**/*.css"
23
+ ],
24
+ "repository": {
25
+ "type": "git",
26
+ "url": "https://github.com/twentyfourg/chat-kit.git"
27
+ },
28
+ "publishConfig": {
29
+ "registry": "https://registry.npmjs.org/",
30
+ "access": "public"
31
+ },
32
+ "scripts": {
33
+ "build": "vite build && vue-tsc -p tsconfig.build.json",
34
+ "lint": "oxlint .",
35
+ "test": "vitest run",
36
+ "type-check": "vue-tsc --noEmit -p tsconfig.json",
37
+ "prepublishOnly": "pnpm build"
38
+ },
39
+ "pnpm": {
40
+ "ignoredBuiltDependencies": [
41
+ "esbuild"
42
+ ]
43
+ },
44
+ "peerDependencies": {
45
+ "mathlive": ">=0.104.0",
46
+ "vue": "^3.5.0"
47
+ },
48
+ "peerDependenciesMeta": {
49
+ "mathlive": {
50
+ "optional": true
51
+ }
52
+ },
53
+ "dependencies": {
54
+ "dompurify": "^3.4.12",
55
+ "marked": "^17.0.1"
56
+ },
57
+ "devDependencies": {
58
+ "@semantic-release/changelog": "^6.0.3",
59
+ "@semantic-release/git": "^10.0.1",
60
+ "@types/node": "^24.13.3",
61
+ "@vitejs/plugin-vue": "^6.0.8",
62
+ "@vue/test-utils": "^2.5.0",
63
+ "@vue/tsconfig": "^0.9.1",
64
+ "conventional-changelog-conventionalcommits": "^9.3.1",
65
+ "jsdom": "^30.0.1",
66
+ "mathlive": "^0.110.0",
67
+ "oxlint": "~1.60.0",
68
+ "semantic-release": "^25.0.9",
69
+ "typescript": "~6.0.3",
70
+ "vite": "^8.1.5",
71
+ "vite-plugin-lib-inject-css": "^2.2.2",
72
+ "vitest": "^3.2.4",
73
+ "vue": "^3.5.32",
74
+ "vue-tsc": "^3.2.6"
75
+ }
76
+ }