juneau 0.1.1 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -3,13 +3,16 @@
3
3
  React component library for building AI chat interfaces. Streaming-first, adapter-based, fully themeable.
4
4
 
5
5
  ```tsx
6
- import { JuneauProvider, AiSidebar, createSseAdapter } from 'juneau';
6
+ import { JuneauProvider, AiChatProvider, AiSidebar, createSseAdapter } from 'juneau';
7
7
  import 'juneau/dist/style.css';
8
8
 
9
9
  const adapter = createSseAdapter('/api/chat');
10
10
 
11
11
  <JuneauProvider>
12
- <AiSidebar adapter={adapter} />
12
+ <AiChatProvider adapter={adapter}>
13
+ <App />
14
+ <AiSidebar />
15
+ </AiChatProvider>
13
16
  </JuneauProvider>
14
17
  ```
15
18
 
@@ -41,10 +44,10 @@ It receives the conversation history and streams back typed events:
41
44
 
42
45
  ```ts
43
46
  type AiStreamEvent =
44
- | { type: 'text'; text: string } // streamed text chunk — append to current bubble
45
- | { type: 'part'; part: AiTablePart | AiProposalPart | AiErrorPart } // rich UI block
46
- | { type: 'done' } // stream finished cleanly
47
- | { type: 'error'; message: string } // stream failed
47
+ | { type: 'text'; text: string } // streamed text chunk — append to current bubble
48
+ | { type: 'part'; part: AiMessagePart } // rich UI block (table, proposal, activity, error)
49
+ | { type: 'done' } // stream finished cleanly
50
+ | { type: 'error'; message: string } // stream failed
48
51
  ```
49
52
 
50
53
  This means your API keys stay on your backend, you control auth, rate limiting, model selection — Juneau just renders whatever comes back.
@@ -187,22 +190,23 @@ Shipped for local development. No backend needed — responds to keywords in the
187
190
  |---|---|
188
191
  | `"show"`, `"list"`, `"data"`, `"table"` | A rendered data table |
189
192
  | `"suggest"`, `"recommend"`, `"proposal"` | A proposal card with confirm/cancel |
193
+ | `"activity"`, `"progress"`, `"document"` | An activity timeline with running/done states |
190
194
  | `"help"`, `"what can you do"` | Capability overview |
191
195
  | `"error"`, `"fail"` | Simulated error response |
192
196
  | anything else | Explains the available triggers |
193
197
 
194
198
  ```ts
195
199
  import { mockAdapter } from 'juneau';
196
- <AiSidebar adapter={mockAdapter} />
200
+ // Pass to AiChatProvider — see below
197
201
  ```
198
202
 
199
203
  ---
200
204
 
201
- ## Components
205
+ ## Providers
202
206
 
203
207
  ### `<JuneauProvider>`
204
208
 
205
- Wrap your app (or any subtree) once. Provides theme tokens and UI labels to all Juneau components below it.
209
+ Wrap your app once. Provides theme tokens and UI labels to all Juneau components below it.
206
210
 
207
211
  ```tsx
208
212
  import { JuneauProvider, juneauCs } from 'juneau';
@@ -224,28 +228,75 @@ import { JuneauProvider, juneauCs } from 'juneau';
224
228
 
225
229
  ---
226
230
 
231
+ ### `<AiChatProvider>`
232
+
233
+ Holds the shared conversation state. Wrap your app (or layout) once — any page can then render `<AiSidebar />` without losing conversation history on navigation.
234
+
235
+ ```tsx
236
+ import { AiChatProvider } from 'juneau';
237
+
238
+ <AiChatProvider
239
+ adapter={myAdapter}
240
+ onProposalConfirm={(id, payload) => handleAction(id, payload)}
241
+ onProposalCancel={(id) => handleDismiss(id)}
242
+ >
243
+ <App />
244
+ </AiChatProvider>
245
+ ```
246
+
247
+ | Prop | Type | Description |
248
+ |---|---|---|
249
+ | `adapter` | `AiBackendAdapter` | **Required.** Your adapter. |
250
+ | `context` | `Record<string, unknown>` | Initial context forwarded to every adapter call. Update per-page via `setContext()`. |
251
+ | `onProposalConfirm` | `(id, payload) => void` | Called when user confirms a proposal card. |
252
+ | `onProposalCancel` | `(id) => void` | Called when user cancels a proposal card. |
253
+
254
+ **Updating context per page:**
255
+
256
+ Use `setContext()` from `useAiChatContext()` to tell the AI where the user is on each page. The context is forwarded opaquely to every `adapter.sendMessage` call as `input.context`.
257
+
258
+ ```tsx
259
+ import { useAiChatContext } from 'juneau';
260
+
261
+ function InvoicesPage() {
262
+ const { setContext } = useAiChatContext();
263
+
264
+ useEffect(() => {
265
+ setContext({
266
+ page: 'invoices',
267
+ availableTools: ['search', 'export'],
268
+ userRole: 'admin',
269
+ });
270
+ }, []);
271
+
272
+ return <main>...</main>;
273
+ }
274
+ ```
275
+
276
+ ---
277
+
278
+ ## Components
279
+
227
280
  ### `<AiSidebar>`
228
281
 
229
- The fastest path to a working chat UI. Self-containedmanages its own state internally via `useAiChat`.
282
+ Fixed-position chat panel. Reads all state from the nearest `<AiChatProvider>` renders wherever you place it, conversation persists across navigation.
283
+
284
+ The sidebar anchors to the bottom-right of the viewport and opens at 60% screen height by default. Users can minimize it to a compact header bar.
230
285
 
231
286
  ```tsx
