@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 +102 -0
- package/dist/index.d.ts +213 -0
- package/dist/index.js +609 -0
- package/dist/index.js.map +1 -0
- package/package.json +64 -0
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
|
+
```
|
package/dist/index.d.ts
ADDED
|
@@ -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
|
+
}
|