@mcp-b/char 0.0.1 → 0.0.3
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 +73 -35
- package/THIRD_PARTY_NOTICES.md +52 -0
- package/dist/VoiceHandoffPanel-DWAZTOLa.js +253 -0
- package/dist/VoiceHandoffPanel-DWAZTOLa.js.map +1 -0
- package/dist/button-BLnLZvxR.js +105 -0
- package/dist/button-BLnLZvxR.js.map +1 -0
- package/dist/index.d.ts +238 -25
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10612 -6519
- package/dist/index.js.map +1 -1
- package/dist/styles/globals.css +1 -1
- package/dist/web-component-standalone.css.map +1 -0
- package/dist/web-component-standalone.iife.js +2371 -0
- package/dist/web-component-standalone.iife.js.map +1 -0
- package/dist/web-component.d.ts +138 -9
- package/dist/web-component.d.ts.map +1 -1
- package/dist/web-component.js +11752 -7642
- package/dist/web-component.js.map +1 -1
- package/package.json +31 -21
- package/dist/FormToolUI-Ci9uSHed.js +0 -6537
- package/dist/FormToolUI-Ci9uSHed.js.map +0 -1
- package/dist/FormToolUI-l5ZaWQ-T.js +0 -3
- package/dist/jsx.d.ts +0 -253
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# @mcp-b/
|
|
1
|
+
# @mcp-b/char
|
|
2
2
|
|
|
3
3
|
An Intercom-like AI chat widget with MCP tool support and voice mode. Drop it into your app and you're done.
|
|
4
4
|
|
|
@@ -6,12 +6,12 @@ Styles are isolated using Shadow DOM. Customize appearance by setting CSS variab
|
|
|
6
6
|
|
|
7
7
|
## Build Architecture
|
|
8
8
|
|
|
9
|
-
This package provides the
|
|
9
|
+
This package provides the `<char-agent>` custom element in two formats:
|
|
10
10
|
|
|
11
11
|
| Export | Format | Use Case |
|
|
12
12
|
|--------|--------|----------|
|
|
13
|
-
| `@mcp-b/
|
|
14
|
-
| `@mcp-b/
|
|
13
|
+
| `@mcp-b/char` / `@mcp-b/char/web-component` | ESM web component | Bundlers, monorepo dev |
|
|
14
|
+
| `@mcp-b/char/standalone` | IIFE web component | `<script>` tag embeds |
|
|
15
15
|
|
|
16
16
|
### Bundle Sizes
|
|
17
17
|
|
|
@@ -49,7 +49,7 @@ Host Page (React 18)
|
|
|
49
49
|
├── <div id="root"> ← Host's React
|
|
50
50
|
│ └── ... host app ...
|
|
51
51
|
│
|
|
52
|
-
└──
|
|
52
|
+
└── <char-agent>
|
|
53
53
|
└── #shadow-root ← Isolation boundary
|
|
54
54
|
└── <div> ← Bundled React 19
|
|
55
55
|
└── ... widget ...
|
|
@@ -58,12 +58,12 @@ Host Page (React 18)
|
|
|
58
58
|
## Installation
|
|
59
59
|
|
|
60
60
|
```bash
|
|
61
|
-
npm install @mcp-b/
|
|
61
|
+
npm install @mcp-b/char
|
|
62
62
|
```
|
|
63
63
|
|
|
64
64
|
## Usage
|
|
65
65
|
|
|
66
|
-
|
|
66
|
+
`<char-agent>` renders as a full-viewport overlay.
|
|
67
67
|
|
|
68
68
|
### Recommended: Imperative Authentication (Secure)
|
|
69
69
|
|
|
@@ -71,12 +71,12 @@ The `connect()` method is the recommended way to authenticate. It keeps tokens o
|
|
|
71
71
|
|
|
72
72
|
```tsx
|
|
73
73
|
import { useRef, useEffect } from "react";
|
|
74
|
-
import "@mcp-b/
|
|
75
|
-
import type {
|
|
74
|
+
import "@mcp-b/char/web-component";
|
|
75
|
+
import type { CharAgentElement } from "@mcp-b/char/web-component";
|
|
76
76
|
|
|
77
77
|
function App() {
|
|
78
78
|
const { session } = useOktaAuth(); // or Azure, Auth0, Google, etc.
|
|
79
|
-
const agentRef = useRef<
|
|
79
|
+
const agentRef = useRef<CharAgentElement>(null);
|
|
80
80
|
|
|
81
81
|
useEffect(() => {
|
|
82
82
|
if (agentRef.current && session?.idToken) {
|
|
@@ -91,10 +91,10 @@ function App() {
|
|
|
91
91
|
### Vanilla JavaScript
|
|
92
92
|
|
|
93
93
|
```html
|
|
94
|
-
|
|
94
|
+
<char-agent></char-agent>
|
|
95
95
|
|
|
96
96
|
<script type="module">
|
|
97
|
-
import "@mcp-b/
|
|
97
|
+
import "@mcp-b/char/web-component";
|
|
98
98
|
|
|
99
99
|
const agent = document.querySelector("char-agent");
|
|
100
100
|
// Call connect() when you have the token
|
|
@@ -106,11 +106,11 @@ function App() {
|
|
|
106
106
|
|
|
107
107
|
```tsx
|
|
108
108
|
import { useRef, useEffect } from "react";
|
|
109
|
-
import "@mcp-b/
|
|
110
|
-
import type {
|
|
109
|
+
import "@mcp-b/char/web-component";
|
|
110
|
+
import type { CharAgentElement } from "@mcp-b/char/web-component";
|
|
111
111
|
|
|
112
112
|
const authToken = session?.idToken ?? "";
|
|
113
|
-
const agentRef = useRef<
|
|
113
|
+
const agentRef = useRef<CharAgentElement>(null);
|
|
114
114
|
|
|
115
115
|
useEffect(() => {
|
|
116
116
|
const agent = agentRef.current ?? document.querySelector("char-agent");
|
|
@@ -119,9 +119,9 @@ useEffect(() => {
|
|
|
119
119
|
const newAgent = document.createElement("char-agent");
|
|
120
120
|
document.body.appendChild(newAgent);
|
|
121
121
|
// Connect after element is in DOM
|
|
122
|
-
(newAgent as
|
|
122
|
+
(newAgent as CharAgentElement).connect({ idToken: authToken });
|
|
123
123
|
} else if (authToken) {
|
|
124
|
-
(agent as
|
|
124
|
+
(agent as CharAgentElement).connect({ idToken: authToken });
|
|
125
125
|
}
|
|
126
126
|
|
|
127
127
|
// Set other attributes (these don't contain sensitive data)
|
|
@@ -138,8 +138,8 @@ Use `defer` for best performance - it loads the script without blocking page ren
|
|
|
138
138
|
|
|
139
139
|
```html
|
|
140
140
|
<!-- Recommended: defer loads async, executes after DOM ready -->
|
|
141
|
-
<script src="https://unpkg.com/@mcp-b/
|
|
142
|
-
|
|
141
|
+
<script src="https://unpkg.com/@mcp-b/char/dist/web-component-standalone.iife.js" defer></script>
|
|
142
|
+
<char-agent></char-agent>
|
|
143
143
|
|
|
144
144
|
<script>
|
|
145
145
|
// Authenticate after the page loads (tokens not in DOM attributes)
|
|
@@ -154,13 +154,13 @@ Use `defer` for best performance - it loads the script without blocking page ren
|
|
|
154
154
|
Alternative CDN (jsdelivr):
|
|
155
155
|
|
|
156
156
|
```html
|
|
157
|
-
<script src="https://cdn.jsdelivr.net/npm/@mcp-b/
|
|
157
|
+
<script src="https://cdn.jsdelivr.net/npm/@mcp-b/char/dist/web-component-standalone.iife.js" defer></script>
|
|
158
158
|
```
|
|
159
159
|
|
|
160
160
|
Pin to a specific version for production:
|
|
161
161
|
|
|
162
162
|
```html
|
|
163
|
-
<script src="https://unpkg.com/@mcp-b/
|
|
163
|
+
<script src="https://unpkg.com/@mcp-b/char@0.0.2/dist/web-component-standalone.iife.js" defer></script>
|
|
164
164
|
```
|
|
165
165
|
|
|
166
166
|
### Web Component Attributes
|
|
@@ -178,6 +178,44 @@ Pin to a specific version for production:
|
|
|
178
178
|
</script>
|
|
179
179
|
```
|
|
180
180
|
|
|
181
|
+
## SSR Frameworks (Astro, Next.js, etc.)
|
|
182
|
+
|
|
183
|
+
Char requires a browser environment — it uses Shadow DOM, custom elements, and other browser APIs. In SSR frameworks, you must ensure Char only renders on the client.
|
|
184
|
+
|
|
185
|
+
### Astro
|
|
186
|
+
|
|
187
|
+
Use `client:only="react"` to skip server-side rendering entirely:
|
|
188
|
+
|
|
189
|
+
```astro
|
|
190
|
+
---
|
|
191
|
+
import { Char } from '@mcp-b/char'
|
|
192
|
+
---
|
|
193
|
+
|
|
194
|
+
<Char
|
|
195
|
+
client:only="react"
|
|
196
|
+
devMode={{ anthropicApiKey: import.meta.env.PUBLIC_ANTHROPIC_API_KEY }}
|
|
197
|
+
open={true}
|
|
198
|
+
/>
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
`client:load` or `client:visible` will not work because Astro still attempts to render the component on the server first. `client:only="react"` skips SSR entirely and renders only in the browser.
|
|
202
|
+
|
|
203
|
+
### Next.js
|
|
204
|
+
|
|
205
|
+
Use dynamic imports with `ssr: false`:
|
|
206
|
+
|
|
207
|
+
```tsx
|
|
208
|
+
import dynamic from 'next/dynamic'
|
|
209
|
+
|
|
210
|
+
const Char = dynamic(() => import('@mcp-b/char').then(m => ({ default: m.Char })), {
|
|
211
|
+
ssr: false,
|
|
212
|
+
})
|
|
213
|
+
|
|
214
|
+
export default function Page() {
|
|
215
|
+
return <Char open={true} devMode={{ anthropicApiKey: process.env.NEXT_PUBLIC_ANTHROPIC_API_KEY }} />
|
|
216
|
+
}
|
|
217
|
+
```
|
|
218
|
+
|
|
181
219
|
## Props / Attributes
|
|
182
220
|
|
|
183
221
|
| Prop | Attribute | Type | Description |
|
|
@@ -194,7 +232,7 @@ Pin to a specific version for production:
|
|
|
194
232
|
Customize appearance by setting CSS variables on the host page:
|
|
195
233
|
|
|
196
234
|
```css
|
|
197
|
-
char {
|
|
235
|
+
char-agent {
|
|
198
236
|
/* Brand colors */
|
|
199
237
|
--wm-color-primary: #8b5cf6;
|
|
200
238
|
--wm-color-primary-foreground: #ffffff;
|
|
@@ -237,7 +275,7 @@ char {
|
|
|
237
275
|
}
|
|
238
276
|
|
|
239
277
|
/* Dark mode */
|
|
240
|
-
char.dark {
|
|
278
|
+
char-agent.dark {
|
|
241
279
|
--wm-color-background: #1a1a1a;
|
|
242
280
|
--wm-color-foreground: #ffffff;
|
|
243
281
|
--wm-color-muted: #2a2a2a;
|
|
@@ -273,13 +311,13 @@ Always use `defer` or `async` when embedding the standalone script to avoid bloc
|
|
|
273
311
|
|
|
274
312
|
```html
|
|
275
313
|
<!-- GOOD: defer - loads in parallel, executes after DOM ready -->
|
|
276
|
-
<script src="https://unpkg.com/@mcp-b/
|
|
314
|
+
<script src="https://unpkg.com/@mcp-b/char/dist/web-component-standalone.iife.js" defer></script>
|
|
277
315
|
|
|
278
316
|
<!-- GOOD: async - loads in parallel, executes ASAP -->
|
|
279
|
-
<script src="https://unpkg.com/@mcp-b/
|
|
317
|
+
<script src="https://unpkg.com/@mcp-b/char/dist/web-component-standalone.iife.js" async></script>
|
|
280
318
|
|
|
281
319
|
<!-- BAD: blocks page rendering until script loads -->
|
|
282
|
-
<script src="https://unpkg.com/@mcp-b/
|
|
320
|
+
<script src="https://unpkg.com/@mcp-b/char/dist/web-component-standalone.iife.js"></script>
|
|
283
321
|
```
|
|
284
322
|
|
|
285
323
|
**When to use which:**
|
|
@@ -328,7 +366,7 @@ Speed up loading by adding a preconnect hint in your `<head>`:
|
|
|
328
366
|
></char-agent>
|
|
329
367
|
|
|
330
368
|
<!-- NEW (SSO-first with connect method): -->
|
|
331
|
-
|
|
369
|
+
<char-agent></char-agent>
|
|
332
370
|
<script>
|
|
333
371
|
const agent = document.querySelector('char-agent');
|
|
334
372
|
agent.connect({ idToken: 'eyJhbGciOi...' });
|
|
@@ -339,10 +377,10 @@ Speed up loading by adding a preconnect hint in your `<head>`:
|
|
|
339
377
|
|
|
340
378
|
<!-- Customize via CSS: -->
|
|
341
379
|
<style>
|
|
342
|
-
char {
|
|
380
|
+
char-agent {
|
|
343
381
|
--wm-color-primary: #ff0000;
|
|
344
382
|
}
|
|
345
|
-
char.dark {
|
|
383
|
+
char-agent.dark {
|
|
346
384
|
--wm-color-background: #1a1a1a;
|
|
347
385
|
}
|
|
348
386
|
</style>
|
|
@@ -358,10 +396,10 @@ Speed up loading by adding a preconnect hint in your `<head>`:
|
|
|
358
396
|
## Development
|
|
359
397
|
|
|
360
398
|
```bash
|
|
361
|
-
pnpm --filter @mcp-b/
|
|
362
|
-
pnpm --filter @mcp-b/
|
|
363
|
-
pnpm --filter @mcp-b/
|
|
364
|
-
pnpm --filter @mcp-b/
|
|
365
|
-
pnpm --filter @mcp-b/
|
|
366
|
-
pnpm --filter @mcp-b/
|
|
399
|
+
pnpm --filter @mcp-b/char dev # Watch TS build
|
|
400
|
+
pnpm --filter @mcp-b/char dev:css # Watch Tailwind CSS
|
|
401
|
+
pnpm --filter @mcp-b/char storybook # http://localhost:6006
|
|
402
|
+
pnpm --filter @mcp-b/char build
|
|
403
|
+
pnpm --filter @mcp-b/char check:types
|
|
404
|
+
pnpm --filter @mcp-b/char test
|
|
367
405
|
```
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# Third-Party Notices
|
|
2
|
+
|
|
3
|
+
This package (@mcp-b/char) bundles the following third-party software.
|
|
4
|
+
Each is listed with its license type. Full license texts are available
|
|
5
|
+
in the respective packages' source repositories.
|
|
6
|
+
|
|
7
|
+
## MIT License
|
|
8
|
+
|
|
9
|
+
- **@assistant-ui/react** - Chat UI primitives
|
|
10
|
+
- **@assistant-ui/react-markdown** - Markdown rendering for assistant-ui
|
|
11
|
+
- **@cloudflare/ai-chat** - Cloudflare AI chat client
|
|
12
|
+
- **@modelcontextprotocol/sdk** - Model Context Protocol SDK
|
|
13
|
+
- **@r2wc/react-to-web-component** - React to Web Component bridge
|
|
14
|
+
- **@rjsf/shadcn** - JSON Schema Form shadcn theme
|
|
15
|
+
- **agents** - Cloudflare Agents SDK
|
|
16
|
+
- **assistant-stream** - Streaming utilities for assistant-ui
|
|
17
|
+
- **clsx** - Utility for constructing className strings
|
|
18
|
+
- **date-fns** - Date utility library
|
|
19
|
+
- **fast-json-stable-stringify** - Deterministic JSON.stringify
|
|
20
|
+
- **motion** - Animation library (Framer Motion)
|
|
21
|
+
- **react-day-picker** - Date picker component
|
|
22
|
+
- **react-error-boundary** - React error boundary component
|
|
23
|
+
- **react-markdown** - Markdown renderer for React
|
|
24
|
+
- **remark-gfm** - GitHub Flavored Markdown support
|
|
25
|
+
- **sonner** - Toast notification library
|
|
26
|
+
- **tailwind-merge** - Tailwind CSS class merging
|
|
27
|
+
- **tw-animate-css** - Tailwind CSS animations
|
|
28
|
+
- **zod** - TypeScript-first schema validation
|
|
29
|
+
- **zustand** - State management
|
|
30
|
+
|
|
31
|
+
## Apache-2.0 License
|
|
32
|
+
|
|
33
|
+
- **@ai-sdk/react** - Vercel AI SDK React bindings
|
|
34
|
+
- **ai** - Vercel AI SDK
|
|
35
|
+
- **@cloudflare/realtimekit-react** - Cloudflare RealtimeKit React bindings
|
|
36
|
+
- **@cloudflare/realtimekit-react-ui** - Cloudflare RealtimeKit UI components
|
|
37
|
+
- **@rjsf/core** - React JSON Schema Form core
|
|
38
|
+
- **@rjsf/utils** - React JSON Schema Form utilities
|
|
39
|
+
- **@rjsf/validator-ajv8** - JSON Schema Form AJV8 validator
|
|
40
|
+
- **class-variance-authority** - CSS variant utility
|
|
41
|
+
|
|
42
|
+
## ISC License
|
|
43
|
+
|
|
44
|
+
- **jsonrepair** - JSON repair utility
|
|
45
|
+
- **lucide-react** - Icon library
|
|
46
|
+
|
|
47
|
+
## Peer Dependencies (not bundled in ESM build)
|
|
48
|
+
|
|
49
|
+
- **react** - MIT License - Copyright (c) Meta Platforms, Inc. and affiliates
|
|
50
|
+
- **react-dom** - MIT License - Copyright (c) Meta Platforms, Inc. and affiliates
|
|
51
|
+
|
|
52
|
+
Note: The standalone IIFE build bundles React and ReactDOM.
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
/*! @mcp-b/char | Copyright (c) 2025 Kukumis Inc. All rights reserved. | UNLICENSED */
|
|
2
|
+
import { r as cn, t as Button } from "./button-BLnLZvxR.js";
|
|
3
|
+
import { c } from "react-compiler-runtime";
|
|
4
|
+
import { useCallback, useEffect, useMemo, useRef } from "react";
|
|
5
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
6
|
+
import { RealtimeKitProvider, useRealtimeKitClient } from "@cloudflare/realtimekit-react";
|
|
7
|
+
import { RtkMeeting } from "@cloudflare/realtimekit-react-ui";
|
|
8
|
+
|
|
9
|
+
//#region src/components/voice/RealtimeKitCallPanel.tsx
|
|
10
|
+
const RealtimeKitCallPanel = ({ authToken, meetingId, onEnded, showSetupScreen = true, debugLabel = "rtk:customer" }) => {
|
|
11
|
+
const [meeting, initMeeting] = useRealtimeKitClient();
|
|
12
|
+
const onEndedRef = useRef(onEnded);
|
|
13
|
+
const lastMeetingStateRef = useRef(null);
|
|
14
|
+
const debugEnabled = useMemo(() => {
|
|
15
|
+
if (typeof window === "undefined") return false;
|
|
16
|
+
try {
|
|
17
|
+
return window.__CHAR_RTK_DEBUG__ === true || window.localStorage.getItem("char:rtk-debug") === "true";
|
|
18
|
+
} catch {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
}, []);
|
|
22
|
+
const logDebug = useCallback((message, payload) => {
|
|
23
|
+
if (!debugEnabled) return;
|
|
24
|
+
if (payload) console.debug(`[${debugLabel}] ${message}`, payload);
|
|
25
|
+
else console.debug(`[${debugLabel}] ${message}`);
|
|
26
|
+
}, [debugEnabled, debugLabel]);
|
|
27
|
+
useEffect(() => {
|
|
28
|
+
onEndedRef.current = onEnded;
|
|
29
|
+
}, [onEnded]);
|
|
30
|
+
useEffect(() => {
|
|
31
|
+
if (!authToken) return;
|
|
32
|
+
let cancelled = false;
|
|
33
|
+
logDebug("initMeeting:start", {
|
|
34
|
+
meetingId,
|
|
35
|
+
hasToken: Boolean(authToken),
|
|
36
|
+
tokenLength: authToken.length
|
|
37
|
+
});
|
|
38
|
+
initMeeting({
|
|
39
|
+
authToken,
|
|
40
|
+
defaults: {
|
|
41
|
+
audio: true,
|
|
42
|
+
video: false
|
|
43
|
+
}
|
|
44
|
+
}).then((client) => {
|
|
45
|
+
if (cancelled) return;
|
|
46
|
+
if (client) logDebug("initMeeting:success", { meetingId });
|
|
47
|
+
else logDebug("initMeeting:undefined", { meetingId });
|
|
48
|
+
}).catch((error) => {
|
|
49
|
+
if (cancelled) return;
|
|
50
|
+
logDebug("initMeeting:error", {
|
|
51
|
+
meetingId,
|
|
52
|
+
error: error instanceof Error ? error.message : String(error)
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
return () => {
|
|
56
|
+
cancelled = true;
|
|
57
|
+
};
|
|
58
|
+
}, [
|
|
59
|
+
authToken,
|
|
60
|
+
initMeeting,
|
|
61
|
+
logDebug,
|
|
62
|
+
meetingId
|
|
63
|
+
]);
|
|
64
|
+
const handleStatesUpdate = useCallback((event) => {
|
|
65
|
+
const nextState = event.detail?.meeting ?? null;
|
|
66
|
+
const prevState = lastMeetingStateRef.current;
|
|
67
|
+
lastMeetingStateRef.current = nextState;
|
|
68
|
+
logDebug("meeting:state", {
|
|
69
|
+
meetingId,
|
|
70
|
+
state: nextState
|
|
71
|
+
});
|
|
72
|
+
if (prevState && nextState === "ended" && prevState !== "ended") onEndedRef.current?.();
|
|
73
|
+
}, [logDebug, meetingId]);
|
|
74
|
+
if (!meeting) return /* @__PURE__ */ jsx("div", {
|
|
75
|
+
className: "flex h-full items-center justify-center text-sm text-muted-foreground",
|
|
76
|
+
children: "Connecting to the call..."
|
|
77
|
+
});
|
|
78
|
+
return /* @__PURE__ */ jsx(RealtimeKitProvider, {
|
|
79
|
+
value: meeting,
|
|
80
|
+
children: /* @__PURE__ */ jsx(RtkMeeting, {
|
|
81
|
+
mode: "fill",
|
|
82
|
+
meeting,
|
|
83
|
+
showSetupScreen,
|
|
84
|
+
onRtkStatesUpdate: handleStatesUpdate
|
|
85
|
+
})
|
|
86
|
+
});
|
|
87
|
+
};
|
|
88
|
+
var RealtimeKitCallPanel_default = RealtimeKitCallPanel;
|
|
89
|
+
|
|
90
|
+
//#endregion
|
|
91
|
+
//#region src/components/voice/VoiceHandoffPanel.tsx
|
|
92
|
+
const AI_ONLY_HANDOFF_STATUS = "ai_only";
|
|
93
|
+
const HANDOFF_PENDING_STATUS = "handoff_pending";
|
|
94
|
+
const HUMAN_ACTIVE_STATUS = "human_active";
|
|
95
|
+
const VoiceHandoffPanel = (t0) => {
|
|
96
|
+
const $ = c(36);
|
|
97
|
+
const { handoffStatus, joinResult, isRequesting, error, onRequestToken, onEnd } = t0;
|
|
98
|
+
let t1;
|
|
99
|
+
if ($[0] !== onRequestToken) {
|
|
100
|
+
t1 = () => {
|
|
101
|
+
onRequestToken().catch(_temp);
|
|
102
|
+
};
|
|
103
|
+
$[0] = onRequestToken;
|
|
104
|
+
$[1] = t1;
|
|
105
|
+
} else t1 = $[1];
|
|
106
|
+
const requestToken = t1;
|
|
107
|
+
let t2;
|
|
108
|
+
if ($[2] !== onEnd) {
|
|
109
|
+
t2 = () => {
|
|
110
|
+
onEnd().catch(_temp2);
|
|
111
|
+
};
|
|
112
|
+
$[2] = onEnd;
|
|
113
|
+
$[3] = t2;
|
|
114
|
+
} else t2 = $[3];
|
|
115
|
+
const endCall = t2;
|
|
116
|
+
let t3;
|
|
117
|
+
let t4;
|
|
118
|
+
if ($[4] !== error || $[5] !== handoffStatus || $[6] !== isRequesting || $[7] !== joinResult || $[8] !== requestToken) {
|
|
119
|
+
t3 = () => {
|
|
120
|
+
if (handoffStatus === AI_ONLY_HANDOFF_STATUS) return;
|
|
121
|
+
if (joinResult || isRequesting || error) return;
|
|
122
|
+
requestToken();
|
|
123
|
+
};
|
|
124
|
+
t4 = [
|
|
125
|
+
handoffStatus,
|
|
126
|
+
joinResult,
|
|
127
|
+
isRequesting,
|
|
128
|
+
error,
|
|
129
|
+
requestToken
|
|
130
|
+
];
|
|
131
|
+
$[4] = error;
|
|
132
|
+
$[5] = handoffStatus;
|
|
133
|
+
$[6] = isRequesting;
|
|
134
|
+
$[7] = joinResult;
|
|
135
|
+
$[8] = requestToken;
|
|
136
|
+
$[9] = t3;
|
|
137
|
+
$[10] = t4;
|
|
138
|
+
} else {
|
|
139
|
+
t3 = $[9];
|
|
140
|
+
t4 = $[10];
|
|
141
|
+
}
|
|
142
|
+
useEffect(t3, t4);
|
|
143
|
+
if (handoffStatus === AI_ONLY_HANDOFF_STATUS) return null;
|
|
144
|
+
const isLive = handoffStatus === HUMAN_ACTIVE_STATUS;
|
|
145
|
+
const t5 = isLive ? "bg-emerald-500" : "bg-amber-500 animate-pulse";
|
|
146
|
+
let t6;
|
|
147
|
+
if ($[11] !== t5) {
|
|
148
|
+
t6 = cn("h-2 w-2 rounded-full", t5);
|
|
149
|
+
$[11] = t5;
|
|
150
|
+
$[12] = t6;
|
|
151
|
+
} else t6 = $[12];
|
|
152
|
+
let t7;
|
|
153
|
+
if ($[13] !== t6) {
|
|
154
|
+
t7 = /* @__PURE__ */ jsx("span", { className: t6 });
|
|
155
|
+
$[13] = t6;
|
|
156
|
+
$[14] = t7;
|
|
157
|
+
} else t7 = $[14];
|
|
158
|
+
const t8 = isLive ? "Live support" : "Connecting support";
|
|
159
|
+
let t9;
|
|
160
|
+
if ($[15] !== t7 || $[16] !== t8) {
|
|
161
|
+
t9 = /* @__PURE__ */ jsxs("div", {
|
|
162
|
+
className: "flex items-center gap-2 text-xs font-medium uppercase tracking-wide text-muted-foreground",
|
|
163
|
+
children: [t7, t8]
|
|
164
|
+
});
|
|
165
|
+
$[15] = t7;
|
|
166
|
+
$[16] = t8;
|
|
167
|
+
$[17] = t9;
|
|
168
|
+
} else t9 = $[17];
|
|
169
|
+
let t10;
|
|
170
|
+
if ($[18] !== endCall || $[19] !== isRequesting) {
|
|
171
|
+
t10 = /* @__PURE__ */ jsx(Button, {
|
|
172
|
+
type: "button",
|
|
173
|
+
variant: "outline",
|
|
174
|
+
size: "sm",
|
|
175
|
+
onClick: endCall,
|
|
176
|
+
disabled: isRequesting,
|
|
177
|
+
children: "End call"
|
|
178
|
+
});
|
|
179
|
+
$[18] = endCall;
|
|
180
|
+
$[19] = isRequesting;
|
|
181
|
+
$[20] = t10;
|
|
182
|
+
} else t10 = $[20];
|
|
183
|
+
let t11;
|
|
184
|
+
if ($[21] !== t10 || $[22] !== t9) {
|
|
185
|
+
t11 = /* @__PURE__ */ jsxs("div", {
|
|
186
|
+
className: "flex items-center justify-between gap-2 pb-2",
|
|
187
|
+
children: [t9, t10]
|
|
188
|
+
});
|
|
189
|
+
$[21] = t10;
|
|
190
|
+
$[22] = t9;
|
|
191
|
+
$[23] = t11;
|
|
192
|
+
} else t11 = $[23];
|
|
193
|
+
let t12;
|
|
194
|
+
if ($[24] !== error) {
|
|
195
|
+
t12 = error ? /* @__PURE__ */ jsx("div", {
|
|
196
|
+
className: "mb-2 text-xs text-destructive",
|
|
197
|
+
children: error
|
|
198
|
+
}) : null;
|
|
199
|
+
$[24] = error;
|
|
200
|
+
$[25] = t12;
|
|
201
|
+
} else t12 = $[25];
|
|
202
|
+
let t13;
|
|
203
|
+
if ($[26] !== endCall || $[27] !== joinResult) {
|
|
204
|
+
t13 = /* @__PURE__ */ jsx("div", {
|
|
205
|
+
className: "h-56 overflow-hidden rounded-xl border border-border/60 bg-background md:h-64",
|
|
206
|
+
children: joinResult ? /* @__PURE__ */ jsx(RealtimeKitCallPanel_default, {
|
|
207
|
+
authToken: joinResult.authToken,
|
|
208
|
+
meetingId: joinResult.meetingId,
|
|
209
|
+
onEnded: endCall
|
|
210
|
+
}) : /* @__PURE__ */ jsx("div", {
|
|
211
|
+
className: "flex h-full items-center justify-center text-sm text-muted-foreground",
|
|
212
|
+
children: "Preparing the call..."
|
|
213
|
+
})
|
|
214
|
+
});
|
|
215
|
+
$[26] = endCall;
|
|
216
|
+
$[27] = joinResult;
|
|
217
|
+
$[28] = t13;
|
|
218
|
+
} else t13 = $[28];
|
|
219
|
+
let t14;
|
|
220
|
+
if ($[29] !== handoffStatus) {
|
|
221
|
+
t14 = handoffStatus === HANDOFF_PENDING_STATUS ? /* @__PURE__ */ jsx("p", {
|
|
222
|
+
className: "mt-2 text-xs text-muted-foreground",
|
|
223
|
+
children: "Waiting for a support agent to join…"
|
|
224
|
+
}) : null;
|
|
225
|
+
$[29] = handoffStatus;
|
|
226
|
+
$[30] = t14;
|
|
227
|
+
} else t14 = $[30];
|
|
228
|
+
let t15;
|
|
229
|
+
if ($[31] !== t11 || $[32] !== t12 || $[33] !== t13 || $[34] !== t14) {
|
|
230
|
+
t15 = /* @__PURE__ */ jsxs("div", {
|
|
231
|
+
className: "rounded-2xl border border-border bg-muted/30 p-3 shadow-sm",
|
|
232
|
+
children: [
|
|
233
|
+
t11,
|
|
234
|
+
t12,
|
|
235
|
+
t13,
|
|
236
|
+
t14
|
|
237
|
+
]
|
|
238
|
+
});
|
|
239
|
+
$[31] = t11;
|
|
240
|
+
$[32] = t12;
|
|
241
|
+
$[33] = t13;
|
|
242
|
+
$[34] = t14;
|
|
243
|
+
$[35] = t15;
|
|
244
|
+
} else t15 = $[35];
|
|
245
|
+
return t15;
|
|
246
|
+
};
|
|
247
|
+
var VoiceHandoffPanel_default = VoiceHandoffPanel;
|
|
248
|
+
function _temp() {}
|
|
249
|
+
function _temp2() {}
|
|
250
|
+
|
|
251
|
+
//#endregion
|
|
252
|
+
export { VoiceHandoffPanel_default as default };
|
|
253
|
+
//# sourceMappingURL=VoiceHandoffPanel-DWAZTOLa.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"VoiceHandoffPanel-DWAZTOLa.js","names":["RealtimeKitProvider","useRealtimeKitClient","RtkMeeting","useCallback","useEffect","useMemo","useRef","FC","RtkMeetingState","meeting","RealtimeKitCallPanelProps","authToken","meetingId","onEnded","showSetupScreen","debugLabel","RealtimeKitCallPanel","initMeeting","onEndedRef","lastMeetingStateRef","debugEnabled","window","globalWindow","Window","__CHAR_RTK_DEBUG__","localStorage","getItem","logDebug","message","payload","Record","console","debug","current","cancelled","hasToken","Boolean","tokenLength","length","defaults","audio","video","then","client","catch","error","Error","String","handleStatesUpdate","event","CustomEvent","nextState","detail","prevState","state","Button","cn","useCallback","useEffect","FC","RealtimeKitCallPanel","RealtimeKitJoinResult","VoiceSessionState","HandoffStatus","AI_ONLY_HANDOFF_STATUS","HANDOFF_PENDING_STATUS","HUMAN_ACTIVE_STATUS","VoiceHandoffPanelProps","handoffStatus","joinResult","isRequesting","error","onRequestToken","Promise","onEnd","VoiceHandoffPanel","t0","$","_c","t1","catch","_temp","requestToken","t2","_temp2","endCall","t3","t4","isLive","t5","t6","t7","t8","t9","t10","t11","t12","t13","authToken","meetingId","t14","t15"],"sources":["../src/components/voice/RealtimeKitCallPanel.tsx","../src/components/voice/VoiceHandoffPanel.tsx"],"sourcesContent":["import { RealtimeKitProvider, useRealtimeKitClient } from '@cloudflare/realtimekit-react'\nimport { RtkMeeting } from '@cloudflare/realtimekit-react-ui'\nimport { useCallback, useEffect, useMemo, useRef, type FC } from 'react'\n\ntype RtkMeetingState = {\n\tmeeting?: 'idle' | 'setup' | 'joined' | 'ended' | 'waiting'\n}\n\ninterface RealtimeKitCallPanelProps {\n\tauthToken: string\n\tmeetingId?: string\n\tonEnded?: () => void\n\tshowSetupScreen?: boolean\n\tdebugLabel?: string\n}\n\nconst RealtimeKitCallPanel: FC<RealtimeKitCallPanelProps> = ({\n\tauthToken,\n\tmeetingId,\n\tonEnded,\n\tshowSetupScreen = true,\n\tdebugLabel = 'rtk:customer',\n}) => {\n\tconst [meeting, initMeeting] = useRealtimeKitClient()\n\tconst onEndedRef = useRef(onEnded)\n\tconst lastMeetingStateRef = useRef<RtkMeetingState['meeting'] | null>(null)\n\tconst debugEnabled = useMemo(() => {\n\t\tif (typeof window === 'undefined') return false\n\t\ttry {\n\t\t\tconst globalWindow = window as Window & { __CHAR_RTK_DEBUG__?: boolean }\n\t\t\treturn (\n\t\t\t\tglobalWindow.__CHAR_RTK_DEBUG__ === true ||\n\t\t\t\twindow.localStorage.getItem('char:rtk-debug') === 'true'\n\t\t\t)\n\t\t} catch {\n\t\t\treturn false\n\t\t}\n\t}, [])\n\n\tconst logDebug = useCallback(\n\t\t(message: string, payload?: Record<string, unknown>) => {\n\t\t\tif (!debugEnabled) return\n\t\t\tif (payload) {\n\t\t\t\tconsole.debug(`[${debugLabel}] ${message}`, payload)\n\t\t\t} else {\n\t\t\t\tconsole.debug(`[${debugLabel}] ${message}`)\n\t\t\t}\n\t\t},\n\t\t[debugEnabled, debugLabel],\n\t)\n\n\tuseEffect(() => {\n\t\tonEndedRef.current = onEnded\n\t}, [onEnded])\n\n\tuseEffect(() => {\n\t\tif (!authToken) return\n\t\tlet cancelled = false\n\t\tlogDebug('initMeeting:start', {\n\t\t\tmeetingId,\n\t\t\thasToken: Boolean(authToken),\n\t\t\ttokenLength: authToken.length,\n\t\t})\n\t\tvoid initMeeting({\n\t\t\tauthToken,\n\t\t\tdefaults: {\n\t\t\t\taudio: true,\n\t\t\t\tvideo: false,\n\t\t\t},\n\t\t})\n\t\t\t.then((client) => {\n\t\t\t\tif (cancelled) return\n\t\t\t\tif (client) {\n\t\t\t\t\tlogDebug('initMeeting:success', { meetingId })\n\t\t\t\t} else {\n\t\t\t\t\tlogDebug('initMeeting:undefined', { meetingId })\n\t\t\t\t}\n\t\t\t})\n\t\t\t.catch((error) => {\n\t\t\t\tif (cancelled) return\n\t\t\t\tlogDebug('initMeeting:error', {\n\t\t\t\t\tmeetingId,\n\t\t\t\t\terror: error instanceof Error ? error.message : String(error),\n\t\t\t\t})\n\t\t\t})\n\n\t\treturn () => {\n\t\t\tcancelled = true\n\t\t}\n\t}, [authToken, initMeeting, logDebug, meetingId])\n\n\tconst handleStatesUpdate = useCallback((event: CustomEvent<RtkMeetingState>) => {\n\t\tconst nextState = event.detail?.meeting ?? null\n\t\tconst prevState = lastMeetingStateRef.current\n\t\tlastMeetingStateRef.current = nextState\n\t\tlogDebug('meeting:state', { meetingId, state: nextState })\n\n\t\tif (prevState && nextState === 'ended' && prevState !== 'ended') {\n\t\t\tonEndedRef.current?.()\n\t\t}\n\t}, [logDebug, meetingId])\n\n\tif (!meeting) {\n\t\treturn (\n\t\t\t<div className=\"flex h-full items-center justify-center text-sm text-muted-foreground\">\n\t\t\t\tConnecting to the call...\n\t\t\t</div>\n\t\t)\n\t}\n\n\treturn (\n\t\t<RealtimeKitProvider value={meeting}>\n\t\t\t<RtkMeeting\n\t\t\t\tmode=\"fill\"\n\t\t\t\tmeeting={meeting}\n\t\t\t\tshowSetupScreen={showSetupScreen}\n\t\t\t\tonRtkStatesUpdate={handleStatesUpdate}\n\t\t\t/>\n\t\t</RealtimeKitProvider>\n\t)\n}\n\nexport default RealtimeKitCallPanel\n","import { Button } from '@/components/ui/button'\nimport { cn } from '@/utils/cn'\nimport { useCallback, useEffect, type FC } from 'react'\n\nimport RealtimeKitCallPanel from './RealtimeKitCallPanel'\n\nimport type { RealtimeKitJoinResult, VoiceSessionState } from '@mcp-b/shared-types'\n\ntype HandoffStatus = VoiceSessionState['handoffStatus']\nconst AI_ONLY_HANDOFF_STATUS: HandoffStatus = 'ai_only'\nconst HANDOFF_PENDING_STATUS: HandoffStatus = 'handoff_pending'\nconst HUMAN_ACTIVE_STATUS: HandoffStatus = 'human_active'\n\ninterface VoiceHandoffPanelProps {\n\thandoffStatus: HandoffStatus\n\tjoinResult: RealtimeKitJoinResult | null\n\tisRequesting: boolean\n\terror: string | null\n\tonRequestToken: () => Promise<void>\n\tonEnd: () => Promise<void>\n}\n\nconst VoiceHandoffPanel: FC<VoiceHandoffPanelProps> = ({\n\thandoffStatus,\n\tjoinResult,\n\tisRequesting,\n\terror,\n\tonRequestToken,\n\tonEnd,\n}) => {\n\tconst requestToken = useCallback(() => {\n\t\tonRequestToken().catch(() => undefined)\n\t}, [onRequestToken])\n\n\tconst endCall = useCallback(() => {\n\t\tonEnd().catch(() => undefined)\n\t}, [onEnd])\n\n\tuseEffect(() => {\n\t\tif (handoffStatus === AI_ONLY_HANDOFF_STATUS) return\n\t\tif (joinResult || isRequesting || error) return\n\t\trequestToken()\n\t}, [handoffStatus, joinResult, isRequesting, error, requestToken])\n\n\tif (handoffStatus === AI_ONLY_HANDOFF_STATUS) {\n\t\treturn null\n\t}\n\n\tconst isLive = handoffStatus === HUMAN_ACTIVE_STATUS\n\n\treturn (\n\t\t<div className=\"rounded-2xl border border-border bg-muted/30 p-3 shadow-sm\">\n\t\t\t<div className=\"flex items-center justify-between gap-2 pb-2\">\n\t\t\t\t<div className=\"flex items-center gap-2 text-xs font-medium uppercase tracking-wide text-muted-foreground\">\n\t\t\t\t\t<span\n\t\t\t\t\t\tclassName={cn(\n\t\t\t\t\t\t\t'h-2 w-2 rounded-full',\n\t\t\t\t\t\t\tisLive ? 'bg-emerald-500' : 'bg-amber-500 animate-pulse',\n\t\t\t\t\t\t)}\n\t\t\t\t\t/>\n\t\t\t\t\t{isLive ? 'Live support' : 'Connecting support'}\n\t\t\t\t</div>\n\t\t\t\t\t<Button\n\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\tvariant=\"outline\"\n\t\t\t\t\t\tsize=\"sm\"\n\t\t\t\t\t\tonClick={endCall}\n\t\t\t\t\t\tdisabled={isRequesting}\n\t\t\t\t\t>\n\t\t\t\t\tEnd call\n\t\t\t\t</Button>\n\t\t\t</div>\n\n\t\t\t{error ? (\n\t\t\t\t<div className=\"mb-2 text-xs text-destructive\">{error}</div>\n\t\t\t) : null}\n\n\t\t\t<div className=\"h-56 overflow-hidden rounded-xl border border-border/60 bg-background md:h-64\">\n\t\t\t\t{joinResult ? (\n\t\t\t\t\t\t<RealtimeKitCallPanel\n\t\t\t\t\t\t\tauthToken={joinResult.authToken}\n\t\t\t\t\t\t\tmeetingId={joinResult.meetingId}\n\t\t\t\t\t\t\tonEnded={endCall}\n\t\t\t\t\t\t/>\n\t\t\t\t) : (\n\t\t\t\t\t<div className=\"flex h-full items-center justify-center text-sm text-muted-foreground\">\n\t\t\t\t\t\tPreparing the call...\n\t\t\t\t\t</div>\n\t\t\t\t)}\n\t\t\t</div>\n\n\t\t\t{handoffStatus === HANDOFF_PENDING_STATUS ? (\n\t\t\t\t<p className=\"mt-2 text-xs text-muted-foreground\">\n\t\t\t\t\tWaiting for a support agent to join…\n\t\t\t\t</p>\n\t\t\t) : null}\n\t\t</div>\n\t)\n}\n\nexport default VoiceHandoffPanel\n"],"mappings":"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"}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/*! @mcp-b/char | Copyright (c) 2025 Kukumis Inc. All rights reserved. | UNLICENSED */
|
|
2
|
+
import { c } from "react-compiler-runtime";
|
|
3
|
+
import "react";
|
|
4
|
+
import { clsx } from "clsx";
|
|
5
|
+
import { twMerge } from "tailwind-merge";
|
|
6
|
+
import { jsx } from "react/jsx-runtime";
|
|
7
|
+
import { Slot } from "@radix-ui/react-slot";
|
|
8
|
+
import { cva } from "class-variance-authority";
|
|
9
|
+
|
|
10
|
+
//#region src/utils/cn.ts
|
|
11
|
+
/**
|
|
12
|
+
* Merges Tailwind CSS classes with proper precedence handling.
|
|
13
|
+
* Combines clsx for conditional classes and tailwind-merge for deduplication.
|
|
14
|
+
*/
|
|
15
|
+
function cn(...inputs) {
|
|
16
|
+
return twMerge(clsx(inputs));
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
//#endregion
|
|
20
|
+
//#region src/components/ui/button.tsx
|
|
21
|
+
const buttonVariants = cva("inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive", {
|
|
22
|
+
variants: {
|
|
23
|
+
variant: {
|
|
24
|
+
default: "bg-primary text-primary-foreground hover:bg-primary/90",
|
|
25
|
+
destructive: "bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
|
|
26
|
+
outline: "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
|
|
27
|
+
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
|
28
|
+
ghost: "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
|
|
29
|
+
link: "text-primary underline-offset-4 hover:underline"
|
|
30
|
+
},
|
|
31
|
+
size: {
|
|
32
|
+
default: "h-9 px-4 py-2 has-[>svg]:px-3",
|
|
33
|
+
xs: "h-6 gap-1 rounded-md px-2 text-xs has-[>svg]:px-1.5 [&_svg:not([class*='size-'])]:size-3",
|
|
34
|
+
sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
|
|
35
|
+
lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
|
|
36
|
+
icon: "size-9",
|
|
37
|
+
"icon-xs": "size-6 rounded-md [&_svg:not([class*='size-'])]:size-3",
|
|
38
|
+
"icon-sm": "size-8",
|
|
39
|
+
"icon-lg": "size-10"
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
defaultVariants: {
|
|
43
|
+
variant: "default",
|
|
44
|
+
size: "default"
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
function Button(t0) {
|
|
48
|
+
const $ = c(16);
|
|
49
|
+
let className;
|
|
50
|
+
let props;
|
|
51
|
+
let t1;
|
|
52
|
+
let t2;
|
|
53
|
+
let t3;
|
|
54
|
+
if ($[0] !== t0) {
|
|
55
|
+
({className, variant: t1, size: t2, asChild: t3, ...props} = t0);
|
|
56
|
+
$[0] = t0;
|
|
57
|
+
$[1] = className;
|
|
58
|
+
$[2] = props;
|
|
59
|
+
$[3] = t1;
|
|
60
|
+
$[4] = t2;
|
|
61
|
+
$[5] = t3;
|
|
62
|
+
} else {
|
|
63
|
+
className = $[1];
|
|
64
|
+
props = $[2];
|
|
65
|
+
t1 = $[3];
|
|
66
|
+
t2 = $[4];
|
|
67
|
+
t3 = $[5];
|
|
68
|
+
}
|
|
69
|
+
const variant = t1 === void 0 ? "default" : t1;
|
|
70
|
+
const size = t2 === void 0 ? "default" : t2;
|
|
71
|
+
const Comp = (t3 === void 0 ? false : t3) ? Slot : "button";
|
|
72
|
+
let t4;
|
|
73
|
+
if ($[6] !== className || $[7] !== size || $[8] !== variant) {
|
|
74
|
+
t4 = cn(buttonVariants({
|
|
75
|
+
variant,
|
|
76
|
+
size,
|
|
77
|
+
className
|
|
78
|
+
}));
|
|
79
|
+
$[6] = className;
|
|
80
|
+
$[7] = size;
|
|
81
|
+
$[8] = variant;
|
|
82
|
+
$[9] = t4;
|
|
83
|
+
} else t4 = $[9];
|
|
84
|
+
let t5;
|
|
85
|
+
if ($[10] !== Comp || $[11] !== props || $[12] !== size || $[13] !== t4 || $[14] !== variant) {
|
|
86
|
+
t5 = /* @__PURE__ */ jsx(Comp, {
|
|
87
|
+
"data-slot": "button",
|
|
88
|
+
"data-variant": variant,
|
|
89
|
+
"data-size": size,
|
|
90
|
+
className: t4,
|
|
91
|
+
...props
|
|
92
|
+
});
|
|
93
|
+
$[10] = Comp;
|
|
94
|
+
$[11] = props;
|
|
95
|
+
$[12] = size;
|
|
96
|
+
$[13] = t4;
|
|
97
|
+
$[14] = variant;
|
|
98
|
+
$[15] = t5;
|
|
99
|
+
} else t5 = $[15];
|
|
100
|
+
return t5;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
//#endregion
|
|
104
|
+
export { buttonVariants as n, cn as r, Button as t };
|
|
105
|
+
//# sourceMappingURL=button-BLnLZvxR.js.map
|