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 +194 -28
- package/dist/components/AiChat/AiChat.d.ts +2 -1
- package/dist/components/AiChat/AiChat.d.ts.map +1 -1
- package/dist/components/AiMessageList/AiMessageList.d.ts +3 -1
- package/dist/components/AiMessageList/AiMessageList.d.ts.map +1 -1
- package/dist/components/AiSidebar/AiSidebar.d.ts.map +1 -1
- package/dist/components/AiTypingIndicator/AiTypingIndicator.d.ts +6 -1
- package/dist/components/AiTypingIndicator/AiTypingIndicator.d.ts.map +1 -1
- package/dist/core/stream.d.ts +16 -1
- package/dist/core/stream.d.ts.map +1 -1
- package/dist/hooks/useAiChat.d.ts +6 -0
- package/dist/hooks/useAiChat.d.ts.map +1 -1
- package/dist/i18n/locales/cs.d.ts.map +1 -1
- package/dist/i18n/locales/en.d.ts.map +1 -1
- package/dist/i18n/types.d.ts +2 -0
- package/dist/i18n/types.d.ts.map +1 -1
- package/dist/index.cjs +24 -24
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +974 -951
- package/dist/index.js.map +1 -1
- package/dist/style.css +1 -1
- package/package.json +1 -1
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
|
-
<
|
|
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 }
|
|
45
|
-
| { type: 'part'; part:
|
|
46
|
-
| { type: 'done' }
|
|
47
|
-
| { type: 'error'; message: string }
|
|
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
|
-
|
|
200
|
+
// Pass to AiChatProvider — see below
|
|
197
201
|
```
|
|
198
202
|
|
|
199
203
|
---
|
|
200
204
|
|
|
201
|
-
##
|
|
205
|
+
## Providers
|
|
202
206
|
|
|
203
207
|
### `<JuneauProvider>`
|
|
204
208
|
|
|
205
|
-
Wrap your app
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
| `
|
|
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
|
-
| `
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
421
|
+
const { setContext } = useAiChatContext();
|
|
422
|
+
|
|
423
|
+
useEffect(() => {
|
|
424
|
+
setContext({ pageId: 'invoices', entityId: invoice.id, userRole: 'admin' });
|
|
425
|
+
}, [invoice.id]);
|
|
349
426
|
```
|
|
350
|
-
|
|
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
|
-
| `
|
|
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
|
|
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,+
|
|
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,+
|
|
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,+
|
|
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
|
-
|
|
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":"
|
|
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"}
|
package/dist/core/stream.d.ts
CHANGED
|
@@ -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;
|
|
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,
|
|
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,
|
|
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,
|
|
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"}
|
package/dist/i18n/types.d.ts
CHANGED
package/dist/i18n/types.d.ts.map
CHANGED
|
@@ -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;
|
|
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"}
|