@pikku/assistant-ui 0.12.0

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.
@@ -0,0 +1,219 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useState } from 'react';
3
+ import { AssistantRuntimeProvider, ThreadPrimitive, MessagePrimitive, ComposerPrimitive, } from '@assistant-ui/react';
4
+ import { usePikkuAgentRuntime, } from './use-pikku-agent-runtime.js';
5
+ const ToolCallDisplay = ({ toolName, args, result, status, addResult }) => {
6
+ const [expanded, setExpanded] = useState(false);
7
+ const isApproval = status.type === 'requires-action';
8
+ const approvalReason = args?.__approvalReason;
9
+ const displayArgs = { ...args };
10
+ delete displayArgs.__approvalReason;
11
+ const [responded, setResponded] = useState(null);
12
+ if (isApproval && !responded) {
13
+ return (_jsxs("div", { style: {
14
+ border: '1px solid #e9a211',
15
+ borderRadius: 6,
16
+ padding: 12,
17
+ margin: '4px 0',
18
+ backgroundColor: '#fef9e7',
19
+ }, children: [_jsx("div", { style: {
20
+ display: 'flex',
21
+ alignItems: 'center',
22
+ gap: 6,
23
+ marginBottom: 8,
24
+ fontWeight: 600,
25
+ fontSize: 13,
26
+ }, children: "Approval required" }), approvalReason && (_jsx("div", { style: { fontSize: 13, marginBottom: 4 }, children: approvalReason })), _jsxs("div", { style: { fontSize: 12, color: '#666', marginBottom: 4 }, children: ["The agent wants to call ", _jsx("code", { children: toolName })] }), _jsx("pre", { style: {
27
+ fontSize: 11,
28
+ background: '#f5f5f5',
29
+ padding: 8,
30
+ borderRadius: 4,
31
+ overflow: 'auto',
32
+ marginBottom: 8,
33
+ }, children: JSON.stringify(displayArgs, null, 2) }), _jsxs("div", { style: { display: 'flex', gap: 8 }, children: [_jsx("button", { onClick: () => {
34
+ setResponded('approved');
35
+ addResult?.({ approved: true });
36
+ }, style: {
37
+ padding: '4px 12px',
38
+ fontSize: 12,
39
+ border: '1px solid #2e7d32',
40
+ borderRadius: 4,
41
+ background: '#e8f5e9',
42
+ color: '#2e7d32',
43
+ cursor: 'pointer',
44
+ }, children: "Approve" }), _jsx("button", { onClick: () => {
45
+ setResponded('denied');
46
+ addResult?.({ approved: false });
47
+ }, style: {
48
+ padding: '4px 12px',
49
+ fontSize: 12,
50
+ border: '1px solid #c62828',
51
+ borderRadius: 4,
52
+ background: '#ffebee',
53
+ color: '#c62828',
54
+ cursor: 'pointer',
55
+ }, children: "Deny" })] })] }));
56
+ }
57
+ if (isApproval && responded) {
58
+ return (_jsxs("div", { style: {
59
+ border: '1px solid #ddd',
60
+ borderRadius: 6,
61
+ padding: 8,
62
+ margin: '4px 0',
63
+ display: 'flex',
64
+ alignItems: 'center',
65
+ gap: 8,
66
+ fontSize: 13,
67
+ }, children: [_jsx("span", { style: { fontWeight: 500 }, children: toolName }), _jsx("span", { style: {
68
+ fontSize: 11,
69
+ padding: '2px 6px',
70
+ borderRadius: 3,
71
+ background: responded === 'approved' ? '#e8f5e9' : '#ffebee',
72
+ color: responded === 'approved' ? '#2e7d32' : '#c62828',
73
+ }, children: responded })] }));
74
+ }
75
+ return (_jsxs("div", { style: {
76
+ border: '1px solid #ddd',
77
+ borderRadius: 6,
78
+ padding: 8,
79
+ margin: '4px 0',
80
+ }, children: [_jsxs("button", { onClick: () => setExpanded((e) => !e), style: {
81
+ background: 'none',
82
+ border: 'none',
83
+ cursor: 'pointer',
84
+ display: 'flex',
85
+ alignItems: 'center',
86
+ gap: 6,
87
+ width: '100%',
88
+ padding: 0,
89
+ fontSize: 13,
90
+ }, children: [_jsx("span", { children: expanded ? '\u25BC' : '\u25B6' }), _jsx("span", { style: { fontFamily: 'monospace', fontWeight: 500 }, children: toolName }), status.type === 'running' && (_jsx("span", { style: { fontSize: 11, color: '#888' }, children: "running..." })), status.type === 'complete' && (_jsx("span", { style: {
91
+ fontSize: 11,
92
+ padding: '1px 5px',
93
+ borderRadius: 3,
94
+ background: '#e8f5e9',
95
+ color: '#2e7d32',
96
+ }, children: "done" }))] }), expanded && (_jsxs("div", { style: { marginTop: 8 }, children: [_jsx("div", { style: { fontSize: 12, color: '#888', marginBottom: 2 }, children: "Arguments:" }), _jsx("pre", { style: {
97
+ fontSize: 11,
98
+ background: '#f5f5f5',
99
+ padding: 8,
100
+ borderRadius: 4,
101
+ overflow: 'auto',
102
+ }, children: JSON.stringify(displayArgs, null, 2) }), result !== undefined && (_jsxs(_Fragment, { children: [_jsx("div", { style: { fontSize: 12, color: '#888', marginTop: 8, marginBottom: 2 }, children: "Result:" }), _jsx("pre", { style: {
103
+ fontSize: 11,
104
+ background: '#f5f5f5',
105
+ padding: 8,
106
+ borderRadius: 4,
107
+ overflow: 'auto',
108
+ }, children: typeof result === 'string'
109
+ ? result
110
+ : JSON.stringify(result, null, 2) })] }))] }))] }));
111
+ };
112
+ const UserMessage = () => (_jsx("div", { style: {
113
+ display: 'flex',
114
+ justifyContent: 'flex-end',
115
+ width: '100%',
116
+ }, children: _jsxs("div", { style: { maxWidth: '80%' }, children: [_jsx("div", { style: {
117
+ fontSize: 12,
118
+ color: '#888',
119
+ marginBottom: 4,
120
+ textAlign: 'right',
121
+ }, children: "You" }), _jsx("div", { style: {
122
+ padding: 12,
123
+ borderRadius: 12,
124
+ backgroundColor: '#e3f2fd',
125
+ }, children: _jsx(MessagePrimitive.Content, { components: {
126
+ Text: ({ text }) => (_jsx("span", { style: { fontSize: 14, whiteSpace: 'pre-wrap' }, children: text })),
127
+ } }) })] }) }));
128
+ const AssistantMessage = () => (_jsx("div", { style: {
129
+ display: 'flex',
130
+ justifyContent: 'flex-start',
131
+ width: '100%',
132
+ }, children: _jsxs("div", { style: { maxWidth: '80%' }, children: [_jsx("div", { style: { fontSize: 12, color: '#888', marginBottom: 4 }, children: "Assistant" }), _jsxs("div", { style: {
133
+ padding: 12,
134
+ borderRadius: 12,
135
+ backgroundColor: '#f5f5f5',
136
+ }, children: [_jsx(MessagePrimitive.Content, { components: {
137
+ Text: ({ text }) => (_jsx("span", { style: { fontSize: 14, whiteSpace: 'pre-wrap' }, children: text })),
138
+ tools: {
139
+ Fallback: (props) => (_jsx(ToolCallDisplay, { toolName: props.toolName, args: props.args, result: props.result, status: props.status, addResult: props.addResult })),
140
+ },
141
+ } }), _jsx(MessagePrimitive.If, { last: true, children: _jsx(ThreadPrimitive.If, { running: true, children: _jsx("div", { style: {
142
+ display: 'flex',
143
+ alignItems: 'center',
144
+ gap: 6,
145
+ marginTop: 8,
146
+ fontSize: 13,
147
+ color: '#888',
148
+ }, children: "Thinking..." }) }) })] })] }) }));
149
+ const PikkuComposer = () => (_jsx("div", { style: { padding: '8px 0 16px' }, children: _jsx(ComposerPrimitive.Root, { children: _jsxs("div", { style: {
150
+ border: '1px solid #ddd',
151
+ borderRadius: 12,
152
+ overflow: 'hidden',
153
+ display: 'flex',
154
+ alignItems: 'flex-end',
155
+ padding: '6px 12px',
156
+ gap: 8,
157
+ }, children: [_jsx(ComposerPrimitive.Input, { placeholder: "Message...", rows: 2, style: {
158
+ flex: 1,
159
+ border: 'none',
160
+ outline: 'none',
161
+ resize: 'none',
162
+ fontSize: 14,
163
+ fontFamily: 'inherit',
164
+ padding: '4px 0',
165
+ background: 'transparent',
166
+ } }), _jsx(ComposerPrimitive.Send, { style: {
167
+ width: 28,
168
+ height: 28,
169
+ borderRadius: '50%',
170
+ border: 'none',
171
+ background: '#1976d2',
172
+ color: '#fff',
173
+ cursor: 'pointer',
174
+ display: 'flex',
175
+ alignItems: 'center',
176
+ justifyContent: 'center',
177
+ flexShrink: 0,
178
+ marginBottom: 2,
179
+ fontSize: 14,
180
+ }, children: "\u25B6" })] }) }) }));
181
+ export function PikkuAgentChat(props) {
182
+ const { emptyMessage, ...runtimeOptions } = props;
183
+ const runtime = usePikkuAgentRuntime(runtimeOptions);
184
+ return (_jsx(AssistantRuntimeProvider, { runtime: runtime, children: _jsx("div", { style: {
185
+ height: '100%',
186
+ display: 'flex',
187
+ flexDirection: 'column',
188
+ }, children: _jsxs(ThreadPrimitive.Root, { style: {
189
+ display: 'flex',
190
+ flexDirection: 'column',
191
+ flex: 1,
192
+ minHeight: 0,
193
+ }, children: [_jsx(ThreadPrimitive.Viewport, { style: {
194
+ flex: 1,
195
+ minHeight: 0,
196
+ overflowY: 'auto',
197
+ }, children: _jsxs("div", { style: {
198
+ maxWidth: 768,
199
+ margin: '0 auto',
200
+ padding: 16,
201
+ display: 'flex',
202
+ flexDirection: 'column',
203
+ gap: 16,
204
+ }, children: [_jsx(ThreadPrimitive.Empty, { children: _jsx("div", { style: {
205
+ display: 'flex',
206
+ alignItems: 'center',
207
+ justifyContent: 'center',
208
+ minHeight: 300,
209
+ color: '#888',
210
+ textAlign: 'center',
211
+ fontSize: 14,
212
+ }, children: emptyMessage ??
213
+ (props.threadId
214
+ ? 'Send a message to start the conversation.'
215
+ : 'Start a new conversation.') }) }), _jsx(ThreadPrimitive.Messages, { components: {
216
+ UserMessage,
217
+ AssistantMessage,
218
+ } })] }) }), _jsx("div", { style: { maxWidth: 768, margin: '0 auto', width: '100%', padding: '0 16px' }, children: _jsx(PikkuComposer, {}) })] }) }) }));
219
+ }
@@ -0,0 +1,13 @@
1
+ export interface PikkuAgentRuntimeOptions {
2
+ api: string;
3
+ threadId?: string | null;
4
+ initialMessages?: any[];
5
+ onThreadCreated?: (id: string) => void;
6
+ onFinish?: () => void;
7
+ onApprovalRequest?: (data: {
8
+ toolCallId: string;
9
+ }) => void;
10
+ credentials?: RequestCredentials;
11
+ headers?: Record<string, string>;
12
+ }
13
+ export declare function usePikkuAgentRuntime(options: PikkuAgentRuntimeOptions): import("@assistant-ui/react").AssistantRuntime;
@@ -0,0 +1,163 @@
1
+ import { useMemo, useEffect, useRef, useCallback } from 'react';
2
+ import { useDataStreamRuntime } from '@assistant-ui/react-data-stream';
3
+ const convertDbMessages = (dbMessages) => {
4
+ const result = [];
5
+ let currentAssistant = null;
6
+ for (const msg of dbMessages) {
7
+ if (msg.role === 'user') {
8
+ if (currentAssistant) {
9
+ result.push(currentAssistant);
10
+ currentAssistant = null;
11
+ }
12
+ result.push({
13
+ role: 'user',
14
+ content: msg.content || '',
15
+ id: msg.id,
16
+ createdAt: new Date(msg.createdAt),
17
+ });
18
+ continue;
19
+ }
20
+ if (msg.role === 'tool' &&
21
+ currentAssistant &&
22
+ Array.isArray(msg.toolResults)) {
23
+ const parts = Array.isArray(currentAssistant.content)
24
+ ? [...currentAssistant.content]
25
+ : currentAssistant.content
26
+ ? [
27
+ {
28
+ type: 'text',
29
+ text: currentAssistant.content,
30
+ },
31
+ ]
32
+ : [];
33
+ for (const tr of msg.toolResults) {
34
+ const tcIdx = parts.findIndex((p) => p.type === 'tool-call' && p.toolCallId === tr.id);
35
+ if (tcIdx !== -1) {
36
+ parts[tcIdx] = {
37
+ ...parts[tcIdx],
38
+ result: typeof tr.result === 'string'
39
+ ? tr.result
40
+ : JSON.stringify(tr.result),
41
+ };
42
+ }
43
+ }
44
+ currentAssistant = {
45
+ role: currentAssistant.role,
46
+ id: currentAssistant.id,
47
+ createdAt: currentAssistant.createdAt,
48
+ status: currentAssistant.status,
49
+ content: parts,
50
+ };
51
+ continue;
52
+ }
53
+ if (msg.role === 'tool')
54
+ continue;
55
+ const parts = [];
56
+ if (msg.content) {
57
+ parts.push({ type: 'text', text: msg.content });
58
+ }
59
+ if (Array.isArray(msg.toolCalls)) {
60
+ for (const tc of msg.toolCalls) {
61
+ parts.push({
62
+ type: 'tool-call',
63
+ toolCallId: tc.id,
64
+ toolName: tc.name,
65
+ args: tc.args || {},
66
+ });
67
+ }
68
+ }
69
+ if (currentAssistant) {
70
+ const prev = currentAssistant.content;
71
+ const existingParts = Array.isArray(prev)
72
+ ? [...prev]
73
+ : prev
74
+ ? [{ type: 'text', text: prev }]
75
+ : [];
76
+ currentAssistant = {
77
+ role: currentAssistant.role,
78
+ id: currentAssistant.id,
79
+ createdAt: currentAssistant.createdAt,
80
+ status: currentAssistant.status,
81
+ content: [...existingParts, ...parts],
82
+ };
83
+ }
84
+ else {
85
+ currentAssistant = {
86
+ role: 'assistant',
87
+ content: parts.length > 0 ? parts : '',
88
+ id: msg.id,
89
+ createdAt: new Date(msg.createdAt),
90
+ status: { type: 'complete', reason: 'stop' },
91
+ };
92
+ }
93
+ }
94
+ if (currentAssistant) {
95
+ result.push(currentAssistant);
96
+ }
97
+ return result;
98
+ };
99
+ export function usePikkuAgentRuntime(options) {
100
+ const { api, threadId = null, initialMessages: rawInitialMessages, onThreadCreated, onFinish, onApprovalRequest, credentials, headers, } = options;
101
+ const threadIdRef = useRef(threadId);
102
+ threadIdRef.current = threadId;
103
+ const justCreatedThreadRef = useRef(false);
104
+ const bodyFn = useCallback(() => {
105
+ let currentThreadId = threadIdRef.current;
106
+ if (!currentThreadId) {
107
+ currentThreadId = crypto.randomUUID();
108
+ justCreatedThreadRef.current = true;
109
+ onThreadCreated?.(currentThreadId);
110
+ }
111
+ return { threadId: currentThreadId };
112
+ }, [onThreadCreated]);
113
+ const onData = useCallback((event) => {
114
+ if (event.name === 'approval-request') {
115
+ const approval = event.data;
116
+ onApprovalRequest?.({
117
+ toolCallId: approval.toolCallId,
118
+ });
119
+ }
120
+ }, [onApprovalRequest]);
121
+ const onFinishCb = useCallback(() => {
122
+ onFinish?.();
123
+ }, [onFinish]);
124
+ const initialMessages = useMemo(() => (rawInitialMessages ? convertDbMessages(rawInitialMessages) : []), [rawInitialMessages]);
125
+ const runtime = useDataStreamRuntime({
126
+ api,
127
+ protocol: 'ui-message-stream',
128
+ body: bodyFn,
129
+ onData,
130
+ onFinish: onFinishCb,
131
+ initialMessages,
132
+ credentials,
133
+ headers,
134
+ });
135
+ const prevThreadIdRef = useRef(threadId);
136
+ const hasResetRef = useRef(false);
137
+ useEffect(() => {
138
+ if (prevThreadIdRef.current !== threadId) {
139
+ prevThreadIdRef.current = threadId;
140
+ if (justCreatedThreadRef.current) {
141
+ justCreatedThreadRef.current = false;
142
+ hasResetRef.current = true;
143
+ return;
144
+ }
145
+ hasResetRef.current = false;
146
+ if (rawInitialMessages) {
147
+ ;
148
+ runtime.thread.reset(convertDbMessages(rawInitialMessages));
149
+ hasResetRef.current = true;
150
+ }
151
+ else {
152
+ ;
153
+ runtime.thread.reset([]);
154
+ }
155
+ }
156
+ else if (!hasResetRef.current && rawInitialMessages) {
157
+ ;
158
+ runtime.thread.reset(convertDbMessages(rawInitialMessages));
159
+ hasResetRef.current = true;
160
+ }
161
+ }, [threadId, rawInitialMessages, runtime]);
162
+ return runtime;
163
+ }
@@ -0,0 +1 @@
1
+ {"root":["../src/index.ts","../src/use-pikku-agent-runtime.ts","../src/pikku-agent-chat.tsx"],"version":"5.9.3"}
@@ -0,0 +1 @@
1
+ {"root":["../src/index.ts","../src/use-pikku-agent-runtime.ts","../src/pikku-agent-chat.tsx"],"version":"5.9.3"}
package/package.json ADDED
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "@pikku/assistant-ui",
3
+ "version": "0.12.0",
4
+ "author": "yasser.fadl@gmail.com",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "main": "dist/cjs/index.js",
8
+ "module": "dist/esm/index.js",
9
+ "exports": {
10
+ ".": {
11
+ "import": "./dist/esm/index.js",
12
+ "require": "./dist/cjs/index.js"
13
+ }
14
+ },
15
+ "scripts": {
16
+ "tsc": "tsc",
17
+ "build:esm": "tsc -b && echo '{\"type\": \"module\"}' > dist/esm/package.json",
18
+ "build:cjs": "tsc -b tsconfig.cjs.json && echo '{\"type\": \"commonjs\"}' > dist/cjs/package.json",
19
+ "build": "yarn build:esm && yarn build:cjs"
20
+ },
21
+ "dependencies": {
22
+ "@assistant-ui/react": "^0.12.12",
23
+ "@assistant-ui/react-data-stream": "^0.12.5"
24
+ },
25
+ "peerDependencies": {
26
+ "react": "^18 || ^19",
27
+ "react-dom": "^18 || ^19"
28
+ },
29
+ "devDependencies": {
30
+ "@types/react": "^19",
31
+ "@types/react-dom": "^19",
32
+ "typescript": "^5.9"
33
+ }
34
+ }
package/src/index.ts ADDED
@@ -0,0 +1,4 @@
1
+ export { usePikkuAgentRuntime } from './use-pikku-agent-runtime.js'
2
+ export type { PikkuAgentRuntimeOptions } from './use-pikku-agent-runtime.js'
3
+ export { PikkuAgentChat } from './pikku-agent-chat.js'
4
+ export type { PikkuAgentChatProps } from './pikku-agent-chat.js'