@ai-me-chat/react 0.0.1 → 0.2.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/README.md +233 -0
- package/dist/index.cjs +47 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +118 -10
- package/dist/index.d.ts +118 -10
- package/dist/index.js +47 -4
- package/dist/index.js.map +1 -1
- package/package.json +18 -4
package/dist/index.d.ts
CHANGED
|
@@ -3,15 +3,59 @@ import * as react from 'react';
|
|
|
3
3
|
import { ReactNode } from 'react';
|
|
4
4
|
import * as ai from 'ai';
|
|
5
5
|
|
|
6
|
+
/** Payload passed to the `onAction` callback */
|
|
7
|
+
interface AIMeAction {
|
|
8
|
+
/** Action type — e.g. "navigate", "prefill", "open-modal" */
|
|
9
|
+
type: string;
|
|
10
|
+
/** Flexible additional payload supplied by the AI tool */
|
|
11
|
+
[key: string]: unknown;
|
|
12
|
+
}
|
|
13
|
+
interface AIMeContextValue {
|
|
14
|
+
/** API endpoint for AI-Me handler */
|
|
15
|
+
endpoint: string;
|
|
16
|
+
/** Additional headers to send with requests */
|
|
17
|
+
headers?: Record<string, string>;
|
|
18
|
+
/**
|
|
19
|
+
* Optional callback for client-side action intents emitted by the AI.
|
|
20
|
+
* Use this to handle navigation, form pre-fill, modal opening, etc.
|
|
21
|
+
* without the AI making an API call directly.
|
|
22
|
+
*/
|
|
23
|
+
onAction?: (action: AIMeAction) => void;
|
|
24
|
+
}
|
|
25
|
+
declare function useAIMeContext(): AIMeContextValue;
|
|
26
|
+
|
|
6
27
|
interface AIMeProviderProps {
|
|
7
28
|
/** API endpoint for AI-Me handler */
|
|
8
29
|
endpoint: string;
|
|
9
30
|
/** Optional: additional headers to send with requests */
|
|
10
31
|
headers?: Record<string, string>;
|
|
32
|
+
/**
|
|
33
|
+
* Optional callback for client-side action intents emitted by the AI.
|
|
34
|
+
*
|
|
35
|
+
* The AI can emit structured actions (e.g. "navigate", "prefill",
|
|
36
|
+
* "open-modal") that your app handles without making an API call.
|
|
37
|
+
* Wire the corresponding `__navigate` / `__prefill` tools in your
|
|
38
|
+
* AI-Me handler to produce these actions, then consume them here.
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* ```tsx
|
|
42
|
+
* <AIMeProvider
|
|
43
|
+
* endpoint="/api/ai-me"
|
|
44
|
+
* onAction={(action) => {
|
|
45
|
+
* if (action.type === "navigate") {
|
|
46
|
+
* router.push(action.href as string);
|
|
47
|
+
* }
|
|
48
|
+
* }}
|
|
49
|
+
* >
|
|
50
|
+
* {children}
|
|
51
|
+
* </AIMeProvider>
|
|
52
|
+
* ```
|
|
53
|
+
*/
|
|
54
|
+
onAction?: (action: AIMeAction) => void;
|
|
11
55
|
/** Child components */
|
|
12
56
|
children: ReactNode;
|
|
13
57
|
}
|
|
14
|
-
declare function AIMeProvider({ endpoint, headers, children }: AIMeProviderProps): react_jsx_runtime.JSX.Element;
|
|
58
|
+
declare function AIMeProvider({ endpoint, headers, onAction, children }: AIMeProviderProps): react_jsx_runtime.JSX.Element;
|
|
15
59
|
|
|
16
60
|
interface AIMeTheme {
|
|
17
61
|
primaryColor?: string;
|
|
@@ -21,6 +65,25 @@ interface AIMeTheme {
|
|
|
21
65
|
fontFamily?: string;
|
|
22
66
|
}
|
|
23
67
|
|
|
68
|
+
/** Tool execution result passed to onToolComplete */
|
|
69
|
+
interface ToolCompleteEvent {
|
|
70
|
+
/** Tool (function) name */
|
|
71
|
+
name: string;
|
|
72
|
+
/** HTTP method used, if available from the tool result */
|
|
73
|
+
httpMethod?: string;
|
|
74
|
+
/** API path called, if available from the tool result */
|
|
75
|
+
path?: string;
|
|
76
|
+
/** Raw result returned by the tool */
|
|
77
|
+
result: unknown;
|
|
78
|
+
/** Whether the tool required user confirmation */
|
|
79
|
+
requiresConfirmation?: boolean;
|
|
80
|
+
}
|
|
81
|
+
/** Completed assistant message passed to onMessageComplete */
|
|
82
|
+
interface MessageCompleteEvent {
|
|
83
|
+
role: string;
|
|
84
|
+
content: string;
|
|
85
|
+
toolCalls?: unknown[];
|
|
86
|
+
}
|
|
24
87
|
interface AIMeChatProps {
|
|
25
88
|
/** Position of chat panel */
|
|
26
89
|
position?: "bottom-right" | "bottom-left" | "inline";
|
|
@@ -34,8 +97,61 @@ interface AIMeChatProps {
|
|
|
34
97
|
defaultOpen?: boolean;
|
|
35
98
|
/** Callback when chat opens/closes */
|
|
36
99
|
onToggle?: (open: boolean) => void;
|
|
100
|
+
/**
|
|
101
|
+
* Fired after each tool execution completes (after the API call returns).
|
|
102
|
+
* Use this to trigger client-side data refreshes when the AI mutates data.
|
|
103
|
+
*/
|
|
104
|
+
onToolComplete?: (tool: ToolCompleteEvent) => void;
|
|
105
|
+
/**
|
|
106
|
+
* Fired when the assistant finishes a full response (status transitions
|
|
107
|
+
* from "streaming" to "ready").
|
|
108
|
+
*/
|
|
109
|
+
onMessageComplete?: (message: MessageCompleteEvent) => void;
|
|
110
|
+
/**
|
|
111
|
+
* Custom renderer for the tool confirmation dialog.
|
|
112
|
+
*
|
|
113
|
+
* When provided, AI-Me will call this function instead of showing the
|
|
114
|
+
* default confirmation UI. Return any React node — a modal, an inline
|
|
115
|
+
* card, a drawer, etc.
|
|
116
|
+
*
|
|
117
|
+
* If not provided, the built-in `<AIMeConfirm>` dialog is used.
|
|
118
|
+
*
|
|
119
|
+
* @example
|
|
120
|
+
* ```tsx
|
|
121
|
+
* <AIMeChat
|
|
122
|
+
* renderConfirmation={({ tool, params, onConfirm, onCancel }) => (
|
|
123
|
+
* <MyCustomConfirmDialog
|
|
124
|
+
* title={`Run ${tool.name}?`}
|
|
125
|
+
* description={tool.description}
|
|
126
|
+
* params={params}
|
|
127
|
+
* onConfirm={onConfirm}
|
|
128
|
+
* onCancel={onCancel}
|
|
129
|
+
* />
|
|
130
|
+
* )}
|
|
131
|
+
* />
|
|
132
|
+
* ```
|
|
133
|
+
*/
|
|
134
|
+
renderConfirmation?: (props: {
|
|
135
|
+
/** Metadata about the tool that is about to be executed */
|
|
136
|
+
tool: {
|
|
137
|
+
/** Tool (function) name */
|
|
138
|
+
name: string;
|
|
139
|
+
/** HTTP method the tool maps to, e.g. "POST" */
|
|
140
|
+
httpMethod: string;
|
|
141
|
+
/** API path the tool will call, e.g. "/api/projects" */
|
|
142
|
+
path: string;
|
|
143
|
+
/** Human-readable description of what the tool does */
|
|
144
|
+
description: string;
|
|
145
|
+
};
|
|
146
|
+
/** Resolved parameters the tool will be called with */
|
|
147
|
+
params: Record<string, unknown>;
|
|
148
|
+
/** Call to proceed with the tool execution */
|
|
149
|
+
onConfirm: () => void;
|
|
150
|
+
/** Call to abort without executing the tool */
|
|
151
|
+
onCancel: () => void;
|
|
152
|
+
}) => ReactNode;
|
|
37
153
|
}
|
|
38
|
-
declare function AIMeChat({ position, theme, welcomeMessage, suggestedPrompts, defaultOpen, onToggle, }: AIMeChatProps): react_jsx_runtime.JSX.Element;
|
|
154
|
+
declare function AIMeChat({ position, theme, welcomeMessage, suggestedPrompts, defaultOpen, onToggle, onToolComplete, onMessageComplete, }: AIMeChatProps): react_jsx_runtime.JSX.Element;
|
|
39
155
|
|
|
40
156
|
interface AIMeCommandPaletteProps {
|
|
41
157
|
/** List of available commands/actions */
|
|
@@ -129,14 +245,6 @@ declare function useAIMe(): {
|
|
|
129
245
|
clearMessages: () => void;
|
|
130
246
|
};
|
|
131
247
|
|
|
132
|
-
interface AIMeContextValue {
|
|
133
|
-
/** API endpoint for AI-Me handler */
|
|
134
|
-
endpoint: string;
|
|
135
|
-
/** Additional headers to send with requests */
|
|
136
|
-
headers?: Record<string, string>;
|
|
137
|
-
}
|
|
138
|
-
declare function useAIMeContext(): AIMeContextValue;
|
|
139
|
-
|
|
140
248
|
/**
|
|
141
249
|
* Lightweight inline markdown renderer for chat messages.
|
|
142
250
|
* Supports: **bold**, *italic*, `code`, ```code blocks```, [links](url), - lists
|
package/dist/index.js
CHANGED
|
@@ -13,8 +13,8 @@ function useAIMeContext() {
|
|
|
13
13
|
|
|
14
14
|
// src/provider.tsx
|
|
15
15
|
import { jsx } from "react/jsx-runtime";
|
|
16
|
-
function AIMeProvider({ endpoint, headers, children }) {
|
|
17
|
-
return /* @__PURE__ */ jsx(AIMeContext, { value: { endpoint, headers }, children });
|
|
16
|
+
function AIMeProvider({ endpoint, headers, onAction, children }) {
|
|
17
|
+
return /* @__PURE__ */ jsx(AIMeContext, { value: { endpoint, headers, onAction }, children });
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
// src/chat.tsx
|
|
@@ -325,13 +325,17 @@ function AIMeChat({
|
|
|
325
325
|
welcomeMessage = "Hi! I can help you navigate and use this app. What would you like to do?",
|
|
326
326
|
suggestedPrompts,
|
|
327
327
|
defaultOpen = false,
|
|
328
|
-
onToggle
|
|
328
|
+
onToggle,
|
|
329
|
+
onToolComplete,
|
|
330
|
+
onMessageComplete
|
|
329
331
|
}) {
|
|
330
332
|
const [open, setOpen] = useState2(defaultOpen);
|
|
331
333
|
const messagesEndRef = useRef2(null);
|
|
332
334
|
const inputRef = useRef2(null);
|
|
333
335
|
const panelRef = useRef2(null);
|
|
334
336
|
const triggerRef = useRef2(null);
|
|
337
|
+
const firedToolResults = useRef2(/* @__PURE__ */ new Set());
|
|
338
|
+
const prevStatus = useRef2(null);
|
|
335
339
|
const {
|
|
336
340
|
messages,
|
|
337
341
|
input,
|
|
@@ -362,6 +366,44 @@ function AIMeChat({
|
|
|
362
366
|
useEffect2(() => {
|
|
363
367
|
messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
|
|
364
368
|
}, [messages]);
|
|
369
|
+
useEffect2(() => {
|
|
370
|
+
if (!onToolComplete) return;
|
|
371
|
+
for (const message of messages) {
|
|
372
|
+
for (const part of message.parts) {
|
|
373
|
+
if (part.type !== "tool-result") continue;
|
|
374
|
+
const id = part.toolCallId;
|
|
375
|
+
const dedupeKey = id ?? `${message.id}:${part.type}`;
|
|
376
|
+
if (firedToolResults.current.has(dedupeKey)) continue;
|
|
377
|
+
firedToolResults.current.add(dedupeKey);
|
|
378
|
+
onToolComplete({
|
|
379
|
+
name: part.toolName ?? "",
|
|
380
|
+
result: part.result
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
}, [messages, onToolComplete]);
|
|
385
|
+
useEffect2(() => {
|
|
386
|
+
const prev = prevStatus.current;
|
|
387
|
+
prevStatus.current = status;
|
|
388
|
+
if (!onMessageComplete) return;
|
|
389
|
+
if (status !== "ready") return;
|
|
390
|
+
if (prev !== "streaming" && prev !== "submitted") return;
|
|
391
|
+
let lastAssistant;
|
|
392
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
393
|
+
if (messages[i].role === "assistant") {
|
|
394
|
+
lastAssistant = messages[i];
|
|
395
|
+
break;
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
if (!lastAssistant) return;
|
|
399
|
+
const textContent = lastAssistant.parts.filter((p) => p.type === "text").map((p) => p.text).join("");
|
|
400
|
+
const toolCalls = lastAssistant.parts.filter((p) => p.type === "tool-call");
|
|
401
|
+
onMessageComplete({
|
|
402
|
+
role: lastAssistant.role,
|
|
403
|
+
content: textContent,
|
|
404
|
+
toolCalls: toolCalls.length > 0 ? toolCalls : void 0
|
|
405
|
+
});
|
|
406
|
+
}, [status, messages, onMessageComplete]);
|
|
365
407
|
useEffect2(() => {
|
|
366
408
|
if (open) {
|
|
367
409
|
panelRef.current?.focus();
|
|
@@ -424,7 +466,8 @@ function AIMeChat({
|
|
|
424
466
|
bottom: 24,
|
|
425
467
|
...position === "bottom-right" ? { right: 24 } : { left: 24 },
|
|
426
468
|
width: 380,
|
|
427
|
-
|
|
469
|
+
height: "70vh",
|
|
470
|
+
maxHeight: 600,
|
|
428
471
|
display: open ? "flex" : "none",
|
|
429
472
|
flexDirection: "column",
|
|
430
473
|
fontFamily: "var(--ai-me-font)",
|