@djangocfg/layouts 2.1.227 → 2.1.228
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 +3 -17
- package/package.json +18 -18
- package/src/components/errors/ErrorLayout.tsx +2 -2
- package/src/components/errors/ErrorsTracker/index.ts +1 -0
- package/src/components/errors/ErrorsTracker/utils/formatters.ts +23 -1
- package/src/hooks/useLogout.ts +9 -12
- package/src/layouts/AppLayout/AppLayout.tsx +20 -8
- package/src/layouts/AppLayout/BaseApp.tsx +5 -28
- package/src/layouts/AuthLayout/AuthLayout.tsx +51 -22
- package/src/layouts/AuthLayout/README.md +78 -0
- package/src/layouts/AuthLayout/components/shared/AuthDivider.tsx +2 -2
- package/src/layouts/AuthLayout/components/shared/AuthError.tsx +10 -2
- package/src/layouts/AuthLayout/components/shared/AuthFooter.tsx +2 -2
- package/src/layouts/AuthLayout/components/shared/AuthHeader.tsx +3 -2
- package/src/layouts/AuthLayout/components/shared/AuthOTPInput.tsx +4 -1
- package/src/layouts/AuthLayout/components/shared/TermsCheckbox.tsx +2 -2
- package/src/layouts/AuthLayout/components/shared/index.ts +0 -2
- package/src/layouts/AuthLayout/components/steps/IdentifierStep.tsx +25 -80
- package/src/layouts/AuthLayout/components/steps/OTPStep.tsx +8 -13
- package/src/layouts/AuthLayout/components/steps/SetupStep/SetupComplete.tsx +2 -2
- package/src/layouts/AuthLayout/components/steps/SetupStep/SetupLoading.tsx +2 -2
- package/src/layouts/AuthLayout/components/steps/SetupStep/SetupQRCode.tsx +2 -2
- package/src/layouts/AuthLayout/components/steps/TwoFactorStep.tsx +61 -42
- package/src/layouts/AuthLayout/context.tsx +0 -2
- package/src/layouts/AuthLayout/index.ts +9 -6
- package/src/layouts/AuthLayout/styles/auth.css +265 -120
- package/src/layouts/AuthLayout/types.ts +60 -7
- package/src/layouts/ProfileLayout/.claude/.sidecar/activity.jsonl +2 -0
- package/src/layouts/ProfileLayout/.claude/.sidecar/history/2026-03-15.md +35 -0
- package/src/layouts/ProfileLayout/.claude/.sidecar/review.md +35 -0
- package/src/layouts/ProfileLayout/.claude/.sidecar/scan.log +3 -0
- package/src/layouts/ProfileLayout/.claude/.sidecar/tasks/T-001.md +18 -0
- package/src/layouts/ProfileLayout/.claude/.sidecar/tasks/T-002.md +19 -0
- package/src/layouts/ProfileLayout/.claude/.sidecar/tasks/T-003.md +18 -0
- package/src/layouts/ProfileLayout/.claude/.sidecar/tasks/T-004.md +18 -0
- package/src/layouts/ProfileLayout/.claude/.sidecar/tasks/T-005.md +18 -0
- package/src/layouts/ProfileLayout/.claude/.sidecar/usage.json +5 -0
- package/src/layouts/ProfileLayout/ProfileLayout.tsx +52 -403
- package/src/layouts/ProfileLayout/components/ActionButton.tsx +38 -0
- package/src/layouts/ProfileLayout/components/DeleteAccountSection.tsx +109 -148
- package/src/layouts/ProfileLayout/components/EditableField.tsx +119 -0
- package/src/layouts/ProfileLayout/components/Section.tsx +22 -0
- package/src/layouts/ProfileLayout/components/index.ts +4 -1
- package/src/layouts/ProfileLayout/context.tsx +31 -0
- package/src/layouts/PublicLayout/components/PublicMobileDrawer.tsx +2 -2
- package/src/layouts/PublicLayout/components/PublicNavigation.tsx +2 -2
- package/src/layouts/_components/UserMenu.tsx +2 -2
- package/src/layouts/types/README.md +0 -20
- package/src/layouts/types/index.ts +2 -2
- package/src/layouts/types/layout.types.ts +2 -5
- package/src/layouts/types/providers.types.ts +0 -27
- package/src/snippets/AuthDialog/AuthDialog.tsx +2 -2
- package/src/snippets/Breadcrumbs.tsx +2 -2
- package/src/snippets/index.ts +0 -67
- package/src/layouts/AuthLayout/components/shared/ChannelToggle.tsx +0 -56
- package/src/snippets/McpChat/README.md +0 -441
- package/src/snippets/McpChat/components/AIChatWidget.tsx +0 -361
- package/src/snippets/McpChat/components/AskAIButton.tsx +0 -92
- package/src/snippets/McpChat/components/ChatMessages.tsx +0 -138
- package/src/snippets/McpChat/components/ChatPanel.tsx +0 -131
- package/src/snippets/McpChat/components/ChatSidebar.tsx +0 -156
- package/src/snippets/McpChat/components/ChatWidget.tsx +0 -115
- package/src/snippets/McpChat/components/MessageBubble.tsx +0 -142
- package/src/snippets/McpChat/components/MessageInput.tsx +0 -140
- package/src/snippets/McpChat/components/index.ts +0 -24
- package/src/snippets/McpChat/config.ts +0 -94
- package/src/snippets/McpChat/context/AIChatContext.tsx +0 -327
- package/src/snippets/McpChat/context/ChatContext.tsx +0 -361
- package/src/snippets/McpChat/context/index.ts +0 -7
- package/src/snippets/McpChat/hooks/index.ts +0 -6
- package/src/snippets/McpChat/hooks/useAIChat.ts +0 -503
- package/src/snippets/McpChat/hooks/useChatLayout.ts +0 -442
- package/src/snippets/McpChat/hooks/useMcpChat.ts +0 -90
- package/src/snippets/McpChat/index.ts +0 -79
- package/src/snippets/McpChat/types.ts +0 -189
- package/src/snippets/PWAInstall/@docs/README.md +0 -92
- package/src/snippets/PWAInstall/@docs/research/ios-android-install-flows.md +0 -576
- package/src/snippets/PWAInstall/README.md +0 -235
- package/src/snippets/PWAInstall/components/A2HSHint.tsx +0 -236
- package/src/snippets/PWAInstall/components/DesktopGuide.tsx +0 -234
- package/src/snippets/PWAInstall/components/IOSGuide.tsx +0 -29
- package/src/snippets/PWAInstall/components/IOSGuideDrawer.tsx +0 -103
- package/src/snippets/PWAInstall/components/IOSGuideModal.tsx +0 -103
- package/src/snippets/PWAInstall/components/PWAPageResumeManager.tsx +0 -33
- package/src/snippets/PWAInstall/context/InstallContext.tsx +0 -102
- package/src/snippets/PWAInstall/hooks/useInstallPrompt.ts +0 -168
- package/src/snippets/PWAInstall/hooks/useIsPWA.ts +0 -116
- package/src/snippets/PWAInstall/hooks/usePWAPageResume.ts +0 -163
- package/src/snippets/PWAInstall/index.ts +0 -80
- package/src/snippets/PWAInstall/types/components.ts +0 -95
- package/src/snippets/PWAInstall/types/config.ts +0 -29
- package/src/snippets/PWAInstall/types/index.ts +0 -26
- package/src/snippets/PWAInstall/types/install.ts +0 -38
- package/src/snippets/PWAInstall/types/platform.ts +0 -29
- package/src/snippets/PWAInstall/utils/localStorage.ts +0 -181
- package/src/snippets/PWAInstall/utils/logger.ts +0 -149
- package/src/snippets/PWAInstall/utils/platform.ts +0 -151
|
@@ -1,361 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
import { Zap } from 'lucide-react';
|
|
4
|
-
import React from 'react';
|
|
5
|
-
|
|
6
|
-
import { Button, Portal } from '@djangocfg/ui-core';
|
|
7
|
-
|
|
8
|
-
import {
|
|
9
|
-
AIChatProvider, useAIChatContext, useAIChatContextOptional
|
|
10
|
-
} from '../context/AIChatContext';
|
|
11
|
-
import { useChatLayout } from '../hooks/useChatLayout';
|
|
12
|
-
import { ChatWidgetConfig, getMcpEndpoints} from '../types';
|
|
13
|
-
import { ChatPanel } from './ChatPanel';
|
|
14
|
-
import { ChatSidebar } from './ChatSidebar';
|
|
15
|
-
|
|
16
|
-
// CSS for game-quality multi-layer animated border with smooth color flow
|
|
17
|
-
const fabAnimationStyles = `
|
|
18
|
-
@keyframes rotate-gradient {
|
|
19
|
-
0% { transform: rotate(0deg); }
|
|
20
|
-
100% { transform: rotate(360deg); }
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
@keyframes rotate-gradient-reverse {
|
|
24
|
-
0% { transform: rotate(360deg); }
|
|
25
|
-
100% { transform: rotate(0deg); }
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
@keyframes color-shift-glow {
|
|
29
|
-
0%, 100% {
|
|
30
|
-
box-shadow:
|
|
31
|
-
0 0 20px rgba(251, 191, 36, 0.5),
|
|
32
|
-
0 0 40px rgba(168, 85, 247, 0.3),
|
|
33
|
-
0 0 60px rgba(20, 184, 166, 0.2);
|
|
34
|
-
}
|
|
35
|
-
33% {
|
|
36
|
-
box-shadow:
|
|
37
|
-
0 0 20px rgba(168, 85, 247, 0.5),
|
|
38
|
-
0 0 40px rgba(20, 184, 166, 0.3),
|
|
39
|
-
0 0 60px rgba(251, 191, 36, 0.2);
|
|
40
|
-
}
|
|
41
|
-
66% {
|
|
42
|
-
box-shadow:
|
|
43
|
-
0 0 20px rgba(20, 184, 166, 0.5),
|
|
44
|
-
0 0 40px rgba(236, 72, 153, 0.3),
|
|
45
|
-
0 0 60px rgba(168, 85, 247, 0.2);
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
@keyframes icon-pulse {
|
|
50
|
-
0%, 100% {
|
|
51
|
-
opacity: 1;
|
|
52
|
-
transform: scale(1);
|
|
53
|
-
filter: drop-shadow(0 0 4px rgba(251, 191, 36, 0.7));
|
|
54
|
-
}
|
|
55
|
-
50% {
|
|
56
|
-
opacity: 0.85;
|
|
57
|
-
transform: scale(1.15);
|
|
58
|
-
filter: drop-shadow(0 0 12px rgba(251, 191, 36, 1));
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
@keyframes border-pulse {
|
|
63
|
-
0%, 100% {
|
|
64
|
-
opacity: 1;
|
|
65
|
-
filter: blur(0px);
|
|
66
|
-
}
|
|
67
|
-
50% {
|
|
68
|
-
opacity: 0.85;
|
|
69
|
-
filter: blur(0.5px);
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
@keyframes inner-glow-pulse {
|
|
74
|
-
0%, 100% {
|
|
75
|
-
box-shadow:
|
|
76
|
-
inset 0 0 15px rgba(251, 191, 36, 0.3),
|
|
77
|
-
inset 0 0 25px rgba(168, 85, 247, 0.2);
|
|
78
|
-
}
|
|
79
|
-
50% {
|
|
80
|
-
box-shadow:
|
|
81
|
-
inset 0 0 20px rgba(168, 85, 247, 0.35),
|
|
82
|
-
inset 0 0 30px rgba(20, 184, 166, 0.25);
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
@keyframes fab-entrance {
|
|
87
|
-
0% {
|
|
88
|
-
transform: scale(0);
|
|
89
|
-
}
|
|
90
|
-
50% {
|
|
91
|
-
transform: scale(1.08);
|
|
92
|
-
}
|
|
93
|
-
70% {
|
|
94
|
-
transform: scale(0.98);
|
|
95
|
-
}
|
|
96
|
-
85% {
|
|
97
|
-
transform: scale(1.02);
|
|
98
|
-
}
|
|
99
|
-
100% {
|
|
100
|
-
transform: scale(1);
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
@keyframes fab-glow-entrance {
|
|
105
|
-
0% {
|
|
106
|
-
box-shadow: 0 0 0 rgba(251, 191, 36, 0);
|
|
107
|
-
}
|
|
108
|
-
40% {
|
|
109
|
-
box-shadow:
|
|
110
|
-
0 0 25px rgba(251, 191, 36, 0.6),
|
|
111
|
-
0 0 50px rgba(168, 85, 247, 0.4),
|
|
112
|
-
0 0 75px rgba(20, 184, 166, 0.25);
|
|
113
|
-
}
|
|
114
|
-
100% {
|
|
115
|
-
box-shadow:
|
|
116
|
-
0 0 20px rgba(251, 191, 36, 0.5),
|
|
117
|
-
0 0 40px rgba(168, 85, 247, 0.3),
|
|
118
|
-
0 0 60px rgba(20, 184, 166, 0.2);
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
`;
|
|
122
|
-
|
|
123
|
-
export interface AIChatWidgetProps extends ChatWidgetConfig {
|
|
124
|
-
/** Custom class name for the container */
|
|
125
|
-
className?: string;
|
|
126
|
-
/** Enable streaming responses (default: true) */
|
|
127
|
-
enableStreaming?: boolean;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
/**
|
|
131
|
-
* Internal AI chat widget that uses context
|
|
132
|
-
*/
|
|
133
|
-
const AIChatWidgetInternal = React.memo<{ className?: string }>(({ className }) => {
|
|
134
|
-
const { config, displayMode, openChat, isMobile } = useAIChatContext();
|
|
135
|
-
|
|
136
|
-
// Use layout hook for consistent positioning
|
|
137
|
-
const { getFabStyles, getFloatingStyles } = useChatLayout();
|
|
138
|
-
|
|
139
|
-
const position = config.position || 'bottom-right';
|
|
140
|
-
const fabStyles = getFabStyles(position);
|
|
141
|
-
const floatingStyles = getFloatingStyles(position);
|
|
142
|
-
|
|
143
|
-
// Mode: closed - just show FAB with game-quality multi-layer animated border
|
|
144
|
-
if (displayMode === 'closed') {
|
|
145
|
-
return (
|
|
146
|
-
<Portal>
|
|
147
|
-
<style>{fabAnimationStyles}</style>
|
|
148
|
-
<div style={fabStyles} className={className || ''}>
|
|
149
|
-
{/* Outer glow container with entrance and color-shifting animations */}
|
|
150
|
-
<div
|
|
151
|
-
className="relative rounded-full"
|
|
152
|
-
style={{
|
|
153
|
-
width: '68px',
|
|
154
|
-
height: '68px',
|
|
155
|
-
overflow: 'hidden',
|
|
156
|
-
animation: 'fab-entrance 0.6s cubic-bezier(0.34, 1.45, 0.64, 1) 0s 1 normal forwards, fab-glow-entrance 0.8s ease-out 0s 1 normal forwards, color-shift-glow 8s ease-in-out 0.6s infinite',
|
|
157
|
-
}}
|
|
158
|
-
>
|
|
159
|
-
{/* Border container - multiple layers for depth */}
|
|
160
|
-
<div
|
|
161
|
-
className="absolute rounded-full"
|
|
162
|
-
style={{
|
|
163
|
-
inset: '0',
|
|
164
|
-
overflow: 'hidden',
|
|
165
|
-
}}
|
|
166
|
-
>
|
|
167
|
-
{/* Layer 1: Base smooth gradient with more color stops */}
|
|
168
|
-
<div
|
|
169
|
-
className="absolute rounded-full"
|
|
170
|
-
style={{
|
|
171
|
-
inset: '0',
|
|
172
|
-
background: `conic-gradient(
|
|
173
|
-
from 0deg,
|
|
174
|
-
rgba(251, 191, 36, 1) 0%,
|
|
175
|
-
rgba(251, 191, 36, 0.7) 8%,
|
|
176
|
-
rgba(251, 191, 36, 0) 15%,
|
|
177
|
-
rgba(168, 85, 247, 0) 20%,
|
|
178
|
-
rgba(168, 85, 247, 0.7) 28%,
|
|
179
|
-
rgba(168, 85, 247, 1) 35%,
|
|
180
|
-
rgba(168, 85, 247, 0.7) 42%,
|
|
181
|
-
rgba(168, 85, 247, 0) 50%,
|
|
182
|
-
rgba(20, 184, 166, 0) 55%,
|
|
183
|
-
rgba(20, 184, 166, 0.7) 63%,
|
|
184
|
-
rgba(20, 184, 166, 1) 70%,
|
|
185
|
-
rgba(20, 184, 166, 0.7) 77%,
|
|
186
|
-
rgba(20, 184, 166, 0) 85%,
|
|
187
|
-
rgba(236, 72, 153, 0) 88%,
|
|
188
|
-
rgba(236, 72, 153, 0.7) 93%,
|
|
189
|
-
rgba(236, 72, 153, 1) 97%,
|
|
190
|
-
rgba(251, 191, 36, 1) 100%
|
|
191
|
-
)`,
|
|
192
|
-
animation: 'rotate-gradient 7s linear infinite, border-pulse 4s ease-in-out infinite',
|
|
193
|
-
filter: 'blur(1px)',
|
|
194
|
-
opacity: 0.95,
|
|
195
|
-
}}
|
|
196
|
-
/>
|
|
197
|
-
|
|
198
|
-
{/* Layer 2: Secondary gradient (counter-clockwise) - stronger */}
|
|
199
|
-
<div
|
|
200
|
-
className="absolute rounded-full"
|
|
201
|
-
style={{
|
|
202
|
-
inset: '1px',
|
|
203
|
-
background: `conic-gradient(
|
|
204
|
-
from 180deg,
|
|
205
|
-
rgba(168, 85, 247, 0.85) 0%,
|
|
206
|
-
rgba(168, 85, 247, 0.5) 10%,
|
|
207
|
-
rgba(168, 85, 247, 0) 20%,
|
|
208
|
-
rgba(20, 184, 166, 0) 30%,
|
|
209
|
-
rgba(20, 184, 166, 0.5) 40%,
|
|
210
|
-
rgba(20, 184, 166, 0.85) 50%,
|
|
211
|
-
rgba(20, 184, 166, 0.5) 60%,
|
|
212
|
-
rgba(20, 184, 166, 0) 70%,
|
|
213
|
-
rgba(251, 191, 36, 0) 75%,
|
|
214
|
-
rgba(251, 191, 36, 0.5) 85%,
|
|
215
|
-
rgba(251, 191, 36, 0.85) 95%,
|
|
216
|
-
rgba(168, 85, 247, 0.85) 100%
|
|
217
|
-
)`,
|
|
218
|
-
animation: 'rotate-gradient-reverse 9s linear infinite',
|
|
219
|
-
filter: 'blur(0.75px)',
|
|
220
|
-
opacity: 0.75,
|
|
221
|
-
}}
|
|
222
|
-
/>
|
|
223
|
-
|
|
224
|
-
{/* Inner mask with glowing edge */}
|
|
225
|
-
<div
|
|
226
|
-
className="absolute rounded-full bg-background"
|
|
227
|
-
style={{
|
|
228
|
-
inset: '4px',
|
|
229
|
-
animation: 'inner-glow-pulse 5s ease-in-out infinite',
|
|
230
|
-
}}
|
|
231
|
-
/>
|
|
232
|
-
|
|
233
|
-
{/* Main FAB button */}
|
|
234
|
-
<Button
|
|
235
|
-
onClick={openChat}
|
|
236
|
-
variant="ghost"
|
|
237
|
-
className="absolute rounded-full hover:scale-105 transition-all duration-300 bg-background/80 hover:bg-background/95 border-0 backdrop-blur-sm"
|
|
238
|
-
style={{
|
|
239
|
-
inset: '2.5px',
|
|
240
|
-
width: 'auto',
|
|
241
|
-
height: 'auto',
|
|
242
|
-
}}
|
|
243
|
-
>
|
|
244
|
-
<Zap
|
|
245
|
-
className="h-6 w-6"
|
|
246
|
-
style={{
|
|
247
|
-
animation: 'icon-pulse 2.5s ease-in-out infinite',
|
|
248
|
-
color: '#fbbf24',
|
|
249
|
-
fill: '#fbbf24',
|
|
250
|
-
}}
|
|
251
|
-
/>
|
|
252
|
-
</Button>
|
|
253
|
-
</div>
|
|
254
|
-
</div>
|
|
255
|
-
</div>
|
|
256
|
-
</Portal>
|
|
257
|
-
);
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
// Mode: sidebar - full-height panel on the right (desktop only)
|
|
261
|
-
if (displayMode === 'sidebar') {
|
|
262
|
-
return (
|
|
263
|
-
<Portal>
|
|
264
|
-
<ChatSidebar />
|
|
265
|
-
</Portal>
|
|
266
|
-
);
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
// Mode: floating - fullscreen on mobile (via Portal), floating panel on desktop
|
|
270
|
-
if (isMobile) {
|
|
271
|
-
return (
|
|
272
|
-
<Portal>
|
|
273
|
-
<div
|
|
274
|
-
className="z-[400] overflow-hidden"
|
|
275
|
-
style={{
|
|
276
|
-
position: 'fixed',
|
|
277
|
-
top: 0,
|
|
278
|
-
left: 0,
|
|
279
|
-
right: 0,
|
|
280
|
-
bottom: 0,
|
|
281
|
-
width: '100vw',
|
|
282
|
-
height: '100dvh',
|
|
283
|
-
}}
|
|
284
|
-
>
|
|
285
|
-
<ChatPanel />
|
|
286
|
-
</div>
|
|
287
|
-
</Portal>
|
|
288
|
-
);
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
return (
|
|
292
|
-
<Portal>
|
|
293
|
-
<div style={floatingStyles} className={className || ''}>
|
|
294
|
-
<ChatPanel />
|
|
295
|
-
</div>
|
|
296
|
-
</Portal>
|
|
297
|
-
);
|
|
298
|
-
});
|
|
299
|
-
|
|
300
|
-
AIChatWidgetInternal.displayName = 'AIChatWidgetInternal';
|
|
301
|
-
|
|
302
|
-
/**
|
|
303
|
-
* AI Chat Widget component
|
|
304
|
-
*
|
|
305
|
-
* AI-powered documentation assistant with streaming support.
|
|
306
|
-
* Uses Mastra agent backend for intelligent responses.
|
|
307
|
-
*
|
|
308
|
-
* Can be used in two ways:
|
|
309
|
-
* 1. Standalone (wraps itself in AIChatProvider)
|
|
310
|
-
* 2. Inside an AIChatProvider (uses context directly)
|
|
311
|
-
*
|
|
312
|
-
* @example
|
|
313
|
-
* ```tsx
|
|
314
|
-
* // Standalone usage (always uses production API)
|
|
315
|
-
* <AIChatWidget />
|
|
316
|
-
*
|
|
317
|
-
* // Auto-detect environment (dev/prod)
|
|
318
|
-
* <AIChatWidget autoDetectEnvironment={true} />
|
|
319
|
-
*
|
|
320
|
-
* // With provider for custom control
|
|
321
|
-
* <AIChatProvider apiEndpoint="/api/ai/chat">
|
|
322
|
-
* <MyApp />
|
|
323
|
-
* <AIChatWidget />
|
|
324
|
-
* </AIChatProvider>
|
|
325
|
-
* ```
|
|
326
|
-
*/
|
|
327
|
-
export const AIChatWidget: React.FC<AIChatWidgetProps> = ({
|
|
328
|
-
apiEndpoint,
|
|
329
|
-
title = 'DjangoCFG AI',
|
|
330
|
-
placeholder = 'Ask about DjangoCFG...',
|
|
331
|
-
greeting = "Hi! I'm your DjangoCFG AI assistant powered by GPT. Ask me anything about configuration, features, or how to use the library.",
|
|
332
|
-
position = 'bottom-right',
|
|
333
|
-
variant = 'default',
|
|
334
|
-
className,
|
|
335
|
-
enableStreaming = true,
|
|
336
|
-
autoDetectEnvironment = false,
|
|
337
|
-
}) => {
|
|
338
|
-
// Check if we're inside an AIChatProvider
|
|
339
|
-
const existingContext = useAIChatContextOptional();
|
|
340
|
-
|
|
341
|
-
// If already in context, use internal widget directly
|
|
342
|
-
if (existingContext) {
|
|
343
|
-
return <AIChatWidgetInternal className={className} />;
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
// Determine API endpoint: use provided, or get from config based on autoDetect
|
|
347
|
-
const finalApiEndpoint = apiEndpoint || getMcpEndpoints(autoDetectEnvironment).chat;
|
|
348
|
-
|
|
349
|
-
// Otherwise, wrap in provider
|
|
350
|
-
return (
|
|
351
|
-
<AIChatProvider
|
|
352
|
-
apiEndpoint={finalApiEndpoint}
|
|
353
|
-
config={{ title, placeholder, greeting, position, variant, autoDetectEnvironment }}
|
|
354
|
-
enableStreaming={enableStreaming}
|
|
355
|
-
>
|
|
356
|
-
<AIChatWidgetInternal className={className} />
|
|
357
|
-
</AIChatProvider>
|
|
358
|
-
);
|
|
359
|
-
};
|
|
360
|
-
|
|
361
|
-
AIChatWidget.displayName = 'AIChatWidget';
|
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
import { Bot } from 'lucide-react';
|
|
4
|
-
|
|
5
|
-
import { Button, ButtonProps} from '@djangocfg/ui-core';
|
|
6
|
-
|
|
7
|
-
import { useMcpChat } from '../hooks/useMcpChat';
|
|
8
|
-
|
|
9
|
-
import type { McpChatEventDetail } from '../types';
|
|
10
|
-
|
|
11
|
-
export interface AskAIButtonProps extends Omit<ButtonProps, 'onClick'> {
|
|
12
|
-
/** Message to send to AI */
|
|
13
|
-
message: string;
|
|
14
|
-
/** Additional context data */
|
|
15
|
-
contextData?: Record<string, any>;
|
|
16
|
-
/** Source component name */
|
|
17
|
-
source?: string;
|
|
18
|
-
/** Auto-send message (default: true) */
|
|
19
|
-
autoSend?: boolean;
|
|
20
|
-
/** Show icon (default: true) */
|
|
21
|
-
showIcon?: boolean;
|
|
22
|
-
/** Callback after sending */
|
|
23
|
-
onSent?: () => void;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Universal AI chat trigger button
|
|
28
|
-
*
|
|
29
|
-
* @example Basic usage
|
|
30
|
-
* ```tsx
|
|
31
|
-
* <AskAIButton message="Explain this feature">
|
|
32
|
-
* Explain this
|
|
33
|
-
* </AskAIButton>
|
|
34
|
-
* ```
|
|
35
|
-
*
|
|
36
|
-
* @example With context
|
|
37
|
-
* ```tsx
|
|
38
|
-
* <AskAIButton
|
|
39
|
-
* message="Why is this failing?"
|
|
40
|
-
* contextData={{ error: error.stack }}
|
|
41
|
-
* source="ErrorBoundary"
|
|
42
|
-
* >
|
|
43
|
-
* Ask AI
|
|
44
|
-
* </AskAIButton>
|
|
45
|
-
* ```
|
|
46
|
-
*/
|
|
47
|
-
export function AskAIButton({
|
|
48
|
-
message,
|
|
49
|
-
contextData,
|
|
50
|
-
source,
|
|
51
|
-
autoSend = true,
|
|
52
|
-
showIcon = true,
|
|
53
|
-
onSent,
|
|
54
|
-
children = 'Ask AI',
|
|
55
|
-
variant = 'outline',
|
|
56
|
-
size = 'default',
|
|
57
|
-
className,
|
|
58
|
-
...buttonProps
|
|
59
|
-
}: AskAIButtonProps) {
|
|
60
|
-
const { sendToChat } = useMcpChat();
|
|
61
|
-
|
|
62
|
-
const handleClick = () => {
|
|
63
|
-
const detail: McpChatEventDetail = {
|
|
64
|
-
message,
|
|
65
|
-
autoSend,
|
|
66
|
-
// No displayMode - chat will use remembered mode automatically
|
|
67
|
-
};
|
|
68
|
-
|
|
69
|
-
if (contextData || source) {
|
|
70
|
-
detail.context = {
|
|
71
|
-
data: contextData,
|
|
72
|
-
source,
|
|
73
|
-
};
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
sendToChat(detail);
|
|
77
|
-
onSent?.();
|
|
78
|
-
};
|
|
79
|
-
|
|
80
|
-
return (
|
|
81
|
-
<Button
|
|
82
|
-
onClick={handleClick}
|
|
83
|
-
variant={variant}
|
|
84
|
-
size={size}
|
|
85
|
-
className={className}
|
|
86
|
-
{...buttonProps}
|
|
87
|
-
>
|
|
88
|
-
{showIcon && <Bot className="h-4 w-4 mr-2" />}
|
|
89
|
-
{children}
|
|
90
|
-
</Button>
|
|
91
|
-
);
|
|
92
|
-
}
|
|
@@ -1,138 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
import { Bot, MessageSquare, StopCircle } from 'lucide-react';
|
|
4
|
-
import React, { forwardRef, useImperativeHandle, useRef } from 'react';
|
|
5
|
-
|
|
6
|
-
import { Button } from '@djangocfg/ui-core';
|
|
7
|
-
|
|
8
|
-
import { MessageBubble } from './MessageBubble';
|
|
9
|
-
|
|
10
|
-
import type { AIChatMessage } from '../types';
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* ChatMessages imperative handle
|
|
14
|
-
*/
|
|
15
|
-
export interface ChatMessagesHandle {
|
|
16
|
-
scrollToBottom: (instant?: boolean) => void;
|
|
17
|
-
scrollToLastMessage: (instant?: boolean) => void;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export interface ChatMessagesProps {
|
|
21
|
-
/** Messages to display */
|
|
22
|
-
messages: AIChatMessage[];
|
|
23
|
-
/** Whether loading/streaming is in progress */
|
|
24
|
-
isLoading: boolean;
|
|
25
|
-
/** Greeting to show when no messages */
|
|
26
|
-
greeting?: string;
|
|
27
|
-
/** Callback to stop streaming */
|
|
28
|
-
onStopStreaming?: () => void;
|
|
29
|
-
/** Use compact layout (smaller bubbles) */
|
|
30
|
-
isCompact?: boolean;
|
|
31
|
-
/** Use larger icon for greeting (sidebar mode) */
|
|
32
|
-
largeGreetingIcon?: boolean;
|
|
33
|
-
/** Custom greeting icon */
|
|
34
|
-
greetingIcon?: 'bot' | 'message';
|
|
35
|
-
/** Custom greeting title */
|
|
36
|
-
greetingTitle?: string;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* ChatMessages - Shared component for displaying chat messages
|
|
41
|
-
*
|
|
42
|
-
* Uses flex-col-reverse for natural bottom scroll behavior:
|
|
43
|
-
* - Scroll position 0 = visual bottom
|
|
44
|
-
* - New content naturally stays at bottom
|
|
45
|
-
* - No complex JS scroll management needed
|
|
46
|
-
*/
|
|
47
|
-
export const ChatMessages = forwardRef<ChatMessagesHandle, ChatMessagesProps>(
|
|
48
|
-
(
|
|
49
|
-
{
|
|
50
|
-
messages,
|
|
51
|
-
isLoading,
|
|
52
|
-
greeting,
|
|
53
|
-
onStopStreaming,
|
|
54
|
-
isCompact = false,
|
|
55
|
-
largeGreetingIcon = false,
|
|
56
|
-
greetingIcon = 'bot',
|
|
57
|
-
greetingTitle,
|
|
58
|
-
},
|
|
59
|
-
ref
|
|
60
|
-
) => {
|
|
61
|
-
const scrollContainerRef = useRef<HTMLDivElement>(null);
|
|
62
|
-
|
|
63
|
-
// Expose scroll methods via ref (for compatibility)
|
|
64
|
-
useImperativeHandle(ref, () => ({
|
|
65
|
-
scrollToBottom: () => {
|
|
66
|
-
// With flex-col-reverse, scroll to top = visual bottom
|
|
67
|
-
scrollContainerRef.current?.scrollTo({ top: 0, behavior: 'smooth' });
|
|
68
|
-
},
|
|
69
|
-
scrollToLastMessage: () => {
|
|
70
|
-
scrollContainerRef.current?.scrollTo({ top: 0, behavior: 'smooth' });
|
|
71
|
-
},
|
|
72
|
-
}), []);
|
|
73
|
-
|
|
74
|
-
// No manual scroll effects needed - flex-col-reverse handles it naturally
|
|
75
|
-
|
|
76
|
-
const GreetingIcon = greetingIcon === 'message' ? MessageSquare : Bot;
|
|
77
|
-
const iconSize = largeGreetingIcon ? { container: '64px', icon: 'h-8 w-8' } : { container: '48px', icon: 'h-6 w-6' };
|
|
78
|
-
const padding = largeGreetingIcon ? 'py-12' : 'py-8';
|
|
79
|
-
|
|
80
|
-
return (
|
|
81
|
-
<div ref={scrollContainerRef} className="h-full w-full overflow-y-auto flex flex-col-reverse">
|
|
82
|
-
<div className={`${isCompact ? 'p-3' : 'p-4'} space-y-4 max-w-full overflow-x-hidden`}>
|
|
83
|
-
{/* Greeting */}
|
|
84
|
-
{messages.length === 0 && greeting && (
|
|
85
|
-
<div className={`text-center ${padding}`}>
|
|
86
|
-
<div
|
|
87
|
-
className="mx-auto mb-4 rounded-full bg-primary/10 flex items-center justify-center"
|
|
88
|
-
style={{ width: iconSize.container, height: iconSize.container }}
|
|
89
|
-
>
|
|
90
|
-
<GreetingIcon className={`${iconSize.icon} text-primary`} />
|
|
91
|
-
</div>
|
|
92
|
-
{greetingTitle && (
|
|
93
|
-
<h4 className="font-medium mb-2">{greetingTitle}</h4>
|
|
94
|
-
)}
|
|
95
|
-
<p className={`text-sm text-muted-foreground ${largeGreetingIcon ? 'max-w-[300px]' : 'max-w-[280px]'} mx-auto`}>
|
|
96
|
-
{greeting}
|
|
97
|
-
</p>
|
|
98
|
-
</div>
|
|
99
|
-
)}
|
|
100
|
-
|
|
101
|
-
{/* Messages */}
|
|
102
|
-
{messages.map((message) => (
|
|
103
|
-
<div key={message.id} data-message-bubble>
|
|
104
|
-
<MessageBubble message={message} isCompact={isCompact} />
|
|
105
|
-
</div>
|
|
106
|
-
))}
|
|
107
|
-
|
|
108
|
-
{/* Loading indicator with stop button */}
|
|
109
|
-
{isLoading && messages.length > 0 && (
|
|
110
|
-
<div className="flex items-center justify-between text-muted-foreground text-sm">
|
|
111
|
-
<div className="flex items-center gap-2">
|
|
112
|
-
<div className="flex gap-1">
|
|
113
|
-
<span className="animate-bounce" style={{ animationDelay: '0ms' }}>.</span>
|
|
114
|
-
<span className="animate-bounce" style={{ animationDelay: '150ms' }}>.</span>
|
|
115
|
-
<span className="animate-bounce" style={{ animationDelay: '300ms' }}>.</span>
|
|
116
|
-
</div>
|
|
117
|
-
<span>Generating response...</span>
|
|
118
|
-
</div>
|
|
119
|
-
{onStopStreaming && (
|
|
120
|
-
<Button
|
|
121
|
-
variant="ghost"
|
|
122
|
-
size="sm"
|
|
123
|
-
onClick={onStopStreaming}
|
|
124
|
-
className="h-6 px-2 text-xs"
|
|
125
|
-
>
|
|
126
|
-
<StopCircle className="h-3 w-3 mr-1" />
|
|
127
|
-
Stop
|
|
128
|
-
</Button>
|
|
129
|
-
)}
|
|
130
|
-
</div>
|
|
131
|
-
)}
|
|
132
|
-
</div>
|
|
133
|
-
</div>
|
|
134
|
-
);
|
|
135
|
-
}
|
|
136
|
-
);
|
|
137
|
-
|
|
138
|
-
ChatMessages.displayName = 'ChatMessages';
|