232
287
  <AiSidebar
233
- adapter={adapter}
234
288
  title="AI Assistant"
235
- onProposalConfirm={(id, payload) => handleAction(id, payload)}
289
+ height="70vh"
236
290
  />
237
291
  ```
238
292
 
239
293
  | Prop | Type | Description |
240
294
  |---|---|---|
241
- | `adapter` | `AiBackendAdapter` | **Required.** Your adapter. |
242
295
  | `title` | `string` | Overrides the `sidebarTitle` label for this instance. |
243
- | `context` | `Record<string, unknown>` | Passed through to every `adapter.sendMessage` call. Useful for page-level context (current entity ID, user role, etc). |
244
- | `icon` | `ReactNode` | Override the header + avatar icon. Defaults to a brain icon. |
296
+ | `icon` | `ReactNode` | Override the header + avatar icon. Defaults to a wand icon. |
245
297
  | `actions` | `AiInputAction[]` | Toolbar buttons left of send. Pass `[]` to hide entirely. |
246
298
  | `sendIcon` | `ReactNode` | Override the send button icon. |
247
- | `onProposalConfirm` | `(id, payload) => void` | Called when user confirms a proposal card. |
248
- | `onProposalCancel` | `(id) => void` | Called when user cancels a proposal card. |
299
+ | `height` | `string` | Height when open. Any CSS value. Defaults to `'60vh'`. |
249
300
  | `className` | `string` | Added to the `<aside>` element. |
250
301
  | `style` | `CSSProperties` | Inline styles on the `<aside>`. |
251
302
 
@@ -314,6 +365,7 @@ const {
314
365
  sendGreeting, // (contextHint: string) => Promise<void> — assistant speaks first, no user bubble shown
315
366
  stop, // () => void — abort in-flight stream, keep existing messages
316
367
  isLoading, // boolean — true while streaming
368
+ isConnecting, // boolean — true from send until first token arrives
317
369
  error, // string | null — last error, cleared on next send
318
370
  reset, // () => void — clear all messages and abort any stream
319
371
  confirmProposal, // (id: string, payload: unknown) => void
@@ -326,28 +378,101 @@ const {
326
378
  });
327
379
  ```
328
380
 
381
+ ## `useAiChatContext` hook
382
+
383
+ Reads the shared state from `<AiChatProvider>`. Includes everything from `useAiChat` plus `setContext()`.
384
+
385
+ ```ts
386
+ const {
387
+ // all useAiChat fields +
388
+ setContext, // (ctx: Record<string, unknown>) => void — update context forwarded to adapter
389
+ } = useAiChatContext();
390
+ ```
391
+
392
+ Throws a descriptive error if called outside `<AiChatProvider>`.
393
+
394
+ ---
395
+
329
396
  ### Useful patterns
330
397
 
