@cntyclub/agent-react 0.1.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 ADDED
@@ -0,0 +1,102 @@
1
+ # @cntyclub/agent-react
2
+
3
+ Embeddable AI agent chat widget for Country Club dashboards — the frontend for the
4
+ backend's **Agent Mode** (`agent_mode` Django app). A floating button opens a popup
5
+ chat panel (desktop) or fullscreen chat (mobile); an **Agent Mode** button expands the
6
+ popup to fullscreen. The agent can call MCP tools on the user's behalf: read-only tools
7
+ run automatically, write tools require in-chat user approval. Tabular tool results are
8
+ rendered as paginated, searchable tables.
9
+
10
+ Built **exclusively** on [`@cntyclub/ui-react`](https://uikit.country.club/AGENT.md) —
11
+ no custom styles or components. Light/dark mode follows the host dashboard's theme
12
+ tokens automatically.
13
+
14
+ ## Install
15
+
16
+ ```bash
17
+ pnpm add @cntyclub/agent-react @cntyclub/ui-react
18
+ ```
19
+
20
+ Requirements:
21
+
22
+ - React 19+
23
+ - Tailwind CSS v4 with the UI kit stylesheet + sources configured:
24
+
25
+ ```css
26
+ @import "tailwindcss";
27
+ @import "@cntyclub/ui-react/styles.css";
28
+ @source "../node_modules/@cntyclub/ui-react/src";
29
+ @source "../node_modules/@cntyclub/agent-react/src";
30
+ ```
31
+
32
+ - An **Agent Mode client ID**, created in the Country Club office panel
33
+ (Office → AI → Agent Mode → Create Agent). The client ID identifies the agent only;
34
+ the backend resolves the MCP connector, model, and system prompt from it.
35
+ - The host page's origin must be on the agent's **allowed domains** list — other
36
+ origins get a CORS error from the backend.
37
+ - A logged-in user: every request carries the user's Country Club JWT. Users who are
38
+ not signed in cannot chat.
39
+
40
+ ## Usage
41
+
42
+ 1. Create the agent config file (`lib/agent/agent-config.ts`):
43
+
44
+ ```ts
45
+ import { defineAgentConfig } from "@cntyclub/agent-react";
46
+ import { getAccessToken } from "@/lib/api/tokens";
47
+
48
+ export const agentConfig = defineAgentConfig({
49
+ clientId: process.env.NEXT_PUBLIC_AGENT_CLIENT_ID!,
50
+ apiBaseUrl: process.env.NEXT_PUBLIC_API_URL!,
51
+ getAccessToken,
52
+ agentName: "Assistant",
53
+ suggestions: ["Show my target companies", "List my products"],
54
+ pages: [
55
+ {
56
+ tools: ["business_target_companies_list"],
57
+ route: "/dashboard/target-companies",
58
+ title: "Target Companies",
59
+ },
60
+ {
61
+ tools: ["business_companies_get"],
62
+ route: "/dashboard/company/:id",
63
+ title: "Company",
64
+ buildRoute: (args) => (args.id ? `/dashboard/company/${args.id}` : null),
65
+ },
66
+ ],
67
+ });
68
+ ```
69
+
70
+ 2. Mount the widget inside your authenticated layout (client component):
71
+
72
+ ```tsx
73
+ "use client";
74
+ import { AgentWidget } from "@cntyclub/agent-react";
75
+ import { useRouter } from "next/navigation";
76
+ import { agentConfig } from "@/lib/agent/agent-config";
77
+
78
+ export function DashboardAgent() {
79
+ const router = useRouter();
80
+ return <AgentWidget config={{ ...agentConfig, navigate: (path) => router.push(path) }} />;
81
+ }
82
+ ```
83
+
84
+ `pages` + `navigate` power **page-follow**: in popup mode, when the agent uses a tool
85
+ that maps to a page, the host app navigates there while the chat shows the same data.
86
+
87
+ ## Exports
88
+
89
+ - `AgentWidget` — the complete widget (launcher + popup + fullscreen Agent Mode).
90
+ - `AgentPanel` — just the chat surface, for custom shells.
91
+ - `useAgentChat(config)` — headless chat state (messages, approvals, conversations).
92
+ - `AgentApiClient` — low-level API client.
93
+ - `defineAgentConfig`, plus all public types.
94
+
95
+ ## Development
96
+
97
+ ```bash
98
+ pnpm install # uses a link: to the sibling UI kit repo for local dev
99
+ pnpm typecheck
100
+ pnpm build # tsup → dist/
101
+ ./publish.sh # publish to npm (needs NPM_TOKEN in .env)
102
+ ```
@@ -0,0 +1,213 @@
1
+ import * as React from 'react';
2
+
3
+ /** A structured UI block attached to a message (currently: result tables). */
4
+ interface TableBlock {
5
+ type: "table";
6
+ tool_name: string;
7
+ columns: string[];
8
+ rows: Record<string, unknown>[];
9
+ row_count: number;
10
+ truncated?: boolean;
11
+ pagination?: Record<string, unknown>;
12
+ }
13
+ type MessageBlock = TableBlock;
14
+ interface ToolCallInfo {
15
+ id?: string;
16
+ name?: string;
17
+ arguments?: Record<string, unknown>;
18
+ }
19
+ interface AgentMessage {
20
+ id: string;
21
+ role: "user" | "assistant" | "tool";
22
+ content: string;
23
+ created_at: string;
24
+ blocks?: MessageBlock[];
25
+ tool_calls?: ToolCallInfo[];
26
+ tool_name?: string;
27
+ tool_call_id?: string;
28
+ }
29
+ interface PendingApproval {
30
+ id: string;
31
+ tool_name: string;
32
+ arguments: Record<string, unknown>;
33
+ created_at: string;
34
+ }
35
+ interface ConversationSummary {
36
+ id: string;
37
+ title: string;
38
+ created_at: string;
39
+ updated_at: string;
40
+ }
41
+ interface AgentPublicConfig {
42
+ name: string;
43
+ description: string;
44
+ has_mcp: boolean;
45
+ is_public: boolean;
46
+ }
47
+ interface ChatResponse {
48
+ status: "success" | "error";
49
+ message?: string;
50
+ conversation_id?: string;
51
+ messages?: AgentMessage[];
52
+ pending_approvals?: PendingApproval[];
53
+ }
54
+ /** Maps MCP tools to a page in the host app so the agent can navigate alongside chat. */
55
+ interface AgentPageMapping {
56
+ /** MCP tool names whose execution relates to this page. */
57
+ tools: string[];
58
+ /** Host-app route (path) to navigate to, e.g. "/dashboard/target-companies". */
59
+ route: string;
60
+ /** Human title shown in navigation hints. */
61
+ title: string;
62
+ /**
63
+ * Optional builder for dynamic routes: receives the tool arguments and
64
+ * returns the concrete path (e.g. `/dashboard/company/${args.id}`).
65
+ * Return null to skip navigation for that call.
66
+ */
67
+ buildRoute?: (args: Record<string, unknown>) => string | null;
68
+ }
69
+ interface AgentConfig {
70
+ /** The Agent Mode client ID from the office panel (identifies the agent only). */
71
+ clientId: string;
72
+ /** Country Club backend base URL, e.g. https://api.country.club */
73
+ apiBaseUrl: string;
74
+ /** Returns the logged-in user's Country Club JWT (or null when logged out). */
75
+ getAccessToken: () => string | null | Promise<string | null>;
76
+ /** Display name override; falls back to the backend-configured agent name. */
77
+ agentName?: string;
78
+ /** Short line under the title in the panel header. */
79
+ tagline?: string;
80
+ /** Suggested prompts shown on the empty state. */
81
+ suggestions?: string[];
82
+ /** Navigate the host app (e.g. Next router push). Enables page-follow in popup mode. */
83
+ navigate?: (path: string) => void;
84
+ /** Tool → page mappings used for page-follow navigation and hints. */
85
+ pages?: AgentPageMapping[];
86
+ /** Widget appearance tweaks. All styling comes from the host's UI-kit theme tokens. */
87
+ appearance?: {
88
+ /** Floating button position. Default: "bottom-right". */
89
+ position?: "bottom-right" | "bottom-left";
90
+ /** Desktop popup panel size. Defaults: 400 x 640. */
91
+ panelWidth?: number;
92
+ panelHeight?: number;
93
+ /** Hide the floating button (render the panel through your own trigger). */
94
+ hideLauncher?: boolean;
95
+ };
96
+ }
97
+
98
+ declare class AgentApiError extends Error {
99
+ status: number;
100
+ constructor(message: string, status: number);
101
+ }
102
+ /**
103
+ * Thin fetch client for the Country Club Agent Mode API.
104
+ *
105
+ * Every request carries the agent client_id (query param — also used by the
106
+ * backend's per-agent CORS middleware, including on preflights) and the
107
+ * logged-in user's bearer token.
108
+ */
109
+ declare class AgentApiClient {
110
+ private config;
111
+ constructor(config: AgentConfig);
112
+ private request;
113
+ getConfig(): Promise<AgentPublicConfig>;
114
+ sendMessage(message: string, conversationId?: string | null): Promise<ChatResponse>;
115
+ resolveApproval(callId: string, approve: boolean): Promise<ChatResponse>;
116
+ listConversations(): Promise<{
117
+ conversations: ConversationSummary[];
118
+ }>;
119
+ getConversation(conversationId: string): Promise<{
120
+ conversation: ConversationSummary;
121
+ messages: ChatResponse["messages"];
122
+ pending_approvals: ChatResponse["pending_approvals"];
123
+ }>;
124
+ deleteConversation(conversationId: string): Promise<void>;
125
+ }
126
+
127
+ interface AgentChatState {
128
+ agentInfo: AgentPublicConfig | null;
129
+ conversationId: string | null;
130
+ conversations: ConversationSummary[];
131
+ messages: AgentMessage[];
132
+ pendingApprovals: PendingApproval[];
133
+ sending: boolean;
134
+ resolvingApprovalId: string | null;
135
+ error: string | null;
136
+ }
137
+ interface AgentChatActions {
138
+ send: (text: string) => Promise<void>;
139
+ resolveApproval: (callId: string, approve: boolean) => Promise<void>;
140
+ newChat: () => void;
141
+ openConversation: (conversationId: string) => Promise<void>;
142
+ refreshConversations: () => Promise<void>;
143
+ deleteConversation: (conversationId: string) => Promise<void>;
144
+ clearError: () => void;
145
+ }
146
+ /**
147
+ * Headless chat state for an Agent Mode agent: message history, sending,
148
+ * pending write-tool approvals, and conversation management. UI-agnostic.
149
+ */
150
+ declare function useAgentChat(config: AgentConfig): AgentChatState & AgentChatActions;
151
+
152
+ interface AgentPanelProps {
153
+ config: AgentConfig;
154
+ chat: AgentChatState & AgentChatActions;
155
+ /** Fullscreen "Agent Mode" is active. */
156
+ agentMode: boolean;
157
+ /** Hide the expand button (mobile is always fullscreen). */
158
+ canToggleAgentMode: boolean;
159
+ onToggleAgentMode: () => void;
160
+ onClose: () => void;
161
+ className?: string;
162
+ }
163
+ /**
164
+ * The chat surface shared by the popup panel and fullscreen Agent Mode.
165
+ * Header (title, history, new chat, expand/collapse, close) + messages + input.
166
+ */
167
+ declare function AgentPanel({ agentMode, canToggleAgentMode, chat, className, config, onClose, onToggleAgentMode, }: AgentPanelProps): React.JSX.Element;
168
+
169
+ interface AgentWidgetProps {
170
+ config: AgentConfig;
171
+ /** Controlled open state (optional). */
172
+ open?: boolean;
173
+ onOpenChange?: (open: boolean) => void;
174
+ }
175
+ /**
176
+ * The embeddable Agent Mode widget.
177
+ *
178
+ * - Closed: a floating round button (bottom-right by default).
179
+ * - Open on desktop: a popup panel anchored to the button, with an
180
+ * "Agent Mode" button that expands to a fullscreen chat. While in the popup,
181
+ * the widget can navigate the host app to pages related to the tools the
182
+ * agent uses (via `config.pages` + `config.navigate`).
183
+ * - Open on mobile: fullscreen from the start.
184
+ *
185
+ * All colors come from the host app's UI-kit theme tokens, so light/dark mode
186
+ * follows the dashboard automatically.
187
+ */
188
+ declare function AgentWidget({ config, onOpenChange, open: controlledOpen }: AgentWidgetProps): React.JSX.Element;
189
+
190
+ /** Renders one conversation message: user/assistant bubbles or tool results. */
191
+ declare function MessageItem({ message }: {
192
+ message: AgentMessage;
193
+ }): React.JSX.Element | null;
194
+
195
+ interface ToolApprovalCardProps {
196
+ approval: PendingApproval;
197
+ resolving: boolean;
198
+ onResolve: (callId: string, approve: boolean) => void;
199
+ }
200
+ /**
201
+ * Approval prompt for a write action the agent wants to perform. The stored
202
+ * arguments are shown so the user knows exactly what will change; nothing runs
203
+ * until they approve.
204
+ */
205
+ declare function ToolApprovalCard({ approval, onResolve, resolving }: ToolApprovalCardProps): React.JSX.Element;
206
+
207
+ /**
208
+ * Identity helper that gives host apps full type inference/checking when
209
+ * declaring their agent config file.
210
+ */
211
+ declare function defineAgentConfig(config: AgentConfig): AgentConfig;
212
+
213
+ export { AgentApiClient, AgentApiError, type AgentChatActions, type AgentChatState, type AgentConfig, type AgentMessage, type AgentPageMapping, AgentPanel, type AgentPanelProps, type AgentPublicConfig, AgentWidget, type AgentWidgetProps, type ChatResponse, type ConversationSummary, type MessageBlock, MessageItem, type PendingApproval, type TableBlock, ToolApprovalCard, type ToolApprovalCardProps, type ToolCallInfo, defineAgentConfig, useAgentChat };
package/dist/index.js ADDED
@@ -0,0 +1,609 @@
1
+ import { ChatMessage, ChatMessageBubble, Markdown, DataTablePaged, ChatToolCard, ChatToolCardHeader, ChatToolCardActions, Button, Spinner, Tooltip, TooltipTrigger, TooltipContent, Chat, ChatMessages, ChatTypingIndicator, cn, ChatInput, useMediaQuery, TooltipProvider, Fab } from '@cntyclub/ui-react';
2
+ import { WrenchIcon, CheckCircle2Icon, ShieldAlertIcon, SparklesIcon, HistoryIcon, SquarePenIcon, Minimize2Icon, Maximize2Icon, XIcon, Trash2Icon } from 'lucide-react';
3
+ import * as React2 from 'react';
4
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
5
+
6
+ // src/api-client.ts
7
+ var AgentApiError = class extends Error {
8
+ status;
9
+ constructor(message, status) {
10
+ super(message);
11
+ this.name = "AgentApiError";
12
+ this.status = status;
13
+ }
14
+ };
15
+ var AgentApiClient = class {
16
+ config;
17
+ constructor(config) {
18
+ this.config = config;
19
+ }
20
+ async request(path, options = {}) {
21
+ const token = await this.config.getAccessToken();
22
+ if (!token) {
23
+ throw new AgentApiError("You need to be signed in to use the assistant.", 401);
24
+ }
25
+ const base = this.config.apiBaseUrl.replace(/\/$/, "");
26
+ const separator = path.includes("?") ? "&" : "?";
27
+ const url = `${base}${path}${separator}client_id=${encodeURIComponent(this.config.clientId)}`;
28
+ const response = await fetch(url, {
29
+ body: options.body === void 0 ? void 0 : JSON.stringify(options.body),
30
+ headers: {
31
+ Authorization: `Bearer ${token}`,
32
+ "Content-Type": "application/json",
33
+ "X-Agent-Client-Id": this.config.clientId
34
+ },
35
+ method: options.method ?? "GET"
36
+ });
37
+ if (response.status === 204) return void 0;
38
+ let payload = {};
39
+ try {
40
+ payload = await response.json();
41
+ } catch {
42
+ }
43
+ if (!response.ok) {
44
+ const detail = payload.detail || payload.message || `Request failed (${response.status})`;
45
+ throw new AgentApiError(detail, response.status);
46
+ }
47
+ return payload;
48
+ }
49
+ getConfig() {
50
+ return this.request("/agent-mode/config/");
51
+ }
52
+ sendMessage(message, conversationId) {
53
+ return this.request("/agent-mode/chat/", {
54
+ body: {
55
+ client_id: this.config.clientId,
56
+ conversation_id: conversationId ?? null,
57
+ message
58
+ },
59
+ method: "POST"
60
+ });
61
+ }
62
+ resolveApproval(callId, approve) {
63
+ return this.request(`/agent-mode/approvals/${callId}/resolve/`, {
64
+ body: { approve, client_id: this.config.clientId },
65
+ method: "POST"
66
+ });
67
+ }
68
+ listConversations() {
69
+ return this.request("/agent-mode/conversations/");
70
+ }
71
+ getConversation(conversationId) {
72
+ return this.request(`/agent-mode/conversations/${conversationId}/`);
73
+ }
74
+ deleteConversation(conversationId) {
75
+ return this.request(`/agent-mode/conversations/${conversationId}/`, {
76
+ method: "DELETE"
77
+ });
78
+ }
79
+ };
80
+ function humanizeToolName(toolName) {
81
+ return toolName.replace(/^[a-z]+_/, "").replaceAll("_", " ");
82
+ }
83
+ function MessageItem({ message }) {
84
+ if (message.role === "user") {
85
+ return /* @__PURE__ */ jsx(ChatMessage, { from: "user", children: /* @__PURE__ */ jsx(ChatMessageBubble, { from: "user", children: message.content }) });
86
+ }
87
+ if (message.role === "assistant") {
88
+ const requestedTools = message.tool_calls ?? [];
89
+ if (!message.content && requestedTools.length === 0) return null;
90
+ return /* @__PURE__ */ jsxs(ChatMessage, { from: "assistant", children: [
91
+ message.content ? /* @__PURE__ */ jsx(ChatMessageBubble, { from: "assistant", children: /* @__PURE__ */ jsx(Markdown, { children: message.content }) }) : null,
92
+ requestedTools.map((toolCall) => /* @__PURE__ */ jsxs(
93
+ "div",
94
+ {
95
+ className: "flex items-center gap-1.5 px-1 text-muted-foreground text-xs",
96
+ children: [
97
+ /* @__PURE__ */ jsx(WrenchIcon, { className: "size-3" }),
98
+ "Using ",
99
+ humanizeToolName(toolCall.name ?? "tool"),
100
+ "\u2026"
101
+ ]
102
+ },
103
+ toolCall.id ?? toolCall.name
104
+ ))
105
+ ] });
106
+ }
107
+ const tableBlocks = (message.blocks ?? []).filter((block) => block.type === "table");
108
+ return /* @__PURE__ */ jsx(ChatMessage, { from: "assistant", children: tableBlocks.length > 0 ? tableBlocks.map((block, index) => /* @__PURE__ */ jsx("div", { className: "w-full max-w-full", children: /* @__PURE__ */ jsx(
109
+ DataTablePaged,
110
+ {
111
+ columns: block.columns.map((key) => ({ key })),
112
+ pageSize: 8,
113
+ rows: block.rows,
114
+ totalCount: typeof block.pagination?.count === "number" ? block.pagination.count : block.row_count
115
+ }
116
+ ) }, `${message.id}-${index}`)) : /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 px-1 text-muted-foreground text-xs", children: [
117
+ /* @__PURE__ */ jsx(CheckCircle2Icon, { className: "size-3" }),
118
+ humanizeToolName(message.tool_name ?? "tool"),
119
+ " finished"
120
+ ] }) });
121
+ }
122
+ function humanizeToolName2(toolName) {
123
+ return toolName.replace(/^[a-z]+_/, "").replaceAll("_", " ");
124
+ }
125
+ function ToolApprovalCard({ approval, onResolve, resolving }) {
126
+ const argumentEntries = Object.entries(approval.arguments ?? {});
127
+ return /* @__PURE__ */ jsxs(ChatToolCard, { className: "border-warning/40", children: [
128
+ /* @__PURE__ */ jsxs(ChatToolCardHeader, { children: [
129
+ /* @__PURE__ */ jsx(ShieldAlertIcon, { className: "size-4 text-warning" }),
130
+ "Approval needed"
131
+ ] }),
132
+ /* @__PURE__ */ jsxs("p", { className: "mt-1 text-muted-foreground", children: [
133
+ "The assistant wants to run",
134
+ " ",
135
+ /* @__PURE__ */ jsx("span", { className: "font-medium text-foreground", children: humanizeToolName2(approval.tool_name) }),
136
+ " ",
137
+ "\u2014 this will change your data."
138
+ ] }),
139
+ argumentEntries.length > 0 && /* @__PURE__ */ jsxs("dl", { className: "mt-2 space-y-0.5 rounded-lg bg-muted p-2 font-mono text-xs", children: [
140
+ argumentEntries.slice(0, 8).map(([key, value]) => /* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
141
+ /* @__PURE__ */ jsxs("dt", { className: "shrink-0 text-muted-foreground", children: [
142
+ key,
143
+ ":"
144
+ ] }),
145
+ /* @__PURE__ */ jsx("dd", { className: "truncate", children: typeof value === "object" ? JSON.stringify(value) : String(value) })
146
+ ] }, key)),
147
+ argumentEntries.length > 8 && /* @__PURE__ */ jsxs("div", { className: "text-muted-foreground", children: [
148
+ "\u2026and ",
149
+ argumentEntries.length - 8,
150
+ " more"
151
+ ] })
152
+ ] }),
153
+ /* @__PURE__ */ jsxs(ChatToolCardActions, { children: [
154
+ /* @__PURE__ */ jsx(
155
+ Button,
156
+ {
157
+ disabled: resolving,
158
+ onClick: () => onResolve(approval.id, false),
159
+ size: "sm",
160
+ variant: "outline",
161
+ children: "Decline"
162
+ }
163
+ ),
164
+ /* @__PURE__ */ jsxs(Button, { disabled: resolving, onClick: () => onResolve(approval.id, true), size: "sm", children: [
165
+ resolving ? /* @__PURE__ */ jsx(Spinner, {}) : null,
166
+ "Approve"
167
+ ] })
168
+ ] })
169
+ ] });
170
+ }
171
+ function AgentPanel({
172
+ agentMode,
173
+ canToggleAgentMode,
174
+ chat,
175
+ className,
176
+ config,
177
+ onClose,
178
+ onToggleAgentMode
179
+ }) {
180
+ const [draft, setDraft] = React2.useState("");
181
+ const [view, setView] = React2.useState("chat");
182
+ const agentName = config.agentName ?? chat.agentInfo?.name ?? "Assistant";
183
+ const suggestions = config.suggestions ?? [
184
+ "What can you help me with?",
185
+ "Give me a quick summary of my data."
186
+ ];
187
+ const handleSubmit = React2.useCallback(
188
+ (text) => {
189
+ setDraft("");
190
+ void chat.send(text);
191
+ },
192
+ [chat]
193
+ );
194
+ const openHistory = React2.useCallback(() => {
195
+ setView("history");
196
+ void chat.refreshConversations();
197
+ }, [chat]);
198
+ const busy = chat.sending || chat.resolvingApprovalId !== null;
199
+ return /* @__PURE__ */ jsxs(
200
+ "div",
201
+ {
202
+ className: cn(
203
+ "flex min-h-0 flex-col overflow-hidden bg-background text-foreground",
204
+ className
205
+ ),
206
+ "data-slot": "agent-panel",
207
+ children: [
208
+ /* @__PURE__ */ jsxs("div", { className: "flex shrink-0 items-center gap-1 border-border border-b px-3 py-2", children: [
209
+ /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-1 items-center gap-2", children: [
210
+ /* @__PURE__ */ jsx("span", { className: "flex size-7 shrink-0 items-center justify-center rounded-full bg-primary text-primary-foreground", children: /* @__PURE__ */ jsx(SparklesIcon, { className: "size-4" }) }),
211
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
212
+ /* @__PURE__ */ jsx("div", { className: "truncate font-medium text-sm", children: agentName }),
213
+ config.tagline ? /* @__PURE__ */ jsx("div", { className: "truncate text-muted-foreground text-xs", children: config.tagline }) : null
214
+ ] })
215
+ ] }),
216
+ /* @__PURE__ */ jsxs(Tooltip, { children: [
217
+ /* @__PURE__ */ jsx(
218
+ TooltipTrigger,
219
+ {
220
+ render: /* @__PURE__ */ jsx(
221
+ Button,
222
+ {
223
+ "aria-label": "Conversation history",
224
+ onClick: view === "history" ? () => setView("chat") : openHistory,
225
+ size: "icon-sm",
226
+ variant: "ghost",
227
+ children: /* @__PURE__ */ jsx(HistoryIcon, {})
228
+ }
229
+ )
230
+ }
231
+ ),
232
+ /* @__PURE__ */ jsx(TooltipContent, { children: "History" })
233
+ ] }),
234
+ /* @__PURE__ */ jsxs(Tooltip, { children: [
235
+ /* @__PURE__ */ jsx(
236
+ TooltipTrigger,
237
+ {
238
+ render: /* @__PURE__ */ jsx(
239
+ Button,
240
+ {
241
+ "aria-label": "New chat",
242
+ onClick: () => {
243
+ chat.newChat();
244
+ setView("chat");
245
+ },
246
+ size: "icon-sm",
247
+ variant: "ghost",
248
+ children: /* @__PURE__ */ jsx(SquarePenIcon, {})
249
+ }
250
+ )
251
+ }
252
+ ),
253
+ /* @__PURE__ */ jsx(TooltipContent, { children: "New chat" })
254
+ ] }),
255
+ canToggleAgentMode ? /* @__PURE__ */ jsxs(Tooltip, { children: [
256
+ /* @__PURE__ */ jsx(
257
+ TooltipTrigger,
258
+ {
259
+ render: /* @__PURE__ */ jsx(
260
+ Button,
261
+ {
262
+ "aria-label": agentMode ? "Exit Agent Mode" : "Enter Agent Mode",
263
+ onClick: onToggleAgentMode,
264
+ size: "icon-sm",
265
+ variant: "ghost",
266
+ children: agentMode ? /* @__PURE__ */ jsx(Minimize2Icon, {}) : /* @__PURE__ */ jsx(Maximize2Icon, {})
267
+ }
268
+ )
269
+ }
270
+ ),
271
+ /* @__PURE__ */ jsx(TooltipContent, { children: agentMode ? "Exit Agent Mode" : "Agent Mode" })
272
+ ] }) : null,
273
+ /* @__PURE__ */ jsx(Button, { "aria-label": "Close", onClick: onClose, size: "icon-sm", variant: "ghost", children: /* @__PURE__ */ jsx(XIcon, {}) })
274
+ ] }),
275
+ view === "history" ? /* @__PURE__ */ jsx("div", { className: "flex min-h-0 flex-1 flex-col gap-1 overflow-y-auto p-2", children: chat.conversations.length === 0 ? /* @__PURE__ */ jsx("p", { className: "p-4 text-center text-muted-foreground text-sm", children: "No conversations yet." }) : chat.conversations.map((conversation) => /* @__PURE__ */ jsxs(
276
+ "div",
277
+ {
278
+ className: "group flex items-center gap-1 rounded-lg hover:bg-accent",
279
+ children: [
280
+ /* @__PURE__ */ jsxs(
281
+ "button",
282
+ {
283
+ className: "min-w-0 flex-1 cursor-pointer px-3 py-2 text-left",
284
+ onClick: () => {
285
+ void chat.openConversation(conversation.id);
286
+ setView("chat");
287
+ },
288
+ type: "button",
289
+ children: [
290
+ /* @__PURE__ */ jsx("span", { className: "block truncate text-sm", children: conversation.title || "Untitled" }),
291
+ /* @__PURE__ */ jsx("span", { className: "block text-muted-foreground text-xs", children: new Date(conversation.updated_at).toLocaleString() })
292
+ ]
293
+ }
294
+ ),
295
+ /* @__PURE__ */ jsx(
296
+ Button,
297
+ {
298
+ "aria-label": "Delete conversation",
299
+ className: "opacity-0 transition-opacity group-hover:opacity-100",
300
+ onClick: () => void chat.deleteConversation(conversation.id),
301
+ size: "icon-xs",
302
+ variant: "destructive-ghost",
303
+ children: /* @__PURE__ */ jsx(Trash2Icon, {})
304
+ }
305
+ )
306
+ ]
307
+ },
308
+ conversation.id
309
+ )) }) : /* @__PURE__ */ jsxs(Chat, { children: [
310
+ /* @__PURE__ */ jsx(ChatMessages, { className: cn(agentMode && "mx-auto w-full max-w-3xl"), children: chat.messages.length === 0 && !chat.sending ? /* @__PURE__ */ jsxs("div", { className: "flex flex-1 flex-col items-center justify-center gap-3 p-6 text-center", children: [
311
+ /* @__PURE__ */ jsx("span", { className: "flex size-12 items-center justify-center rounded-full bg-primary/10 text-primary", children: /* @__PURE__ */ jsx(SparklesIcon, { className: "size-6" }) }),
312
+ /* @__PURE__ */ jsxs("div", { children: [
313
+ /* @__PURE__ */ jsxs("p", { className: "font-medium text-sm", children: [
314
+ "Hi! I'm ",
315
+ agentName,
316
+ "."
317
+ ] }),
318
+ /* @__PURE__ */ jsx("p", { className: "mt-1 text-muted-foreground text-sm", children: "Ask me anything about your data \u2014 I can look things up and take actions (with your approval)." })
319
+ ] }),
320
+ /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-2", children: suggestions.slice(0, 3).map((suggestion) => /* @__PURE__ */ jsx(
321
+ Button,
322
+ {
323
+ onClick: () => handleSubmit(suggestion),
324
+ size: "sm",
325
+ variant: "outline",
326
+ children: suggestion
327
+ },
328
+ suggestion
329
+ )) })
330
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
331
+ chat.messages.map((message) => /* @__PURE__ */ jsx(MessageItem, { message }, message.id)),
332
+ chat.pendingApprovals.map((approval) => /* @__PURE__ */ jsx(
333
+ ToolApprovalCard,
334
+ {
335
+ approval,
336
+ onResolve: (callId, approve) => void chat.resolveApproval(callId, approve),
337
+ resolving: chat.resolvingApprovalId === approval.id
338
+ },
339
+ approval.id
340
+ )),
341
+ busy ? /* @__PURE__ */ jsx(ChatTypingIndicator, {}) : null
342
+ ] }) }),
343
+ chat.error ? /* @__PURE__ */ jsxs("div", { className: "mx-3 mb-1 flex items-center justify-between gap-2 rounded-lg bg-destructive/10 px-3 py-2 text-destructive text-xs", children: [
344
+ /* @__PURE__ */ jsx("span", { className: "min-w-0 truncate", children: chat.error }),
345
+ /* @__PURE__ */ jsx("button", { className: "shrink-0 cursor-pointer font-medium", onClick: chat.clearError, type: "button", children: "Dismiss" })
346
+ ] }) : null,
347
+ /* @__PURE__ */ jsx(
348
+ ChatInput,
349
+ {
350
+ className: cn(agentMode && "mx-auto w-full max-w-3xl border-t-0"),
351
+ disabled: busy || chat.pendingApprovals.length > 0,
352
+ onSubmit: handleSubmit,
353
+ onValueChange: setDraft,
354
+ placeholder: chat.pendingApprovals.length > 0 ? "Approve or decline the pending action first\u2026" : `Ask ${agentName}\u2026`,
355
+ value: draft
356
+ }
357
+ )
358
+ ] })
359
+ ]
360
+ }
361
+ );
362
+ }
363
+ function useAgentChat(config) {
364
+ const client = React2.useMemo(() => new AgentApiClient(config), [config]);
365
+ const [agentInfo, setAgentInfo] = React2.useState(null);
366
+ const [conversationId, setConversationId] = React2.useState(null);
367
+ const [conversations, setConversations] = React2.useState([]);
368
+ const [messages, setMessages] = React2.useState([]);
369
+ const [pendingApprovals, setPendingApprovals] = React2.useState([]);
370
+ const [sending, setSending] = React2.useState(false);
371
+ const [resolvingApprovalId, setResolvingApprovalId] = React2.useState(null);
372
+ const [error, setError] = React2.useState(null);
373
+ React2.useEffect(() => {
374
+ let cancelled = false;
375
+ client.getConfig().then((info) => {
376
+ if (!cancelled) setAgentInfo(info);
377
+ }).catch(() => {
378
+ });
379
+ return () => {
380
+ cancelled = true;
381
+ };
382
+ }, [client]);
383
+ const handleFailure = React2.useCallback((err) => {
384
+ const message = err instanceof AgentApiError ? err.message : "Something went wrong. Please try again.";
385
+ setError(message);
386
+ }, []);
387
+ const applyResponse = React2.useCallback(
388
+ (response) => {
389
+ if (response.status === "error" && !response.messages?.length) {
390
+ setError(response.message ?? "The assistant could not answer.");
391
+ return;
392
+ }
393
+ if (response.conversation_id) setConversationId(response.conversation_id);
394
+ if (response.messages?.length) {
395
+ setMessages((current) => {
396
+ const seen = new Set(current.map((message) => message.id));
397
+ const fresh = response.messages?.filter((message) => !seen.has(message.id)) ?? [];
398
+ return [...current, ...fresh];
399
+ });
400
+ }
401
+ setPendingApprovals(response.pending_approvals ?? []);
402
+ if (response.status === "error" && response.message) {
403
+ setError(response.message);
404
+ }
405
+ },
406
+ []
407
+ );
408
+ const send = React2.useCallback(
409
+ async (text) => {
410
+ if (sending) return;
411
+ setSending(true);
412
+ setError(null);
413
+ try {
414
+ const response = await client.sendMessage(text, conversationId);
415
+ applyResponse(response);
416
+ } catch (err) {
417
+ handleFailure(err);
418
+ } finally {
419
+ setSending(false);
420
+ }
421
+ },
422
+ [applyResponse, client, conversationId, handleFailure, sending]
423
+ );
424
+ const resolveApproval = React2.useCallback(
425
+ async (callId, approve) => {
426
+ if (resolvingApprovalId) return;
427
+ setResolvingApprovalId(callId);
428
+ setError(null);
429
+ try {
430
+ const response = await client.resolveApproval(callId, approve);
431
+ applyResponse(response);
432
+ } catch (err) {
433
+ handleFailure(err);
434
+ } finally {
435
+ setResolvingApprovalId(null);
436
+ }
437
+ },
438
+ [applyResponse, client, handleFailure, resolvingApprovalId]
439
+ );
440
+ const newChat = React2.useCallback(() => {
441
+ setConversationId(null);
442
+ setMessages([]);
443
+ setPendingApprovals([]);
444
+ setError(null);
445
+ }, []);
446
+ const refreshConversations = React2.useCallback(async () => {
447
+ try {
448
+ const { conversations: list } = await client.listConversations();
449
+ setConversations(list);
450
+ } catch (err) {
451
+ handleFailure(err);
452
+ }
453
+ }, [client, handleFailure]);
454
+ const openConversation = React2.useCallback(
455
+ async (id) => {
456
+ setError(null);
457
+ try {
458
+ const payload = await client.getConversation(id);
459
+ setConversationId(id);
460
+ setMessages(payload.messages ?? []);
461
+ setPendingApprovals(payload.pending_approvals ?? []);
462
+ } catch (err) {
463
+ handleFailure(err);
464
+ }
465
+ },
466
+ [client, handleFailure]
467
+ );
468
+ const deleteConversation = React2.useCallback(
469
+ async (id) => {
470
+ try {
471
+ await client.deleteConversation(id);
472
+ setConversations((current) => current.filter((c) => c.id !== id));
473
+ if (conversationId === id) newChat();
474
+ } catch (err) {
475
+ handleFailure(err);
476
+ }
477
+ },
478
+ [client, conversationId, handleFailure, newChat]
479
+ );
480
+ const clearError = React2.useCallback(() => setError(null), []);
481
+ return {
482
+ agentInfo,
483
+ clearError,
484
+ conversationId,
485
+ conversations,
486
+ deleteConversation,
487
+ error,
488
+ messages,
489
+ newChat,
490
+ openConversation,
491
+ pendingApprovals,
492
+ refreshConversations,
493
+ resolveApproval,
494
+ resolvingApprovalId,
495
+ send,
496
+ sending
497
+ };
498
+ }
499
+ function AgentWidget({ config, onOpenChange, open: controlledOpen }) {
500
+ const [uncontrolledOpen, setUncontrolledOpen] = React2.useState(false);
501
+ const open = controlledOpen ?? uncontrolledOpen;
502
+ const setOpen = React2.useCallback(
503
+ (next) => {
504
+ setUncontrolledOpen(next);
505
+ onOpenChange?.(next);
506
+ },
507
+ [onOpenChange]
508
+ );
509
+ const [agentMode, setAgentMode] = React2.useState(false);
510
+ const isDesktop = useMediaQuery("(min-width: 640px)", {
511
+ defaultSSRValue: true,
512
+ ssr: true
513
+ });
514
+ const fullscreen = !isDesktop || agentMode;
515
+ const chat = useAgentChat(config);
516
+ const lastNavigatedMessageId = React2.useRef(null);
517
+ const { messages } = chat;
518
+ React2.useEffect(() => {
519
+ if (!open || fullscreen || !config.navigate || !config.pages?.length) return;
520
+ const toolMessages = messages.filter((message) => message.role === "tool");
521
+ const latest = toolMessages[toolMessages.length - 1];
522
+ if (!latest || latest.id === lastNavigatedMessageId.current) return;
523
+ lastNavigatedMessageId.current = latest.id;
524
+ const mapping = config.pages.find((page) => page.tools.includes(latest.tool_name ?? ""));
525
+ if (!mapping) return;
526
+ const args = findToolArguments(messages, latest);
527
+ const path = mapping.buildRoute ? mapping.buildRoute(args) : mapping.route;
528
+ if (path) config.navigate(path);
529
+ }, [config, fullscreen, messages, open]);
530
+ const closePanel = React2.useCallback(() => {
531
+ setOpen(false);
532
+ setAgentMode(false);
533
+ }, [setOpen]);
534
+ return /* @__PURE__ */ jsxs(TooltipProvider, { children: [
535
+ !open && !config.appearance?.hideLauncher ? /* @__PURE__ */ jsx(
536
+ Fab,
537
+ {
538
+ "aria-label": "Open assistant",
539
+ onClick: () => setOpen(true),
540
+ position: config.appearance?.position ?? "bottom-right",
541
+ children: /* @__PURE__ */ jsx(SparklesIcon, {})
542
+ }
543
+ ) : null,
544
+ open ? fullscreen ? /* @__PURE__ */ jsx(
545
+ "div",
546
+ {
547
+ "aria-modal": "true",
548
+ className: "fixed inset-0 z-50 flex flex-col bg-background",
549
+ role: "dialog",
550
+ children: /* @__PURE__ */ jsx(
551
+ AgentPanel,
552
+ {
553
+ agentMode,
554
+ canToggleAgentMode: isDesktop,
555
+ chat,
556
+ className: "flex-1",
557
+ config,
558
+ onClose: closePanel,
559
+ onToggleAgentMode: () => setAgentMode((value) => !value)
560
+ }
561
+ )
562
+ }
563
+ ) : /* @__PURE__ */ jsx(
564
+ "div",
565
+ {
566
+ className: cn(
567
+ "fixed z-50 flex flex-col overflow-hidden rounded-2xl border border-border shadow-2xl",
568
+ (config.appearance?.position ?? "bottom-right") === "bottom-left" ? "bottom-4 left-4 sm:bottom-6 sm:left-6" : "right-4 bottom-4 sm:right-6 sm:bottom-6"
569
+ ),
570
+ role: "dialog",
571
+ style: {
572
+ height: `min(${config.appearance?.panelHeight ?? 640}px, calc(100dvh - 3rem))`,
573
+ width: `min(${config.appearance?.panelWidth ?? 400}px, calc(100vw - 2rem))`
574
+ },
575
+ children: /* @__PURE__ */ jsx(
576
+ AgentPanel,
577
+ {
578
+ agentMode: false,
579
+ canToggleAgentMode: true,
580
+ chat,
581
+ className: "flex-1",
582
+ config,
583
+ onClose: closePanel,
584
+ onToggleAgentMode: () => setAgentMode(true)
585
+ }
586
+ )
587
+ }
588
+ ) : null
589
+ ] });
590
+ }
591
+ function findToolArguments(messages, toolMessage) {
592
+ if (!toolMessage.tool_call_id) return {};
593
+ for (let index = messages.length - 1; index >= 0; index -= 1) {
594
+ const message = messages[index];
595
+ if (message.role !== "assistant" || !message.tool_calls) continue;
596
+ const match = message.tool_calls.find((call) => call.id === toolMessage.tool_call_id);
597
+ if (match) return match.arguments ?? {};
598
+ }
599
+ return {};
600
+ }
601
+
602
+ // src/config.ts
603
+ function defineAgentConfig(config) {
604
+ return config;
605
+ }
606
+
607
+ export { AgentApiClient, AgentApiError, AgentPanel, AgentWidget, MessageItem, ToolApprovalCard, defineAgentConfig, useAgentChat };
608
+ //# sourceMappingURL=index.js.map
609
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/api-client.ts","../src/components/message-item.tsx","../src/components/tool-approval-card.tsx","../src/components/agent-panel.tsx","../src/use-agent-chat.ts","../src/components/agent-widget.tsx","../src/config.ts"],"names":["humanizeToolName","jsxs","jsx","React","Button","React3","SparklesIcon","cn"],"mappings":"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","file":"index.js","sourcesContent":["import type {\n AgentConfig,\n AgentPublicConfig,\n ChatResponse,\n ConversationSummary,\n} from \"./types\";\n\nexport class AgentApiError extends Error {\n status: number;\n\n constructor(message: string, status: number) {\n super(message);\n this.name = \"AgentApiError\";\n this.status = status;\n }\n}\n\n/**\n * Thin fetch client for the Country Club Agent Mode API.\n *\n * Every request carries the agent client_id (query param — also used by the\n * backend's per-agent CORS middleware, including on preflights) and the\n * logged-in user's bearer token.\n */\nexport class AgentApiClient {\n private config: AgentConfig;\n\n constructor(config: AgentConfig) {\n this.config = config;\n }\n\n private async request<T>(\n path: string,\n options: { method?: string; body?: unknown } = {},\n ): Promise<T> {\n const token = await this.config.getAccessToken();\n if (!token) {\n throw new AgentApiError(\"You need to be signed in to use the assistant.\", 401);\n }\n\n const base = this.config.apiBaseUrl.replace(/\\/$/, \"\");\n const separator = path.includes(\"?\") ? \"&\" : \"?\";\n const url = `${base}${path}${separator}client_id=${encodeURIComponent(this.config.clientId)}`;\n\n const response = await fetch(url, {\n body: options.body === undefined ? undefined : JSON.stringify(options.body),\n headers: {\n Authorization: `Bearer ${token}`,\n \"Content-Type\": \"application/json\",\n \"X-Agent-Client-Id\": this.config.clientId,\n },\n method: options.method ?? \"GET\",\n });\n\n if (response.status === 204) return undefined as T;\n\n let payload: Record<string, unknown> = {};\n try {\n payload = (await response.json()) as Record<string, unknown>;\n } catch {\n // Non-JSON error body — fall through to the status check.\n }\n\n if (!response.ok) {\n const detail =\n (payload.detail as string) ||\n (payload.message as string) ||\n `Request failed (${response.status})`;\n throw new AgentApiError(detail, response.status);\n }\n return payload as T;\n }\n\n getConfig(): Promise<AgentPublicConfig> {\n return this.request<AgentPublicConfig>(\"/agent-mode/config/\");\n }\n\n sendMessage(message: string, conversationId?: string | null): Promise<ChatResponse> {\n return this.request<ChatResponse>(\"/agent-mode/chat/\", {\n body: {\n client_id: this.config.clientId,\n conversation_id: conversationId ?? null,\n message,\n },\n method: \"POST\",\n });\n }\n\n resolveApproval(callId: string, approve: boolean): Promise<ChatResponse> {\n return this.request<ChatResponse>(`/agent-mode/approvals/${callId}/resolve/`, {\n body: { approve, client_id: this.config.clientId },\n method: \"POST\",\n });\n }\n\n listConversations(): Promise<{ conversations: ConversationSummary[] }> {\n return this.request(\"/agent-mode/conversations/\");\n }\n\n getConversation(conversationId: string): Promise<{\n conversation: ConversationSummary;\n messages: ChatResponse[\"messages\"];\n pending_approvals: ChatResponse[\"pending_approvals\"];\n }> {\n return this.request(`/agent-mode/conversations/${conversationId}/`);\n }\n\n deleteConversation(conversationId: string): Promise<void> {\n return this.request(`/agent-mode/conversations/${conversationId}/`, {\n method: \"DELETE\",\n });\n }\n}\n","\"use client\";\n\nimport {\n ChatMessage,\n ChatMessageBubble,\n DataTablePaged,\n Markdown,\n} from \"@cntyclub/ui-react\";\nimport { CheckCircle2Icon, WrenchIcon } from \"lucide-react\";\nimport * as React from \"react\";\n\nimport type { AgentMessage } from \"../types\";\n\nfunction humanizeToolName(toolName: string): string {\n return toolName.replace(/^[a-z]+_/, \"\").replaceAll(\"_\", \" \");\n}\n\n/** Renders one conversation message: user/assistant bubbles or tool results. */\nexport function MessageItem({ message }: { message: AgentMessage }) {\n if (message.role === \"user\") {\n return (\n <ChatMessage from=\"user\">\n <ChatMessageBubble from=\"user\">{message.content}</ChatMessageBubble>\n </ChatMessage>\n );\n }\n\n if (message.role === \"assistant\") {\n const requestedTools = message.tool_calls ?? [];\n if (!message.content && requestedTools.length === 0) return null;\n return (\n <ChatMessage from=\"assistant\">\n {message.content ? (\n <ChatMessageBubble from=\"assistant\">\n <Markdown>{message.content}</Markdown>\n </ChatMessageBubble>\n ) : null}\n {requestedTools.map((toolCall) => (\n <div\n className=\"flex items-center gap-1.5 px-1 text-muted-foreground text-xs\"\n key={toolCall.id ?? toolCall.name}\n >\n <WrenchIcon className=\"size-3\" />\n Using {humanizeToolName(toolCall.name ?? \"tool\")}…\n </div>\n ))}\n </ChatMessage>\n );\n }\n\n // Tool result: show table blocks when present, otherwise a completion line.\n const tableBlocks = (message.blocks ?? []).filter((block) => block.type === \"table\");\n return (\n <ChatMessage from=\"assistant\">\n {tableBlocks.length > 0 ? (\n tableBlocks.map((block, index) => (\n <div className=\"w-full max-w-full\" key={`${message.id}-${index}`}>\n <DataTablePaged\n columns={block.columns.map((key) => ({ key }))}\n pageSize={8}\n rows={block.rows}\n totalCount={\n typeof block.pagination?.count === \"number\"\n ? (block.pagination.count as number)\n : block.row_count\n }\n />\n </div>\n ))\n ) : (\n <div className=\"flex items-center gap-1.5 px-1 text-muted-foreground text-xs\">\n <CheckCircle2Icon className=\"size-3\" />\n {humanizeToolName(message.tool_name ?? \"tool\")} finished\n </div>\n )}\n </ChatMessage>\n );\n}\n","\"use client\";\n\nimport {\n Button,\n ChatToolCard,\n ChatToolCardActions,\n ChatToolCardHeader,\n Spinner,\n} from \"@cntyclub/ui-react\";\nimport { ShieldAlertIcon } from \"lucide-react\";\nimport * as React from \"react\";\n\nimport type { PendingApproval } from \"../types\";\n\nfunction humanizeToolName(toolName: string): string {\n return toolName.replace(/^[a-z]+_/, \"\").replaceAll(\"_\", \" \");\n}\n\nexport interface ToolApprovalCardProps {\n approval: PendingApproval;\n resolving: boolean;\n onResolve: (callId: string, approve: boolean) => void;\n}\n\n/**\n * Approval prompt for a write action the agent wants to perform. The stored\n * arguments are shown so the user knows exactly what will change; nothing runs\n * until they approve.\n */\nexport function ToolApprovalCard({ approval, onResolve, resolving }: ToolApprovalCardProps) {\n const argumentEntries = Object.entries(approval.arguments ?? {});\n\n return (\n <ChatToolCard className=\"border-warning/40\">\n <ChatToolCardHeader>\n <ShieldAlertIcon className=\"size-4 text-warning\" />\n Approval needed\n </ChatToolCardHeader>\n <p className=\"mt-1 text-muted-foreground\">\n The assistant wants to run{\" \"}\n <span className=\"font-medium text-foreground\">{humanizeToolName(approval.tool_name)}</span>{\" \"}\n — this will change your data.\n </p>\n {argumentEntries.length > 0 && (\n <dl className=\"mt-2 space-y-0.5 rounded-lg bg-muted p-2 font-mono text-xs\">\n {argumentEntries.slice(0, 8).map(([key, value]) => (\n <div className=\"flex gap-2\" key={key}>\n <dt className=\"shrink-0 text-muted-foreground\">{key}:</dt>\n <dd className=\"truncate\">{typeof value === \"object\" ? JSON.stringify(value) : String(value)}</dd>\n </div>\n ))}\n {argumentEntries.length > 8 && (\n <div className=\"text-muted-foreground\">…and {argumentEntries.length - 8} more</div>\n )}\n </dl>\n )}\n <ChatToolCardActions>\n <Button\n disabled={resolving}\n onClick={() => onResolve(approval.id, false)}\n size=\"sm\"\n variant=\"outline\"\n >\n Decline\n </Button>\n <Button disabled={resolving} onClick={() => onResolve(approval.id, true)} size=\"sm\">\n {resolving ? <Spinner /> : null}\n Approve\n </Button>\n </ChatToolCardActions>\n </ChatToolCard>\n );\n}\n","\"use client\";\n\nimport {\n Button,\n Chat,\n ChatInput,\n ChatMessages,\n ChatTypingIndicator,\n cn,\n Tooltip,\n TooltipContent,\n TooltipTrigger,\n} from \"@cntyclub/ui-react\";\nimport {\n HistoryIcon,\n Maximize2Icon,\n Minimize2Icon,\n SparklesIcon,\n SquarePenIcon,\n Trash2Icon,\n XIcon,\n} from \"lucide-react\";\nimport * as React from \"react\";\n\nimport type { AgentChatActions, AgentChatState } from \"../use-agent-chat\";\nimport type { AgentConfig } from \"../types\";\nimport { MessageItem } from \"./message-item\";\nimport { ToolApprovalCard } from \"./tool-approval-card\";\n\nexport interface AgentPanelProps {\n config: AgentConfig;\n chat: AgentChatState & AgentChatActions;\n /** Fullscreen \"Agent Mode\" is active. */\n agentMode: boolean;\n /** Hide the expand button (mobile is always fullscreen). */\n canToggleAgentMode: boolean;\n onToggleAgentMode: () => void;\n onClose: () => void;\n className?: string;\n}\n\n/**\n * The chat surface shared by the popup panel and fullscreen Agent Mode.\n * Header (title, history, new chat, expand/collapse, close) + messages + input.\n */\nexport function AgentPanel({\n agentMode,\n canToggleAgentMode,\n chat,\n className,\n config,\n onClose,\n onToggleAgentMode,\n}: AgentPanelProps) {\n const [draft, setDraft] = React.useState(\"\");\n const [view, setView] = React.useState<\"chat\" | \"history\">(\"chat\");\n\n const agentName = config.agentName ?? chat.agentInfo?.name ?? \"Assistant\";\n const suggestions = config.suggestions ?? [\n \"What can you help me with?\",\n \"Give me a quick summary of my data.\",\n ];\n\n const handleSubmit = React.useCallback(\n (text: string) => {\n setDraft(\"\");\n void chat.send(text);\n },\n [chat],\n );\n\n const openHistory = React.useCallback(() => {\n setView(\"history\");\n void chat.refreshConversations();\n }, [chat]);\n\n const busy = chat.sending || chat.resolvingApprovalId !== null;\n\n return (\n <div\n className={cn(\n \"flex min-h-0 flex-col overflow-hidden bg-background text-foreground\",\n className,\n )}\n data-slot=\"agent-panel\"\n >\n {/* Header */}\n <div className=\"flex shrink-0 items-center gap-1 border-border border-b px-3 py-2\">\n <div className=\"flex min-w-0 flex-1 items-center gap-2\">\n <span className=\"flex size-7 shrink-0 items-center justify-center rounded-full bg-primary text-primary-foreground\">\n <SparklesIcon className=\"size-4\" />\n </span>\n <div className=\"min-w-0\">\n <div className=\"truncate font-medium text-sm\">{agentName}</div>\n {config.tagline ? (\n <div className=\"truncate text-muted-foreground text-xs\">{config.tagline}</div>\n ) : null}\n </div>\n </div>\n\n <Tooltip>\n <TooltipTrigger\n render={\n <Button\n aria-label=\"Conversation history\"\n onClick={view === \"history\" ? () => setView(\"chat\") : openHistory}\n size=\"icon-sm\"\n variant=\"ghost\"\n >\n <HistoryIcon />\n </Button>\n }\n />\n <TooltipContent>History</TooltipContent>\n </Tooltip>\n\n <Tooltip>\n <TooltipTrigger\n render={\n <Button\n aria-label=\"New chat\"\n onClick={() => {\n chat.newChat();\n setView(\"chat\");\n }}\n size=\"icon-sm\"\n variant=\"ghost\"\n >\n <SquarePenIcon />\n </Button>\n }\n />\n <TooltipContent>New chat</TooltipContent>\n </Tooltip>\n\n {canToggleAgentMode ? (\n <Tooltip>\n <TooltipTrigger\n render={\n <Button\n aria-label={agentMode ? \"Exit Agent Mode\" : \"Enter Agent Mode\"}\n onClick={onToggleAgentMode}\n size=\"icon-sm\"\n variant=\"ghost\"\n >\n {agentMode ? <Minimize2Icon /> : <Maximize2Icon />}\n </Button>\n }\n />\n <TooltipContent>{agentMode ? \"Exit Agent Mode\" : \"Agent Mode\"}</TooltipContent>\n </Tooltip>\n ) : null}\n\n <Button aria-label=\"Close\" onClick={onClose} size=\"icon-sm\" variant=\"ghost\">\n <XIcon />\n </Button>\n </div>\n\n {view === \"history\" ? (\n <div className=\"flex min-h-0 flex-1 flex-col gap-1 overflow-y-auto p-2\">\n {chat.conversations.length === 0 ? (\n <p className=\"p-4 text-center text-muted-foreground text-sm\">\n No conversations yet.\n </p>\n ) : (\n chat.conversations.map((conversation) => (\n <div\n className=\"group flex items-center gap-1 rounded-lg hover:bg-accent\"\n key={conversation.id}\n >\n <button\n className=\"min-w-0 flex-1 cursor-pointer px-3 py-2 text-left\"\n onClick={() => {\n void chat.openConversation(conversation.id);\n setView(\"chat\");\n }}\n type=\"button\"\n >\n <span className=\"block truncate text-sm\">{conversation.title || \"Untitled\"}</span>\n <span className=\"block text-muted-foreground text-xs\">\n {new Date(conversation.updated_at).toLocaleString()}\n </span>\n </button>\n <Button\n aria-label=\"Delete conversation\"\n className=\"opacity-0 transition-opacity group-hover:opacity-100\"\n onClick={() => void chat.deleteConversation(conversation.id)}\n size=\"icon-xs\"\n variant=\"destructive-ghost\"\n >\n <Trash2Icon />\n </Button>\n </div>\n ))\n )}\n </div>\n ) : (\n <Chat>\n <ChatMessages className={cn(agentMode && \"mx-auto w-full max-w-3xl\")}>\n {chat.messages.length === 0 && !chat.sending ? (\n <div className=\"flex flex-1 flex-col items-center justify-center gap-3 p-6 text-center\">\n <span className=\"flex size-12 items-center justify-center rounded-full bg-primary/10 text-primary\">\n <SparklesIcon className=\"size-6\" />\n </span>\n <div>\n <p className=\"font-medium text-sm\">Hi! I'm {agentName}.</p>\n <p className=\"mt-1 text-muted-foreground text-sm\">\n Ask me anything about your data — I can look things up and take actions\n (with your approval).\n </p>\n </div>\n <div className=\"flex flex-col gap-2\">\n {suggestions.slice(0, 3).map((suggestion) => (\n <Button\n key={suggestion}\n onClick={() => handleSubmit(suggestion)}\n size=\"sm\"\n variant=\"outline\"\n >\n {suggestion}\n </Button>\n ))}\n </div>\n </div>\n ) : (\n <>\n {chat.messages.map((message) => (\n <MessageItem key={message.id} message={message} />\n ))}\n {chat.pendingApprovals.map((approval) => (\n <ToolApprovalCard\n approval={approval}\n key={approval.id}\n onResolve={(callId, approve) => void chat.resolveApproval(callId, approve)}\n resolving={chat.resolvingApprovalId === approval.id}\n />\n ))}\n {busy ? <ChatTypingIndicator /> : null}\n </>\n )}\n </ChatMessages>\n\n {chat.error ? (\n <div className=\"mx-3 mb-1 flex items-center justify-between gap-2 rounded-lg bg-destructive/10 px-3 py-2 text-destructive text-xs\">\n <span className=\"min-w-0 truncate\">{chat.error}</span>\n <button className=\"shrink-0 cursor-pointer font-medium\" onClick={chat.clearError} type=\"button\">\n Dismiss\n </button>\n </div>\n ) : null}\n\n <ChatInput\n className={cn(agentMode && \"mx-auto w-full max-w-3xl border-t-0\")}\n disabled={busy || chat.pendingApprovals.length > 0}\n onSubmit={handleSubmit}\n onValueChange={setDraft}\n placeholder={\n chat.pendingApprovals.length > 0\n ? \"Approve or decline the pending action first…\"\n : `Ask ${agentName}…`\n }\n value={draft}\n />\n </Chat>\n )}\n </div>\n );\n}\n","\"use client\";\n\nimport * as React from \"react\";\n\nimport { AgentApiClient, AgentApiError } from \"./api-client\";\nimport type {\n AgentConfig,\n AgentMessage,\n AgentPublicConfig,\n ConversationSummary,\n PendingApproval,\n} from \"./types\";\n\nexport interface AgentChatState {\n agentInfo: AgentPublicConfig | null;\n conversationId: string | null;\n conversations: ConversationSummary[];\n messages: AgentMessage[];\n pendingApprovals: PendingApproval[];\n sending: boolean;\n resolvingApprovalId: string | null;\n error: string | null;\n}\n\nexport interface AgentChatActions {\n send: (text: string) => Promise<void>;\n resolveApproval: (callId: string, approve: boolean) => Promise<void>;\n newChat: () => void;\n openConversation: (conversationId: string) => Promise<void>;\n refreshConversations: () => Promise<void>;\n deleteConversation: (conversationId: string) => Promise<void>;\n clearError: () => void;\n}\n\n/**\n * Headless chat state for an Agent Mode agent: message history, sending,\n * pending write-tool approvals, and conversation management. UI-agnostic.\n */\nexport function useAgentChat(config: AgentConfig): AgentChatState & AgentChatActions {\n const client = React.useMemo(() => new AgentApiClient(config), [config]);\n\n const [agentInfo, setAgentInfo] = React.useState<AgentPublicConfig | null>(null);\n const [conversationId, setConversationId] = React.useState<string | null>(null);\n const [conversations, setConversations] = React.useState<ConversationSummary[]>([]);\n const [messages, setMessages] = React.useState<AgentMessage[]>([]);\n const [pendingApprovals, setPendingApprovals] = React.useState<PendingApproval[]>([]);\n const [sending, setSending] = React.useState(false);\n const [resolvingApprovalId, setResolvingApprovalId] = React.useState<string | null>(null);\n const [error, setError] = React.useState<string | null>(null);\n\n React.useEffect(() => {\n let cancelled = false;\n client\n .getConfig()\n .then((info) => {\n if (!cancelled) setAgentInfo(info);\n })\n .catch(() => {\n // Non-fatal: the panel falls back to config-provided naming.\n });\n return () => {\n cancelled = true;\n };\n }, [client]);\n\n const handleFailure = React.useCallback((err: unknown) => {\n const message =\n err instanceof AgentApiError\n ? err.message\n : \"Something went wrong. Please try again.\";\n setError(message);\n }, []);\n\n const applyResponse = React.useCallback(\n (response: {\n status?: string;\n message?: string;\n conversation_id?: string;\n messages?: AgentMessage[];\n pending_approvals?: PendingApproval[];\n }) => {\n if (response.status === \"error\" && !response.messages?.length) {\n setError(response.message ?? \"The assistant could not answer.\");\n return;\n }\n if (response.conversation_id) setConversationId(response.conversation_id);\n if (response.messages?.length) {\n setMessages((current) => {\n const seen = new Set(current.map((message) => message.id));\n const fresh = response.messages?.filter((message) => !seen.has(message.id)) ?? [];\n return [...current, ...fresh];\n });\n }\n setPendingApprovals(response.pending_approvals ?? []);\n if (response.status === \"error\" && response.message) {\n setError(response.message);\n }\n },\n [],\n );\n\n const send = React.useCallback(\n async (text: string) => {\n if (sending) return;\n setSending(true);\n setError(null);\n try {\n const response = await client.sendMessage(text, conversationId);\n applyResponse(response);\n } catch (err) {\n handleFailure(err);\n } finally {\n setSending(false);\n }\n },\n [applyResponse, client, conversationId, handleFailure, sending],\n );\n\n const resolveApproval = React.useCallback(\n async (callId: string, approve: boolean) => {\n if (resolvingApprovalId) return;\n setResolvingApprovalId(callId);\n setError(null);\n try {\n const response = await client.resolveApproval(callId, approve);\n applyResponse(response);\n } catch (err) {\n handleFailure(err);\n } finally {\n setResolvingApprovalId(null);\n }\n },\n [applyResponse, client, handleFailure, resolvingApprovalId],\n );\n\n const newChat = React.useCallback(() => {\n setConversationId(null);\n setMessages([]);\n setPendingApprovals([]);\n setError(null);\n }, []);\n\n const refreshConversations = React.useCallback(async () => {\n try {\n const { conversations: list } = await client.listConversations();\n setConversations(list);\n } catch (err) {\n handleFailure(err);\n }\n }, [client, handleFailure]);\n\n const openConversation = React.useCallback(\n async (id: string) => {\n setError(null);\n try {\n const payload = await client.getConversation(id);\n setConversationId(id);\n setMessages(payload.messages ?? []);\n setPendingApprovals(payload.pending_approvals ?? []);\n } catch (err) {\n handleFailure(err);\n }\n },\n [client, handleFailure],\n );\n\n const deleteConversation = React.useCallback(\n async (id: string) => {\n try {\n await client.deleteConversation(id);\n setConversations((current) => current.filter((c) => c.id !== id));\n if (conversationId === id) newChat();\n } catch (err) {\n handleFailure(err);\n }\n },\n [client, conversationId, handleFailure, newChat],\n );\n\n const clearError = React.useCallback(() => setError(null), []);\n\n return {\n agentInfo,\n clearError,\n conversationId,\n conversations,\n deleteConversation,\n error,\n messages,\n newChat,\n openConversation,\n pendingApprovals,\n refreshConversations,\n resolveApproval,\n resolvingApprovalId,\n send,\n sending,\n };\n}\n","\"use client\";\n\nimport { cn, Fab, TooltipProvider, useMediaQuery } from \"@cntyclub/ui-react\";\nimport { SparklesIcon, XIcon } from \"lucide-react\";\nimport * as React from \"react\";\n\nimport type { AgentConfig, AgentMessage } from \"../types\";\nimport { useAgentChat } from \"../use-agent-chat\";\nimport { AgentPanel } from \"./agent-panel\";\n\nexport interface AgentWidgetProps {\n config: AgentConfig;\n /** Controlled open state (optional). */\n open?: boolean;\n onOpenChange?: (open: boolean) => void;\n}\n\n/**\n * The embeddable Agent Mode widget.\n *\n * - Closed: a floating round button (bottom-right by default).\n * - Open on desktop: a popup panel anchored to the button, with an\n * \"Agent Mode\" button that expands to a fullscreen chat. While in the popup,\n * the widget can navigate the host app to pages related to the tools the\n * agent uses (via `config.pages` + `config.navigate`).\n * - Open on mobile: fullscreen from the start.\n *\n * All colors come from the host app's UI-kit theme tokens, so light/dark mode\n * follows the dashboard automatically.\n */\nexport function AgentWidget({ config, onOpenChange, open: controlledOpen }: AgentWidgetProps) {\n const [uncontrolledOpen, setUncontrolledOpen] = React.useState(false);\n const open = controlledOpen ?? uncontrolledOpen;\n const setOpen = React.useCallback(\n (next: boolean) => {\n setUncontrolledOpen(next);\n onOpenChange?.(next);\n },\n [onOpenChange],\n );\n\n const [agentMode, setAgentMode] = React.useState(false);\n const isDesktop = useMediaQuery(\"(min-width: 640px)\", {\n defaultSSRValue: true,\n ssr: true,\n });\n const fullscreen = !isDesktop || agentMode;\n\n const chat = useAgentChat(config);\n\n // ----- page-follow navigation (popup mode only) -----\n const lastNavigatedMessageId = React.useRef<string | null>(null);\n const { messages } = chat;\n React.useEffect(() => {\n if (!open || fullscreen || !config.navigate || !config.pages?.length) return;\n const toolMessages = messages.filter((message) => message.role === \"tool\");\n const latest = toolMessages[toolMessages.length - 1];\n if (!latest || latest.id === lastNavigatedMessageId.current) return;\n lastNavigatedMessageId.current = latest.id;\n\n const mapping = config.pages.find((page) => page.tools.includes(latest.tool_name ?? \"\"));\n if (!mapping) return;\n\n const args = findToolArguments(messages, latest);\n const path = mapping.buildRoute ? mapping.buildRoute(args) : mapping.route;\n if (path) config.navigate(path);\n }, [config, fullscreen, messages, open]);\n\n const closePanel = React.useCallback(() => {\n setOpen(false);\n setAgentMode(false);\n }, [setOpen]);\n\n return (\n <TooltipProvider>\n {!open && !config.appearance?.hideLauncher ? (\n <Fab\n aria-label=\"Open assistant\"\n onClick={() => setOpen(true)}\n position={config.appearance?.position ?? \"bottom-right\"}\n >\n <SparklesIcon />\n </Fab>\n ) : null}\n\n {open ? (\n fullscreen ? (\n <div\n aria-modal=\"true\"\n className=\"fixed inset-0 z-50 flex flex-col bg-background\"\n role=\"dialog\"\n >\n <AgentPanel\n agentMode={agentMode}\n canToggleAgentMode={isDesktop}\n chat={chat}\n className=\"flex-1\"\n config={config}\n onClose={closePanel}\n onToggleAgentMode={() => setAgentMode((value) => !value)}\n />\n </div>\n ) : (\n <div\n className={cn(\n \"fixed z-50 flex flex-col overflow-hidden rounded-2xl border border-border shadow-2xl\",\n (config.appearance?.position ?? \"bottom-right\") === \"bottom-left\"\n ? \"bottom-4 left-4 sm:bottom-6 sm:left-6\"\n : \"right-4 bottom-4 sm:right-6 sm:bottom-6\",\n )}\n role=\"dialog\"\n style={{\n height: `min(${config.appearance?.panelHeight ?? 640}px, calc(100dvh - 3rem))`,\n width: `min(${config.appearance?.panelWidth ?? 400}px, calc(100vw - 2rem))`,\n }}\n >\n <AgentPanel\n agentMode={false}\n canToggleAgentMode\n chat={chat}\n className=\"flex-1\"\n config={config}\n onClose={closePanel}\n onToggleAgentMode={() => setAgentMode(true)}\n />\n </div>\n )\n ) : null}\n </TooltipProvider>\n );\n}\n\n/** Look up the arguments of the assistant tool_call a tool message answers. */\nfunction findToolArguments(\n messages: AgentMessage[],\n toolMessage: AgentMessage,\n): Record<string, unknown> {\n if (!toolMessage.tool_call_id) return {};\n for (let index = messages.length - 1; index >= 0; index -= 1) {\n const message = messages[index];\n if (message.role !== \"assistant\" || !message.tool_calls) continue;\n const match = message.tool_calls.find((call) => call.id === toolMessage.tool_call_id);\n if (match) return match.arguments ?? {};\n }\n return {};\n}\n","import type { AgentConfig } from \"./types\";\n\n/**\n * Identity helper that gives host apps full type inference/checking when\n * declaring their agent config file.\n */\nexport function defineAgentConfig(config: AgentConfig): AgentConfig {\n return config;\n}\n"]}
package/package.json ADDED
@@ -0,0 +1,64 @@
1
+ {
2
+ "name": "@cntyclub/agent-react",
3
+ "version": "0.1.0",
4
+ "description": "Embeddable AI agent chat widget for Country Club dashboards — floating button, popup panel, fullscreen Agent Mode, MCP tool approvals, and paginated result tables. Built exclusively on @cntyclub/ui-react.",
5
+ "type": "module",
6
+ "sideEffects": false,
7
+ "main": "./src/index.ts",
8
+ "module": "./src/index.ts",
9
+ "types": "./src/index.ts",
10
+ "exports": {
11
+ ".": {
12
+ "types": "./src/index.ts",
13
+ "import": "./src/index.ts"
14
+ }
15
+ },
16
+ "publishConfig": {
17
+ "access": "public",
18
+ "main": "./dist/index.js",
19
+ "module": "./dist/index.js",
20
+ "types": "./dist/index.d.ts",
21
+ "exports": {
22
+ ".": {
23
+ "types": "./dist/index.d.ts",
24
+ "import": "./dist/index.js"
25
+ }
26
+ }
27
+ },
28
+ "files": [
29
+ "dist",
30
+ "README.md"
31
+ ],
32
+ "scripts": {
33
+ "build": "tsup",
34
+ "dev": "tsup --watch",
35
+ "typecheck": "tsc --noEmit",
36
+ "clean": "rm -rf dist",
37
+ "prepublishOnly": "tsup"
38
+ },
39
+ "dependencies": {
40
+ "lucide-react": "^0.561.0"
41
+ },
42
+ "peerDependencies": {
43
+ "@cntyclub/ui-react": ">=0.2.0",
44
+ "react": "^19.0.0",
45
+ "react-dom": "^19.0.0"
46
+ },
47
+ "devDependencies": {
48
+ "@cntyclub/ui-react": "link:../DashboardsCountryClub-UIKit/packages/ui-react",
49
+ "@types/react": "^19.2.13",
50
+ "@types/react-dom": "^19.2.3",
51
+ "react": "^19.2.5",
52
+ "react-dom": "^19.2.5",
53
+ "tsup": "^8.5.1",
54
+ "typescript": "^5.9.3"
55
+ },
56
+ "engines": {
57
+ "node": ">=20"
58
+ },
59
+ "license": "UNLICENSED",
60
+ "repository": {
61
+ "type": "git",
62
+ "url": "https://github.com/Momentumos/DashboardsCountryClub-AIAgent.git"
63
+ }
64
+ }