@djangocfg/ui-tools 2.1.382 → 2.1.383

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.
Files changed (62) hide show
  1. package/dist/DictationField-U25MEYAL.mjs +4 -0
  2. package/dist/{DictationField-2ZLQWLYV.mjs.map → DictationField-U25MEYAL.mjs.map} +1 -1
  3. package/dist/DictationField-XWR5VOID.cjs +13 -0
  4. package/dist/{DictationField-IPPJ54CU.cjs.map → DictationField-XWR5VOID.cjs.map} +1 -1
  5. package/dist/{chunk-KMSBGNVC.cjs → chunk-4PFW7MIJ.cjs} +4 -2
  6. package/dist/chunk-4PFW7MIJ.cjs.map +1 -0
  7. package/dist/{chunk-4LXG3NBV.mjs → chunk-C2YN6WEO.mjs} +3 -3
  8. package/dist/chunk-C2YN6WEO.mjs.map +1 -0
  9. package/dist/index.cjs +139 -2
  10. package/dist/index.cjs.map +1 -1
  11. package/dist/index.d.cts +68 -1
  12. package/dist/index.d.ts +68 -1
  13. package/dist/index.mjs +141 -6
  14. package/dist/index.mjs.map +1 -1
  15. package/package.json +6 -13
  16. package/src/tools/Chat/index.ts +15 -0
  17. package/dist/DictationField-2ZLQWLYV.mjs +0 -4
  18. package/dist/DictationField-IPPJ54CU.cjs +0 -13
  19. package/dist/chunk-4LXG3NBV.mjs.map +0 -1
  20. package/dist/chunk-KMSBGNVC.cjs.map +0 -1
  21. package/src/components/markdown/MarkdownMessage/MarkdownMessage.story.tsx +0 -771
  22. package/src/stories/index.ts +0 -63
  23. package/src/tools/AudioPlayer/AudioPlayer.story.tsx +0 -481
  24. package/src/tools/Chat/stories/01-basic.story.tsx +0 -64
  25. package/src/tools/Chat/stories/02-bubbles.story.tsx +0 -21
  26. package/src/tools/Chat/stories/03-tool-calls.story.tsx +0 -59
  27. package/src/tools/Chat/stories/04-personas.story.tsx +0 -78
  28. package/src/tools/Chat/stories/05-launcher.story.tsx +0 -321
  29. package/src/tools/Chat/stories/06-header.story.tsx +0 -147
  30. package/src/tools/Chat/stories/07-audio-actions.story.tsx +0 -112
  31. package/src/tools/Chat/stories/shared/Frame.tsx +0 -21
  32. package/src/tools/Chat/stories/shared/index.ts +0 -5
  33. package/src/tools/Chat/stories/shared/messages.ts +0 -39
  34. package/src/tools/Chat/stories/shared/personas.ts +0 -13
  35. package/src/tools/Chat/stories/shared/seeds.ts +0 -92
  36. package/src/tools/Chat/stories/shared/transports.ts +0 -36
  37. package/src/tools/CodeEditor/CodeEditor.story.tsx +0 -202
  38. package/src/tools/CronScheduler/CronScheduler.story.tsx +0 -300
  39. package/src/tools/Gallery/Gallery.story.tsx +0 -237
  40. package/src/tools/ImageViewer/ImageViewer.story.tsx +0 -85
  41. package/src/tools/JsonForm/JsonForm.story.tsx +0 -350
  42. package/src/tools/JsonTree/JsonTree.story.tsx +0 -141
  43. package/src/tools/LottiePlayer/LottiePlayer.story.tsx +0 -95
  44. package/src/tools/Map/Map.story.tsx +0 -458
  45. package/src/tools/MarkdownEditor/MarkdownEditor.story.tsx +0 -225
  46. package/src/tools/Mermaid/Mermaid.story.tsx +0 -251
  47. package/src/tools/OpenapiViewer/OpenapiViewer.story.tsx +0 -230
  48. package/src/tools/PrettyCode/PrettyCode.story.tsx +0 -304
  49. package/src/tools/SpeechRecognition/stories/01-basic.story.tsx +0 -32
  50. package/src/tools/SpeechRecognition/stories/02-dictation-field.story.tsx +0 -32
  51. package/src/tools/SpeechRecognition/stories/03-push-to-talk.story.tsx +0 -27
  52. package/src/tools/SpeechRecognition/stories/04-mic-meter.story.tsx +0 -35
  53. package/src/tools/SpeechRecognition/stories/05-custom-engine-http.story.tsx +0 -40
  54. package/src/tools/SpeechRecognition/stories/06-custom-engine-ws.story.tsx +0 -48
  55. package/src/tools/SpeechRecognition/stories/07-language-device.story.tsx +0 -57
  56. package/src/tools/SpeechRecognition/stories/08-errors-permissions.story.tsx +0 -25
  57. package/src/tools/SpeechRecognition/stories/09-chat-voice.story.tsx +0 -90
  58. package/src/tools/SpeechRecognition/stories/shared.tsx +0 -123
  59. package/src/tools/Tour/Tour.story.tsx +0 -279
  60. package/src/tools/Tree/Tree.story.tsx +0 -620
  61. package/src/tools/Uploader/Uploader.story.tsx +0 -415
  62. package/src/tools/VideoPlayer/VideoPlayer.story.tsx +0 -87
