@pikku/assistant-ui 0.12.3 → 0.12.5
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/CHANGELOG.md +34 -1
- package/dist/cjs/pikku-agent-chat.d.ts +16 -0
- package/dist/cjs/pikku-agent-chat.js +162 -70
- package/dist/cjs/use-pikku-agent-runtime.js +70 -19
- package/dist/esm/pikku-agent-chat.d.ts +16 -0
- package/dist/esm/pikku-agent-chat.js +175 -76
- package/dist/esm/use-pikku-agent-runtime.js +70 -19
- package/package.json +1 -1
- package/src/pikku-agent-chat.tsx +379 -154
- package/src/use-pikku-agent-runtime.ts +103 -15
package/CHANGELOG.md
CHANGED
|
@@ -1,11 +1,44 @@
|
|
|
1
1
|
# @pikku/assistant-ui
|
|
2
2
|
|
|
3
|
+
## 0.12.5
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 9060165: The console now shows function version history, live queue depths with a Failed column, and scheduler last-run status with run history. Workflow canvas and run selector have been polished. The console build is ~6.5× faster thanks to a switch to rolldown-vite (Vite 7 + Oxc React transform).
|
|
8
|
+
|
|
9
|
+
## 0.12.4
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- 424c777: `PikkuAgentChat` now accepts a `toolComponents` prop — a map of
|
|
14
|
+
`toolName` → React component — for per-tool custom rendering inside
|
|
15
|
+
the assistant bubble. Unmatched tool calls continue to fall through to
|
|
16
|
+
the default expandable tool-call display.
|
|
17
|
+
|
|
18
|
+
This unlocks generative-UI patterns: register a `renderWidget` tool on
|
|
19
|
+
the agent, return structured props from it, and mount real UI (charts,
|
|
20
|
+
diffs, cards) inline in the chat from the persisted tool-call args.
|
|
21
|
+
Because the rendered widget is just a tool call under the hood, it
|
|
22
|
+
survives refresh, streams correctly, and stays part of the thread's
|
|
23
|
+
history.
|
|
24
|
+
|
|
25
|
+
```tsx
|
|
26
|
+
<PikkuAgentChat
|
|
27
|
+
agentName="myAgent"
|
|
28
|
+
threadId={threadId}
|
|
29
|
+
resourceId={userId}
|
|
30
|
+
api="/rpc/agent"
|
|
31
|
+
toolComponents={{
|
|
32
|
+
renderWidget: ({ args }) => <WidgetRegistry spec={args} />,
|
|
33
|
+
}}
|
|
34
|
+
/>
|
|
35
|
+
```
|
|
36
|
+
|
|
3
37
|
## 0.12.3
|
|
4
38
|
|
|
5
39
|
### Patch Changes
|
|
6
40
|
|
|
7
41
|
- f85c234: Add unified credential system with per-user OAuth and AI agent pre-flight checks
|
|
8
|
-
|
|
9
42
|
- Unified CredentialService with lazy loading per user via pikkuUserId
|
|
10
43
|
- wire.getCredential() for typed single credential lookup
|
|
11
44
|
- MissingCredentialError with structured payload for client-side connect flows
|
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
import { type ComponentType, type ReactNode } from 'react';
|
|
2
|
+
import { type ToolCallMessagePartComponent } from '@assistant-ui/react';
|
|
1
3
|
import { type PikkuAgentRuntimeOptions } from './use-pikku-agent-runtime.js';
|
|
2
4
|
export interface PikkuAgentChatProps extends PikkuAgentRuntimeOptions {
|
|
5
|
+
initialPrompt?: string;
|
|
3
6
|
emptyMessage?: string;
|
|
4
7
|
/** Hide tool calls from the chat display.
|
|
5
8
|
* - `true`: hide all non-approval tool calls
|
|
@@ -9,5 +12,18 @@ export interface PikkuAgentChatProps extends PikkuAgentRuntimeOptions {
|
|
|
9
12
|
dark?: boolean;
|
|
10
13
|
/** Max width of the chat content area. Defaults to 768. Set to 'none' for full width. */
|
|
11
14
|
maxWidth?: number | 'none';
|
|
15
|
+
/**
|
|
16
|
+
* Per-tool renderers. Map `toolName` → React component to replace the
|
|
17
|
+
* default expandable tool-call box for that tool. Enables generative-UI
|
|
18
|
+
* patterns: e.g. register a `renderWidget` tool on the agent and mount
|
|
19
|
+
* real UI (charts, diffs, cards) inline in the assistant bubble from the
|
|
20
|
+
* persisted tool-call args.
|
|
21
|
+
*
|
|
22
|
+
* Any tool without an entry here falls through to the default renderer
|
|
23
|
+
* (which still respects `hideToolCalls` and the approval-request UI).
|
|
24
|
+
*/
|
|
25
|
+
toolComponents?: Record<string, ToolCallMessagePartComponent>;
|
|
26
|
+
renderAssistantText?: (text: string) => ReactNode;
|
|
27
|
+
generativeUIComponents?: Record<string, ComponentType<any>>;
|
|
12
28
|
}
|
|
13
29
|
export declare function PikkuAgentChat(props: PikkuAgentChatProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -59,6 +59,9 @@ const darkColors = {
|
|
|
59
59
|
};
|
|
60
60
|
const ColorsContext = (0, react_1.createContext)(lightColors);
|
|
61
61
|
const HideToolCallsContext = (0, react_1.createContext)(undefined);
|
|
62
|
+
const ToolComponentsContext = (0, react_1.createContext)(undefined);
|
|
63
|
+
const GenerativeUIComponentsContext = (0, react_1.createContext)(undefined);
|
|
64
|
+
const RenderAssistantTextContext = (0, react_1.createContext)(undefined);
|
|
62
65
|
function shouldHideToolCall(hideToolCalls, toolName) {
|
|
63
66
|
if (!hideToolCalls)
|
|
64
67
|
return false;
|
|
@@ -149,7 +152,9 @@ const ToolCallDisplay = ({ toolCallId, toolName, args, result, status, addResult
|
|
|
149
152
|
padding: '2px 6px',
|
|
150
153
|
borderRadius: 3,
|
|
151
154
|
background: responded === 'approved' ? colors.successBg : colors.errorBg,
|
|
152
|
-
color: responded === 'approved'
|
|
155
|
+
color: responded === 'approved'
|
|
156
|
+
? colors.successColor
|
|
157
|
+
: colors.errorColor,
|
|
153
158
|
}, children: responded })] }));
|
|
154
159
|
}
|
|
155
160
|
return ((0, jsx_runtime_1.jsxs)("div", { style: {
|
|
@@ -199,7 +204,12 @@ const ToolCallDisplay = ({ toolCallId, toolName, args, result, status, addResult
|
|
|
199
204
|
borderRadius: 4,
|
|
200
205
|
overflow: 'auto',
|
|
201
206
|
color: colors.text,
|
|
202
|
-
}, children: JSON.stringify(displayArgs, null, 2) }), result !== undefined && ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("div", { style: {
|
|
207
|
+
}, children: JSON.stringify(displayArgs, null, 2) }), result !== undefined && ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("div", { style: {
|
|
208
|
+
fontSize: 12,
|
|
209
|
+
color: colors.textMuted,
|
|
210
|
+
marginTop: 8,
|
|
211
|
+
marginBottom: 2,
|
|
212
|
+
}, children: "Result:" }), (0, jsx_runtime_1.jsx)("pre", { style: {
|
|
203
213
|
fontSize: 11,
|
|
204
214
|
background: colors.codeBg,
|
|
205
215
|
padding: 8,
|
|
@@ -210,25 +220,68 @@ const ToolCallDisplay = ({ toolCallId, toolName, args, result, status, addResult
|
|
|
210
220
|
? result
|
|
211
221
|
: JSON.stringify(result, null, 2) })] }))] }))] }));
|
|
212
222
|
};
|
|
213
|
-
const MarkdownText = ({ text, colors }) => {
|
|
223
|
+
const MarkdownText = ({ text, colors, }) => {
|
|
214
224
|
const components = (0, react_1.useMemo)(() => ({
|
|
215
|
-
p: ({ children }) => ((0, jsx_runtime_1.jsx)("p", { style: {
|
|
225
|
+
p: ({ children }) => ((0, jsx_runtime_1.jsx)("p", { style: {
|
|
226
|
+
margin: '0 0 8px',
|
|
227
|
+
fontSize: 14,
|
|
228
|
+
lineHeight: 1.6,
|
|
229
|
+
color: colors.text,
|
|
230
|
+
}, children: children })),
|
|
216
231
|
strong: ({ children }) => ((0, jsx_runtime_1.jsx)("strong", { style: { fontWeight: 600, color: colors.text }, children: children })),
|
|
217
232
|
em: ({ children }) => ((0, jsx_runtime_1.jsx)("em", { style: { color: colors.text }, children: children })),
|
|
218
|
-
ul: ({ children }) => ((0, jsx_runtime_1.jsx)("ul", { style: {
|
|
219
|
-
|
|
233
|
+
ul: ({ children }) => ((0, jsx_runtime_1.jsx)("ul", { style: {
|
|
234
|
+
margin: '4px 0 8px',
|
|
235
|
+
paddingLeft: 20,
|
|
236
|
+
fontSize: 14,
|
|
237
|
+
color: colors.text,
|
|
238
|
+
}, children: children })),
|
|
239
|
+
ol: ({ children }) => ((0, jsx_runtime_1.jsx)("ol", { style: {
|
|
240
|
+
margin: '4px 0 8px',
|
|
241
|
+
paddingLeft: 20,
|
|
242
|
+
fontSize: 14,
|
|
243
|
+
color: colors.text,
|
|
244
|
+
}, children: children })),
|
|
220
245
|
li: ({ children }) => ((0, jsx_runtime_1.jsx)("li", { style: { marginBottom: 2, lineHeight: 1.6 }, children: children })),
|
|
221
246
|
code: ({ children, className }) => {
|
|
222
247
|
const isBlock = className === null || className === void 0 ? void 0 : className.startsWith('language-');
|
|
223
248
|
if (isBlock) {
|
|
224
|
-
return ((0, jsx_runtime_1.jsx)("pre", { style: {
|
|
249
|
+
return ((0, jsx_runtime_1.jsx)("pre", { style: {
|
|
250
|
+
background: colors.codeBg,
|
|
251
|
+
padding: 10,
|
|
252
|
+
borderRadius: 4,
|
|
253
|
+
overflow: 'auto',
|
|
254
|
+
margin: '4px 0 8px',
|
|
255
|
+
fontSize: 12,
|
|
256
|
+
}, children: (0, jsx_runtime_1.jsx)("code", { style: { color: colors.text }, children: children }) }));
|
|
225
257
|
}
|
|
226
|
-
return ((0, jsx_runtime_1.jsx)("code", { style: {
|
|
258
|
+
return ((0, jsx_runtime_1.jsx)("code", { style: {
|
|
259
|
+
background: colors.codeBg,
|
|
260
|
+
padding: '1px 4px',
|
|
261
|
+
borderRadius: 3,
|
|
262
|
+
fontSize: 13,
|
|
263
|
+
color: colors.text,
|
|
264
|
+
}, children: children }));
|
|
227
265
|
},
|
|
228
266
|
pre: ({ children }) => (0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, { children: children }),
|
|
229
|
-
h1: ({ children }) => (0, jsx_runtime_1.jsx)("h3", { style: {
|
|
230
|
-
|
|
231
|
-
|
|
267
|
+
h1: ({ children }) => ((0, jsx_runtime_1.jsx)("h3", { style: {
|
|
268
|
+
margin: '8px 0 4px',
|
|
269
|
+
fontSize: 16,
|
|
270
|
+
fontWeight: 600,
|
|
271
|
+
color: colors.text,
|
|
272
|
+
}, children: children })),
|
|
273
|
+
h2: ({ children }) => ((0, jsx_runtime_1.jsx)("h4", { style: {
|
|
274
|
+
margin: '8px 0 4px',
|
|
275
|
+
fontSize: 15,
|
|
276
|
+
fontWeight: 600,
|
|
277
|
+
color: colors.text,
|
|
278
|
+
}, children: children })),
|
|
279
|
+
h3: ({ children }) => ((0, jsx_runtime_1.jsx)("h5", { style: {
|
|
280
|
+
margin: '8px 0 4px',
|
|
281
|
+
fontSize: 14,
|
|
282
|
+
fontWeight: 600,
|
|
283
|
+
color: colors.text,
|
|
284
|
+
}, children: children })),
|
|
232
285
|
a: ({ href, children }) => ((0, jsx_runtime_1.jsx)("a", { href: href, target: "_blank", rel: "noopener noreferrer", style: { color: colors.textMuted, textDecoration: 'underline' }, children: children })),
|
|
233
286
|
}), [colors]);
|
|
234
287
|
return (0, jsx_runtime_1.jsx)(react_markdown_1.default, { components: components, children: text });
|
|
@@ -249,11 +302,18 @@ const UserMessage = () => {
|
|
|
249
302
|
borderRadius: 12,
|
|
250
303
|
backgroundColor: colors.userBubble,
|
|
251
304
|
}, children: (0, jsx_runtime_1.jsx)(react_2.MessagePrimitive.Content, { components: {
|
|
252
|
-
Text: ({ text }) => ((0, jsx_runtime_1.jsx)("span", { style: {
|
|
305
|
+
Text: ({ text }) => ((0, jsx_runtime_1.jsx)("span", { style: {
|
|
306
|
+
fontSize: 14,
|
|
307
|
+
whiteSpace: 'pre-wrap',
|
|
308
|
+
color: colors.text,
|
|
309
|
+
}, children: text })),
|
|
253
310
|
} }) })] }) }));
|
|
254
311
|
};
|
|
255
312
|
const AssistantMessage = () => {
|
|
256
313
|
const colors = (0, react_1.useContext)(ColorsContext);
|
|
314
|
+
const toolComponents = (0, react_1.useContext)(ToolComponentsContext);
|
|
315
|
+
const generativeUIComponents = (0, react_1.useContext)(GenerativeUIComponentsContext);
|
|
316
|
+
const renderAssistantText = (0, react_1.useContext)(RenderAssistantTextContext);
|
|
257
317
|
return ((0, jsx_runtime_1.jsx)("div", { style: {
|
|
258
318
|
display: 'flex',
|
|
259
319
|
justifyContent: 'flex-start',
|
|
@@ -262,12 +322,16 @@ const AssistantMessage = () => {
|
|
|
262
322
|
padding: 12,
|
|
263
323
|
borderRadius: 12,
|
|
264
324
|
backgroundColor: colors.assistantBubble,
|
|
265
|
-
}, children: [(0, jsx_runtime_1.jsx)(react_2.MessagePrimitive.Content, { components: {
|
|
266
|
-
|
|
267
|
-
tools: {
|
|
325
|
+
}, children: [(0, jsx_runtime_1.jsx)(react_2.MessagePrimitive.Content, { components: Object.assign({ Text: ({ text }) => renderAssistantText ? ((0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, { children: renderAssistantText(text) })) : ((0, jsx_runtime_1.jsx)(MarkdownText, { text: text, colors: colors })), tools: {
|
|
326
|
+
by_name: toolComponents,
|
|
268
327
|
Fallback: (props) => ((0, jsx_runtime_1.jsx)(ToolCallDisplay, { toolCallId: props.toolCallId, toolName: props.toolName, args: props.args, result: props.result, status: (0, use_pikku_agent_runtime_js_1.resolvePikkuToolStatus)(props.status, props.result), addResult: props.addResult })),
|
|
269
|
-
},
|
|
270
|
-
|
|
328
|
+
} }, (generativeUIComponents
|
|
329
|
+
? {
|
|
330
|
+
generativeUI: {
|
|
331
|
+
components: generativeUIComponents,
|
|
332
|
+
},
|
|
333
|
+
}
|
|
334
|
+
: {})) }), (0, jsx_runtime_1.jsx)(react_2.MessagePrimitive.If, { last: true, children: (0, jsx_runtime_1.jsx)(react_2.ThreadPrimitive.If, { running: true, children: (0, jsx_runtime_1.jsx)("div", { style: {
|
|
271
335
|
display: 'flex',
|
|
272
336
|
alignItems: 'center',
|
|
273
337
|
gap: 6,
|
|
@@ -276,71 +340,99 @@ const AssistantMessage = () => {
|
|
|
276
340
|
color: colors.textMuted,
|
|
277
341
|
}, children: "Thinking..." }) }) })] })] }) }));
|
|
278
342
|
};
|
|
343
|
+
const ComposerPrefill = ({ text }) => {
|
|
344
|
+
const composer = (0, react_2.useComposerRuntime)();
|
|
345
|
+
const filled = (0, react_1.useRef)(false);
|
|
346
|
+
(0, react_1.useEffect)(() => {
|
|
347
|
+
if (filled.current || !text)
|
|
348
|
+
return;
|
|
349
|
+
filled.current = true;
|
|
350
|
+
if (composer.getState().text === '')
|
|
351
|
+
composer.setText(text);
|
|
352
|
+
}, [text, composer]);
|
|
353
|
+
return null;
|
|
354
|
+
};
|
|
279
355
|
const PikkuComposer = ({ disabled, }) => {
|
|
280
356
|
const colors = (0, react_1.useContext)(ColorsContext);
|
|
281
|
-
return ((0, jsx_runtime_1.jsx)("div", { style: { padding: '8px 0 16px' }, children: (0, jsx_runtime_1.jsx)(react_2.ComposerPrimitive.Root, { children: (0, jsx_runtime_1.jsxs)("div", { style: Object.assign({
|
|
282
|
-
|
|
357
|
+
return ((0, jsx_runtime_1.jsx)("div", { style: { padding: '8px 0 16px' }, children: (0, jsx_runtime_1.jsx)(react_2.ComposerPrimitive.Root, { children: (0, jsx_runtime_1.jsxs)("div", { style: Object.assign({ position: 'relative', zIndex: 2, display: 'flex', flexDirection: 'column', gap: 10, backgroundColor: colors.assistantBubble, border: `1px solid ${colors.border}`, borderRadius: 24, padding: '14px 12px 10px', boxShadow: darkColors.text === colors.text
|
|
358
|
+
? '0 14px 30px rgba(0,0,0,0.24)'
|
|
359
|
+
: '0 14px 30px rgba(0,0,0,0.08)' }, (disabled
|
|
360
|
+
? { opacity: 0.5, pointerEvents: 'none' }
|
|
361
|
+
: {})), children: [(0, jsx_runtime_1.jsx)(react_2.ComposerPrimitive.Input, { placeholder: disabled ? 'Respond to approval request above...' : 'Message...', rows: 1, disabled: disabled !== null && disabled !== void 0 ? disabled : false, style: {
|
|
362
|
+
width: '100%',
|
|
363
|
+
background: 'transparent',
|
|
283
364
|
border: 'none',
|
|
284
365
|
outline: 'none',
|
|
285
|
-
|
|
366
|
+
color: colors.text,
|
|
286
367
|
fontSize: 14,
|
|
287
368
|
fontFamily: 'inherit',
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
} }), (0, jsx_runtime_1.jsx)(
|
|
292
|
-
width:
|
|
293
|
-
height: 28,
|
|
294
|
-
borderRadius: '50%',
|
|
295
|
-
border: 'none',
|
|
296
|
-
background: disabled ? colors.textMuted : colors.sendBg,
|
|
297
|
-
color: colors.sendColor,
|
|
298
|
-
cursor: disabled ? 'not-allowed' : 'pointer',
|
|
369
|
+
resize: 'none',
|
|
370
|
+
lineHeight: 1.5,
|
|
371
|
+
overflowY: 'auto',
|
|
372
|
+
} }), (0, jsx_runtime_1.jsx)("div", { style: {
|
|
373
|
+
width: '100%',
|
|
299
374
|
display: 'flex',
|
|
300
375
|
alignItems: 'center',
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
376
|
+
gap: 6,
|
|
377
|
+
minWidth: 0,
|
|
378
|
+
justifyContent: 'flex-end',
|
|
379
|
+
}, children: (0, jsx_runtime_1.jsx)(react_2.ComposerPrimitive.Send, { disabled: disabled !== null && disabled !== void 0 ? disabled : false, style: {
|
|
380
|
+
width: 30,
|
|
381
|
+
height: 30,
|
|
382
|
+
borderRadius: 999,
|
|
383
|
+
border: `1px solid ${colors.border}`,
|
|
384
|
+
background: '#e8e8e8',
|
|
385
|
+
color: '#111111',
|
|
386
|
+
cursor: disabled ? 'not-allowed' : 'pointer',
|
|
387
|
+
display: 'flex',
|
|
388
|
+
alignItems: 'center',
|
|
389
|
+
justifyContent: 'center',
|
|
390
|
+
flexShrink: 0,
|
|
391
|
+
transition: 'background-color 150ms ease, color 150ms ease, border-color 150ms ease',
|
|
392
|
+
}, children: "\u2191" }) })] }) }) }));
|
|
306
393
|
};
|
|
307
394
|
function PikkuAgentChat(props) {
|
|
308
|
-
const { emptyMessage, hideToolCalls, dark, maxWidth = 768 } = props, runtimeOptions = __rest(props, ["emptyMessage", "hideToolCalls", "dark", "maxWidth"]);
|
|
395
|
+
const { emptyMessage, hideToolCalls, dark, maxWidth = 768, toolComponents, renderAssistantText, generativeUIComponents, initialPrompt } = props, runtimeOptions = __rest(props, ["emptyMessage", "hideToolCalls", "dark", "maxWidth", "toolComponents", "renderAssistantText", "generativeUIComponents", "initialPrompt"]);
|
|
309
396
|
const { runtime, isAwaitingApproval, pendingApprovals, handleApproval } = (0, use_pikku_agent_runtime_js_1.usePikkuAgentRuntime)(runtimeOptions);
|
|
310
397
|
const colors = dark ? darkColors : lightColors;
|
|
311
|
-
return ((0, jsx_runtime_1.jsx)(ColorsContext.Provider, { value: colors, children: (0, jsx_runtime_1.jsx)(use_pikku_agent_runtime_js_1.PikkuApprovalContext.Provider, { value: { pendingApprovals, handleApproval }, children: (0, jsx_runtime_1.jsx)(HideToolCallsContext.Provider, { value: hideToolCalls, children: (0, jsx_runtime_1.jsx)(react_2.AssistantRuntimeProvider, { runtime: runtime, children: (0, jsx_runtime_1.jsx)("div", { style: {
|
|
312
|
-
|
|
313
|
-
display: 'flex',
|
|
314
|
-
flexDirection: 'column',
|
|
315
|
-
background: colors.bg,
|
|
316
|
-
}, children: (0, jsx_runtime_1.jsxs)(react_2.ThreadPrimitive.Root, { style: {
|
|
317
|
-
display: 'flex',
|
|
318
|
-
flexDirection: 'column',
|
|
319
|
-
flex: 1,
|
|
320
|
-
minHeight: 0,
|
|
321
|
-
}, children: [(0, jsx_runtime_1.jsx)(react_2.ThreadPrimitive.Viewport, { style: {
|
|
322
|
-
flex: 1,
|
|
323
|
-
minHeight: 0,
|
|
324
|
-
overflowY: 'auto',
|
|
325
|
-
}, children: (0, jsx_runtime_1.jsxs)("div", { style: {
|
|
326
|
-
maxWidth: maxWidth === 'none' ? undefined : maxWidth,
|
|
327
|
-
margin: '0 auto',
|
|
328
|
-
padding: 16,
|
|
398
|
+
return ((0, jsx_runtime_1.jsx)(ColorsContext.Provider, { value: colors, children: (0, jsx_runtime_1.jsx)(use_pikku_agent_runtime_js_1.PikkuApprovalContext.Provider, { value: { pendingApprovals, handleApproval }, children: (0, jsx_runtime_1.jsx)(HideToolCallsContext.Provider, { value: hideToolCalls, children: (0, jsx_runtime_1.jsx)(ToolComponentsContext.Provider, { value: toolComponents, children: (0, jsx_runtime_1.jsx)(GenerativeUIComponentsContext.Provider, { value: generativeUIComponents, children: (0, jsx_runtime_1.jsx)(RenderAssistantTextContext.Provider, { value: renderAssistantText, children: (0, jsx_runtime_1.jsxs)(react_2.AssistantRuntimeProvider, { runtime: runtime, children: [(0, jsx_runtime_1.jsx)(ComposerPrefill, { text: initialPrompt }), (0, jsx_runtime_1.jsx)("div", { style: {
|
|
399
|
+
height: '100%',
|
|
329
400
|
display: 'flex',
|
|
330
401
|
flexDirection: 'column',
|
|
331
|
-
|
|
332
|
-
}, children:
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
402
|
+
background: colors.bg,
|
|
403
|
+
}, children: (0, jsx_runtime_1.jsxs)(react_2.ThreadPrimitive.Root, { style: {
|
|
404
|
+
display: 'flex',
|
|
405
|
+
flexDirection: 'column',
|
|
406
|
+
flex: 1,
|
|
407
|
+
minHeight: 0,
|
|
408
|
+
}, children: [(0, jsx_runtime_1.jsx)(react_2.ThreadPrimitive.Viewport, { style: {
|
|
409
|
+
flex: 1,
|
|
410
|
+
minHeight: 0,
|
|
411
|
+
overflowY: 'auto',
|
|
412
|
+
}, children: (0, jsx_runtime_1.jsxs)("div", { style: {
|
|
413
|
+
maxWidth: maxWidth === 'none' ? undefined : maxWidth,
|
|
414
|
+
margin: '0 auto',
|
|
415
|
+
padding: 16,
|
|
416
|
+
display: 'flex',
|
|
417
|
+
flexDirection: 'column',
|
|
418
|
+
gap: 16,
|
|
419
|
+
}, children: [(0, jsx_runtime_1.jsx)(react_2.ThreadPrimitive.Empty, { children: (0, jsx_runtime_1.jsx)("div", { style: {
|
|
420
|
+
display: 'flex',
|
|
421
|
+
alignItems: 'center',
|
|
422
|
+
justifyContent: 'center',
|
|
423
|
+
minHeight: 300,
|
|
424
|
+
color: colors.textMuted,
|
|
425
|
+
textAlign: 'center',
|
|
426
|
+
fontSize: 14,
|
|
427
|
+
}, children: emptyMessage !== null && emptyMessage !== void 0 ? emptyMessage : (props.threadId
|
|
428
|
+
? 'Send a message to start the conversation.'
|
|
429
|
+
: 'Start a new conversation.') }) }), (0, jsx_runtime_1.jsx)(react_2.ThreadPrimitive.Messages, { components: {
|
|
430
|
+
UserMessage,
|
|
431
|
+
AssistantMessage,
|
|
432
|
+
} })] }) }), (0, jsx_runtime_1.jsx)("div", { style: {
|
|
433
|
+
maxWidth: maxWidth === 'none' ? undefined : maxWidth,
|
|
434
|
+
margin: '0 auto',
|
|
435
|
+
width: '100%',
|
|
436
|
+
padding: '0 16px',
|
|
437
|
+
}, children: (0, jsx_runtime_1.jsx)(PikkuComposer, { disabled: isAwaitingApproval }) })] }) })] }) }) }) }) }) }) }));
|
|
346
438
|
}
|
|
@@ -73,7 +73,7 @@ function parseSSEStream(reader) {
|
|
|
73
73
|
* Shared helper: consume an SSE stream and populate text/toolCalls.
|
|
74
74
|
* Returns an array of PendingApprovals when the stream requests them, or empty when done.
|
|
75
75
|
*/
|
|
76
|
-
function processStream(reader, text, toolCalls, yieldContent, onFinish) {
|
|
76
|
+
function processStream(reader, text, toolCalls, structuredParts, yieldContent, onFinish) {
|
|
77
77
|
return __awaiter(this, void 0, void 0, function* () {
|
|
78
78
|
var _a, e_1, _b, _c;
|
|
79
79
|
const pendingApprovals = [];
|
|
@@ -122,6 +122,31 @@ function processStream(reader, text, toolCalls, yieldContent, onFinish) {
|
|
|
122
122
|
}
|
|
123
123
|
break;
|
|
124
124
|
}
|
|
125
|
+
case 'generative-ui': {
|
|
126
|
+
const nextPart = {
|
|
127
|
+
type: 'generative-ui',
|
|
128
|
+
spec: event.spec,
|
|
129
|
+
};
|
|
130
|
+
const existingIndex = structuredParts.findIndex((part) => part.type === 'generative-ui');
|
|
131
|
+
if (existingIndex === -1)
|
|
132
|
+
structuredParts.push(nextPart);
|
|
133
|
+
else
|
|
134
|
+
structuredParts[existingIndex] = nextPart;
|
|
135
|
+
break;
|
|
136
|
+
}
|
|
137
|
+
case 'data': {
|
|
138
|
+
const nextPart = {
|
|
139
|
+
type: 'data',
|
|
140
|
+
name: event.name,
|
|
141
|
+
data: event.data,
|
|
142
|
+
};
|
|
143
|
+
const existingIndex = structuredParts.findIndex((part) => part.type === 'data' && part.name === event.name);
|
|
144
|
+
if (existingIndex === -1)
|
|
145
|
+
structuredParts.push(nextPart);
|
|
146
|
+
else
|
|
147
|
+
structuredParts[existingIndex] = nextPart;
|
|
148
|
+
break;
|
|
149
|
+
}
|
|
125
150
|
case 'approval-request':
|
|
126
151
|
pendingApprovals.push({
|
|
127
152
|
toolCallId: event.toolCallId,
|
|
@@ -205,13 +230,32 @@ function resolvePikkuToolStatus(status, result) {
|
|
|
205
230
|
return { type: 'error' };
|
|
206
231
|
return { type: 'completed' };
|
|
207
232
|
}
|
|
208
|
-
function
|
|
233
|
+
function buildRichContent(text, structuredParts, toolCalls) {
|
|
209
234
|
const content = [];
|
|
210
235
|
if (text.value)
|
|
211
236
|
content.push({ type: 'text', text: text.value });
|
|
237
|
+
content.push(...structuredParts);
|
|
212
238
|
content.push(...toolCalls);
|
|
213
239
|
return content;
|
|
214
240
|
}
|
|
241
|
+
function buildContentFromAgentResult(result) {
|
|
242
|
+
const content = [];
|
|
243
|
+
if (typeof result === 'string') {
|
|
244
|
+
if (result)
|
|
245
|
+
content.push({ type: 'text', text: result });
|
|
246
|
+
return content;
|
|
247
|
+
}
|
|
248
|
+
if (!result || typeof result !== 'object')
|
|
249
|
+
return content;
|
|
250
|
+
const record = result;
|
|
251
|
+
if (typeof record.text === 'string' && record.text) {
|
|
252
|
+
content.push({ type: 'text', text: record.text });
|
|
253
|
+
}
|
|
254
|
+
if (record.ui != null) {
|
|
255
|
+
content.push({ type: 'generative-ui', spec: record.ui });
|
|
256
|
+
}
|
|
257
|
+
return content;
|
|
258
|
+
}
|
|
215
259
|
function createPikkuStreamingAdapter(optionsRef, pendingApprovalsRef, approvalDecisionsRef, setPendingApprovalsRef, onFinishRef) {
|
|
216
260
|
return {
|
|
217
261
|
run(_a) {
|
|
@@ -237,6 +281,7 @@ function createPikkuStreamingAdapter(optionsRef, pendingApprovalsRef, approvalDe
|
|
|
237
281
|
// The last resume triggers continuation (next LLM step).
|
|
238
282
|
let lastText = { value: '' };
|
|
239
283
|
let lastToolCalls = [];
|
|
284
|
+
let lastStructuredParts = [];
|
|
240
285
|
let nextApprovals = [];
|
|
241
286
|
for (let i = 0; i < decisions.length; i++) {
|
|
242
287
|
const decision = decisions[i];
|
|
@@ -262,8 +307,9 @@ function createPikkuStreamingAdapter(optionsRef, pendingApprovalsRef, approvalDe
|
|
|
262
307
|
}
|
|
263
308
|
const text = { value: '' };
|
|
264
309
|
const toolCalls = [];
|
|
310
|
+
const structuredParts = [];
|
|
265
311
|
const reader = resumeResponse.body.getReader();
|
|
266
|
-
const streamApprovals = yield __await(processStream(reader, text, toolCalls, () => { }, i === decisions.length - 1
|
|
312
|
+
const streamApprovals = yield __await(processStream(reader, text, toolCalls, structuredParts, () => { }, i === decisions.length - 1
|
|
267
313
|
? ((_d = onFinishRef.current) !== null && _d !== void 0 ? _d : undefined)
|
|
268
314
|
: undefined)
|
|
269
315
|
// Keep the last resume's output (it has continuation content)
|
|
@@ -271,12 +317,13 @@ function createPikkuStreamingAdapter(optionsRef, pendingApprovalsRef, approvalDe
|
|
|
271
317
|
// Keep the last resume's output (it has continuation content)
|
|
272
318
|
lastText = text;
|
|
273
319
|
lastToolCalls = toolCalls;
|
|
320
|
+
lastStructuredParts = structuredParts;
|
|
274
321
|
if (streamApprovals.length > 0) {
|
|
275
322
|
nextApprovals = streamApprovals;
|
|
276
323
|
}
|
|
277
324
|
}
|
|
278
325
|
// Build content from the last resume's output
|
|
279
|
-
const content =
|
|
326
|
+
const content = buildRichContent(lastText, lastStructuredParts, lastToolCalls);
|
|
280
327
|
if (nextApprovals.length > 0) {
|
|
281
328
|
// More approvals from continuation — show them
|
|
282
329
|
pendingApprovalsRef.current = nextApprovals;
|
|
@@ -301,7 +348,7 @@ function createPikkuStreamingAdapter(optionsRef, pendingApprovalsRef, approvalDe
|
|
|
301
348
|
lastToolCalls.push(approvalToolCall);
|
|
302
349
|
}
|
|
303
350
|
}
|
|
304
|
-
const updatedContent =
|
|
351
|
+
const updatedContent = buildRichContent(lastText, lastStructuredParts, lastToolCalls);
|
|
305
352
|
yield yield __await({
|
|
306
353
|
content: updatedContent,
|
|
307
354
|
status: {
|
|
@@ -380,14 +427,15 @@ function createPikkuStreamingAdapter(optionsRef, pendingApprovalsRef, approvalDe
|
|
|
380
427
|
}
|
|
381
428
|
const text = { value: '' };
|
|
382
429
|
const toolCalls = [];
|
|
430
|
+
const structuredParts = [];
|
|
383
431
|
let pendingContent = null;
|
|
384
432
|
const yieldContent = () => {
|
|
385
|
-
const content =
|
|
433
|
+
const content = buildRichContent(text, structuredParts, toolCalls);
|
|
386
434
|
if (content.length > 0)
|
|
387
435
|
pendingContent = content;
|
|
388
436
|
};
|
|
389
437
|
const reader = response.body.getReader();
|
|
390
|
-
const approvals = yield __await(processStream(reader, text, toolCalls, yieldContent, (_e = onFinishRef.current) !== null && _e !== void 0 ? _e : undefined));
|
|
438
|
+
const approvals = yield __await(processStream(reader, text, toolCalls, structuredParts, yieldContent, (_e = onFinishRef.current) !== null && _e !== void 0 ? _e : undefined));
|
|
391
439
|
if (approvals.length === 0) {
|
|
392
440
|
// No approval needed — yield final content and done
|
|
393
441
|
if (pendingContent) {
|
|
@@ -427,7 +475,7 @@ function createPikkuStreamingAdapter(optionsRef, pendingApprovalsRef, approvalDe
|
|
|
427
475
|
toolCalls.splice(i, 1);
|
|
428
476
|
}
|
|
429
477
|
}
|
|
430
|
-
const content =
|
|
478
|
+
const content = buildRichContent(text, structuredParts, toolCalls);
|
|
431
479
|
yield yield __await({
|
|
432
480
|
content,
|
|
433
481
|
status: {
|
|
@@ -497,7 +545,12 @@ const convertDbMessages = (dbMessages) => {
|
|
|
497
545
|
continue;
|
|
498
546
|
const parts = [];
|
|
499
547
|
if (msg.content) {
|
|
500
|
-
|
|
548
|
+
if (Array.isArray(msg.content)) {
|
|
549
|
+
parts.push(...msg.content);
|
|
550
|
+
}
|
|
551
|
+
else {
|
|
552
|
+
parts.push({ type: 'text', text: msg.content });
|
|
553
|
+
}
|
|
501
554
|
}
|
|
502
555
|
if (Array.isArray(msg.toolCalls)) {
|
|
503
556
|
for (const tc of msg.toolCalls) {
|
|
@@ -587,6 +640,7 @@ function createPikkuNonStreamingAdapter(optionsRef, pendingApprovalsRef, approva
|
|
|
587
640
|
// Resume uses SSE (same as streaming mode)
|
|
588
641
|
let lastText = { value: '' };
|
|
589
642
|
let lastToolCalls = [];
|
|
643
|
+
let lastStructuredParts = [];
|
|
590
644
|
let nextApprovals = [];
|
|
591
645
|
for (let i = 0; i < decisions.length; i++) {
|
|
592
646
|
const decision = decisions[i];
|
|
@@ -611,17 +665,19 @@ function createPikkuNonStreamingAdapter(optionsRef, pendingApprovalsRef, approva
|
|
|
611
665
|
}
|
|
612
666
|
const text = { value: '' };
|
|
613
667
|
const toolCalls = [];
|
|
668
|
+
const structuredParts = [];
|
|
614
669
|
const reader = resumeResponse.body.getReader();
|
|
615
|
-
const streamApprovals = yield __await(processStream(reader, text, toolCalls, () => { }, i === decisions.length - 1
|
|
670
|
+
const streamApprovals = yield __await(processStream(reader, text, toolCalls, structuredParts, () => { }, i === decisions.length - 1
|
|
616
671
|
? ((_d = onFinishRef.current) !== null && _d !== void 0 ? _d : undefined)
|
|
617
672
|
: undefined));
|
|
618
673
|
lastText = text;
|
|
619
674
|
lastToolCalls = toolCalls;
|
|
675
|
+
lastStructuredParts = structuredParts;
|
|
620
676
|
if (streamApprovals.length > 0) {
|
|
621
677
|
nextApprovals = streamApprovals;
|
|
622
678
|
}
|
|
623
679
|
}
|
|
624
|
-
const content =
|
|
680
|
+
const content = buildRichContent(lastText, lastStructuredParts, lastToolCalls);
|
|
625
681
|
if (nextApprovals.length > 0) {
|
|
626
682
|
pendingApprovalsRef.current = nextApprovals;
|
|
627
683
|
setPendingApprovalsRef.current(nextApprovals);
|
|
@@ -644,7 +700,7 @@ function createPikkuNonStreamingAdapter(optionsRef, pendingApprovalsRef, approva
|
|
|
644
700
|
lastToolCalls.push(approvalToolCall);
|
|
645
701
|
}
|
|
646
702
|
}
|
|
647
|
-
const updatedContent =
|
|
703
|
+
const updatedContent = buildRichContent(lastText, lastStructuredParts, lastToolCalls);
|
|
648
704
|
yield yield __await({
|
|
649
705
|
content: updatedContent,
|
|
650
706
|
status: {
|
|
@@ -701,9 +757,7 @@ function createPikkuNonStreamingAdapter(optionsRef, pendingApprovalsRef, approva
|
|
|
701
757
|
: {})), (approval.reason ? { __approvalReason: approval.reason } : {})),
|
|
702
758
|
}));
|
|
703
759
|
const content = [];
|
|
704
|
-
|
|
705
|
-
content.push({ type: 'text', text: json.result });
|
|
706
|
-
}
|
|
760
|
+
content.push(...buildContentFromAgentResult(json.result));
|
|
707
761
|
content.push(...toolCalls);
|
|
708
762
|
yield yield __await({
|
|
709
763
|
content,
|
|
@@ -716,10 +770,7 @@ function createPikkuNonStreamingAdapter(optionsRef, pendingApprovalsRef, approva
|
|
|
716
770
|
}
|
|
717
771
|
// No approvals — yield complete content
|
|
718
772
|
(_f = onFinishRef.current) === null || _f === void 0 ? void 0 : _f.call(onFinishRef);
|
|
719
|
-
const content =
|
|
720
|
-
if (json.result) {
|
|
721
|
-
content.push({ type: 'text', text: String(json.result) });
|
|
722
|
-
}
|
|
773
|
+
const content = buildContentFromAgentResult(json.result);
|
|
723
774
|
if (content.length > 0) {
|
|
724
775
|
yield yield __await({ content });
|
|
725
776
|
}
|
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
import { type ComponentType, type ReactNode } from 'react';
|
|
2
|
+
import { type ToolCallMessagePartComponent } from '@assistant-ui/react';
|
|
1
3
|
import { type PikkuAgentRuntimeOptions } from './use-pikku-agent-runtime.js';
|
|
2
4
|
export interface PikkuAgentChatProps extends PikkuAgentRuntimeOptions {
|
|
5
|
+
initialPrompt?: string;
|
|
3
6
|
emptyMessage?: string;
|
|
4
7
|
/** Hide tool calls from the chat display.
|
|
5
8
|
* - `true`: hide all non-approval tool calls
|
|
@@ -9,5 +12,18 @@ export interface PikkuAgentChatProps extends PikkuAgentRuntimeOptions {
|
|
|
9
12
|
dark?: boolean;
|
|
10
13
|
/** Max width of the chat content area. Defaults to 768. Set to 'none' for full width. */
|
|
11
14
|
maxWidth?: number | 'none';
|
|
15
|
+
/**
|
|
16
|
+
* Per-tool renderers. Map `toolName` → React component to replace the
|
|
17
|
+
* default expandable tool-call box for that tool. Enables generative-UI
|
|
18
|
+
* patterns: e.g. register a `renderWidget` tool on the agent and mount
|
|
19
|
+
* real UI (charts, diffs, cards) inline in the assistant bubble from the
|
|
20
|
+
* persisted tool-call args.
|
|
21
|
+
*
|
|
22
|
+
* Any tool without an entry here falls through to the default renderer
|
|
23
|
+
* (which still respects `hideToolCalls` and the approval-request UI).
|
|
24
|
+
*/
|
|
25
|
+
toolComponents?: Record<string, ToolCallMessagePartComponent>;
|
|
26
|
+
renderAssistantText?: (text: string) => ReactNode;
|
|
27
|
+
generativeUIComponents?: Record<string, ComponentType<any>>;
|
|
12
28
|
}
|
|
13
29
|
export declare function PikkuAgentChat(props: PikkuAgentChatProps): import("react/jsx-runtime").JSX.Element;
|