@agentforge-io/chat-react 2.0.16 → 3.0.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.
- package/dist/ChatPanel.d.ts +19 -1
- package/dist/ChatPanel.js +149 -2
- package/dist/index.d.ts +2 -2
- package/package.json +4 -4
package/dist/ChatPanel.d.ts
CHANGED
|
@@ -1,4 +1,19 @@
|
|
|
1
1
|
import { type CSSProperties } from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* Optional handler the host wires when the chat panel is embedded
|
|
4
|
+
* inside an admin surface (e.g. the dashboard's agent playground).
|
|
5
|
+
*
|
|
6
|
+
* When provided, an `awaiting_approval` bubble renders Approve/Deny
|
|
7
|
+
* buttons that call this function — the host implementation typically
|
|
8
|
+
* POSTs to `/admin/tenants/:tid/approvals/:id/{approve,deny}` and then
|
|
9
|
+
* resolves so the panel knows to auto-continue. When omitted (e.g.
|
|
10
|
+
* a public-chat embed where the visitor isn't an admin), the bubble
|
|
11
|
+
* renders a read-only "waiting" state with a link to `/approvals`.
|
|
12
|
+
*/
|
|
13
|
+
export type ApprovalActionHandler = (args: {
|
|
14
|
+
approvalId: string;
|
|
15
|
+
action: 'approve' | 'deny';
|
|
16
|
+
}) => Promise<void>;
|
|
2
17
|
export interface ChatPanelProps {
|
|
3
18
|
/** Override the header title. Falls back to theme.title, then agent.name. */
|
|
4
19
|
title?: string;
|
|
@@ -11,6 +26,9 @@ export interface ChatPanelProps {
|
|
|
11
26
|
* the height. */
|
|
12
27
|
style?: CSSProperties;
|
|
13
28
|
className?: string;
|
|
29
|
+
/** Wire an admin-side handler to render Approve/Deny buttons on
|
|
30
|
+
* `awaiting_approval` bubbles. See `ApprovalActionHandler`. */
|
|
31
|
+
onApprovalAction?: ApprovalActionHandler;
|
|
14
32
|
}
|
|
15
33
|
/**
|
|
16
34
|
* Reference rendering of a chat panel. Pure React, no portal, no fixed
|
|
@@ -18,4 +36,4 @@ export interface ChatPanelProps {
|
|
|
18
36
|
* (modal, sidebar, page section). For the floating launcher pattern, see
|
|
19
37
|
* the script-tag widget at /widget.js.
|
|
20
38
|
*/
|
|
21
|
-
export declare function ChatPanel({ title, placeholder, subtitle, style, className, }: ChatPanelProps): import("react/jsx-runtime").JSX.Element;
|
|
39
|
+
export declare function ChatPanel({ title, placeholder, subtitle, style, className, onApprovalAction, }: ChatPanelProps): import("react/jsx-runtime").JSX.Element;
|
package/dist/ChatPanel.js
CHANGED
|
@@ -11,7 +11,7 @@ const hooks_1 = require("./hooks");
|
|
|
11
11
|
* (modal, sidebar, page section). For the floating launcher pattern, see
|
|
12
12
|
* the script-tag widget at /widget.js.
|
|
13
13
|
*/
|
|
14
|
-
function ChatPanel({ title, placeholder = 'Type a message…', subtitle, style, className, }) {
|
|
14
|
+
function ChatPanel({ title, placeholder = 'Type a message…', subtitle, style, className, onApprovalAction, }) {
|
|
15
15
|
const { agent, theme } = (0, hooks_1.useAgent)();
|
|
16
16
|
const { messages, status, send, isBusy, error } = (0, hooks_1.useChat)();
|
|
17
17
|
const resolvedTitle = title ?? theme?.title ?? agent?.name ?? 'Chat';
|
|
@@ -81,7 +81,20 @@ function ChatPanel({ title, placeholder = 'Type a message…', subtitle, style,
|
|
|
81
81
|
display: 'flex',
|
|
82
82
|
flexDirection: 'column',
|
|
83
83
|
gap: 10,
|
|
84
|
-
}, children: messages.map((m) =>
|
|
84
|
+
}, children: messages.map((m) => {
|
|
85
|
+
// Branch on structured metadata before falling back to the
|
|
86
|
+
// plain-text bubble. Unknown kinds also fall through — the
|
|
87
|
+
// body still carries the human-readable line the server
|
|
88
|
+
// generated.
|
|
89
|
+
const kind = m.metadata?.kind;
|
|
90
|
+
if (kind === 'awaiting_approval') {
|
|
91
|
+
return ((0, jsx_runtime_1.jsx)(ApprovalCardBubble, { message: m, primary: primary, onAction: onApprovalAction, onContinue: () => send('continue') }, m.id));
|
|
92
|
+
}
|
|
93
|
+
if (kind === 'tool_blocked') {
|
|
94
|
+
return ((0, jsx_runtime_1.jsx)(BlockedCardBubble, { message: m }, m.id));
|
|
95
|
+
}
|
|
96
|
+
return ((0, jsx_runtime_1.jsx)(Bubble, { role: m.role, streaming: m.isStreaming, primary: primary, children: m.content }, m.id));
|
|
97
|
+
}) }), error && ((0, jsx_runtime_1.jsx)("div", { style: {
|
|
85
98
|
padding: '10px 16px',
|
|
86
99
|
fontSize: 12,
|
|
87
100
|
color: '#b91c1c',
|
|
@@ -183,3 +196,137 @@ function hexToRgba(hex, alpha) {
|
|
|
183
196
|
const b = parseInt(h.slice(4, 6), 16);
|
|
184
197
|
return `rgba(${r},${g},${b},${alpha})`;
|
|
185
198
|
}
|
|
199
|
+
/**
|
|
200
|
+
* Bubble variant for `metadata.kind === 'awaiting_approval'`. Shows the
|
|
201
|
+
* tool name + a countdown, and either Approve/Deny buttons (when the
|
|
202
|
+
* host wired `onApprovalAction`) or a read-only hint pointing the user
|
|
203
|
+
* to `/approvals`. After a successful Approve, calls `onContinue` so
|
|
204
|
+
* the chat sends a follow-up turn that consumes the approval through
|
|
205
|
+
* the gate's fast path.
|
|
206
|
+
*/
|
|
207
|
+
function ApprovalCardBubble({ message, primary, onAction, onContinue, }) {
|
|
208
|
+
const meta = message.metadata;
|
|
209
|
+
const [busy, setBusy] = (0, react_1.useState)(null);
|
|
210
|
+
const [decided, setDecided] = (0, react_1.useState)(null);
|
|
211
|
+
const [err, setErr] = (0, react_1.useState)(null);
|
|
212
|
+
const remaining = useExpiresIn(meta?.expiresAt);
|
|
213
|
+
if (!meta)
|
|
214
|
+
return null;
|
|
215
|
+
const act = async (action) => {
|
|
216
|
+
if (!onAction)
|
|
217
|
+
return;
|
|
218
|
+
setBusy(action);
|
|
219
|
+
setErr(null);
|
|
220
|
+
try {
|
|
221
|
+
await onAction({ approvalId: meta.approvalId, action });
|
|
222
|
+
setDecided(action === 'approve' ? 'approved' : 'denied');
|
|
223
|
+
if (action === 'approve') {
|
|
224
|
+
// Small delay so the user sees the "Approved" pill flash
|
|
225
|
+
// before the next turn starts streaming on top.
|
|
226
|
+
setTimeout(onContinue, 250);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
catch (e) {
|
|
230
|
+
setErr(e instanceof Error ? e.message : String(e));
|
|
231
|
+
}
|
|
232
|
+
finally {
|
|
233
|
+
setBusy(null);
|
|
234
|
+
}
|
|
235
|
+
};
|
|
236
|
+
return ((0, jsx_runtime_1.jsxs)("div", { style: {
|
|
237
|
+
alignSelf: 'flex-start',
|
|
238
|
+
maxWidth: '90%',
|
|
239
|
+
padding: '12px 14px',
|
|
240
|
+
borderRadius: 14,
|
|
241
|
+
borderBottomLeftRadius: 4,
|
|
242
|
+
fontSize: 14,
|
|
243
|
+
background: '#fffbeb',
|
|
244
|
+
border: '1px solid #fde68a',
|
|
245
|
+
color: '#7c2d12',
|
|
246
|
+
}, children: [(0, jsx_runtime_1.jsx)("div", { style: { fontWeight: 600, fontSize: 13, marginBottom: 4 }, children: "Awaiting approval" }), (0, jsx_runtime_1.jsxs)("div", { style: { fontSize: 13, lineHeight: 1.5 }, children: ["The agent wants to run", ' ', (0, jsx_runtime_1.jsx)("code", { style: {
|
|
247
|
+
background: 'rgba(0,0,0,0.06)',
|
|
248
|
+
padding: '1px 6px',
|
|
249
|
+
borderRadius: 4,
|
|
250
|
+
fontSize: 12,
|
|
251
|
+
}, children: meta.toolName }), "."] }), remaining && !decided && ((0, jsx_runtime_1.jsx)("div", { style: { fontSize: 11, marginTop: 6, color: '#92400e' }, children: remaining })), decided && ((0, jsx_runtime_1.jsx)("div", { style: {
|
|
252
|
+
display: 'inline-block',
|
|
253
|
+
marginTop: 8,
|
|
254
|
+
padding: '2px 8px',
|
|
255
|
+
borderRadius: 999,
|
|
256
|
+
fontSize: 11,
|
|
257
|
+
fontWeight: 600,
|
|
258
|
+
background: decided === 'approved' ? '#dcfce7' : '#fee2e2',
|
|
259
|
+
color: decided === 'approved' ? '#166534' : '#991b1b',
|
|
260
|
+
}, children: decided === 'approved' ? 'Approved — retrying…' : 'Denied' })), !decided && onAction && ((0, jsx_runtime_1.jsxs)("div", { style: { display: 'flex', gap: 8, marginTop: 10 }, children: [(0, jsx_runtime_1.jsx)("button", { type: "button", disabled: busy !== null, onClick: () => act('approve'), style: {
|
|
261
|
+
background: primary,
|
|
262
|
+
color: 'white',
|
|
263
|
+
border: 'none',
|
|
264
|
+
borderRadius: 8,
|
|
265
|
+
padding: '6px 12px',
|
|
266
|
+
fontSize: 12,
|
|
267
|
+
fontWeight: 600,
|
|
268
|
+
cursor: busy === null ? 'pointer' : 'not-allowed',
|
|
269
|
+
opacity: busy === null ? 1 : 0.6,
|
|
270
|
+
}, children: busy === 'approve' ? 'Approving…' : 'Approve' }), (0, jsx_runtime_1.jsx)("button", { type: "button", disabled: busy !== null, onClick: () => act('deny'), style: {
|
|
271
|
+
background: 'white',
|
|
272
|
+
color: '#7c2d12',
|
|
273
|
+
border: '1px solid #fde68a',
|
|
274
|
+
borderRadius: 8,
|
|
275
|
+
padding: '6px 12px',
|
|
276
|
+
fontSize: 12,
|
|
277
|
+
fontWeight: 600,
|
|
278
|
+
cursor: busy === null ? 'pointer' : 'not-allowed',
|
|
279
|
+
opacity: busy === null ? 1 : 0.6,
|
|
280
|
+
}, children: busy === 'deny' ? 'Denying…' : 'Deny' })] })), !decided && !onAction && ((0, jsx_runtime_1.jsxs)("div", { style: { fontSize: 12, marginTop: 8, color: '#7c2d12' }, children: ["A workspace admin needs to review this. Visit", ' ', (0, jsx_runtime_1.jsx)("code", { style: { fontSize: 12 }, children: "/approvals" }), " in the dashboard."] })), err && ((0, jsx_runtime_1.jsx)("div", { style: { fontSize: 11, marginTop: 6, color: '#b91c1c' }, children: err }))] }));
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* Bubble variant for `metadata.kind === 'tool_blocked'`. Terminal —
|
|
284
|
+
* no action affordance. Renders the reason if the server supplied
|
|
285
|
+
* one, otherwise a generic line.
|
|
286
|
+
*/
|
|
287
|
+
function BlockedCardBubble({ message }) {
|
|
288
|
+
const meta = message.metadata;
|
|
289
|
+
if (!meta)
|
|
290
|
+
return null;
|
|
291
|
+
return ((0, jsx_runtime_1.jsxs)("div", { style: {
|
|
292
|
+
alignSelf: 'flex-start',
|
|
293
|
+
maxWidth: '90%',
|
|
294
|
+
padding: '10px 14px',
|
|
295
|
+
borderRadius: 14,
|
|
296
|
+
borderBottomLeftRadius: 4,
|
|
297
|
+
fontSize: 13,
|
|
298
|
+
background: '#fef2f2',
|
|
299
|
+
border: '1px solid #fecaca',
|
|
300
|
+
color: '#7f1d1d',
|
|
301
|
+
}, children: [(0, jsx_runtime_1.jsx)("div", { style: { fontWeight: 600, fontSize: 13, marginBottom: 4 }, children: "Tool blocked" }), (0, jsx_runtime_1.jsxs)("div", { style: { fontSize: 12, lineHeight: 1.5 }, children: [(0, jsx_runtime_1.jsx)("code", { style: {
|
|
302
|
+
background: 'rgba(0,0,0,0.06)',
|
|
303
|
+
padding: '1px 6px',
|
|
304
|
+
borderRadius: 4,
|
|
305
|
+
fontSize: 12,
|
|
306
|
+
}, children: meta.toolName }), ' ', "is blocked by the workspace admin. ", meta.reason ?? ''] })] }));
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* Live countdown for an ISO timestamp. Returns null when the deadline
|
|
310
|
+
* has passed — caller renders nothing in that case. Tick interval 1s
|
|
311
|
+
* to match the `/approvals` page's badge.
|
|
312
|
+
*/
|
|
313
|
+
function useExpiresIn(iso) {
|
|
314
|
+
const [, force] = (0, react_1.useState)(0);
|
|
315
|
+
(0, react_1.useEffect)(() => {
|
|
316
|
+
if (!iso)
|
|
317
|
+
return;
|
|
318
|
+
const t = setInterval(() => force((n) => n + 1), 1000);
|
|
319
|
+
return () => clearInterval(t);
|
|
320
|
+
}, [iso]);
|
|
321
|
+
if (!iso)
|
|
322
|
+
return null;
|
|
323
|
+
const ms = new Date(iso).getTime() - Date.now();
|
|
324
|
+
if (!Number.isFinite(ms) || ms <= 0)
|
|
325
|
+
return null;
|
|
326
|
+
const totalSeconds = Math.floor(ms / 1000);
|
|
327
|
+
const m = Math.floor(totalSeconds / 60);
|
|
328
|
+
const s = totalSeconds % 60;
|
|
329
|
+
if (m >= 60)
|
|
330
|
+
return `Expires in ${Math.floor(m / 60)}h ${m % 60}m`;
|
|
331
|
+
return `Expires in ${m}m ${s.toString().padStart(2, '0')}s`;
|
|
332
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -26,5 +26,5 @@ export { AgentChatProvider, useAgentChatContext, useChatSessionState } from './p
|
|
|
26
26
|
export type { AgentChatProviderProps, ChatUserContext, } from './provider';
|
|
27
27
|
export { useChat, useAgent } from './hooks';
|
|
28
28
|
export { ChatPanel } from './ChatPanel';
|
|
29
|
-
export type { ChatPanelProps } from './ChatPanel';
|
|
30
|
-
export type { ChatAgentSummary, ChatEvent, ChatMessage, ChatRole, ChatSessionStatus, ChatTheme, } from '@agentforge-io/chat-sdk';
|
|
29
|
+
export type { ChatPanelProps, ApprovalActionHandler } from './ChatPanel';
|
|
30
|
+
export type { ChatAgentSummary, ChatEvent, ChatMessage, ChatMessageMetadata, ChatRole, ChatSessionStatus, ChatTheme, } from '@agentforge-io/chat-sdk';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agentforge-io/chat-react",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.0",
|
|
4
4
|
"description": "React + Next.js adapter for @agentforge-io/chat-sdk. Provider, hooks, and a reference ChatPanel component.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -16,10 +16,10 @@
|
|
|
16
16
|
"peerDependencies": {
|
|
17
17
|
"react": "^18.0.0 || ^19.0.0",
|
|
18
18
|
"react-dom": "^18.0.0 || ^19.0.0",
|
|
19
|
-
"@agentforge-io/chat-sdk": "^
|
|
19
|
+
"@agentforge-io/chat-sdk": "^3.0.0"
|
|
20
20
|
},
|
|
21
21
|
"dependencies": {
|
|
22
|
-
"@agentforge-io/chat-sdk": "^
|
|
22
|
+
"@agentforge-io/chat-sdk": "^3.0.0"
|
|
23
23
|
},
|
|
24
24
|
"devDependencies": {
|
|
25
25
|
"@types/node": "^20.0.0",
|
|
@@ -29,4 +29,4 @@
|
|
|
29
29
|
"react-dom": "^18.0.0 || ^19.0.0",
|
|
30
30
|
"typescript": "^5.0.0"
|
|
31
31
|
}
|
|
32
|
-
}
|
|
32
|
+
}
|