@pikku/assistant-ui 0.12.3 → 0.12.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/CHANGELOG.md +28 -0
- package/dist/cjs/pikku-agent-chat.d.ts +12 -0
- package/dist/cjs/pikku-agent-chat.js +110 -46
- package/dist/esm/pikku-agent-chat.d.ts +12 -0
- package/dist/esm/pikku-agent-chat.js +112 -48
- package/package.json +1 -1
- package/src/pikku-agent-chat.tsx +20 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,33 @@
|
|
|
1
1
|
# @pikku/assistant-ui
|
|
2
2
|
|
|
3
|
+
## 0.12.4
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 424c777: `PikkuAgentChat` now accepts a `toolComponents` prop — a map of
|
|
8
|
+
`toolName` → React component — for per-tool custom rendering inside
|
|
9
|
+
the assistant bubble. Unmatched tool calls continue to fall through to
|
|
10
|
+
the default expandable tool-call display.
|
|
11
|
+
|
|
12
|
+
This unlocks generative-UI patterns: register a `renderWidget` tool on
|
|
13
|
+
the agent, return structured props from it, and mount real UI (charts,
|
|
14
|
+
diffs, cards) inline in the chat from the persisted tool-call args.
|
|
15
|
+
Because the rendered widget is just a tool call under the hood, it
|
|
16
|
+
survives refresh, streams correctly, and stays part of the thread's
|
|
17
|
+
history.
|
|
18
|
+
|
|
19
|
+
```tsx
|
|
20
|
+
<PikkuAgentChat
|
|
21
|
+
agentName="myAgent"
|
|
22
|
+
threadId={threadId}
|
|
23
|
+
resourceId={userId}
|
|
24
|
+
api="/rpc/agent"
|
|
25
|
+
toolComponents={{
|
|
26
|
+
renderWidget: ({ args }) => <WidgetRegistry spec={args} />,
|
|
27
|
+
}}
|
|
28
|
+
/>
|
|
29
|
+
```
|
|
30
|
+
|
|
3
31
|
## 0.12.3
|
|
4
32
|
|
|
5
33
|
### Patch Changes
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type ToolCallMessagePartComponent } from '@assistant-ui/react';
|
|
1
2
|
import { type PikkuAgentRuntimeOptions } from './use-pikku-agent-runtime.js';
|
|
2
3
|
export interface PikkuAgentChatProps extends PikkuAgentRuntimeOptions {
|
|
3
4
|
emptyMessage?: string;
|
|
@@ -9,5 +10,16 @@ export interface PikkuAgentChatProps extends PikkuAgentRuntimeOptions {
|
|
|
9
10
|
dark?: boolean;
|
|
10
11
|
/** Max width of the chat content area. Defaults to 768. Set to 'none' for full width. */
|
|
11
12
|
maxWidth?: number | 'none';
|
|
13
|
+
/**
|
|
14
|
+
* Per-tool renderers. Map `toolName` → React component to replace the
|
|
15
|
+
* default expandable tool-call box for that tool. Enables generative-UI
|
|
16
|
+
* patterns: e.g. register a `renderWidget` tool on the agent and mount
|
|
17
|
+
* real UI (charts, diffs, cards) inline in the assistant bubble from the
|
|
18
|
+
* persisted tool-call args.
|
|
19
|
+
*
|
|
20
|
+
* Any tool without an entry here falls through to the default renderer
|
|
21
|
+
* (which still respects `hideToolCalls` and the approval-request UI).
|
|
22
|
+
*/
|
|
23
|
+
toolComponents?: Record<string, ToolCallMessagePartComponent>;
|
|
12
24
|
}
|
|
13
25
|
export declare function PikkuAgentChat(props: PikkuAgentChatProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -59,6 +59,7 @@ 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);
|
|
62
63
|
function shouldHideToolCall(hideToolCalls, toolName) {
|
|
63
64
|
if (!hideToolCalls)
|
|
64
65
|
return false;
|
|
@@ -149,7 +150,9 @@ const ToolCallDisplay = ({ toolCallId, toolName, args, result, status, addResult
|
|
|
149
150
|
padding: '2px 6px',
|
|
150
151
|
borderRadius: 3,
|
|
151
152
|
background: responded === 'approved' ? colors.successBg : colors.errorBg,
|
|
152
|
-
color: responded === 'approved'
|
|
153
|
+
color: responded === 'approved'
|
|
154
|
+
? colors.successColor
|
|
155
|
+
: colors.errorColor,
|
|
153
156
|
}, children: responded })] }));
|
|
154
157
|
}
|
|
155
158
|
return ((0, jsx_runtime_1.jsxs)("div", { style: {
|
|
@@ -199,7 +202,12 @@ const ToolCallDisplay = ({ toolCallId, toolName, args, result, status, addResult
|
|
|
199
202
|
borderRadius: 4,
|
|
200
203
|
overflow: 'auto',
|
|
201
204
|
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: {
|
|
205
|
+
}, 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: {
|
|
206
|
+
fontSize: 12,
|
|
207
|
+
color: colors.textMuted,
|
|
208
|
+
marginTop: 8,
|
|
209
|
+
marginBottom: 2,
|
|
210
|
+
}, children: "Result:" }), (0, jsx_runtime_1.jsx)("pre", { style: {
|
|
203
211
|
fontSize: 11,
|
|
204
212
|
background: colors.codeBg,
|
|
205
213
|
padding: 8,
|
|
@@ -210,25 +218,68 @@ const ToolCallDisplay = ({ toolCallId, toolName, args, result, status, addResult
|
|
|
210
218
|
? result
|
|
211
219
|
: JSON.stringify(result, null, 2) })] }))] }))] }));
|
|
212
220
|
};
|
|
213
|
-
const MarkdownText = ({ text, colors }) => {
|
|
221
|
+
const MarkdownText = ({ text, colors, }) => {
|
|
214
222
|
const components = (0, react_1.useMemo)(() => ({
|
|
215
|
-
p: ({ children }) => ((0, jsx_runtime_1.jsx)("p", { style: {
|
|
223
|
+
p: ({ children }) => ((0, jsx_runtime_1.jsx)("p", { style: {
|
|
224
|
+
margin: '0 0 8px',
|
|
225
|
+
fontSize: 14,
|
|
226
|
+
lineHeight: 1.6,
|
|
227
|
+
color: colors.text,
|
|
228
|
+
}, children: children })),
|
|
216
229
|
strong: ({ children }) => ((0, jsx_runtime_1.jsx)("strong", { style: { fontWeight: 600, color: colors.text }, children: children })),
|
|
217
230
|
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
|
-
|
|
231
|
+
ul: ({ children }) => ((0, jsx_runtime_1.jsx)("ul", { style: {
|
|
232
|
+
margin: '4px 0 8px',
|
|
233
|
+
paddingLeft: 20,
|
|
234
|
+
fontSize: 14,
|
|
235
|
+
color: colors.text,
|
|
236
|
+
}, children: children })),
|
|
237
|
+
ol: ({ children }) => ((0, jsx_runtime_1.jsx)("ol", { style: {
|
|
238
|
+
margin: '4px 0 8px',
|
|
239
|
+
paddingLeft: 20,
|
|
240
|
+
fontSize: 14,
|
|
241
|
+
color: colors.text,
|
|
242
|
+
}, children: children })),
|
|
220
243
|
li: ({ children }) => ((0, jsx_runtime_1.jsx)("li", { style: { marginBottom: 2, lineHeight: 1.6 }, children: children })),
|
|
221
244
|
code: ({ children, className }) => {
|
|
222
245
|
const isBlock = className === null || className === void 0 ? void 0 : className.startsWith('language-');
|
|
223
246
|
if (isBlock) {
|
|
224
|
-
return ((0, jsx_runtime_1.jsx)("pre", { style: {
|
|
247
|
+
return ((0, jsx_runtime_1.jsx)("pre", { style: {
|
|
248
|
+
background: colors.codeBg,
|
|
249
|
+
padding: 10,
|
|
250
|
+
borderRadius: 4,
|
|
251
|
+
overflow: 'auto',
|
|
252
|
+
margin: '4px 0 8px',
|
|
253
|
+
fontSize: 12,
|
|
254
|
+
}, children: (0, jsx_runtime_1.jsx)("code", { style: { color: colors.text }, children: children }) }));
|
|
225
255
|
}
|
|
226
|
-
return ((0, jsx_runtime_1.jsx)("code", { style: {
|
|
256
|
+
return ((0, jsx_runtime_1.jsx)("code", { style: {
|
|
257
|
+
background: colors.codeBg,
|
|
258
|
+
padding: '1px 4px',
|
|
259
|
+
borderRadius: 3,
|
|
260
|
+
fontSize: 13,
|
|
261
|
+
color: colors.text,
|
|
262
|
+
}, children: children }));
|
|
227
263
|
},
|
|
228
264
|
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
|
-
|
|
265
|
+
h1: ({ children }) => ((0, jsx_runtime_1.jsx)("h3", { style: {
|
|
266
|
+
margin: '8px 0 4px',
|
|
267
|
+
fontSize: 16,
|
|
268
|
+
fontWeight: 600,
|
|
269
|
+
color: colors.text,
|
|
270
|
+
}, children: children })),
|
|
271
|
+
h2: ({ children }) => ((0, jsx_runtime_1.jsx)("h4", { style: {
|
|
272
|
+
margin: '8px 0 4px',
|
|
273
|
+
fontSize: 15,
|
|
274
|
+
fontWeight: 600,
|
|
275
|
+
color: colors.text,
|
|
276
|
+
}, children: children })),
|
|
277
|
+
h3: ({ children }) => ((0, jsx_runtime_1.jsx)("h5", { style: {
|
|
278
|
+
margin: '8px 0 4px',
|
|
279
|
+
fontSize: 14,
|
|
280
|
+
fontWeight: 600,
|
|
281
|
+
color: colors.text,
|
|
282
|
+
}, children: children })),
|
|
232
283
|
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
284
|
}), [colors]);
|
|
234
285
|
return (0, jsx_runtime_1.jsx)(react_markdown_1.default, { components: components, children: text });
|
|
@@ -249,11 +300,16 @@ const UserMessage = () => {
|
|
|
249
300
|
borderRadius: 12,
|
|
250
301
|
backgroundColor: colors.userBubble,
|
|
251
302
|
}, children: (0, jsx_runtime_1.jsx)(react_2.MessagePrimitive.Content, { components: {
|
|
252
|
-
Text: ({ text }) => ((0, jsx_runtime_1.jsx)("span", { style: {
|
|
303
|
+
Text: ({ text }) => ((0, jsx_runtime_1.jsx)("span", { style: {
|
|
304
|
+
fontSize: 14,
|
|
305
|
+
whiteSpace: 'pre-wrap',
|
|
306
|
+
color: colors.text,
|
|
307
|
+
}, children: text })),
|
|
253
308
|
} }) })] }) }));
|
|
254
309
|
};
|
|
255
310
|
const AssistantMessage = () => {
|
|
256
311
|
const colors = (0, react_1.useContext)(ColorsContext);
|
|
312
|
+
const toolComponents = (0, react_1.useContext)(ToolComponentsContext);
|
|
257
313
|
return ((0, jsx_runtime_1.jsx)("div", { style: {
|
|
258
314
|
display: 'flex',
|
|
259
315
|
justifyContent: 'flex-start',
|
|
@@ -263,8 +319,9 @@ const AssistantMessage = () => {
|
|
|
263
319
|
borderRadius: 12,
|
|
264
320
|
backgroundColor: colors.assistantBubble,
|
|
265
321
|
}, children: [(0, jsx_runtime_1.jsx)(react_2.MessagePrimitive.Content, { components: {
|
|
266
|
-
Text: ({ text }) => (
|
|
322
|
+
Text: ({ text }) => (0, jsx_runtime_1.jsx)(MarkdownText, { text: text, colors: colors }),
|
|
267
323
|
tools: {
|
|
324
|
+
by_name: toolComponents,
|
|
268
325
|
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
326
|
},
|
|
270
327
|
} }), (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: {
|
|
@@ -278,7 +335,9 @@ const AssistantMessage = () => {
|
|
|
278
335
|
};
|
|
279
336
|
const PikkuComposer = ({ disabled, }) => {
|
|
280
337
|
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({ border: `1px solid ${colors.border}`, borderRadius: 12, overflow: 'hidden', display: 'flex', alignItems: 'flex-end', padding: '6px 12px', gap: 8 }, (disabled
|
|
338
|
+
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({ border: `1px solid ${colors.border}`, borderRadius: 12, overflow: 'hidden', display: 'flex', alignItems: 'flex-end', padding: '6px 12px', gap: 8 }, (disabled
|
|
339
|
+
? { opacity: 0.5, pointerEvents: 'none' }
|
|
340
|
+
: {})), children: [(0, jsx_runtime_1.jsx)(react_2.ComposerPrimitive.Input, { placeholder: disabled ? 'Respond to approval request above...' : 'Message...', rows: 2, disabled: disabled, style: {
|
|
282
341
|
flex: 1,
|
|
283
342
|
border: 'none',
|
|
284
343
|
outline: 'none',
|
|
@@ -305,42 +364,47 @@ const PikkuComposer = ({ disabled, }) => {
|
|
|
305
364
|
}, children: "\u25B6" })] }) }) }));
|
|
306
365
|
};
|
|
307
366
|
function PikkuAgentChat(props) {
|
|
308
|
-
const { emptyMessage, hideToolCalls, dark, maxWidth = 768 } = props, runtimeOptions = __rest(props, ["emptyMessage", "hideToolCalls", "dark", "maxWidth"]);
|
|
367
|
+
const { emptyMessage, hideToolCalls, dark, maxWidth = 768, toolComponents } = props, runtimeOptions = __rest(props, ["emptyMessage", "hideToolCalls", "dark", "maxWidth", "toolComponents"]);
|
|
309
368
|
const { runtime, isAwaitingApproval, pendingApprovals, handleApproval } = (0, use_pikku_agent_runtime_js_1.usePikkuAgentRuntime)(runtimeOptions);
|
|
310
369
|
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: {
|
|
370
|
+
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)(react_2.AssistantRuntimeProvider, { runtime: runtime, children: (0, jsx_runtime_1.jsx)("div", { style: {
|
|
371
|
+
height: '100%',
|
|
317
372
|
display: 'flex',
|
|
318
373
|
flexDirection: 'column',
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
374
|
+
background: colors.bg,
|
|
375
|
+
}, children: (0, jsx_runtime_1.jsxs)(react_2.ThreadPrimitive.Root, { style: {
|
|
376
|
+
display: 'flex',
|
|
377
|
+
flexDirection: 'column',
|
|
378
|
+
flex: 1,
|
|
379
|
+
minHeight: 0,
|
|
380
|
+
}, children: [(0, jsx_runtime_1.jsx)(react_2.ThreadPrimitive.Viewport, { style: {
|
|
381
|
+
flex: 1,
|
|
382
|
+
minHeight: 0,
|
|
383
|
+
overflowY: 'auto',
|
|
384
|
+
}, children: (0, jsx_runtime_1.jsxs)("div", { style: {
|
|
385
|
+
maxWidth: maxWidth === 'none' ? undefined : maxWidth,
|
|
386
|
+
margin: '0 auto',
|
|
387
|
+
padding: 16,
|
|
388
|
+
display: 'flex',
|
|
389
|
+
flexDirection: 'column',
|
|
390
|
+
gap: 16,
|
|
391
|
+
}, children: [(0, jsx_runtime_1.jsx)(react_2.ThreadPrimitive.Empty, { children: (0, jsx_runtime_1.jsx)("div", { style: {
|
|
392
|
+
display: 'flex',
|
|
393
|
+
alignItems: 'center',
|
|
394
|
+
justifyContent: 'center',
|
|
395
|
+
minHeight: 300,
|
|
396
|
+
color: colors.textMuted,
|
|
397
|
+
textAlign: 'center',
|
|
398
|
+
fontSize: 14,
|
|
399
|
+
}, children: emptyMessage !== null && emptyMessage !== void 0 ? emptyMessage : (props.threadId
|
|
400
|
+
? 'Send a message to start the conversation.'
|
|
401
|
+
: 'Start a new conversation.') }) }), (0, jsx_runtime_1.jsx)(react_2.ThreadPrimitive.Messages, { components: {
|
|
402
|
+
UserMessage,
|
|
403
|
+
AssistantMessage,
|
|
404
|
+
} })] }) }), (0, jsx_runtime_1.jsx)("div", { style: {
|
|
326
405
|
maxWidth: maxWidth === 'none' ? undefined : maxWidth,
|
|
327
406
|
margin: '0 auto',
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
gap: 16,
|
|
332
|
-
}, children: [(0, jsx_runtime_1.jsx)(react_2.ThreadPrimitive.Empty, { children: (0, jsx_runtime_1.jsx)("div", { style: {
|
|
333
|
-
display: 'flex',
|
|
334
|
-
alignItems: 'center',
|
|
335
|
-
justifyContent: 'center',
|
|
336
|
-
minHeight: 300,
|
|
337
|
-
color: colors.textMuted,
|
|
338
|
-
textAlign: 'center',
|
|
339
|
-
fontSize: 14,
|
|
340
|
-
}, children: emptyMessage !== null && emptyMessage !== void 0 ? emptyMessage : (props.threadId
|
|
341
|
-
? 'Send a message to start the conversation.'
|
|
342
|
-
: 'Start a new conversation.') }) }), (0, jsx_runtime_1.jsx)(react_2.ThreadPrimitive.Messages, { components: {
|
|
343
|
-
UserMessage,
|
|
344
|
-
AssistantMessage,
|
|
345
|
-
} })] }) }), (0, jsx_runtime_1.jsx)("div", { style: { maxWidth: maxWidth === 'none' ? undefined : maxWidth, margin: '0 auto', width: '100%', padding: '0 16px' }, children: (0, jsx_runtime_1.jsx)(PikkuComposer, { disabled: isAwaitingApproval }) })] }) }) }) }) }) }));
|
|
407
|
+
width: '100%',
|
|
408
|
+
padding: '0 16px',
|
|
409
|
+
}, children: (0, jsx_runtime_1.jsx)(PikkuComposer, { disabled: isAwaitingApproval }) })] }) }) }) }) }) }) }));
|
|
346
410
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type ToolCallMessagePartComponent } from '@assistant-ui/react';
|
|
1
2
|
import { type PikkuAgentRuntimeOptions } from './use-pikku-agent-runtime.js';
|
|
2
3
|
export interface PikkuAgentChatProps extends PikkuAgentRuntimeOptions {
|
|
3
4
|
emptyMessage?: string;
|
|
@@ -9,5 +10,16 @@ export interface PikkuAgentChatProps extends PikkuAgentRuntimeOptions {
|
|
|
9
10
|
dark?: boolean;
|
|
10
11
|
/** Max width of the chat content area. Defaults to 768. Set to 'none' for full width. */
|
|
11
12
|
maxWidth?: number | 'none';
|
|
13
|
+
/**
|
|
14
|
+
* Per-tool renderers. Map `toolName` → React component to replace the
|
|
15
|
+
* default expandable tool-call box for that tool. Enables generative-UI
|
|
16
|
+
* patterns: e.g. register a `renderWidget` tool on the agent and mount
|
|
17
|
+
* real UI (charts, diffs, cards) inline in the assistant bubble from the
|
|
18
|
+
* persisted tool-call args.
|
|
19
|
+
*
|
|
20
|
+
* Any tool without an entry here falls through to the default renderer
|
|
21
|
+
* (which still respects `hideToolCalls` and the approval-request UI).
|
|
22
|
+
*/
|
|
23
|
+
toolComponents?: Record<string, ToolCallMessagePartComponent>;
|
|
12
24
|
}
|
|
13
25
|
export declare function PikkuAgentChat(props: PikkuAgentChatProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
import { createContext, useContext, useState, useMemo } from 'react';
|
|
2
|
+
import { createContext, useContext, useState, useMemo, } from 'react';
|
|
3
3
|
import Markdown from 'react-markdown';
|
|
4
4
|
import { AssistantRuntimeProvider, ThreadPrimitive, MessagePrimitive, ComposerPrimitive, } from '@assistant-ui/react';
|
|
5
5
|
import { usePikkuAgentRuntime, PikkuApprovalContext, usePikkuApproval, resolvePikkuToolStatus, } from './use-pikku-agent-runtime.js';
|
|
@@ -45,6 +45,7 @@ const darkColors = {
|
|
|
45
45
|
};
|
|
46
46
|
const ColorsContext = createContext(lightColors);
|
|
47
47
|
const HideToolCallsContext = createContext(undefined);
|
|
48
|
+
const ToolComponentsContext = createContext(undefined);
|
|
48
49
|
function shouldHideToolCall(hideToolCalls, toolName) {
|
|
49
50
|
if (!hideToolCalls)
|
|
50
51
|
return false;
|
|
@@ -135,7 +136,9 @@ const ToolCallDisplay = ({ toolCallId, toolName, args, result, status, addResult
|
|
|
135
136
|
padding: '2px 6px',
|
|
136
137
|
borderRadius: 3,
|
|
137
138
|
background: responded === 'approved' ? colors.successBg : colors.errorBg,
|
|
138
|
-
color: responded === 'approved'
|
|
139
|
+
color: responded === 'approved'
|
|
140
|
+
? colors.successColor
|
|
141
|
+
: colors.errorColor,
|
|
139
142
|
}, children: responded })] }));
|
|
140
143
|
}
|
|
141
144
|
return (_jsxs("div", { style: {
|
|
@@ -185,7 +188,12 @@ const ToolCallDisplay = ({ toolCallId, toolName, args, result, status, addResult
|
|
|
185
188
|
borderRadius: 4,
|
|
186
189
|
overflow: 'auto',
|
|
187
190
|
color: colors.text,
|
|
188
|
-
}, children: JSON.stringify(displayArgs, null, 2) }), result !== undefined && (_jsxs(_Fragment, { children: [_jsx("div", { style: {
|
|
191
|
+
}, children: JSON.stringify(displayArgs, null, 2) }), result !== undefined && (_jsxs(_Fragment, { children: [_jsx("div", { style: {
|
|
192
|
+
fontSize: 12,
|
|
193
|
+
color: colors.textMuted,
|
|
194
|
+
marginTop: 8,
|
|
195
|
+
marginBottom: 2,
|
|
196
|
+
}, children: "Result:" }), _jsx("pre", { style: {
|
|
189
197
|
fontSize: 11,
|
|
190
198
|
background: colors.codeBg,
|
|
191
199
|
padding: 8,
|
|
@@ -196,25 +204,68 @@ const ToolCallDisplay = ({ toolCallId, toolName, args, result, status, addResult
|
|
|
196
204
|
? result
|
|
197
205
|
: JSON.stringify(result, null, 2) })] }))] }))] }));
|
|
198
206
|
};
|
|
199
|
-
const MarkdownText = ({ text, colors }) => {
|
|
207
|
+
const MarkdownText = ({ text, colors, }) => {
|
|
200
208
|
const components = useMemo(() => ({
|
|
201
|
-
p: ({ children }) => (_jsx("p", { style: {
|
|
209
|
+
p: ({ children }) => (_jsx("p", { style: {
|
|
210
|
+
margin: '0 0 8px',
|
|
211
|
+
fontSize: 14,
|
|
212
|
+
lineHeight: 1.6,
|
|
213
|
+
color: colors.text,
|
|
214
|
+
}, children: children })),
|
|
202
215
|
strong: ({ children }) => (_jsx("strong", { style: { fontWeight: 600, color: colors.text }, children: children })),
|
|
203
216
|
em: ({ children }) => (_jsx("em", { style: { color: colors.text }, children: children })),
|
|
204
|
-
ul: ({ children }) => (_jsx("ul", { style: {
|
|
205
|
-
|
|
217
|
+
ul: ({ children }) => (_jsx("ul", { style: {
|
|
218
|
+
margin: '4px 0 8px',
|
|
219
|
+
paddingLeft: 20,
|
|
220
|
+
fontSize: 14,
|
|
221
|
+
color: colors.text,
|
|
222
|
+
}, children: children })),
|
|
223
|
+
ol: ({ children }) => (_jsx("ol", { style: {
|
|
224
|
+
margin: '4px 0 8px',
|
|
225
|
+
paddingLeft: 20,
|
|
226
|
+
fontSize: 14,
|
|
227
|
+
color: colors.text,
|
|
228
|
+
}, children: children })),
|
|
206
229
|
li: ({ children }) => (_jsx("li", { style: { marginBottom: 2, lineHeight: 1.6 }, children: children })),
|
|
207
230
|
code: ({ children, className }) => {
|
|
208
231
|
const isBlock = className?.startsWith('language-');
|
|
209
232
|
if (isBlock) {
|
|
210
|
-
return (_jsx("pre", { style: {
|
|
233
|
+
return (_jsx("pre", { style: {
|
|
234
|
+
background: colors.codeBg,
|
|
235
|
+
padding: 10,
|
|
236
|
+
borderRadius: 4,
|
|
237
|
+
overflow: 'auto',
|
|
238
|
+
margin: '4px 0 8px',
|
|
239
|
+
fontSize: 12,
|
|
240
|
+
}, children: _jsx("code", { style: { color: colors.text }, children: children }) }));
|
|
211
241
|
}
|
|
212
|
-
return (_jsx("code", { style: {
|
|
242
|
+
return (_jsx("code", { style: {
|
|
243
|
+
background: colors.codeBg,
|
|
244
|
+
padding: '1px 4px',
|
|
245
|
+
borderRadius: 3,
|
|
246
|
+
fontSize: 13,
|
|
247
|
+
color: colors.text,
|
|
248
|
+
}, children: children }));
|
|
213
249
|
},
|
|
214
250
|
pre: ({ children }) => _jsx(_Fragment, { children: children }),
|
|
215
|
-
h1: ({ children }) => _jsx("h3", { style: {
|
|
216
|
-
|
|
217
|
-
|
|
251
|
+
h1: ({ children }) => (_jsx("h3", { style: {
|
|
252
|
+
margin: '8px 0 4px',
|
|
253
|
+
fontSize: 16,
|
|
254
|
+
fontWeight: 600,
|
|
255
|
+
color: colors.text,
|
|
256
|
+
}, children: children })),
|
|
257
|
+
h2: ({ children }) => (_jsx("h4", { style: {
|
|
258
|
+
margin: '8px 0 4px',
|
|
259
|
+
fontSize: 15,
|
|
260
|
+
fontWeight: 600,
|
|
261
|
+
color: colors.text,
|
|
262
|
+
}, children: children })),
|
|
263
|
+
h3: ({ children }) => (_jsx("h5", { style: {
|
|
264
|
+
margin: '8px 0 4px',
|
|
265
|
+
fontSize: 14,
|
|
266
|
+
fontWeight: 600,
|
|
267
|
+
color: colors.text,
|
|
268
|
+
}, children: children })),
|
|
218
269
|
a: ({ href, children }) => (_jsx("a", { href: href, target: "_blank", rel: "noopener noreferrer", style: { color: colors.textMuted, textDecoration: 'underline' }, children: children })),
|
|
219
270
|
}), [colors]);
|
|
220
271
|
return _jsx(Markdown, { components: components, children: text });
|
|
@@ -235,11 +286,16 @@ const UserMessage = () => {
|
|
|
235
286
|
borderRadius: 12,
|
|
236
287
|
backgroundColor: colors.userBubble,
|
|
237
288
|
}, children: _jsx(MessagePrimitive.Content, { components: {
|
|
238
|
-
Text: ({ text }) => (_jsx("span", { style: {
|
|
289
|
+
Text: ({ text }) => (_jsx("span", { style: {
|
|
290
|
+
fontSize: 14,
|
|
291
|
+
whiteSpace: 'pre-wrap',
|
|
292
|
+
color: colors.text,
|
|
293
|
+
}, children: text })),
|
|
239
294
|
} }) })] }) }));
|
|
240
295
|
};
|
|
241
296
|
const AssistantMessage = () => {
|
|
242
297
|
const colors = useContext(ColorsContext);
|
|
298
|
+
const toolComponents = useContext(ToolComponentsContext);
|
|
243
299
|
return (_jsx("div", { style: {
|
|
244
300
|
display: 'flex',
|
|
245
301
|
justifyContent: 'flex-start',
|
|
@@ -249,8 +305,9 @@ const AssistantMessage = () => {
|
|
|
249
305
|
borderRadius: 12,
|
|
250
306
|
backgroundColor: colors.assistantBubble,
|
|
251
307
|
}, children: [_jsx(MessagePrimitive.Content, { components: {
|
|
252
|
-
Text: ({ text }) =>
|
|
308
|
+
Text: ({ text }) => _jsx(MarkdownText, { text: text, colors: colors }),
|
|
253
309
|
tools: {
|
|
310
|
+
by_name: toolComponents,
|
|
254
311
|
Fallback: (props) => (_jsx(ToolCallDisplay, { toolCallId: props.toolCallId, toolName: props.toolName, args: props.args, result: props.result, status: resolvePikkuToolStatus(props.status, props.result), addResult: props.addResult })),
|
|
255
312
|
},
|
|
256
313
|
} }), _jsx(MessagePrimitive.If, { last: true, children: _jsx(ThreadPrimitive.If, { running: true, children: _jsx("div", { style: {
|
|
@@ -272,7 +329,9 @@ const PikkuComposer = ({ disabled, }) => {
|
|
|
272
329
|
alignItems: 'flex-end',
|
|
273
330
|
padding: '6px 12px',
|
|
274
331
|
gap: 8,
|
|
275
|
-
...(disabled
|
|
332
|
+
...(disabled
|
|
333
|
+
? { opacity: 0.5, pointerEvents: 'none' }
|
|
334
|
+
: {}),
|
|
276
335
|
}, children: [_jsx(ComposerPrimitive.Input, { placeholder: disabled ? 'Respond to approval request above...' : 'Message...', rows: 2, disabled: disabled, style: {
|
|
277
336
|
flex: 1,
|
|
278
337
|
border: 'none',
|
|
@@ -300,43 +359,48 @@ const PikkuComposer = ({ disabled, }) => {
|
|
|
300
359
|
}, children: "\u25B6" })] }) }) }));
|
|
301
360
|
};
|
|
302
361
|
export function PikkuAgentChat(props) {
|
|
303
|
-
const { emptyMessage, hideToolCalls, dark, maxWidth = 768, ...runtimeOptions } = props;
|
|
362
|
+
const { emptyMessage, hideToolCalls, dark, maxWidth = 768, toolComponents, ...runtimeOptions } = props;
|
|
304
363
|
const { runtime, isAwaitingApproval, pendingApprovals, handleApproval } = usePikkuAgentRuntime(runtimeOptions);
|
|
305
364
|
const colors = dark ? darkColors : lightColors;
|
|
306
|
-
return (_jsx(ColorsContext.Provider, { value: colors, children: _jsx(PikkuApprovalContext.Provider, { value: { pendingApprovals, handleApproval }, children: _jsx(HideToolCallsContext.Provider, { value: hideToolCalls, children: _jsx(AssistantRuntimeProvider, { runtime: runtime, children: _jsx("div", { style: {
|
|
307
|
-
|
|
308
|
-
display: 'flex',
|
|
309
|
-
flexDirection: 'column',
|
|
310
|
-
background: colors.bg,
|
|
311
|
-
}, children: _jsxs(ThreadPrimitive.Root, { style: {
|
|
365
|
+
return (_jsx(ColorsContext.Provider, { value: colors, children: _jsx(PikkuApprovalContext.Provider, { value: { pendingApprovals, handleApproval }, children: _jsx(HideToolCallsContext.Provider, { value: hideToolCalls, children: _jsx(ToolComponentsContext.Provider, { value: toolComponents, children: _jsx(AssistantRuntimeProvider, { runtime: runtime, children: _jsx("div", { style: {
|
|
366
|
+
height: '100%',
|
|
312
367
|
display: 'flex',
|
|
313
368
|
flexDirection: 'column',
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
369
|
+
background: colors.bg,
|
|
370
|
+
}, children: _jsxs(ThreadPrimitive.Root, { style: {
|
|
371
|
+
display: 'flex',
|
|
372
|
+
flexDirection: 'column',
|
|
373
|
+
flex: 1,
|
|
374
|
+
minHeight: 0,
|
|
375
|
+
}, children: [_jsx(ThreadPrimitive.Viewport, { style: {
|
|
376
|
+
flex: 1,
|
|
377
|
+
minHeight: 0,
|
|
378
|
+
overflowY: 'auto',
|
|
379
|
+
}, children: _jsxs("div", { style: {
|
|
380
|
+
maxWidth: maxWidth === 'none' ? undefined : maxWidth,
|
|
381
|
+
margin: '0 auto',
|
|
382
|
+
padding: 16,
|
|
383
|
+
display: 'flex',
|
|
384
|
+
flexDirection: 'column',
|
|
385
|
+
gap: 16,
|
|
386
|
+
}, children: [_jsx(ThreadPrimitive.Empty, { children: _jsx("div", { style: {
|
|
387
|
+
display: 'flex',
|
|
388
|
+
alignItems: 'center',
|
|
389
|
+
justifyContent: 'center',
|
|
390
|
+
minHeight: 300,
|
|
391
|
+
color: colors.textMuted,
|
|
392
|
+
textAlign: 'center',
|
|
393
|
+
fontSize: 14,
|
|
394
|
+
}, children: emptyMessage ??
|
|
395
|
+
(props.threadId
|
|
396
|
+
? 'Send a message to start the conversation.'
|
|
397
|
+
: 'Start a new conversation.') }) }), _jsx(ThreadPrimitive.Messages, { components: {
|
|
398
|
+
UserMessage,
|
|
399
|
+
AssistantMessage,
|
|
400
|
+
} })] }) }), _jsx("div", { style: {
|
|
321
401
|
maxWidth: maxWidth === 'none' ? undefined : maxWidth,
|
|
322
402
|
margin: '0 auto',
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
gap: 16,
|
|
327
|
-
}, children: [_jsx(ThreadPrimitive.Empty, { children: _jsx("div", { style: {
|
|
328
|
-
display: 'flex',
|
|
329
|
-
alignItems: 'center',
|
|
330
|
-
justifyContent: 'center',
|
|
331
|
-
minHeight: 300,
|
|
332
|
-
color: colors.textMuted,
|
|
333
|
-
textAlign: 'center',
|
|
334
|
-
fontSize: 14,
|
|
335
|
-
}, children: emptyMessage ??
|
|
336
|
-
(props.threadId
|
|
337
|
-
? 'Send a message to start the conversation.'
|
|
338
|
-
: 'Start a new conversation.') }) }), _jsx(ThreadPrimitive.Messages, { components: {
|
|
339
|
-
UserMessage,
|
|
340
|
-
AssistantMessage,
|
|
341
|
-
} })] }) }), _jsx("div", { style: { maxWidth: maxWidth === 'none' ? undefined : maxWidth, margin: '0 auto', width: '100%', padding: '0 16px' }, children: _jsx(PikkuComposer, { disabled: isAwaitingApproval }) })] }) }) }) }) }) }));
|
|
403
|
+
width: '100%',
|
|
404
|
+
padding: '0 16px',
|
|
405
|
+
}, children: _jsx(PikkuComposer, { disabled: isAwaitingApproval }) })] }) }) }) }) }) }) }));
|
|
342
406
|
}
|
package/package.json
CHANGED
package/src/pikku-agent-chat.tsx
CHANGED
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
ThreadPrimitive,
|
|
6
6
|
MessagePrimitive,
|
|
7
7
|
ComposerPrimitive,
|
|
8
|
+
type ToolCallMessagePartComponent,
|
|
8
9
|
} from '@assistant-ui/react'
|
|
9
10
|
import {
|
|
10
11
|
usePikkuAgentRuntime,
|
|
@@ -25,6 +26,17 @@ export interface PikkuAgentChatProps extends PikkuAgentRuntimeOptions {
|
|
|
25
26
|
dark?: boolean
|
|
26
27
|
/** Max width of the chat content area. Defaults to 768. Set to 'none' for full width. */
|
|
27
28
|
maxWidth?: number | 'none'
|
|
29
|
+
/**
|
|
30
|
+
* Per-tool renderers. Map `toolName` → React component to replace the
|
|
31
|
+
* default expandable tool-call box for that tool. Enables generative-UI
|
|
32
|
+
* patterns: e.g. register a `renderWidget` tool on the agent and mount
|
|
33
|
+
* real UI (charts, diffs, cards) inline in the assistant bubble from the
|
|
34
|
+
* persisted tool-call args.
|
|
35
|
+
*
|
|
36
|
+
* Any tool without an entry here falls through to the default renderer
|
|
37
|
+
* (which still respects `hideToolCalls` and the approval-request UI).
|
|
38
|
+
*/
|
|
39
|
+
toolComponents?: Record<string, ToolCallMessagePartComponent>
|
|
28
40
|
}
|
|
29
41
|
|
|
30
42
|
interface ChatColors {
|
|
@@ -92,6 +104,9 @@ const darkColors: ChatColors = {
|
|
|
92
104
|
|
|
93
105
|
const ColorsContext = createContext<ChatColors>(lightColors)
|
|
94
106
|
const HideToolCallsContext = createContext<boolean | string[] | undefined>(undefined)
|
|
107
|
+
const ToolComponentsContext = createContext<
|
|
108
|
+
Record<string, ToolCallMessagePartComponent> | undefined
|
|
109
|
+
>(undefined)
|
|
95
110
|
|
|
96
111
|
function shouldHideToolCall(
|
|
97
112
|
hideToolCalls: boolean | string[] | undefined,
|
|
@@ -470,6 +485,7 @@ const UserMessage: FunctionComponent = () => {
|
|
|
470
485
|
|
|
471
486
|
const AssistantMessage: FunctionComponent = () => {
|
|
472
487
|
const colors = useContext(ColorsContext)
|
|
488
|
+
const toolComponents = useContext(ToolComponentsContext)
|
|
473
489
|
return (
|
|
474
490
|
<div
|
|
475
491
|
style={{
|
|
@@ -495,6 +511,7 @@ const AssistantMessage: FunctionComponent = () => {
|
|
|
495
511
|
<MarkdownText text={text} colors={colors} />
|
|
496
512
|
),
|
|
497
513
|
tools: {
|
|
514
|
+
by_name: toolComponents,
|
|
498
515
|
Fallback: (props) => (
|
|
499
516
|
<ToolCallDisplay
|
|
500
517
|
toolCallId={props.toolCallId}
|
|
@@ -592,7 +609,7 @@ const PikkuComposer: FunctionComponent<{ disabled?: boolean }> = ({
|
|
|
592
609
|
}
|
|
593
610
|
|
|
594
611
|
export function PikkuAgentChat(props: PikkuAgentChatProps) {
|
|
595
|
-
const { emptyMessage, hideToolCalls, dark, maxWidth = 768, ...runtimeOptions } = props
|
|
612
|
+
const { emptyMessage, hideToolCalls, dark, maxWidth = 768, toolComponents, ...runtimeOptions } = props
|
|
596
613
|
const { runtime, isAwaitingApproval, pendingApprovals, handleApproval } =
|
|
597
614
|
usePikkuAgentRuntime(runtimeOptions)
|
|
598
615
|
|
|
@@ -602,6 +619,7 @@ export function PikkuAgentChat(props: PikkuAgentChatProps) {
|
|
|
602
619
|
<ColorsContext.Provider value={colors}>
|
|
603
620
|
<PikkuApprovalContext.Provider value={{ pendingApprovals, handleApproval }}>
|
|
604
621
|
<HideToolCallsContext.Provider value={hideToolCalls}>
|
|
622
|
+
<ToolComponentsContext.Provider value={toolComponents}>
|
|
605
623
|
<AssistantRuntimeProvider runtime={runtime}>
|
|
606
624
|
<div
|
|
607
625
|
style={{
|
|
@@ -668,6 +686,7 @@ export function PikkuAgentChat(props: PikkuAgentChatProps) {
|
|
|
668
686
|
</ThreadPrimitive.Root>
|
|
669
687
|
</div>
|
|
670
688
|
</AssistantRuntimeProvider>
|
|
689
|
+
</ToolComponentsContext.Provider>
|
|
671
690
|
</HideToolCallsContext.Provider>
|
|
672
691
|
</PikkuApprovalContext.Provider>
|
|
673
692
|
</ColorsContext.Provider>
|