@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/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
- maxHeight: "70vh",
469
+ height: "70vh",
470
+ maxHeight: 600,
428
471
  display: open ? "flex" : "none",
429
472
  flexDirection: "column",
430
473
  fontFamily: "var(--ai-me-font)",