@@ -1,78 +0,0 @@
1
- import { useMemo } from 'react';
2
-
3
- import { defineStory } from '@djangocfg/playground';
4
-
5
- import { MessageBubble } from '../components/MessageBubble';
6
- import { ChatRoot } from '../components/ChatRoot';
7
- import type { ChatMessage } from '../types';
8
- import {
9
- ASSISTANT_AURA,
10
- Frame,
11
- SEED_BASIC,
12
- USER_ANNA,
13
- makeBasicTransport,
14
- } from './shared';
15
-
16
- export default defineStory({
17
- title: 'Tools/Chat/Personas',
18
- component: ChatRoot,
19
- description:
20
- 'Default user/assistant identities via `config.user` / `config.assistant`, and per-message overrides via `message.sender` (multi-user / multi-bot).',
21
- });
22
-
23
- export const Default = () => {
24
- const transport = useMemo(() => makeBasicTransport(SEED_BASIC), []);
25
- return (
26
- <Frame>
27
- <ChatRoot
28
- transport={transport}
29
- config={{
30
- greeting: `Hi ${USER_ANNA.name} 👋`,
31
- user: USER_ANNA,
32
- assistant: ASSISTANT_AURA,
33
- }}
34
- />
35
- </Frame>
36
- );
37
- };
38
-
39
- const MULTI_USER: ChatMessage[] = [
40
- {
41
- id: 'm1',
42
- role: 'user',
43
- content: '@aura can you summarise the spec?',
44
- createdAt: Date.now() - 90_000,
45
- sender: USER_ANNA,
46
- },
47
- {
48
- id: 'm2',
49
- role: 'assistant',
50
- content: 'Pulling the latest version from the docs index now.',
51
- createdAt: Date.now() - 80_000,
52
- sender: ASSISTANT_AURA,
53
- },
54
- {
55
- id: 'm3',
56
- role: 'user',
57
- content: 'Make sure to flag the auth migration section.',
58
- createdAt: Date.now() - 60_000,
59
- sender: { name: 'Mark', initials: 'MK' },
60
- },
61
- {
62
- id: 'm4',
63
- role: 'assistant',
64
- content: '👍 Flagged section 4.2 — needs DBA review.',
65
- createdAt: Date.now() - 30_000,
66
- sender: ASSISTANT_AURA,
67
- },
68
- ];
69
-
70
- export const MultiUser = () => (
71
- <Frame h={520}>
72
- <div className="h-full overflow-y-auto py-2">
73
- {MULTI_USER.map((m) => (
74
- <MessageBubble key={m.id} message={m} showActions={false} showTimestamp />
75
- ))}
76
- </div>
77
- </Frame>
78
- );
@@ -1,321 +0,0 @@
1
- import { useEffect, useMemo, useState } from 'react';
2
- import { Bot, MessageCircle, Sparkles, Zap } from 'lucide-react';
3
-
4
- import { defineStory, useBoolean, useNumber, useSelect } from '@djangocfg/playground';
5
- import { Avatar, AvatarFallback, AvatarImage } from '@djangocfg/ui-core/components';
6
-
7
- import { ChatRoot } from '../components/ChatRoot';
8
- import { Composer } from '../components/Composer';
9
- import { EmptyState } from '../components/EmptyState';
10
- import { MessageList } from '../components/MessageList';
11
- import { ChatProvider, useChatContext } from '../context';
12
- import { useChatComposer } from '../hooks/useChatComposer';
13
- import { useChatUnread } from '../hooks/useChatUnread';
14
- import {
15
- ChatFAB,
16
- type ChatFABPosition,
17
- type ChatFABSize,
18
- type ChatFABVariant,
19
- } from '../launcher/ChatFAB';
20
- import { ChatHeaderLanguageButton } from '../launcher/ChatHeaderLanguageButton';
21
- import { ChatLauncher } from '../launcher/ChatLauncher';
22
- import { VoiceComposerSlot } from '../../SpeechRecognition/widgets/VoiceComposerSlot';
23
- import type { ChatMessage } from '../types';
24
- import { SEED_BASIC, makeBasicTransport } from './shared';
25
-
26
- export default defineStory({
27
- title: 'Tools/Chat/Launcher',
28
- component: ChatLauncher,
29
- description:
30
- 'Floating chat launcher (FAB + Dock + Greeting + hotkey). Production-realistic stories pin the FAB to the viewport corner like any chat widget; isolated previews use `inline` mode for the FAB primitive.',
31
- });
32
-
33
- function DemoChat() {
34
- const transport = useMemo(() => makeBasicTransport(SEED_BASIC), []);
35
- // Drop the voice slot into the composer toolbar. No props needed —
36
- // it pulls value/setValue from the registered ComposerHandle and
37
- // auto-hides on browsers that can't dictate.
38
- return (
39
- <ChatRoot
40
- transport={transport}
41
- config={{ greeting: 'How can I help?' }}
42
- composerToolbarEnd={<VoiceComposerSlot />}
43
- />
44
- );
45
- }
46
-
47
- export const Default = () => (
48
- <ChatLauncher
49
- hotkey={{ key: '/', meta: true }}
50
- fab={{ ariaLabel: 'Open chat', tooltip: 'Open chat (⌘/)' }}
51
- dock={{
52
- title: 'Assistant',
53
- height: 600,
54
- // Kebab settings menu in the dock header — language picker
55
- // persists into `useSpeechPrefs` so dictation honours the
56
- // choice across reloads. Composable: pass children to override.
57
- headerActions: <ChatHeaderLanguageButton />,
58
- }}
59
- >
60
- <DemoChat />
61
- </ChatLauncher>
62
- );
63
-
64
- export const Playground = () => {
65
- const [variant] = useSelect('variant', {
66
- options: ['simple', 'animated', 'glass'] as const,
67
- defaultValue: 'simple',
68
- label: 'FAB variant',
69
- });
70
- const [size] = useSelect('size', {
71
- options: ['sm', 'md', 'lg'] as const,
72
- defaultValue: 'md',
73
- label: 'FAB size',
74
- });
75
- const [position] = useSelect('position', {
76
- options: ['bottom-right', 'bottom-left', 'top-right', 'top-left'] as const,
77
- defaultValue: 'bottom-right',
78
- label: 'Position',
79
- });
80
- const [pulse] = useBoolean('pulse', { defaultValue: false });
81
- const [withBadge] = useBoolean('badge', { defaultValue: false });
82
- const [badgeCount] = useNumber('badge count', { defaultValue: 3, min: 1, max: 99 });
83
- const [withTooltip] = useBoolean('tooltip', { defaultValue: true });
84
- const [withGreeting] = useBoolean('greeting', { defaultValue: false });
85
-
86
- return (
87
- <ChatLauncher
88
- fab={{
89
- variant: variant as ChatFABVariant,
90
- size: size as ChatFABSize,
91
- position: position as ChatFABPosition,
92
- pulse,
93
- badge: withBadge ? badgeCount : undefined,
94
- tooltip: withTooltip ? 'Open chat (⌘/)' : undefined,
95
- icon: <MessageCircle className="h-6 w-6" />,
96
- ariaLabel: 'Open chat',
97
- }}
98
- dock={{
99
- title: 'Support',
100
- icon: <MessageCircle className="text-primary h-4 w-4" />,
101
- position: position as ChatFABPosition,
102
- height: 600,
103
- headerActions: <ChatHeaderLanguageButton />,
104
- }}
105
- greeting={
106
- withGreeting
107
- ? {
108
- content: 'Hey 👋 Got a question? Happy to help.',
109
- senderName: 'Anna · Support',
110
- avatar: (
111
- <Avatar className="h-8 w-8">
112
- <AvatarImage src="https://i.pravatar.cc/64?img=47" />
113
- <AvatarFallback>A</AvatarFallback>
114
- </Avatar>
115
- ),
116
- delayMs: 600,
117
- dismissStorageKey: null,
118
- }
119
- : undefined
120
- }
121
- hotkey={{ key: '/', meta: true }}
122
- >
123
- <DemoChat />
124
- </ChatLauncher>
125
- );
126
- };
127
-
128
- export const MobileFullscreen = () => (
129
- <ChatLauncher
130
- fab={{ icon: <MessageCircle className="h-6 w-6" />, tooltip: 'Open chat' }}
131
- dock={{ title: 'Mobile-friendly', width: 480, height: 600 }}
132
- >
133
- <DemoChat />
134
- </ChatLauncher>
135
- );
136
-
137
- /**
138
- * LiveChat / Intercom-style proactive invite: animated FAB + greeting
139
- * bubble with avatar, sender name, and a friendly nudge. The greeting
140
- * fades in after a short delay; click the bubble or the FAB to open
141
- * the chat. Composer auto-focuses on open.
142
- */
143
- export const LiveChatStyle = () => (
144
- <ChatLauncher
145
- fab={{
146
- variant: 'animated',
147
- icon: <Bot className="h-6 w-6" />,
148
- ariaLabel: 'Chat with us',
149
- tooltip: 'Chat with us',
150
- }}
151
- dock={{
152
- title: 'Anna · Sales',
153
- icon: (
154
- <Avatar className="h-5 w-5">
155
- <AvatarImage src="https://i.pravatar.cc/64?img=47" />
156
- <AvatarFallback>A</AvatarFallback>
157
- </Avatar>
158
- ),
159
- height: 620,
160
- }}
161
- greeting={{
162
- content: (
163
- <>
164
- Hi 👋 Looking for something? I&apos;m around — we usually reply{' '}
165
- <strong>within a minute</strong>.
166
- </>
167
- ),
168
- senderName: 'Anna · Sales',
169
- avatar: (
170
- <Avatar className="h-9 w-9">
171
- <AvatarImage src="https://i.pravatar.cc/64?img=47" />
172
- <AvatarFallback>A</AvatarFallback>
173
- </Avatar>
174
- ),
175
- delayMs: 1200,
176
- dismissStorageKey: null,
177
- }}
178
- hotkey={{ key: '/', meta: true }}
179
- >
180
- <DemoChat />
181
- </ChatLauncher>
182
- );
183
-
184
- // ── Live push demo ─────────────────────────────────────────────────────────
185
- //
186
- // Hosts wire push notifications via:
187
- // 1. `useChatUnread({ open })` inside the `<ChatProvider>` for state.
188
- // 2. `chat.injectMessage(...)` to feed inbound messages from Centrifugo / WS.
189
- // 3. `<ChatLauncher unreadMessage onMarkRead>` to surface the preview bubble.
190
-
191
- function PushInjector({ enabled, intervalMs }: { enabled: boolean; intervalMs: number }) {
192
- const chat = useChatContext();
193
- useEffect(() => {
194
- if (!enabled) return;
195
- let i = 0;
196
- const samples = [
197
- 'Hey 👋 just checking in — anything I can help with?',
198
- 'Heads up: your order #1042 just shipped 🚀',
199
- 'New: try the redesigned dashboard — feedback welcome.',
200
- ];
201
- const id = setInterval(() => {
202
- chat.injectMessage({
203
- id: `push-${Date.now()}-${i}`,
204
- role: 'assistant',
205
- content: samples[i % samples.length]!,
206
- createdAt: Date.now(),
207
- sender: { name: 'Anna · Sales', avatarUrl: 'https://i.pravatar.cc/64?img=47' },
208
- });
209
- i++;
210
- }, intervalMs);
211
- return () => clearInterval(id);
212
- }, [chat, enabled, intervalMs]);
213
- return null;
214
- }
215
-
216
- function InlineChatUI() {
217
- // Lightweight chat shell rendered inside an existing ChatProvider — used
218
- // so we don't double-mount provider state in the push demo.
219
- const chat = useChatContext();
220
- const composer = useChatComposer({
221
- onSubmit: (content, attachments) => chat.sendMessage(content, attachments),
222
- disabled: chat.isStreaming,
223
- });
224
- return (
225
- <div className="flex h-full flex-col">
226
- <MessageList
227
- className="flex-1"
228
- renderEmpty={() => <EmptyState greeting="Anna · Sales" />}
229
- />
230
- <Composer composer={composer} />
231
- </div>
232
- );
233
- }
234
-
235
- function LivePushShell({ intervalMs, autoPush }: { intervalMs: number; autoPush: boolean }) {
236
- const [open, setOpen] = useState(false);
237
- const { unread, markRead } = useChatUnread({ open });
238
- return (
239
- <>
240
- <PushInjector enabled={autoPush} intervalMs={intervalMs} />
241
- <ChatLauncher
242
- open={open}
243
- onOpenChange={setOpen}
244
- fab={{ ariaLabel: 'Open chat', tooltip: 'Open chat' }}
245
- dock={{ title: 'Sales · Anna', height: 580 }}
246
- unreadMessage={unread}
247
- onMarkRead={markRead}
248
- >
249
- <InlineChatUI />
250
- </ChatLauncher>
251
- </>
252
- );
253
- }
254
-
255
- /**
256
- * Simulates server-pushed messages every N seconds. While the dock is
257
- * closed, the FAB badge shows the unread indicator and a notification
258
- * bubble appears next to the FAB with sender + message preview. Open the
259
- * chat (FAB / preview / ⌘/) and unread resets to zero.
260
- */
261
- export const WithLivePush = () => {
262
- const [autoPush] = useBoolean('auto-push', { defaultValue: true });
263
- const [intervalSec] = useNumber('interval (s)', {
264
- defaultValue: 6,
265
- min: 2,
266
- max: 30,
267
- });
268
-
269
- const transport = useMemo(() => makeBasicTransport(SEED_BASIC), []);
270
-
271
- return (
272
- <ChatProvider transport={transport} config={{ greeting: 'Sales chat — push demo' }}>
273
- <LivePushShell intervalMs={intervalSec * 1000} autoPush={autoPush} />
274
- </ChatProvider>
275
- );
276
- };
277
-
278
- export const VariantsAndSizes = () => (
279
- <div className="p-6 space-y-6">
280
- <div>
281
- <p className="text-sm font-medium">Variants (inline)</p>
282
- <p className="text-muted-foreground text-xs mb-3">
283
- Inline preview only — production usage drops <code className="text-xs">inline</code> and pins to the viewport corner.
284
- </p>
285
- <div className="grid grid-cols-1 sm:grid-cols-3 gap-4">
286
- {(['simple', 'animated', 'glass'] as const).map((variant) => (
287
- <div key={variant} className="flex flex-col items-center gap-3 rounded-lg border bg-card p-6">
288
- <div className="text-xs font-medium text-muted-foreground">{variant}</div>
289
- <ChatFAB
290
- inline
291
- variant={variant}
292
- size="lg"
293
- onClick={() => {}}
294
- ariaLabel={`${variant} FAB`}
295
- tooltip={variant === 'simple' ? 'Open chat' : undefined}
296
- badge={variant === 'glass' ? 5 : undefined}
297
- pulse={variant === 'simple'}
298
- icon={
299
- variant === 'animated' ? <Zap size={26} /> :
300
- variant === 'glass' ? <Sparkles className="h-6 w-6" /> :
301
- <Bot size={26} />
302
- }
303
- />
304
- </div>
305
- ))}
306
- </div>
307
- </div>
308
-
309
- <div>
310
- <p className="text-sm font-medium">Sizes (inline)</p>
311
- <div className="mt-3 grid grid-cols-3 gap-4">
312
- {(['sm', 'md', 'lg'] as const).map((s) => (
313
- <div key={s} className="flex flex-col items-center gap-2 rounded-lg border bg-card p-4">
314
- <div className="text-xs text-muted-foreground">size={s}</div>
315
- <ChatFAB inline size={s} onClick={() => {}} ariaLabel={`${s} FAB`} />
316
- </div>
317
- ))}
318
- </div>
319
- </div>
320
- </div>
321
- );
@@ -1,147 +0,0 @@
1
- import { useMemo, useState } from 'react';
2
- import { RotateCcw, Settings } from 'lucide-react';
3
-
4
- import { defineStory } from '@djangocfg/playground';
5
- import { Button } from '@djangocfg/ui-core/components';
6
-
7
- import { ChatRoot } from '../components/ChatRoot';
8
- import { ChatDock } from '../launcher/ChatDock';
9
- import { ChatHeader } from '../launcher/ChatHeader';
10
- import { ChatHeaderActionButton } from '../launcher/ChatHeaderActionButton';
11
- import { ChatHeaderModeToggle } from '../launcher/ChatHeaderModeToggle';
12
- import { useChatDockPrefs } from '../hooks/useChatDockPrefs';
13
- import { useChatReset } from '../hooks/useChatReset';
14
- import { SEED_BASIC, makeBasicTransport } from './shared';
15
-
16
- export default defineStory({
17
- title: 'Tools/Chat/Header',
18
- component: ChatHeader,
19
- description:
20
- '`<ChatHeader>` + `headerActions` slot + helpers: `ChatHeaderActionButton`, `ChatHeaderModeToggle`. State persisted via `useChatDockPrefs` (ui-core localStorage).',
21
- });
22
-
23
- function ResetButton() {
24
- const { reset, isResetting } = useChatReset({
25
- onReset: async () => {
26
- await new Promise((r) => setTimeout(r, 800));
27
- return true;
28
- },
29
- onSuccess: () => alert('Context cleared.'),
30
- });
31
- return (
32
- <ChatHeaderActionButton
33
- icon={<RotateCcw className="h-3.5 w-3.5" />}
34
- ariaLabel="Clear conversation context"
35
- onClick={() => reset()}
36
- loading={isResetting}
37
- />
38
- );
39
- }
40
-
41
- /** Standalone `<ChatHeader>` — title + actions + close. No surrounding chrome. */
42
- export const HeaderOnly = () => (
43
- <div className="p-6">
44
- <div className="overflow-hidden rounded-lg border border-border bg-popover">
45
- <ChatHeader
46
- title="Assistant"
47
- onClose={() => alert('close')}
48
- actions={
49
- <>
50
- <ResetButton />
51
- <ChatHeaderActionButton
52
- icon={<Settings className="h-3.5 w-3.5" />}
53
- ariaLabel="Settings"
54
- onClick={() => alert('settings')}
55
- badge={2}
56
- />
57
- </>
58
- }
59
- />
60
- </div>
61
- </div>
62
- );
63
-
64
- /**
65
- * Production-like dock with persistent prefs + mode toggle.
66
- * Reload the story — `mode` and `side` survive via localStorage.
67
- */
68
- export const WithPersistentPrefs = () => {
69
- const [open, setOpen] = useState(true);
70
- const prefs = useChatDockPrefs({ storageKey: 'story.chat.dock.prefs' });
71
- const transport = useMemo(() => makeBasicTransport(SEED_BASIC), []);
72
-
73
- return (
74
- <>
75
- <div className="p-6">
76
- <h2 className="text-foreground text-lg font-semibold">Page content</h2>
77
- <p className="text-muted-foreground mt-2 max-w-md text-sm">
78
- The header toggle switches between popover and side-docked layouts.
79
- When the side dock is active in production, ChatDock auto-reserves
80
- <code className="text-xs"> padding-{prefs.side}</code> on the body so this content
81
- shifts away from the dock. Current prefs: <code className="text-xs">mode={prefs.mode}</code> ·{' '}
82
- <code className="text-xs">side={prefs.side}</code>.
83
- </p>
84
- {!open && (
85
- <Button className="mt-4" onClick={() => setOpen(true)}>
86
- Open chat
87
- </Button>
88
- )}
89
- </div>
90
- <ChatDock
91
- open={open}
92
- onClose={() => setOpen(false)}
93
- mode={prefs.mode}
94
- side={prefs.side}
95
- width={prefs.mode === 'side' ? prefs.sideWidth : 440}
96
- height={600}
97
- title="CRM Assistant"
98
- headerActions={
99
- <>
100
- <ResetButton />
101
- <ChatHeaderModeToggle mode={prefs.mode} onToggle={prefs.toggleMode} />
102
- </>
103
- }
104
- >
105
- <ChatRoot transport={transport} config={{ greeting: 'How can I help?' }} />
106
- </ChatDock>
107
- </>
108
- );
109
- };
110
-
111
- /**
112
- * `mode='side'` pinned to the viewport edge, reserves body padding so page
113
- * content stays visible. This is the production behaviour — no `inline`.
114
- */
115
- export const SideMode = () => {
116
- const [open, setOpen] = useState(true);
117
- const transport = useMemo(() => makeBasicTransport(SEED_BASIC), []);
118
-
119
- return (
120
- <>
121
- <div className="p-6">
122
- <h2 className="text-foreground text-lg font-semibold">Page content</h2>
123
- <p className="text-muted-foreground mt-2 max-w-md text-sm">
124
- The dock is pinned full-height to the right edge. The browser body
125
- gets <code className="text-xs">padding-right</code> equal to the dock width while
126
- it&apos;s open, so this column stays visible.
127
- </p>
128
- {!open && (
129
- <Button className="mt-4" onClick={() => setOpen(true)}>
130
- Open side dock
131
- </Button>
132
- )}
133
- </div>
134
- <ChatDock
135
- open={open}
136
- onClose={() => setOpen(false)}
137
- mode="side"
138
- side="right"
139
- width={400}
140
- title="Workspace Chat"
141
- headerActions={<ResetButton />}
142
- >
143
- <ChatRoot transport={transport} config={{ greeting: 'Side-docked chat.' }} />
144
- </ChatDock>
145
- </>
146
- );
147
- };
@@ -1,112 +0,0 @@
1
- import { useMemo } from 'react';
2
-
3
- import { defineStory } from '@djangocfg/playground';
4
- import { DialogProvider } from '@djangocfg/ui-core/lib';
5
-
6
- import { ChatRoot } from '../components/ChatRoot';
7
- import { useChatAudio } from '../hooks/useChatAudio';
8
- import { ChatDock } from '../launcher/ChatDock';
9
- import { ChatHeaderAudioToggle } from '../launcher/ChatHeaderAudioToggle';
10
- import { ChatHeaderResetButton } from '../launcher/ChatHeaderResetButton';
11
- import { ChatLauncher } from '../launcher/ChatLauncher';
12
- import { SEED_BASIC, makeBasicTransport } from './shared';
13
-
14
- export default defineStory({
15
- title: 'Tools/Chat/Audio & Actions',
16
- component: ChatHeaderAudioToggle,
17
- description:
18
- 'Header audio toggle (auto-injected when `audio` is passed) and header reset action with `window.dialog.confirm`. Wrap stories in `<DialogProvider>` so confirm prompts render.',
19
- });
20
-
21
- // ── Audio toggle ──────────────────────────────────────────────────────────
22
-
23
- function AudioLauncher() {
24
- const transport = useMemo(() => makeBasicTransport(SEED_BASIC), []);
25
- // A real notification sound — bundle your own assets in production.
26
- // No `sounds` prop — uses bundled DEFAULT_CHAT_SOUNDS automatically.
27
- const audio = useChatAudio();
28
-
29
- return (
30
- <ChatLauncher
31
- audio={audio}
32
- hotkey={{ key: '/', meta: true }}
33
- fab={{ ariaLabel: 'Open chat', tooltip: 'Open chat' }}
34
- dock={{ title: 'Assistant', height: 600 }}
35
- >
36
- <ChatRoot transport={transport} />
37
- </ChatLauncher>
38
- );
39
- }
40
-
41
- export const AudioToggleAutoInjected = () => <AudioLauncher />;
42
-
43
- // ── Reset with confirm ────────────────────────────────────────────────────
44
-
45
- function ResetLauncher() {
46
- const transport = useMemo(() => makeBasicTransport(SEED_BASIC), []);
47
-
48
- return (
49
- <DialogProvider>
50
- <ChatLauncher
51
- hotkey={{ key: '/', meta: true }}
52
- fab={{ ariaLabel: 'Open chat', tooltip: 'Open chat' }}
53
- dock={{
54
- title: 'CRM Assistant',
55
- height: 600,
56
- headerActions: (
57
- <ChatHeaderResetButton
58
- onReset={async () => {
59
- await new Promise((r) => setTimeout(r, 600));
60
- return true;
61
- }}
62
- onSuccess={() => alert('Context cleared.')}
63
- />
64
- ),
65
- }}
66
- >
67
- <ChatRoot transport={transport} />
68
- </ChatLauncher>
69
- </DialogProvider>
70
- );
71
- }
72
-
73
- /**
74
- * `ChatHeaderResetButton` prompts `window.dialog.confirm` (destructive
75
- * variant) before running the backend reset. `DialogProvider` from
76
- * `@djangocfg/ui-core` installs that API.
77
- */
78
- export const ResetWithConfirm = () => <ResetLauncher />;
79
-
80
- // ── Both together ────────────────────────────────────────────────────────
81
-
82
- function FullLauncher() {
83
- const transport = useMemo(() => makeBasicTransport(SEED_BASIC), []);
84
- // No `sounds` prop — uses bundled DEFAULT_CHAT_SOUNDS automatically.
85
- const audio = useChatAudio();
86
- return (
87
- <DialogProvider>
88
- <ChatLauncher
89
- audio={audio}
90
- hotkey={{ key: '/', meta: true }}
91
- fab={{ ariaLabel: 'Open chat', tooltip: 'Open chat' }}
92
- dock={{
93
- title: 'CRM Assistant',
94
- height: 600,
95
- headerActions: (
96
- <ChatHeaderResetButton
97
- onReset={async () => {
98
- await new Promise((r) => setTimeout(r, 600));
99
- return true;
100
- }}
101
- onSuccess={() => alert('Context cleared.')}
102
- />
103
- ),
104
- }}
105
- >
106
- <ChatRoot transport={transport} />
107
- </ChatLauncher>
108
- </DialogProvider>
109
- );
110
- }
111
-
112
- export const AudioPlusReset = () => <FullLauncher />;
@@ -1,21 +0,0 @@
1
- import type { ReactNode } from 'react';
2
-
3
- /** Fixed-size container so chat stories have a viewport to live in. */
4
- export function Frame({
5
- children,
6
- h = 560,
7
- w = 480,
8
- }: {
9
- children: ReactNode;
10
- h?: number | string;
11
- w?: number | string;
12
- }) {
13
- return (
14
- <div
15
- className="overflow-hidden rounded-lg border border-border bg-background shadow-sm"
16
- style={{ height: h, width: w }}
17
- >
18
- {children}
19
- </div>
20
- );
21
- }
@@ -1,5 +0,0 @@
1
- export { Frame } from './Frame';
2
- export { makeBasicTransport, makeToolCallsTransport } from './transports';
3
- export { BUBBLE_GALLERY } from './messages';
4
- export { USER_ANNA, ASSISTANT_AURA } from './personas';
5
- export { SEED_BASIC, SEED_TOOL_CALLS, SEED_JSON_PAYLOAD } from './seeds';