331
- **Proactive greeting on mount:**
398
+ **Proactive greeting on mount (`sendGreeting`):**
399
+
400
+ `sendGreeting(hint)` sends the hint as a `system` role message to the adapter and streams the response as an assistant-only message — no user bubble is added to the conversation. Perfect for page-load summaries where the AI speaks first.
401
+
332
402
  ```ts
403
+ const { sendGreeting } = useAiChatContext();
404
+
333
405
  useEffect(() => {
334
- chat.sendGreeting('The user just opened the dashboard. Greet them briefly.');
406
+ sendGreeting(
407
+ 'The user is viewing the Invoices page. ' +
408
+ 'Briefly introduce what you can help with on this page.'
409
+ );
335
410
  }, []);
336
411
  ```
337
412
 
338
413
  **Trigger from outside the chat (e.g. clicking a data row):**
339
414
  ```ts
340
- chat.sendMessageWithText(`Explain invoice #${invoice.id}`);
415
+ const { sendMessageWithText } = useAiChatContext();
416
+ sendMessageWithText(`Summarise order #${order.id} for me`);
341
417
  ```
342
418
 
343
419
  **Pass page context to every request:**
344
420
  ```tsx
345
- <AiSidebar
346
- adapter={adapter}
347
- context={{ pageId: 'invoices', entityId: invoice.id, userRole: 'admin' }}
348
- />
421
+ const { setContext } = useAiChatContext();
422
+
423
+ useEffect(() => {
424
+ setContext({ pageId: 'invoices', entityId: invoice.id, userRole: 'admin' });
425
+ }, [invoice.id]);
349
426
  ```
350
- The `context` object lands in `input.context` inside your adapter's `sendMessage`.
427
+
428
+ The `context` object lands in `input.context` inside every `adapter.sendMessage` call — use it to inject page-level data without polluting the message history.
429
+
430
+ **Reading message text in your adapter:**
431
+
432
+ Don't dig into `parts` manually — use the exported `getMessageText` helper:
433
+
434
+ ```ts
435
+ import { getMessageText } from 'juneau';
436
+
437
+ async *sendMessage({ messages }) {
438
+ const lastUser = [...messages].reverse().find(m => m.role === 'user');
439
+ const text = lastUser ? getMessageText(lastUser) : '';
440
+ // → plain string, all text parts concatenated
441
+ }
442
+ ```
443
+
444
+ **Auth — bearer token from React context:**
445
+ ```ts
446
+ const adapter = createSseAdapter('/api/chat', {
447
+ getHeaders: async () => ({
448
+ Authorization: `Bearer ${await getAccessToken()}`,
449
+ }),
450
+ });
451
+ ```
452
+
453
+ **Auth — session cookie (no extra config needed):**
454
+
455
+ Cookies are sent automatically by `fetch` to same-origin URLs. Just use `createSseAdapter('/api/chat')` with no headers — the browser attaches the session cookie for you.
456
+
457
+ For cross-origin backends, add `credentials: 'include'` by writing a custom adapter:
458
+
459
+ ```ts
460
+ const adapter: AiBackendAdapter = {
461
+ async *sendMessage({ messages }) {
462
+ const res = await fetch('https://api.example.com/chat', {
463
+ method: 'POST',
464
+ credentials: 'include', // sends cookies cross-origin
465
+ headers: { 'Content-Type': 'application/json' },
466
+ body: JSON.stringify({ messages }),
467
+ });
468
+ // …stream response
469
+ },
470
+ };
471
+ ```
472
+
473
+ **Stop button feedback:**
474
+
475
+ When `onStop` is provided, the send button becomes a stop button while streaming. After the user hits stop, `isLoading` goes false immediately and the existing partial response stays visible in the conversation — there's no error state, the stream just ends cleanly where it was cut.
351
476
 
352
477
  ---
353
478
 
@@ -360,8 +485,41 @@ Assistant messages are composed of typed **parts**. Text is streamed chunk by ch
360
485
  | `text` | `{ type: 'text', text: string }` stream events, accumulated | Markdown via `react-markdown` — safe against XSS |
361
486
  | `table` | `{ type: 'part', part: { type: 'table', ... } }` | `AiTablePart` |
362
487
  | `proposal` | `{ type: 'part', part: { type: 'proposal', ... } }` | `AiProposalCard` |
488
+ | `activity` | `{ type: 'part', part: { type: 'activity', ... } }` | `AiActivityPart` |
363
489
  | `error` | `{ type: 'error', message: string }` or `{ type: 'part', part: { type: 'error', ... } }` | Inline error in bubble |
364
490
 
491
+ **Emitting an activity from your adapter:**
492
+
493
+ Activity parts show the user what the AI is doing while it works — skill selection, document search, tool calls, etc. They have three states: `running`, `done`, `failed`.
494
+
495
+ ```ts
496
+ // Show a running activity
497
+ yield {
498
+ type: 'part',
499
+ part: {
500
+ type: 'activity',
501
+ id: 'doc-search', // optional stable ID — enables in-place update
502
+ title: 'Searching document',
503
+ description: 'Looking up document by ID.',
504
+ status: 'running',
505
+ },
506
+ };
507
+
508
+ // Later: update the same activity in-place (same id)
509
+ yield {
510
+ type: 'part',
511
+ part: {
512
+ type: 'activity',
513
+ id: 'doc-search',
514
+ title: 'Document found',
515
+ description: 'Found document INV-2024-0894.',
516
+ status: 'done',
517
+ },
518
+ };
519
+ ```
520
+
521
+ If `id` is provided, a later activity part with the same `id` updates the previous one in-place instead of appending a new row. Without `id`, activity parts append as a timeline.
522
+
365
523
  **Emitting a proposal from your adapter:**
366
524
  ```ts
