@djangocfg/layouts 2.1.17 → 2.1.19
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/package.json +5 -5
- package/src/components/errors/ErrorLayout.tsx +25 -43
- package/src/layouts/AppLayout/AppLayout.tsx +3 -19
- package/src/layouts/AppLayout/BaseApp.tsx +36 -2
- package/src/snippets/McpChat/README.md +441 -0
- package/src/snippets/McpChat/components/AskAIButton.tsx +92 -0
- package/src/snippets/McpChat/components/index.ts +3 -0
- package/src/snippets/McpChat/context/AIChatContext.tsx +62 -1
- package/src/snippets/McpChat/hooks/index.ts +1 -0
- package/src/snippets/McpChat/hooks/useMcpChat.ts +89 -0
- package/src/snippets/McpChat/index.ts +6 -2
- package/src/snippets/McpChat/types.ts +46 -0
- package/src/snippets/index.ts +6 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@djangocfg/layouts",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.19",
|
|
4
4
|
"description": "Simple, straightforward layout components for Next.js - import and use with props",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"layouts",
|
|
@@ -92,9 +92,9 @@
|
|
|
92
92
|
"check": "tsc --noEmit"
|
|
93
93
|
},
|
|
94
94
|
"peerDependencies": {
|
|
95
|
-
"@djangocfg/api": "^2.1.
|
|
96
|
-
"@djangocfg/centrifugo": "^2.1.
|
|
97
|
-
"@djangocfg/ui-nextjs": "^2.1.
|
|
95
|
+
"@djangocfg/api": "^2.1.19",
|
|
96
|
+
"@djangocfg/centrifugo": "^2.1.19",
|
|
97
|
+
"@djangocfg/ui-nextjs": "^2.1.19",
|
|
98
98
|
"@hookform/resolvers": "^5.2.0",
|
|
99
99
|
"consola": "^3.4.2",
|
|
100
100
|
"lucide-react": "^0.545.0",
|
|
@@ -114,7 +114,7 @@
|
|
|
114
114
|
"uuid": "^11.1.0"
|
|
115
115
|
},
|
|
116
116
|
"devDependencies": {
|
|
117
|
-
"@djangocfg/typescript-config": "^2.1.
|
|
117
|
+
"@djangocfg/typescript-config": "^2.1.19",
|
|
118
118
|
"@types/node": "^24.7.2",
|
|
119
119
|
"@types/react": "^19.1.0",
|
|
120
120
|
"@types/react-dom": "^19.1.0",
|
|
@@ -49,54 +49,48 @@ function getErrorIcon(code?: string | number): React.ReactNode {
|
|
|
49
49
|
case '404':
|
|
50
50
|
return (
|
|
51
51
|
<svg
|
|
52
|
-
className="w-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
viewBox="0 0 24 24"
|
|
52
|
+
className="w-32 h-32 mx-auto text-foreground/80"
|
|
53
|
+
viewBox="0 0 32 32"
|
|
54
|
+
fill="currentColor"
|
|
56
55
|
aria-hidden="true"
|
|
57
56
|
>
|
|
58
|
-
{/*
|
|
59
|
-
<
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
57
|
+
{/* Cloud with 404 Icon */}
|
|
58
|
+
<g>
|
|
59
|
+
<path fillRule="evenodd" d="M19.889 21.734v-.947l-.631.947z" />
|
|
60
|
+
<path d="M15.484 19.636h1.032a.017.017 0 0 1 .017.017v3.1a.016.016 0 0 1-.016.016h-1.034a.016.016 0 0 1-.016-.016v-3.1a.017.017 0 0 1 .017-.017z" />
|
|
61
|
+
<g fillRule="evenodd">
|
|
62
|
+
<path d="M12.402 21.734v-.947l-.631.947z" />
|
|
63
|
+
<path d="M16 1.5A14.5 14.5 0 1 0 30.5 16 14.507 14.507 0 0 0 16 1.5zm-2.324 21.234H13.4v.532a.5.5 0 0 1-1 0v-.532h-1.563a.5.5 0 0 1-.416-.778l2.067-3.1a.5.5 0 0 1 .914.28v2.6h.274a.5.5 0 0 1 0 1zm-2.137-9.9A4.14 4.14 0 0 1 15.545 9.8a.5.5 0 0 1 0 1 3.138 3.138 0 0 0-3.04 2.3.5.5 0 0 1-.966-.259zm5.994 9.911a1.017 1.017 0 0 1-1.017 1.016h-1.032a1.017 1.017 0 0 1-1.017-1.016v-3.1a1.017 1.017 0 0 1 1.017-1.016h1.032a1.017 1.017 0 0 1 1.017 1.016zm3.63-.016h-.274v.532a.5.5 0 0 1-1 0v-.532h-1.565a.5.5 0 0 1-.416-.778l2.067-3.1a.5.5 0 0 1 .914.28v2.6h.274a.5.5 0 0 1 0 1zm1.036-1.52a.5.5 0 1 1-.4-.917 3.263 3.263 0 0 0-1.337-6.266.5.5 0 0 1-.5-.5 4.6 4.6 0 0 0-9.2 0 4.45 4.45 0 0 0 .153 1.161.5.5 0 0 1-.411.625 2.633 2.633 0 0 0-.364 5.148.5.5 0 0 1-.278.961 3.63 3.63 0 0 1-.024-6.984 5.467 5.467 0 0 1-.076-.911 5.6 5.6 0 0 1 11.182-.474 4.258 4.258 0 0 1 1.256 8.162z" />
|
|
64
|
+
</g>
|
|
65
|
+
</g>
|
|
65
66
|
</svg>
|
|
66
67
|
);
|
|
67
68
|
case '500':
|
|
68
69
|
return (
|
|
69
70
|
<svg
|
|
70
|
-
className="w-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
viewBox="0 0 24 24"
|
|
71
|
+
className="w-32 h-32 mx-auto text-foreground/80"
|
|
72
|
+
viewBox="0 0 512 512"
|
|
73
|
+
fill="currentColor"
|
|
74
74
|
aria-hidden="true"
|
|
75
75
|
>
|
|
76
|
-
{/* Server Error Icon -
|
|
77
|
-
<
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
76
|
+
{/* Server Error Icon - Circle with Exclamation */}
|
|
77
|
+
<g>
|
|
78
|
+
<path d="M256 118c-76.1 0-138 61.88-138 138s61.9 138.05 138 138.05 138-61.93 138-138S332.1 118 256 118zm0 237.93a30.12 30.12 0 1 1 30.11-30.12A30.15 30.15 0 0 1 256 355.88zm30.11-80.31a8.48 8.48 0 0 1-8.47 8.48h-43.28a8.48 8.48 0 0 1-8.47-8.48v-111a8.47 8.47 0 0 1 8.47-8.47h43.28a8.47 8.47 0 0 1 8.47 8.47z" />
|
|
79
|
+
<path d="M256 312.6a13.17 13.17 0 1 0 13.16 13.16A13.17 13.17 0 0 0 256 312.6zM242.84 173.07h26.32v94.02h-26.32z" />
|
|
80
|
+
<path d="M256 0C114.62 0 0 114.62 0 256s114.62 256 256 256 256-114.62 256-256S397.38 0 256 0zm109.57 365.6A155 155 0 1 1 411 256a153.91 153.91 0 0 1-45.43 109.6z" />
|
|
81
|
+
</g>
|
|
82
82
|
</svg>
|
|
83
83
|
);
|
|
84
84
|
case '403':
|
|
85
85
|
return (
|
|
86
86
|
<svg
|
|
87
|
-
className="w-
|
|
88
|
-
fill="none"
|
|
89
|
-
stroke="currentColor"
|
|
87
|
+
className="w-32 h-32 mx-auto text-foreground/80"
|
|
90
88
|
viewBox="0 0 24 24"
|
|
89
|
+
fill="currentColor"
|
|
91
90
|
aria-hidden="true"
|
|
92
91
|
>
|
|
93
|
-
{/* Forbidden Icon */}
|
|
94
|
-
<path
|
|
95
|
-
strokeLinecap="round"
|
|
96
|
-
strokeLinejoin="round"
|
|
97
|
-
strokeWidth={1.5}
|
|
98
|
-
d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z"
|
|
99
|
-
/>
|
|
92
|
+
{/* Forbidden Icon - Circle with X */}
|
|
93
|
+
<path d="M12 1a11 11 0 1 0 11 11A11.013 11.013 0 0 0 12 1zm4.242 13.829a1 1 0 1 1-1.414 1.414L12 13.414l-2.828 2.829a1 1 0 0 1-1.414-1.414L10.586 12 7.758 9.171a1 1 0 1 1 1.414-1.414L12 10.586l2.828-2.829a1 1 0 1 1 1.414 1.414L13.414 12z" />
|
|
100
94
|
</svg>
|
|
101
95
|
);
|
|
102
96
|
default:
|
|
@@ -148,18 +142,6 @@ export function ErrorLayout({
|
|
|
148
142
|
return (
|
|
149
143
|
<div className="min-h-screen flex items-center justify-center px-4 bg-background">
|
|
150
144
|
<div className="max-w-2xl w-full text-center space-y-8">
|
|
151
|
-
{/* Error Code */}
|
|
152
|
-
{code && (
|
|
153
|
-
<div className="relative">
|
|
154
|
-
<h1
|
|
155
|
-
className="text-[12rem] font-bold leading-none text-muted/20 select-none"
|
|
156
|
-
aria-hidden="true"
|
|
157
|
-
>
|
|
158
|
-
{code}
|
|
159
|
-
</h1>
|
|
160
|
-
</div>
|
|
161
|
-
)}
|
|
162
|
-
|
|
163
145
|
{/* Illustration */}
|
|
164
146
|
{finalIllustration && (
|
|
165
147
|
<div className="flex justify-center py-8">
|
|
@@ -39,7 +39,6 @@
|
|
|
39
39
|
import React, { ReactNode, useMemo } from 'react';
|
|
40
40
|
import { usePathname } from 'next/navigation';
|
|
41
41
|
import { CentrifugoProvider } from '@djangocfg/centrifugo';
|
|
42
|
-
import { ErrorBoundary } from '../../components/errors/ErrorBoundary';
|
|
43
42
|
import { type AuthConfig } from '@djangocfg/api/auth';
|
|
44
43
|
import { type ValidationErrorConfig, type CORSErrorConfig, type NetworkErrorConfig } from '../../components/errors/ErrorsTracker';
|
|
45
44
|
import { AnalyticsProvider } from '../../snippets/Analytics';
|
|
@@ -151,7 +150,6 @@ function AppLayoutContent({
|
|
|
151
150
|
adminLayout,
|
|
152
151
|
analytics,
|
|
153
152
|
centrifugo,
|
|
154
|
-
errorBoundary,
|
|
155
153
|
}: AppLayoutProps) {
|
|
156
154
|
const pathname = usePathname();
|
|
157
155
|
|
|
@@ -165,8 +163,6 @@ function AppLayoutContent({
|
|
|
165
163
|
[pathname, adminLayout, privateLayout, publicLayout]
|
|
166
164
|
);
|
|
167
165
|
|
|
168
|
-
const enableErrorBoundary = errorBoundary?.enabled !== false;
|
|
169
|
-
|
|
170
166
|
// Render appropriate layout based on mode
|
|
171
167
|
const renderLayout = () => {
|
|
172
168
|
switch (layoutMode) {
|
|
@@ -240,20 +236,7 @@ function AppLayoutContent({
|
|
|
240
236
|
{content}
|
|
241
237
|
</CentrifugoProvider>
|
|
242
238
|
);
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
// Wrap with ErrorBoundary if enabled
|
|
246
|
-
if (enableErrorBoundary) {
|
|
247
|
-
return (
|
|
248
|
-
<ErrorBoundary
|
|
249
|
-
supportEmail={errorBoundary?.supportEmail}
|
|
250
|
-
onError={errorBoundary?.onError}
|
|
251
|
-
>
|
|
252
|
-
{content}
|
|
253
|
-
</ErrorBoundary>
|
|
254
|
-
);
|
|
255
|
-
}
|
|
256
|
-
|
|
239
|
+
|
|
257
240
|
return content;
|
|
258
241
|
}
|
|
259
242
|
|
|
@@ -261,13 +244,14 @@ function AppLayoutContent({
|
|
|
261
244
|
* AppLayout - Main Component with All Providers
|
|
262
245
|
*/
|
|
263
246
|
export function AppLayout(props: AppLayoutProps) {
|
|
264
|
-
const { theme, auth, errorTracking } = props;
|
|
247
|
+
const { theme, auth, errorTracking, errorBoundary } = props;
|
|
265
248
|
|
|
266
249
|
return (
|
|
267
250
|
<BaseApp
|
|
268
251
|
theme={theme}
|
|
269
252
|
auth={auth}
|
|
270
253
|
errorTracking={errorTracking}
|
|
254
|
+
errorBoundary={errorBoundary}
|
|
271
255
|
>
|
|
272
256
|
<AppLayoutContent {...props} />
|
|
273
257
|
</BaseApp>
|
|
@@ -7,17 +7,25 @@
|
|
|
7
7
|
* - SWRConfig (data fetching)
|
|
8
8
|
* - AuthProvider (authentication context)
|
|
9
9
|
* - ErrorTrackingProvider (error handling)
|
|
10
|
+
* - ErrorBoundary (React error boundary, enabled by default)
|
|
10
11
|
*
|
|
11
12
|
* @example
|
|
12
13
|
* ```tsx
|
|
13
14
|
* import { BaseApp } from '@djangocfg/layouts';
|
|
14
15
|
*
|
|
16
|
+
* // ErrorBoundary enabled by default
|
|
15
17
|
* <BaseApp
|
|
16
18
|
* theme={{ defaultTheme: 'system' }}
|
|
17
19
|
* auth={{ loginPath: '/auth/login' }}
|
|
20
|
+
* errorBoundary={{ supportEmail: 'support@example.com' }}
|
|
18
21
|
* >
|
|
19
22
|
* {children}
|
|
20
23
|
* </BaseApp>
|
|
24
|
+
*
|
|
25
|
+
* // To disable ErrorBoundary:
|
|
26
|
+
* <BaseApp errorBoundary={{ enabled: false }}>
|
|
27
|
+
* {children}
|
|
28
|
+
* </BaseApp>
|
|
21
29
|
* ```
|
|
22
30
|
*/
|
|
23
31
|
|
|
@@ -28,6 +36,7 @@ import { SWRConfig } from 'swr';
|
|
|
28
36
|
import { ThemeProvider, Toaster, TooltipProvider } from '@djangocfg/ui-nextjs';
|
|
29
37
|
import { AuthProvider, type AuthConfig } from '@djangocfg/api/auth';
|
|
30
38
|
import { ErrorTrackingProvider, type ValidationErrorConfig, type CORSErrorConfig, type NetworkErrorConfig } from '../../components/errors/ErrorsTracker';
|
|
39
|
+
import { ErrorBoundary } from '../../components/errors/ErrorBoundary';
|
|
31
40
|
import { PageProgress } from '../../components/core/PageProgress';
|
|
32
41
|
|
|
33
42
|
export interface BaseAppProps {
|
|
@@ -56,12 +65,19 @@ export interface BaseAppProps {
|
|
|
56
65
|
revalidateOnReconnect?: boolean;
|
|
57
66
|
dedupingInterval?: number;
|
|
58
67
|
};
|
|
68
|
+
|
|
69
|
+
/** Error boundary configuration (enabled by default) */
|
|
70
|
+
errorBoundary?: {
|
|
71
|
+
enabled?: boolean;
|
|
72
|
+
supportEmail?: string;
|
|
73
|
+
onError?: (error: Error, errorInfo: React.ErrorInfo) => void;
|
|
74
|
+
};
|
|
59
75
|
}
|
|
60
76
|
|
|
61
77
|
/**
|
|
62
78
|
* BaseApp - Core providers wrapper for any React/Next.js app
|
|
63
79
|
*
|
|
64
|
-
* Includes: ThemeProvider, TooltipProvider, SWRConfig, AuthProvider, ErrorTrackingProvider
|
|
80
|
+
* Includes: ThemeProvider, TooltipProvider, SWRConfig, AuthProvider, ErrorTrackingProvider, ErrorBoundary (optional)
|
|
65
81
|
* Also renders global Toaster and PageProgress components
|
|
66
82
|
*/
|
|
67
83
|
export function BaseApp({
|
|
@@ -69,9 +85,13 @@ export function BaseApp({
|
|
|
69
85
|
theme,
|
|
70
86
|
auth,
|
|
71
87
|
errorTracking,
|
|
88
|
+
errorBoundary,
|
|
72
89
|
swr,
|
|
73
90
|
}: BaseAppProps) {
|
|
74
|
-
|
|
91
|
+
// ErrorBoundary is enabled by default
|
|
92
|
+
const enableErrorBoundary = errorBoundary?.enabled !== false;
|
|
93
|
+
|
|
94
|
+
const content = (
|
|
75
95
|
<ThemeProvider
|
|
76
96
|
defaultTheme={theme?.defaultTheme || 'system'}
|
|
77
97
|
storageKey={theme?.storageKey}
|
|
@@ -100,4 +120,18 @@ export function BaseApp({
|
|
|
100
120
|
</TooltipProvider>
|
|
101
121
|
</ThemeProvider>
|
|
102
122
|
);
|
|
123
|
+
|
|
124
|
+
// Wrap with ErrorBoundary only if explicitly enabled
|
|
125
|
+
if (enableErrorBoundary) {
|
|
126
|
+
return (
|
|
127
|
+
<ErrorBoundary
|
|
128
|
+
supportEmail={errorBoundary?.supportEmail}
|
|
129
|
+
onError={errorBoundary?.onError}
|
|
130
|
+
>
|
|
131
|
+
{content}
|
|
132
|
+
</ErrorBoundary>
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return content;
|
|
103
137
|
}
|
|
@@ -0,0 +1,441 @@
|
|
|
1
|
+
# MCP Chat - AI Assistant Integration
|
|
2
|
+
|
|
3
|
+
AI-powered chat widget with global trigger system for seamless integration throughout your application.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- 🎯 **Global Trigger System** - Call chat from anywhere using `useMcpChat()` hook
|
|
8
|
+
- 🤖 **Simple AI Button** - Universal `AskAIButton` component with AI assistant icon
|
|
9
|
+
- 💬 **Multiple Display Modes** - Floating panel, sidebar, or closed
|
|
10
|
+
- 🔄 **Streaming Responses** - Real-time AI responses
|
|
11
|
+
- 📱 **Mobile Responsive** - Optimized for all devices
|
|
12
|
+
- 🎭 **Context-Aware** - Send structured context with messages
|
|
13
|
+
- 🔌 **Easy Integration** - Drop-in component with minimal setup
|
|
14
|
+
|
|
15
|
+
## Quick Start
|
|
16
|
+
|
|
17
|
+
### 1. Setup Provider (once in your app)
|
|
18
|
+
|
|
19
|
+
```tsx
|
|
20
|
+
// app/layout.tsx
|
|
21
|
+
import { AIChatProvider, AIChatWidget } from '@djangocfg/layouts/snippets/McpChat';
|
|
22
|
+
|
|
23
|
+
export default function RootLayout({ children }) {
|
|
24
|
+
return (
|
|
25
|
+
<html>
|
|
26
|
+
<body>
|
|
27
|
+
<AIChatProvider>
|
|
28
|
+
{children}
|
|
29
|
+
<AIChatWidget />
|
|
30
|
+
</AIChatProvider>
|
|
31
|
+
</body>
|
|
32
|
+
</html>
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### 2. Use Anywhere
|
|
38
|
+
|
|
39
|
+
#### Option A: Ready-to-use Button (Recommended)
|
|
40
|
+
|
|
41
|
+
```tsx
|
|
42
|
+
import { AskAIButton } from '@djangocfg/layouts/snippets/McpChat';
|
|
43
|
+
|
|
44
|
+
// Simple usage
|
|
45
|
+
<AskAIButton message="Explain this feature">
|
|
46
|
+
Ask AI
|
|
47
|
+
</AskAIButton>
|
|
48
|
+
|
|
49
|
+
// With context data
|
|
50
|
+
<AskAIButton
|
|
51
|
+
message="Why is this failing?"
|
|
52
|
+
contextData={{ error: error.stack, component: 'UserForm' }}
|
|
53
|
+
source="ErrorBoundary"
|
|
54
|
+
>
|
|
55
|
+
Explain error
|
|
56
|
+
</AskAIButton>
|
|
57
|
+
|
|
58
|
+
// Custom styling
|
|
59
|
+
<AskAIButton
|
|
60
|
+
message="Help me optimize this"
|
|
61
|
+
contextData={{ topic: "database-performance" }}
|
|
62
|
+
variant="default"
|
|
63
|
+
size="lg"
|
|
64
|
+
>
|
|
65
|
+
Get advice
|
|
66
|
+
</AskAIButton>
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
#### Option B: Custom Hook
|
|
70
|
+
|
|
71
|
+
```tsx
|
|
72
|
+
import { useMcpChat } from '@djangocfg/layouts/snippets/McpChat';
|
|
73
|
+
|
|
74
|
+
function MyComponent() {
|
|
75
|
+
const { sendToChat } = useMcpChat();
|
|
76
|
+
|
|
77
|
+
const handleClick = () => {
|
|
78
|
+
sendToChat({
|
|
79
|
+
message: 'Explain this error',
|
|
80
|
+
context: {
|
|
81
|
+
data: { error: errorDetails },
|
|
82
|
+
source: 'MyComponent'
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
return <button onClick={handleClick}>Ask AI</button>;
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Components
|
|
92
|
+
|
|
93
|
+
### AskAIButton
|
|
94
|
+
|
|
95
|
+
Universal AI chat trigger button with Bot icon:
|
|
96
|
+
|
|
97
|
+
```tsx
|
|
98
|
+
<AskAIButton
|
|
99
|
+
message="Your question or prompt"
|
|
100
|
+
contextData={{ /* optional context */ }}
|
|
101
|
+
source="ComponentName"
|
|
102
|
+
variant="outline"
|
|
103
|
+
size="default"
|
|
104
|
+
>
|
|
105
|
+
Button text
|
|
106
|
+
</AskAIButton>
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
**Props:**
|
|
110
|
+
- `message` - Question or prompt to send to AI (required)
|
|
111
|
+
- `contextData` - Additional context object (optional)
|
|
112
|
+
- `source` - Source component name for tracking (optional)
|
|
113
|
+
- `autoSend` - Auto-send message after opening (default: `true`)
|
|
114
|
+
- `showIcon` - Show Bot icon (default: `true`)
|
|
115
|
+
- All standard Button props (`variant`, `size`, `className`, etc.)
|
|
116
|
+
|
|
117
|
+
### useMcpChat Hook
|
|
118
|
+
|
|
119
|
+
Custom hook for programmatic chat triggering:
|
|
120
|
+
|
|
121
|
+
```tsx
|
|
122
|
+
const { sendToChat, isChatAvailable } = useMcpChat();
|
|
123
|
+
|
|
124
|
+
sendToChat({
|
|
125
|
+
message: 'Your message',
|
|
126
|
+
context: {
|
|
127
|
+
data: { /* contextual data */ },
|
|
128
|
+
source: 'ComponentName'
|
|
129
|
+
},
|
|
130
|
+
autoSend: true,
|
|
131
|
+
displayMode: 'floating' | 'sidebar'
|
|
132
|
+
});
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
## Real-World Examples
|
|
136
|
+
|
|
137
|
+
### Error Handling
|
|
138
|
+
|
|
139
|
+
```tsx
|
|
140
|
+
function ErrorBoundary({ error, children }) {
|
|
141
|
+
if (error) {
|
|
142
|
+
return (
|
|
143
|
+
<div>
|
|
144
|
+
<h2>Error: {error.message}</h2>
|
|
145
|
+
<AskAIButton
|
|
146
|
+
message="Explain this error and how to fix it"
|
|
147
|
+
contextData={{
|
|
148
|
+
error: error.message,
|
|
149
|
+
stack: error.stack,
|
|
150
|
+
component: 'UserForm'
|
|
151
|
+
}}
|
|
152
|
+
source="ErrorBoundary"
|
|
153
|
+
>
|
|
154
|
+
Explain error
|
|
155
|
+
</AskAIButton>
|
|
156
|
+
</div>
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
return children;
|
|
160
|
+
}
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### Feature Documentation
|
|
164
|
+
|
|
165
|
+
```tsx
|
|
166
|
+
function FeatureCard({ feature }) {
|
|
167
|
+
return (
|
|
168
|
+
<Card>
|
|
169
|
+
<CardHeader>{feature.name}</CardHeader>
|
|
170
|
+
<CardContent>{feature.description}</CardContent>
|
|
171
|
+
<CardFooter>
|
|
172
|
+
<AskAIButton
|
|
173
|
+
message={`Explain how ${feature.name} works`}
|
|
174
|
+
contextData={{
|
|
175
|
+
feature: feature.name,
|
|
176
|
+
version: feature.version
|
|
177
|
+
}}
|
|
178
|
+
source="FeatureCard"
|
|
179
|
+
>
|
|
180
|
+
Learn more
|
|
181
|
+
</AskAIButton>
|
|
182
|
+
</CardFooter>
|
|
183
|
+
</Card>
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
### Settings Page
|
|
189
|
+
|
|
190
|
+
```tsx
|
|
191
|
+
function DatabaseSettings() {
|
|
192
|
+
return (
|
|
193
|
+
<div>
|
|
194
|
+
<h3>Database Configuration</h3>
|
|
195
|
+
<form>{/* form fields */}</form>
|
|
196
|
+
<AskAIButton
|
|
197
|
+
message="What are the best practices for PostgreSQL optimization?"
|
|
198
|
+
contextData={{
|
|
199
|
+
topic: "database-optimization",
|
|
200
|
+
dbType: "PostgreSQL"
|
|
201
|
+
}}
|
|
202
|
+
source="DatabaseSettings"
|
|
203
|
+
>
|
|
204
|
+
Get tips
|
|
205
|
+
</AskAIButton>
|
|
206
|
+
</div>
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
### Pricing Comparison
|
|
212
|
+
|
|
213
|
+
```tsx
|
|
214
|
+
function PricingSection() {
|
|
215
|
+
return (
|
|
216
|
+
<div>
|
|
217
|
+
{/* pricing cards */}
|
|
218
|
+
<AskAIButton
|
|
219
|
+
message="Which package is right for my team?"
|
|
220
|
+
contextData={{
|
|
221
|
+
packages: ["starter", "professional", "enterprise"],
|
|
222
|
+
intent: "decision-making"
|
|
223
|
+
}}
|
|
224
|
+
source="PricingSection"
|
|
225
|
+
>
|
|
226
|
+
Which package?
|
|
227
|
+
</AskAIButton>
|
|
228
|
+
</div>
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
### Installation Help
|
|
234
|
+
|
|
235
|
+
```tsx
|
|
236
|
+
function QuickStart() {
|
|
237
|
+
return (
|
|
238
|
+
<div>
|
|
239
|
+
<h2>Installation</h2>
|
|
240
|
+
<pre>{installCommands}</pre>
|
|
241
|
+
<AskAIButton
|
|
242
|
+
message="I need help with Django-CFG installation"
|
|
243
|
+
contextData={{
|
|
244
|
+
topic: "installation",
|
|
245
|
+
os: detectedOS
|
|
246
|
+
}}
|
|
247
|
+
source="QuickStart"
|
|
248
|
+
>
|
|
249
|
+
Installation help?
|
|
250
|
+
</AskAIButton>
|
|
251
|
+
</div>
|
|
252
|
+
);
|
|
253
|
+
}
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
## Architecture
|
|
257
|
+
|
|
258
|
+
```
|
|
259
|
+
┌─────────────────────────────────────┐
|
|
260
|
+
│ Any Component │
|
|
261
|
+
│ ┌──────────────────────────────┐ │
|
|
262
|
+
│ │ AskAIButton / useMcpChat() │ │
|
|
263
|
+
│ │ sendToChat({ message, ... }) │ │
|
|
264
|
+
│ └──────────────┬───────────────┘ │
|
|
265
|
+
└─────────────────┼───────────────────┘
|
|
266
|
+
│
|
|
267
|
+
│ CustomEvent
|
|
268
|
+
│ 'mcp:chat:send'
|
|
269
|
+
▼
|
|
270
|
+
┌─────────────────────────────────────┐
|
|
271
|
+
│ AIChatProvider (Event Listener) │
|
|
272
|
+
│ ┌──────────────────────────────┐ │
|
|
273
|
+
│ │ 1. Receives event │ │
|
|
274
|
+
│ │ 2. Sends confirmation │ │
|
|
275
|
+
│ │ 3. Opens chat │ │
|
|
276
|
+
│ │ 4. Sends message to AI │ │
|
|
277
|
+
│ └──────────────────────────────┘ │
|
|
278
|
+
└─────────────────────────────────────┘
|
|
279
|
+
│
|
|
280
|
+
▼
|
|
281
|
+
┌─────────────────────────────────────┐
|
|
282
|
+
│ AIChatWidget (UI) │
|
|
283
|
+
│ - Floating panel or sidebar │
|
|
284
|
+
│ - Message display │
|
|
285
|
+
│ - Streaming responses │
|
|
286
|
+
└─────────────────────────────────────┘
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
## API
|
|
290
|
+
|
|
291
|
+
### AskAIButton Props
|
|
292
|
+
|
|
293
|
+
```tsx
|
|
294
|
+
interface AskAIButtonProps extends ButtonProps {
|
|
295
|
+
/** Message to send to AI */
|
|
296
|
+
message: string;
|
|
297
|
+
/** Additional context data */
|
|
298
|
+
contextData?: Record<string, any>;
|
|
299
|
+
/** Source component name */
|
|
300
|
+
source?: string;
|
|
301
|
+
/** Auto-send message (default: true) */
|
|
302
|
+
autoSend?: boolean;
|
|
303
|
+
/** Show icon (default: true) */
|
|
304
|
+
showIcon?: boolean;
|
|
305
|
+
/** Callback after sending */
|
|
306
|
+
onSent?: () => void;
|
|
307
|
+
}
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
### McpChatEventDetail
|
|
311
|
+
|
|
312
|
+
```tsx
|
|
313
|
+
interface McpChatEventDetail {
|
|
314
|
+
message: string;
|
|
315
|
+
context?: {
|
|
316
|
+
data?: Record<string, any>;
|
|
317
|
+
source?: string;
|
|
318
|
+
};
|
|
319
|
+
autoSend?: boolean;
|
|
320
|
+
displayMode?: 'floating' | 'sidebar' | 'closed';
|
|
321
|
+
}
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
### UseMcpChatReturn
|
|
325
|
+
|
|
326
|
+
```tsx
|
|
327
|
+
interface UseMcpChatReturn {
|
|
328
|
+
/** Send message to chat */
|
|
329
|
+
sendToChat: (detail: McpChatEventDetail) => void;
|
|
330
|
+
/** Check if chat is available */
|
|
331
|
+
isChatAvailable: () => boolean;
|
|
332
|
+
}
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
## Best Practices
|
|
336
|
+
|
|
337
|
+
1. **Always provide source** - Helps track where requests come from
|
|
338
|
+
```tsx
|
|
339
|
+
<AskAIButton message="..." source="ComponentName">
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
2. **Include contextData** - More context = better AI responses
|
|
343
|
+
```tsx
|
|
344
|
+
<AskAIButton
|
|
345
|
+
message="..."
|
|
346
|
+
contextData={{ feature: "auth", version: "2.0" }}
|
|
347
|
+
>
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
3. **Keep messages clear** - Write clear questions or prompts
|
|
351
|
+
```tsx
|
|
352
|
+
// Good
|
|
353
|
+
message="Why is Django faster than FastAPI in enterprise scenarios?"
|
|
354
|
+
|
|
355
|
+
// Too vague
|
|
356
|
+
message="Explain this"
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
4. **Short button labels** - Keep button text concise (max 3 words)
|
|
360
|
+
```tsx
|
|
361
|
+
<AskAIButton message="...">
|
|
362
|
+
Explain this {/* Good */}
|
|
363
|
+
</AskAIButton>
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
5. **Check availability** - Use `isChatAvailable()` for conditional UI
|
|
367
|
+
```tsx
|
|
368
|
+
const { isChatAvailable } = useMcpChat();
|
|
369
|
+
if (!isChatAvailable()) {
|
|
370
|
+
return <p>Chat loading...</p>;
|
|
371
|
+
}
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
## Error Handling
|
|
375
|
+
|
|
376
|
+
If chat is not available:
|
|
377
|
+
- Console error logged
|
|
378
|
+
- Alert shown to user
|
|
379
|
+
- Button automatically disabled
|
|
380
|
+
|
|
381
|
+
```tsx
|
|
382
|
+
const { sendToChat, isChatAvailable } = useMcpChat();
|
|
383
|
+
|
|
384
|
+
if (!isChatAvailable()) {
|
|
385
|
+
console.log('Chat not loaded');
|
|
386
|
+
}
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
## TypeScript Support
|
|
390
|
+
|
|
391
|
+
Fully typed with TypeScript for excellent IDE support:
|
|
392
|
+
|
|
393
|
+
```tsx
|
|
394
|
+
import type {
|
|
395
|
+
McpChatEventDetail,
|
|
396
|
+
UseMcpChatReturn,
|
|
397
|
+
AskAIButtonProps
|
|
398
|
+
} from '@djangocfg/layouts/snippets/McpChat';
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
## Common Use Cases
|
|
402
|
+
|
|
403
|
+
### Documentation Pages
|
|
404
|
+
```tsx
|
|
405
|
+
<AskAIButton message="How do I set this up?">
|
|
406
|
+
Setup help?
|
|
407
|
+
</AskAIButton>
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
### Comparison Tables
|
|
411
|
+
```tsx
|
|
412
|
+
<AskAIButton message="Why is option A better than option B?">
|
|
413
|
+
Explain difference
|
|
414
|
+
</AskAIButton>
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
### Feature Sections
|
|
418
|
+
```tsx
|
|
419
|
+
<AskAIButton message="Which features should I explore first?">
|
|
420
|
+
Explore features?
|
|
421
|
+
</AskAIButton>
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
### Benchmarks
|
|
425
|
+
```tsx
|
|
426
|
+
<AskAIButton message="Why is this faster in production?">
|
|
427
|
+
Explain benchmarks?
|
|
428
|
+
</AskAIButton>
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
## Contributing
|
|
432
|
+
|
|
433
|
+
When adding new features:
|
|
434
|
+
1. Update type definitions in `types.ts`
|
|
435
|
+
2. Add examples to this README
|
|
436
|
+
3. Test with different scenarios
|
|
437
|
+
4. Keep the API simple and consistent
|
|
438
|
+
|
|
439
|
+
## License
|
|
440
|
+
|
|
441
|
+
MIT - Part of DjangoCFG project
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { Button, type ButtonProps } from '@djangocfg/ui-nextjs';
|
|
4
|
+
import { Bot } from 'lucide-react';
|
|
5
|
+
import { useMcpChat } from '../hooks/useMcpChat';
|
|
6
|
+
import type { McpChatEventDetail } from '../types';
|
|
7
|
+
|
|
8
|
+
export interface AskAIButtonProps extends Omit<ButtonProps, 'onClick'> {
|
|
9
|
+
/** Message to send to AI */
|
|
10
|
+
message: string;
|
|
11
|
+
/** Additional context data */
|
|
12
|
+
contextData?: Record<string, any>;
|
|
13
|
+
/** Source component name */
|
|
14
|
+
source?: string;
|
|
15
|
+
/** Auto-send message (default: true) */
|
|
16
|
+
autoSend?: boolean;
|
|
17
|
+
/** Show icon (default: true) */
|
|
18
|
+
showIcon?: boolean;
|
|
19
|
+
/** Callback after sending */
|
|
20
|
+
onSent?: () => void;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Universal AI chat trigger button
|
|
25
|
+
*
|
|
26
|
+
* @example Basic usage
|
|
27
|
+
* ```tsx
|
|
28
|
+
* <AskAIButton message="Explain this feature">
|
|
29
|
+
* Explain this
|
|
30
|
+
* </AskAIButton>
|
|
31
|
+
* ```
|
|
32
|
+
*
|
|
33
|
+
* @example With context
|
|
34
|
+
* ```tsx
|
|
35
|
+
* <AskAIButton
|
|
36
|
+
* message="Why is this failing?"
|
|
37
|
+
* contextData={{ error: error.stack }}
|
|
38
|
+
* source="ErrorBoundary"
|
|
39
|
+
* >
|
|
40
|
+
* Ask AI
|
|
41
|
+
* </AskAIButton>
|
|
42
|
+
* ```
|
|
43
|
+
*/
|
|
44
|
+
export function AskAIButton({
|
|
45
|
+
message,
|
|
46
|
+
contextData,
|
|
47
|
+
source,
|
|
48
|
+
autoSend = true,
|
|
49
|
+
showIcon = true,
|
|
50
|
+
onSent,
|
|
51
|
+
children = 'Ask AI',
|
|
52
|
+
variant = 'outline',
|
|
53
|
+
size = 'default',
|
|
54
|
+
className,
|
|
55
|
+
...buttonProps
|
|
56
|
+
}: AskAIButtonProps) {
|
|
57
|
+
const { sendToChat, isChatAvailable } = useMcpChat();
|
|
58
|
+
|
|
59
|
+
const handleClick = () => {
|
|
60
|
+
const detail: McpChatEventDetail = {
|
|
61
|
+
message,
|
|
62
|
+
autoSend,
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
if (contextData || source) {
|
|
66
|
+
detail.context = {
|
|
67
|
+
data: contextData,
|
|
68
|
+
source,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
sendToChat(detail);
|
|
73
|
+
onSent?.();
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const isAvailable = isChatAvailable();
|
|
77
|
+
|
|
78
|
+
return (
|
|
79
|
+
<Button
|
|
80
|
+
onClick={handleClick}
|
|
81
|
+
variant={variant}
|
|
82
|
+
size={size}
|
|
83
|
+
className={className}
|
|
84
|
+
disabled={!isAvailable}
|
|
85
|
+
title={!isAvailable ? 'AI Chat not available' : undefined}
|
|
86
|
+
{...buttonProps}
|
|
87
|
+
>
|
|
88
|
+
{showIcon && <Bot className="h-4 w-4 mr-2" />}
|
|
89
|
+
{children}
|
|
90
|
+
</Button>
|
|
91
|
+
);
|
|
92
|
+
}
|
|
@@ -19,3 +19,6 @@ export type { AIMessageInputProps } from './MessageInput';
|
|
|
19
19
|
|
|
20
20
|
export { ChatSidebar } from './ChatSidebar';
|
|
21
21
|
export type { ChatSidebarProps } from './ChatSidebar';
|
|
22
|
+
|
|
23
|
+
export { AskAIButton } from './AskAIButton';
|
|
24
|
+
export type { AskAIButtonProps } from './AskAIButton';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import
|
|
3
|
+
import { createContext, useContext, useState, useCallback, useMemo, useEffect, type ReactNode } from 'react';
|
|
4
4
|
import { useLocalStorage, useIsMobile } from '@djangocfg/ui-nextjs/hooks';
|
|
5
5
|
import { useAIChat } from '../hooks/useAIChat';
|
|
6
6
|
import { mcpEndpoints, type AIChatMessage, type ChatWidgetConfig, type ChatDisplayMode } from '../types';
|
|
@@ -223,6 +223,67 @@ export function AIChatProvider({
|
|
|
223
223
|
]
|
|
224
224
|
);
|
|
225
225
|
|
|
226
|
+
// =============================================================================
|
|
227
|
+
// Global Chat Event Listener
|
|
228
|
+
// =============================================================================
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Listen for mcp:chat:send events from useMcpChat hook
|
|
232
|
+
* This allows any component to trigger chat from anywhere in the app
|
|
233
|
+
*/
|
|
234
|
+
useEffect(() => {
|
|
235
|
+
// Register chat as available
|
|
236
|
+
if (typeof window !== 'undefined') {
|
|
237
|
+
(window as any).__MCP_CHAT_AVAILABLE__ = true;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const handleChatEvent = (event: Event) => {
|
|
241
|
+
const customEvent = event as CustomEvent<import('../types').McpChatEventDetail>;
|
|
242
|
+
const { message, context, autoSend = true, displayMode: requestedMode } = customEvent.detail;
|
|
243
|
+
|
|
244
|
+
// Send confirmation that event was handled
|
|
245
|
+
window.dispatchEvent(new CustomEvent('mcp:chat:handled'));
|
|
246
|
+
|
|
247
|
+
// Format message with context if provided
|
|
248
|
+
let fullMessage = message;
|
|
249
|
+
if (context) {
|
|
250
|
+
if (context.type) {
|
|
251
|
+
fullMessage = `[${context.type.toUpperCase()}] ${message}`;
|
|
252
|
+
}
|
|
253
|
+
if (context.data) {
|
|
254
|
+
fullMessage += `\n\n**Context:**\n\`\`\`json\n${JSON.stringify(context.data, null, 2)}\n\`\`\``;
|
|
255
|
+
}
|
|
256
|
+
if (context.source) {
|
|
257
|
+
fullMessage += `\n\n_Source: ${context.source}_`;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// Open chat in requested mode (or default to floating)
|
|
262
|
+
if (requestedMode) {
|
|
263
|
+
setDisplayMode(requestedMode);
|
|
264
|
+
} else {
|
|
265
|
+
openChat();
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Auto-send message if requested
|
|
269
|
+
if (autoSend) {
|
|
270
|
+
// Small delay to ensure chat is open and ready
|
|
271
|
+
setTimeout(() => {
|
|
272
|
+
sendMessage(fullMessage);
|
|
273
|
+
}, 100);
|
|
274
|
+
}
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
window.addEventListener('mcp:chat:send', handleChatEvent);
|
|
278
|
+
|
|
279
|
+
return () => {
|
|
280
|
+
window.removeEventListener('mcp:chat:send', handleChatEvent);
|
|
281
|
+
if (typeof window !== 'undefined') {
|
|
282
|
+
(window as any).__MCP_CHAT_AVAILABLE__ = false;
|
|
283
|
+
}
|
|
284
|
+
};
|
|
285
|
+
}, [sendMessage, openChat, setDisplayMode]);
|
|
286
|
+
|
|
226
287
|
return <AIChatContext.Provider value={value}>{children}</AIChatContext.Provider>;
|
|
227
288
|
}
|
|
228
289
|
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useCallback } from 'react';
|
|
4
|
+
import type { McpChatEventDetail, UseMcpChatReturn } from '../types';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Hook to send messages to MCP Chat from anywhere in the app
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```tsx
|
|
11
|
+
* function ErrorBoundary({ error }) {
|
|
12
|
+
* const { sendToChat } = useMcpChat();
|
|
13
|
+
*
|
|
14
|
+
* const explainError = () => {
|
|
15
|
+
* sendToChat({
|
|
16
|
+
* message: `Explain this error: ${error.message}`,
|
|
17
|
+
* context: {
|
|
18
|
+
* type: 'error',
|
|
19
|
+
* data: { error: error.stack },
|
|
20
|
+
* source: 'ErrorBoundary'
|
|
21
|
+
* }
|
|
22
|
+
* });
|
|
23
|
+
* };
|
|
24
|
+
*
|
|
25
|
+
* return <button onClick={explainError}>Explain Error</button>;
|
|
26
|
+
* }
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
export function useMcpChat(): UseMcpChatReturn {
|
|
30
|
+
/**
|
|
31
|
+
* Send message to chat via CustomEvent
|
|
32
|
+
*/
|
|
33
|
+
const sendToChat = useCallback((detail: McpChatEventDetail) => {
|
|
34
|
+
if (typeof window === 'undefined') {
|
|
35
|
+
console.error('[useMcpChat] Cannot send message: window is not available');
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Create custom event
|
|
40
|
+
const event = new CustomEvent('mcp:chat:send', {
|
|
41
|
+
detail,
|
|
42
|
+
bubbles: true,
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
// Set up handler confirmation listener
|
|
46
|
+
let handled = false;
|
|
47
|
+
const handleConfirmation = () => {
|
|
48
|
+
handled = true;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
window.addEventListener('mcp:chat:handled', handleConfirmation, { once: true });
|
|
52
|
+
|
|
53
|
+
// Dispatch event
|
|
54
|
+
window.dispatchEvent(event);
|
|
55
|
+
|
|
56
|
+
// Check if event was handled
|
|
57
|
+
setTimeout(() => {
|
|
58
|
+
window.removeEventListener('mcp:chat:handled', handleConfirmation);
|
|
59
|
+
|
|
60
|
+
if (!handled) {
|
|
61
|
+
const errorMessage = 'AI Chat is not available. Please make sure the chat component is loaded.';
|
|
62
|
+
console.error('[useMcpChat]', errorMessage);
|
|
63
|
+
|
|
64
|
+
// Import consola dynamically if available
|
|
65
|
+
if (typeof window !== 'undefined' && (window as any).consola) {
|
|
66
|
+
(window as any).consola.error('[useMcpChat] Chat not available');
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Show user-friendly alert
|
|
70
|
+
alert(errorMessage);
|
|
71
|
+
}
|
|
72
|
+
}, 100);
|
|
73
|
+
}, []);
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Check if chat is available
|
|
77
|
+
*/
|
|
78
|
+
const isChatAvailable = useCallback(() => {
|
|
79
|
+
if (typeof window === 'undefined') return false;
|
|
80
|
+
|
|
81
|
+
// Check if chat registered itself
|
|
82
|
+
return (window as any).__MCP_CHAT_AVAILABLE__ === true;
|
|
83
|
+
}, []);
|
|
84
|
+
|
|
85
|
+
return {
|
|
86
|
+
sendToChat,
|
|
87
|
+
isChatAvailable,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
@@ -42,12 +42,13 @@
|
|
|
42
42
|
*/
|
|
43
43
|
|
|
44
44
|
// Components
|
|
45
|
-
export { ChatWidget, AIChatWidget, ChatPanel, MessageBubble, AIMessageInput } from './components';
|
|
45
|
+
export { ChatWidget, AIChatWidget, ChatPanel, MessageBubble, AIMessageInput, AskAIButton } from './components';
|
|
46
46
|
export type {
|
|
47
47
|
ChatWidgetProps,
|
|
48
48
|
AIChatWidgetProps,
|
|
49
49
|
MessageBubbleProps,
|
|
50
50
|
AIMessageInputProps,
|
|
51
|
+
AskAIButtonProps,
|
|
51
52
|
} from './components';
|
|
52
53
|
|
|
53
54
|
// Context
|
|
@@ -60,7 +61,7 @@ export type {
|
|
|
60
61
|
} from './context';
|
|
61
62
|
|
|
62
63
|
// Hooks
|
|
63
|
-
export { useAIChat, useChatLayout } from './hooks';
|
|
64
|
+
export { useAIChat, useChatLayout, useMcpChat } from './hooks';
|
|
64
65
|
export type { ChatLayoutConfig, UseChatLayoutReturn } from './hooks';
|
|
65
66
|
|
|
66
67
|
// Types
|
|
@@ -72,4 +73,7 @@ export type {
|
|
|
72
73
|
UseAIChatOptions,
|
|
73
74
|
UseAIChatReturn,
|
|
74
75
|
ChatWidgetConfig,
|
|
76
|
+
McpChatContextType,
|
|
77
|
+
McpChatEventDetail,
|
|
78
|
+
UseMcpChatReturn,
|
|
75
79
|
} from './types';
|
|
@@ -141,3 +141,49 @@ export interface UseAIChatReturn {
|
|
|
141
141
|
clearMessages: () => void;
|
|
142
142
|
stopStreaming: () => void;
|
|
143
143
|
}
|
|
144
|
+
|
|
145
|
+
// =============================================================================
|
|
146
|
+
// MCP Chat Events (Global Chat Trigger)
|
|
147
|
+
// =============================================================================
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Context type for chat messages triggered from different parts of the app
|
|
151
|
+
*/
|
|
152
|
+
export type McpChatContextType =
|
|
153
|
+
| 'error' // Error explanation
|
|
154
|
+
| 'question' // General question
|
|
155
|
+
| 'explain' // Explain something
|
|
156
|
+
| 'advice' // Get advice
|
|
157
|
+
| 'help' // Help request
|
|
158
|
+
| 'custom'; // Custom context
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Event detail for mcp:chat:send custom event
|
|
162
|
+
*/
|
|
163
|
+
export interface McpChatEventDetail {
|
|
164
|
+
/** Message to send to chat */
|
|
165
|
+
message: string;
|
|
166
|
+
/** Optional context about the message */
|
|
167
|
+
context?: {
|
|
168
|
+
/** Type of context */
|
|
169
|
+
type?: McpChatContextType;
|
|
170
|
+
/** Additional data (error object, selected element, etc.) */
|
|
171
|
+
data?: Record<string, any>;
|
|
172
|
+
/** Source component/page that triggered the event */
|
|
173
|
+
source?: string;
|
|
174
|
+
};
|
|
175
|
+
/** Auto-send message after opening chat (default: true) */
|
|
176
|
+
autoSend?: boolean;
|
|
177
|
+
/** Open chat in specific mode (default: 'floating') */
|
|
178
|
+
displayMode?: ChatDisplayMode;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* useMcpChat hook return type
|
|
183
|
+
*/
|
|
184
|
+
export interface UseMcpChatReturn {
|
|
185
|
+
/** Send message to chat from anywhere in the app */
|
|
186
|
+
sendToChat: (detail: McpChatEventDetail) => void;
|
|
187
|
+
/** Check if chat is available */
|
|
188
|
+
isChatAvailable: () => boolean;
|
|
189
|
+
}
|
package/src/snippets/index.ts
CHANGED
|
@@ -18,6 +18,8 @@ export {
|
|
|
18
18
|
useAIChatContextOptional,
|
|
19
19
|
useAIChat,
|
|
20
20
|
useChatLayout,
|
|
21
|
+
useMcpChat,
|
|
22
|
+
AskAIButton,
|
|
21
23
|
} from './McpChat';
|
|
22
24
|
export type {
|
|
23
25
|
AIChatWidgetProps,
|
|
@@ -36,4 +38,8 @@ export type {
|
|
|
36
38
|
UseAIChatOptions,
|
|
37
39
|
UseAIChatReturn,
|
|
38
40
|
ChatWidgetConfig,
|
|
41
|
+
McpChatContextType,
|
|
42
|
+
McpChatEventDetail,
|
|
43
|
+
UseMcpChatReturn,
|
|
44
|
+
AskAIButtonProps,
|
|
39
45
|
} from './McpChat';
|