367
525
  yield {
@@ -459,7 +617,8 @@ Pass any `Partial<JuneauLabels>` — omitted keys fall back to English:
459
617
  | Key | Default (EN) | Used in |
460
618
  |---|---|---|
461
619
  | `sidebarTitle` | `AI Assistant` | `AiChatHeader` title |
462
- | `clearConversation` | `Clear conversation` | `AiChatHeader` reset button |
620
+ | `minimizeSidebar` | `Minimize` | `AiChatHeader` minimize button |
621
+ | `expandSidebar` | `Expand` | `AiChatHeader` expand button (when minimized) |
463
622
  | `inputPlaceholder` | `Ask a question… (Enter to send)` | `AiInput` textarea |
464
623
  | `sendMessage` | `Send message` | `AiInput` send button |
465
624
  | `stopMessage` | `Stop` | `AiInput` stop button (while streaming) |
@@ -482,7 +641,6 @@ The toolbar buttons left of the send button are fully configurable:
482
641
 
483
642
  ```tsx
484
643
  <AiSidebar
485
- adapter={adapter}
486
644
  actions={[
487
645
  {
488
646
  icon: <MyAttachIcon />,
@@ -498,7 +656,7 @@ The toolbar buttons left of the send button are fully configurable:
498
656
  />
499
657
 
500
658
  // Hide the toolbar entirely:
501
- <AiSidebar adapter={adapter} actions={[]} />
659
+ <AiSidebar actions={[]} />
502
660
  ```
503
661
 
504
662
  Each action: `{ icon: ReactNode, label: string, onClick?: () => void }`
@@ -516,3 +674,11 @@ Each action: `{ icon: ReactNode, label: string, onClick?: () => void }`
516
674
  ## License
517
675
 
518
676
  MIT
677
+
678
+ ---
679
+
680
+ ## Icon attribution
681
+
682
+ Icons used internally by Juneau are from [Font Awesome Free](https://fontawesome.com) (v6), licensed under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/). SVG paths are inlined directly — no runtime dependency on the Font Awesome package.
683
+
684
+ Icons used: `crow`, `paper-plane`, `paperclip`, `bolt`, `plus`, `clock-rotate-left`, `scroll`, `arrow-rotate-left`, `window-minimize`, `window-restore`.
@@ -5,6 +5,7 @@ type Props = {
5
5
  messages: AiMessage[];
6
6
  input: string;
7
7
  isLoading: boolean;
8
+ isConnecting: boolean;
8
9
  error: string | null;
9
10
  onInputChange: (value: string) => void;
10
11
  onSend: () => void;
@@ -34,6 +35,6 @@ type Props = {
34
35
  * Used by AiSidebar (inside the sidebar chrome) and directly in dashboard
35
36
  * or any other layout where you want chat without the sidebar wrapper.
36
37
  */
37
- export declare function AiChat({ messages, input, isLoading, error, onInputChange, onSend, onStop, onProposalConfirm, onProposalCancel, placeholder, assistantIcon, actions, sendIcon, className, style, }: Props): import("react").JSX.Element;
38
+ export declare function AiChat({ messages, input, isLoading, isConnecting, error, onInputChange, onSend, onStop, onProposalConfirm, onProposalCancel, placeholder, assistantIcon, actions, sendIcon, className, style, }: Props): import("react").JSX.Element;
38
39
  export {};
39
40
  //# sourceMappingURL=AiChat.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"AiChat.d.ts","sourceRoot":"","sources":["../../../src/components/AiChat/AiChat.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AACtD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAMxD,KAAK,KAAK,GAAG;IACX,QAAQ,EAAE,SAAS,EAAE,CAAC;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,OAAO,CAAC;IACnB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,aAAa,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC,MAAM,EAAE,MAAM,IAAI,CAAC;IACnB,iGAAiG;IACjG,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IACpB,iBAAiB,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC;IAClE,gBAAgB,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/C,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,6EAA6E;IAC7E,aAAa,CAAC,EAAE,SAAS,CAAC;IAC1B;;;;OAIG;IACH,OAAO,CAAC,EAAE,aAAa,EAAE,CAAC;IAC1B,qEAAqE;IACrE,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,aAAa,CAAC;CACvB,CAAC;AAEF;;;;;;;GAOG;AACH,wBAAgB,MAAM,CAAC,EACrB,QAAQ,EACR,KAAK,EACL,SAAS,EACT,KAAK,EACL,aAAa,EACb,MAAM,EACN,MAAM,EACN,iBAAiB,EACjB,gBAAgB,EAChB,WAAW,EACX,aAAa,EACb,OAAO,EACP,QAAQ,EACR,SAAS,EACT,KAAK,GACN,EAAE,KAAK,+BA4BP"}
1
+ {"version":3,"file":"AiChat.d.ts","sourceRoot":"","sources":["../../../src/components/AiChat/AiChat.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AACtD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAMxD,KAAK,KAAK,GAAG;IACX,QAAQ,EAAE,SAAS,EAAE,CAAC;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,OAAO,CAAC;IACnB,YAAY,EAAE,OAAO,CAAC;IACtB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,aAAa,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC,MAAM,EAAE,MAAM,IAAI,CAAC;IACnB,iGAAiG;IACjG,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IACpB,iBAAiB,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC;IAClE,gBAAgB,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/C,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,6EAA6E;IAC7E,aAAa,CAAC,EAAE,SAAS,CAAC;IAC1B;;;;OAIG;IACH,OAAO,CAAC,EAAE,aAAa,EAAE,CAAC;IAC1B,qEAAqE;IACrE,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,aAAa,CAAC;CACvB,CAAC;AAEF;;;;;;;GAOG;AACH,wBAAgB,MAAM,CAAC,EACrB,QAAQ,EACR,KAAK,EACL,SAAS,EACT,YAAY,EACZ,KAAK,EACL,aAAa,EACb,MAAM,EACN,MAAM,EACN,iBAAiB,EACjB,gBAAgB,EAChB,WAAW,EACX,aAAa,EACb,OAAO,EACP,QAAQ,EACR,SAAS,EACT,KAAK,GACN,EAAE,KAAK,+BA6BP"}
@@ -3,11 +3,13 @@ import type { AiMessage } from '../../core/types';
3
3
  type Props = {
4
4
  messages: AiMessage[];
5
5
  isLoading: boolean;
6
+ /** True while waiting for the first token — shows a "connecting" label inside the typing indicator. */
7
+ isConnecting: boolean;
6
8
  onProposalConfirm: (proposalId: string, payload: unknown) => void;
7
9
  onProposalCancel: (proposalId: string) => void;
8
10
  /** Override the assistant avatar icon. Forwarded to every AiMessageBubble. */
9
11
  assistantIcon?: ReactNode;
10
12
  };
11
- export declare function AiMessageList({ messages, isLoading, onProposalConfirm, onProposalCancel, assistantIcon }: Props): import("react").JSX.Element;
13
+ export declare function AiMessageList({ messages, isLoading, isConnecting, onProposalConfirm, onProposalCancel, assistantIcon }: Props): import("react").JSX.Element;
12
14
  export {};
13
15
  //# sourceMappingURL=AiMessageList.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"AiMessageList.d.ts","sourceRoot":"","sources":["../../../src/components/AiMessageList/AiMessageList.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AACvC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAMlD,KAAK,KAAK,GAAG;IACX,QAAQ,EAAE,SAAS,EAAE,CAAC;IACtB,SAAS,EAAE,OAAO,CAAC;IACnB,iBAAiB,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC;IAClE,gBAAgB,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/C,8EAA8E;IAC9E,aAAa,CAAC,EAAE,SAAS,CAAC;CAC3B,CAAC;AAEF,wBAAgB,aAAa,CAAC,EAAE,QAAQ,EAAE,SAAS,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,aAAa,EAAE,EAAE,KAAK,+BAmC/G"}
1
+ {"version":3,"file":"AiMessageList.d.ts","sourceRoot":"","sources":["../../../src/components/AiMessageList/AiMessageList.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AACvC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAMlD,KAAK,KAAK,GAAG;IACX,QAAQ,EAAE,SAAS,EAAE,CAAC;IACtB,SAAS,EAAE,OAAO,CAAC;IACnB,uGAAuG;IACvG,YAAY,EAAE,OAAO,CAAC;IACtB,iBAAiB,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC;IAClE,gBAAgB,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/C,8EAA8E;IAC9E,aAAa,CAAC,EAAE,SAAS,CAAC;CAC3B,CAAC;AAEF,wBAAgB,aAAa,CAAC,EAAE,QAAQ,EAAE,SAAS,EAAE,YAAY,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,aAAa,EAAE,EAAE,KAAK,+BAmC7H"}
@@ -1 +1 @@
1
- {"version":3,"file":"AiSidebar.d.ts","sourceRoot":"","sources":["../../../src/components/AiSidebar/AiSidebar.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AACtD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACzD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAMxD,KAAK,KAAK,GAAG;IACX,OAAO,EAAE,gBAAgB,CAAC;IAC1B,iEAAiE;IACjE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,iBAAiB,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC;IACnE,gBAAgB,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,IAAI,CAAC;IAChD,+EAA+E;IAC/E,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB;;;;OAIG;IACH,OAAO,CAAC,EAAE,aAAa,EAAE,CAAC;IAC1B,qEAAqE;IACrE,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,aAAa,CAAC;CACvB,CAAC;AAEF,wBAAgB,SAAS,CAAC,EACxB,OAAO,EACP,KAAK,EACL,OAAO,EACP,iBAAiB,EACjB,gBAAgB,EAChB,IAAI,EACJ,OAAO,EACP,QAAQ,EACR,SAAS,EACT,KAAK,GACN,EAAE,KAAK,+BA+CP"}
1
+ {"version":3,"file":"AiSidebar.d.ts","sourceRoot":"","sources":["../../../src/components/AiSidebar/AiSidebar.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AACtD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACzD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAMxD,KAAK,KAAK,GAAG;IACX,OAAO,EAAE,gBAAgB,CAAC;IAC1B,iEAAiE;IACjE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,iBAAiB,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC;IACnE,gBAAgB,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,IAAI,CAAC;IAChD,+EAA+E;IAC/E,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB;;;;OAIG;IACH,OAAO,CAAC,EAAE,aAAa,EAAE,CAAC;IAC1B,qEAAqE;IACrE,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,aAAa,CAAC;CACvB,CAAC;AAEF,wBAAgB,SAAS,CAAC,EACxB,OAAO,EACP,KAAK,EACL,OAAO,EACP,iBAAiB,EACjB,gBAAgB,EAChB,IAAI,EACJ,OAAO,EACP,QAAQ,EACR,SAAS,EACT,KAAK,GACN,EAAE,KAAK,+BAiDP"}
@@ -1,2 +1,7 @@
1
- export declare function AiTypingIndicator(): import("react").JSX.Element;
1
+ type Props = {
2
+ /** When true, shows a "Connecting…" label instead of just the dots. */
3
+ connecting?: boolean;
4
+ };
5
+ export declare function AiTypingIndicator({ connecting }: Props): import("react").JSX.Element;
6
+ export {};
2
7
  //# sourceMappingURL=AiTypingIndicator.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"AiTypingIndicator.d.ts","sourceRoot":"","sources":["../../../src/components/AiTypingIndicator/AiTypingIndicator.tsx"],"names":[],"mappings":"AAEA,wBAAgB,iBAAiB,gCAQhC"}
1
+ {"version":3,"file":"AiTypingIndicator.d.ts","sourceRoot":"","sources":["../../../src/components/AiTypingIndicator/AiTypingIndicator.tsx"],"names":[],"mappings":"AAGA,KAAK,KAAK,GAAG;IACX,uEAAuE;IACvE,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB,CAAC;AAEF,wBAAgB,iBAAiB,CAAC,EAAE,UAAkB,EAAE,EAAE,KAAK,+BAgB9D"}
@@ -1,5 +1,20 @@
1
- import type { AiStreamEvent } from './types';
1
+ import type { AiMessage, AiStreamEvent } from './types';
2
2
  export declare const delay: (ms: number) => Promise<void>;
3
+ /**
4
+ * Extracts the plain text content from a message.
5
+ *
6
+ * Concatenates all `text` parts in order. Non-text parts (tables, proposals,
7
+ * errors) are skipped. Returns an empty string if the message has no text parts.
8
+ *
9
+ * Use this in your adapter when you need to read a message's text without
10
+ * digging into the parts array yourself:
11
+ *
12
+ * @example
13
+ * const { messages } = input;
14
+ * const lastUser = [...messages].reverse().find(m => m.role === 'user');
15
+ * const text = lastUser ? getMessageText(lastUser) : '';
16
+ */
17
+ export declare function getMessageText(message: AiMessage): string;
3
18
  /**
4
19
  * Iterates over an AsyncIterable of stream events and calls the handler for each.
5
20
  * Automatically handles cancellation via AbortSignal.
@@ -1 +1 @@
1
- {"version":3,"file":"stream.d.ts","sourceRoot":"","sources":["../../src/core/stream.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAE7C,eAAO,MAAM,KAAK,GAAI,IAAI,MAAM,KAAG,OAAO,CAAC,IAAI,CACE,CAAC;AAElD;;;GAGG;AACH,wBAAsB,aAAa,CACjC,QAAQ,EAAE,aAAa,CAAC,aAAa,CAAC,EACtC,OAAO,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,EACvC,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,IAAI,CAAC,CAMf"}
1
+ {"version":3,"file":"stream.d.ts","sourceRoot":"","sources":["../../src/core/stream.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAExD,eAAO,MAAM,KAAK,GAAI,IAAI,MAAM,KAAG,OAAO,CAAC,IAAI,CACE,CAAC;AAElD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,SAAS,GAAG,MAAM,CAKzD;AAED;;;GAGG;AACH,wBAAsB,aAAa,CACjC,QAAQ,EAAE,aAAa,CAAC,aAAa,CAAC,EACtC,OAAO,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,EACvC,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,IAAI,CAAC,CAMf"}
@@ -24,6 +24,12 @@ export type UseAiChatReturn = {
24
24
  /** Aborts the in-flight stream. No-op if nothing is streaming. */
25
25
  stop: () => void;
26
26
  isLoading: boolean;
27
+ /**
28
+ * True from the moment a message is sent until the first token arrives from the stream.
29
+ * Use this to show a "connecting…" state distinct from the typing indicator shown
30
+ * while tokens are actively streaming in.
31
+ */
32
+ isConnecting: boolean;
27
33
  error: string | null;
28
34
  reset: () => void;
29
35
  confirmProposal: (proposalId: string, payload: unknown) => void;
@@ -1 +1 @@
1
- {"version":3,"file":"useAiChat.d.ts","sourceRoot":"","sources":["../../src/hooks/useAiChat.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,SAAS,EAET,gBAAgB,EAEjB,MAAM,eAAe,CAAC;AAKvB,MAAM,MAAM,gBAAgB,GAAG;IAC7B,OAAO,EAAE,gBAAgB,CAAC;IAC1B,4DAA4D;IAC5D,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,0CAA0C;IAC1C,iBAAiB,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC;IACnE,0CAA0C;IAC1C,gBAAgB,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,IAAI,CAAC;CACjD,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,QAAQ,EAAE,SAAS,EAAE,CAAC;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,WAAW,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACjC,mHAAmH;IACnH,mBAAmB,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACrD;;;;OAIG;IACH,YAAY,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACrD,kEAAkE;IAClE,IAAI,EAAE,MAAM,IAAI,CAAC;IACjB,SAAS,EAAE,OAAO,CAAC;IACnB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB,eAAe,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC;IAChE,cAAc,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,IAAI,CAAC;CAC9C,CAAC;AAEF,wBAAgB,SAAS,CAAC,EACxB,OAAO,EACP,OAAO,EACP,iBAAiB,EACjB,gBAAgB,EACjB,EAAE,gBAAgB,GAAG,eAAe,CA4LpC"}
1
+ {"version":3,"file":"useAiChat.d.ts","sourceRoot":"","sources":["../../src/hooks/useAiChat.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,SAAS,EAET,gBAAgB,EAEjB,MAAM,eAAe,CAAC;AAKvB,MAAM,MAAM,gBAAgB,GAAG;IAC7B,OAAO,EAAE,gBAAgB,CAAC;IAC1B,4DAA4D;IAC5D,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,0CAA0C;IAC1C,iBAAiB,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC;IACnE,0CAA0C;IAC1C,gBAAgB,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,IAAI,CAAC;CACjD,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,QAAQ,EAAE,SAAS,EAAE,CAAC;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,WAAW,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACjC,mHAAmH;IACnH,mBAAmB,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACrD;;;;OAIG;IACH,YAAY,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACrD,kEAAkE;IAClE,IAAI,EAAE,MAAM,IAAI,CAAC;IACjB,SAAS,EAAE,OAAO,CAAC;IACnB;;;;OAIG;IACH,YAAY,EAAE,OAAO,CAAC;IACtB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB,eAAe,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC;IAChE,cAAc,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,IAAI,CAAC;CAC9C,CAAC;AAEF,wBAAgB,SAAS,CAAC,EACxB,OAAO,EACP,OAAO,EACP,iBAAiB,EACjB,gBAAgB,EACjB,EAAE,gBAAgB,GAAG,eAAe,CAuMpC"}
@@ -1 +1 @@
1
- {"version":3,"file":"cs.d.ts","sourceRoot":"","sources":["../../../src/i18n/locales/cs.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAE7C,eAAO,MAAM,QAAQ,EAAE,YAgBtB,CAAC"}
1
+ {"version":3,"file":"cs.d.ts","sourceRoot":"","sources":["../../../src/i18n/locales/cs.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAE7C,eAAO,MAAM,QAAQ,EAAE,YAkBtB,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"en.d.ts","sourceRoot":"","sources":["../../../src/i18n/locales/en.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAE7C,eAAO,MAAM,QAAQ,EAAE,YAgBtB,CAAC"}
1
+ {"version":3,"file":"en.d.ts","sourceRoot":"","sources":["../../../src/i18n/locales/en.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAE7C,eAAO,MAAM,QAAQ,EAAE,YAkBtB,CAAC"}
@@ -10,6 +10,8 @@ export type JuneauLabels = {
10
10
  inputPlaceholder: string;
11
11
  sendMessage: string;
12
12
  stopMessage: string;
13
+ typingMessage: string;
14
+ connectingMessage: string;
13
15
  actionAddFile: string;
14
16
  actionQuickActions: string;
15
17
  actionNew: string;
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/i18n/types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,MAAM,MAAM,YAAY,GAAG;IAEzB,YAAY,EAAE,MAAM,CAAC;IACrB,iBAAiB,EAAE,MAAM,CAAC;IAG1B,gBAAgB,EAAE,MAAM,CAAC;IACzB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;IAGpB,cAAc,EAAE,MAAM,CAAC;IACvB,cAAc,EAAE,MAAM,CAAC;IAGvB,eAAe,EAAE,MAAM,CAAC;IACxB,cAAc,EAAE,MAAM,CAAC;IAGvB,YAAY,EAAE,MAAM,CAAC;CACtB,CAAC"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/i18n/types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,MAAM,MAAM,YAAY,GAAG;IAEzB,YAAY,EAAE,MAAM,CAAC;IACrB,iBAAiB,EAAE,MAAM,CAAC;IAG1B,gBAAgB,EAAE,MAAM,CAAC;IACzB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IAGpB,aAAa,EAAE,MAAM,CAAC;IACtB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,aAAa,EAAE,MAAM,CAAC;IACtB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;IAGpB,cAAc,EAAE,MAAM,CAAC;IACvB,cAAc,EAAE,MAAM,CAAC;IAGvB,eAAe,EAAE,MAAM,CAAC;IACxB,cAAc,EAAE,MAAM,CAAC;IAGvB,YAAY,EAAE,MAAM,CAAC;CACtB,CAAC"}