@djangocfg/ui-tools 2.1.385 → 2.1.389

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 (80) hide show
  1. package/README.md +25 -11
  2. package/dist/ChatRoot-EFNXQXXN.cjs +15 -0
  3. package/dist/{ChatRoot-JVR3M3H2.mjs.map → ChatRoot-EFNXQXXN.cjs.map} +1 -1
  4. package/dist/ChatRoot-FITF5RVP.mjs +6 -0
  5. package/dist/{ChatRoot-LXIUBOXF.cjs.map → ChatRoot-FITF5RVP.mjs.map} +1 -1
  6. package/dist/{DocsLayout-2P3ONDWJ.mjs → DocsLayout-EKASBSP7.mjs} +3 -3
  7. package/dist/{DocsLayout-2P3ONDWJ.mjs.map → DocsLayout-EKASBSP7.mjs.map} +1 -1
  8. package/dist/{DocsLayout-2YZNS5VK.cjs → DocsLayout-OURFYWQE.cjs} +8 -8
  9. package/dist/{DocsLayout-2YZNS5VK.cjs.map → DocsLayout-OURFYWQE.cjs.map} +1 -1
  10. package/dist/MapContainer-AKIPABJK.mjs +4 -0
  11. package/dist/MapContainer-AKIPABJK.mjs.map +1 -0
  12. package/dist/MapContainer-STVDMC36.cjs +17 -0
  13. package/dist/MapContainer-STVDMC36.cjs.map +1 -0
  14. package/dist/{chunk-HIK6BPL7.mjs → chunk-2NG4SXEP.mjs} +6 -5
  15. package/dist/chunk-2NG4SXEP.mjs.map +1 -0
  16. package/dist/chunk-4LFB7I5K.cjs +1387 -0
  17. package/dist/chunk-4LFB7I5K.cjs.map +1 -0
  18. package/dist/{MapContainer-76YL2JXL.cjs → chunk-5D2OCOPQ.cjs} +3 -2
  19. package/dist/chunk-5D2OCOPQ.cjs.map +1 -0
  20. package/dist/chunk-6ZX2G25W.mjs +1361 -0
  21. package/dist/chunk-6ZX2G25W.mjs.map +1 -0
  22. package/dist/{MapContainer-7HXBI3OH.mjs → chunk-7CWGZPO3.mjs} +3 -3
  23. package/dist/chunk-7CWGZPO3.mjs.map +1 -0
  24. package/dist/{chunk-FIRK5CEH.cjs → chunk-7IYXZUJO.cjs} +8 -4
  25. package/dist/chunk-7IYXZUJO.cjs.map +1 -0
  26. package/dist/{chunk-PEKBT75W.mjs → chunk-DMX7W4XZ.mjs} +53 -1387
  27. package/dist/chunk-DMX7W4XZ.mjs.map +1 -0
  28. package/dist/chunk-NTVBIIUD.mjs +1439 -0
  29. package/dist/chunk-NTVBIIUD.mjs.map +1 -0
  30. package/dist/{chunk-HPK3EWBF.cjs → chunk-TBSHZO5R.cjs} +50 -1409
  31. package/dist/chunk-TBSHZO5R.cjs.map +1 -0
  32. package/dist/chunk-W75B7Y6C.cjs +1478 -0
  33. package/dist/chunk-W75B7Y6C.cjs.map +1 -0
  34. package/dist/index.cjs +1269 -1790
  35. package/dist/index.cjs.map +1 -1
  36. package/dist/index.d.cts +660 -623
  37. package/dist/index.d.ts +660 -623
  38. package/dist/index.mjs +856 -1427
  39. package/dist/index.mjs.map +1 -1
  40. package/dist/launcher-5Y42OBSN.mjs +6 -0
  41. package/dist/launcher-5Y42OBSN.mjs.map +1 -0
  42. package/dist/launcher-PMW2YB24.cjs +59 -0
  43. package/dist/launcher-PMW2YB24.cjs.map +1 -0
  44. package/package.json +23 -18
  45. package/src/components/index.ts +2 -2
  46. package/src/index.ts +20 -2
  47. package/src/tools/AudioPlayer/lazy.tsx +100 -0
  48. package/src/tools/Chat/README.md +85 -1
  49. package/src/tools/Chat/components/MessageBubble.tsx +1 -1
  50. package/src/tools/Chat/context/ChatProvider.tsx +42 -0
  51. package/src/tools/Chat/index.ts +1 -1
  52. package/src/tools/Chat/lazy.tsx +300 -1
  53. package/src/tools/CodeEditor/lazy.tsx +70 -0
  54. package/src/tools/Map/lazy.tsx +38 -1
  55. package/src/tools/MarkdownEditor/lazy.tsx +42 -0
  56. package/src/{components/markdown → tools}/MarkdownMessage/CodeBlock.tsx +1 -1
  57. package/src/{components/markdown → tools}/MarkdownMessage/CollapseToggle.tsx +1 -1
  58. package/src/{components/markdown → tools}/MarkdownMessage/MarkdownMessage.tsx +1 -1
  59. package/src/{components/markdown → tools}/MarkdownMessage/components.tsx +2 -2
  60. package/src/tools/OpenapiViewer/components/DocsLayout/ApiIntroSection.tsx +1 -1
  61. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/Header/index.tsx +1 -1
  62. package/src/tools/SpeechRecognition/README.md +48 -0
  63. package/dist/ChatRoot-JVR3M3H2.mjs +0 -5
  64. package/dist/ChatRoot-LXIUBOXF.cjs +0 -14
  65. package/dist/MapContainer-76YL2JXL.cjs.map +0 -1
  66. package/dist/MapContainer-7HXBI3OH.mjs.map +0 -1
  67. package/dist/chunk-FIRK5CEH.cjs.map +0 -1
  68. package/dist/chunk-HIK6BPL7.mjs.map +0 -1
  69. package/dist/chunk-HPK3EWBF.cjs.map +0 -1
  70. package/dist/chunk-PEKBT75W.mjs.map +0 -1
  71. package/src/components/markdown/index.ts +0 -19
  72. /package/src/{components/markdown → hooks}/useCollapsibleContent.ts +0 -0
  73. /package/src/{components/markdown → tools}/MarkdownMessage/ActionRow.tsx +0 -0
  74. /package/src/{components/markdown → tools}/MarkdownMessage/ChatMessageRow.tsx +0 -0
  75. /package/src/{components/markdown → tools}/MarkdownMessage/README.md +0 -0
  76. /package/src/{components/markdown → tools}/MarkdownMessage/index.ts +0 -0
  77. /package/src/{components/markdown → tools}/MarkdownMessage/linkRules.ts +0 -0
  78. /package/src/{components/markdown → tools}/MarkdownMessage/plainText.ts +0 -0
  79. /package/src/{components/markdown → tools}/MarkdownMessage/sanitize.ts +0 -0
  80. /package/src/{components/markdown → tools}/MarkdownMessage/types.ts +0 -0
@@ -1,7 +1,36 @@
1
1
  'use client';
2
2
 
3
+ /**
4
+ * `@djangocfg/ui-tools/chat` subpath entrypoint.
5
+ *
6
+ * This file is the public surface consumers see when they
7
+ * `import … from '@djangocfg/ui-tools/chat'`. It is designed so that the
8
+ * heavy chat UI (~hundreds of KB across MessageList, Composer, ToolCalls,
9
+ * Attachments, MarkdownMessage transitively) loads only when one of the
10
+ * `Lazy*` wrappers actually mounts.
11
+ *
12
+ * Rules of thumb:
13
+ * - **Heavy** (ChatRoot, ChatLauncher, ChatFAB, ChatDock, MessageList,
14
+ * MessageBubble, Composer, ToolCalls, Attachments*, ChatHeader*, …) —
15
+ * loaded only via `Lazy*` wrappers below. Do NOT add synchronous
16
+ * re-exports for these from this file.
17
+ * - **Light** (types, config constants, pure core reducer/utils,
18
+ * transports, hooks without UI, audio prefs, draft sanitizer) —
19
+ * re-exported synchronously here. Types are erased at compile time;
20
+ * helpers and hooks pull in no UI components.
21
+ *
22
+ * Consumers that need synchronous access to the heavy components
23
+ * (custom chat layouts, Storybook stories) can import from the root
24
+ * barrel `@djangocfg/ui-tools` instead.
25
+ */
26
+
3
27
  import { createLazyComponent, LoadingFallback } from '../../components/lazy-wrapper';
4
28
  import type { ChatRootProps } from './components/ChatRoot';
29
+ import type { ChatLauncherProps } from './launcher';
30
+
31
+ // ============================================================================
32
+ // Lazy UI components
33
+ // ============================================================================
5
34
 
6
35
  export const LazyChat = createLazyComponent<ChatRootProps>(
7
36
  () => import('./components/ChatRoot').then((m) => ({ default: m.ChatRoot })),
@@ -11,4 +40,274 @@ export const LazyChat = createLazyComponent<ChatRootProps>(
11
40
  },
12
41
  );
13
42
 
14
- export type { ChatRootProps };
43
+ export const LazyChatLauncher = createLazyComponent<ChatLauncherProps>(
44
+ () => import('./launcher').then((m) => ({ default: m.ChatLauncher })),
45
+ {
46
+ displayName: 'LazyChatLauncher',
47
+ // Launcher renders a floating FAB by default — no inline placeholder.
48
+ fallback: null,
49
+ },
50
+ );
51
+
52
+ // ============================================================================
53
+ // Light surface re-exports — types + pure helpers + hooks without UI
54
+ // ============================================================================
55
+
56
+ // Types (erased at compile time, free to re-export)
57
+ export type {
58
+ ChatRole,
59
+ ChatMessage,
60
+ ChatPersona,
61
+ ChatToolCall,
62
+ ChatAttachment,
63
+ ChatSource,
64
+ ChatDisplayMode,
65
+ ChatUserContext,
66
+ ChatAssistantContext,
67
+ ChatPrefs,
68
+ ChatConfig,
69
+ ChatLabels,
70
+ ChatTransport,
71
+ ChatStreamEvent,
72
+ CreateSessionOptions,
73
+ SessionInfo,
74
+ HistoryPage,
75
+ StreamOptions,
76
+ SendOptions,
77
+ } from './types';
78
+ export { DEFAULT_LABELS } from './types';
79
+
80
+ // Config — plain constants, no UI imports
81
+ export {
82
+ STORAGE_KEYS,
83
+ CSS_VARS,
84
+ DEFAULT_Z_INDEX,
85
+ LIMITS,
86
+ DEFAULT_SIDEBAR,
87
+ HOTKEYS,
88
+ CHAT_EVENT_NAME,
89
+ type ChatEventDetail,
90
+ } from './config';
91
+
92
+ // Core — pure reducer / id / token buffer / persona / initials
93
+ export {
94
+ reducer,
95
+ initialState,
96
+ createId,
97
+ createTokenBuffer,
98
+ resolvePersona,
99
+ deriveInitials,
100
+ type ChatState,
101
+ type ChatAction,
102
+ type TokenBuffer,
103
+ } from './core';
104
+
105
+ // Transports — pure functions, no UI
106
+ export {
107
+ createHttpTransport,
108
+ createMockTransport,
109
+ parseSSE,
110
+ TransportError,
111
+ createPydanticAIChatTransport,
112
+ createToolIdQueue,
113
+ mapPydanticAIEvent,
114
+ createPydanticAISSEMap,
115
+ type HttpTransportConfig,
116
+ type MockTransportOptions,
117
+ type ParseSSEOptions,
118
+ type PydanticAIChatTransportOpts,
119
+ type PydanticAIEvent,
120
+ type ToolIdQueue,
121
+ } from './core/transport';
122
+
123
+ // Hooks — no JSX, no UI
124
+ export {
125
+ useChat,
126
+ useChatComposer,
127
+ useChatScroll,
128
+ useChatHistory,
129
+ useChatLayout,
130
+ useChatAudio,
131
+ useAutoFocusOnStreamEnd,
132
+ useRegisterComposer,
133
+ useChatReset,
134
+ useVisitorFingerprint,
135
+ useChatDockPrefs,
136
+ DEFAULT_DOCK_PREFS,
137
+ useFocusOnEmptyClick,
138
+ useChatUnread,
139
+ useChatLightbox,
140
+ type UseChatUnreadOptions,
141
+ type UseChatUnreadReturn,
142
+ type UseChatConfig,
143
+ type UseChatReturn,
144
+ type UseChatComposerOptions,
145
+ type UseChatComposerReturn,
146
+ type UseChatScrollOptions,
147
+ type UseChatScrollReturn,
148
+ type UseChatHistoryOptions,
149
+ type UseChatLayoutConfig,
150
+ type UseChatLayoutReturn,
151
+ type UseAutoFocusOnStreamEndOptions,
152
+ type Focusable,
153
+ type UseChatResetOptions,
154
+ type UseChatResetReturn,
155
+ type UseVisitorFingerprintOptions,
156
+ type ChatDockPrefs,
157
+ type UseChatDockPrefsOptions,
158
+ type UseChatDockPrefsReturn,
159
+ type UseFocusOnEmptyClickOptions,
160
+ type UseChatLightboxReturn,
161
+ type ChatLightboxState,
162
+ } from './hooks';
163
+
164
+ // Audio
165
+ export {
166
+ useChatAudioPrefs,
167
+ DEFAULT_CHAT_SOUNDS,
168
+ type ChatAudioEvent,
169
+ type ChatAudioSounds,
170
+ type ChatAudioConfig,
171
+ type UseChatAudioReturn,
172
+ } from './core/audio';
173
+
174
+ // Tool-call payload dispatcher — pure
175
+ export {
176
+ dispatchToolPayload,
177
+ isPlainObject,
178
+ isLatLng,
179
+ isGeoJSONFeatureCollection,
180
+ isStringValue,
181
+ type ToolPayloadMatcher,
182
+ type ToolPayloadFallback,
183
+ } from './core/payload-dispatch';
184
+
185
+ // Logger — pure
186
+ export {
187
+ getChatLogger,
188
+ type ChatLogger,
189
+ type ChatLogScope,
190
+ } from './core/logger';
191
+
192
+ // Utils — pure
193
+ export { sanitizeDraft, isSubmittableDraft } from './utils/sanitizeDraft';
194
+ export { collectImageAttachments } from './utils/collectImageAttachments';
195
+
196
+ // Style tokens — strings + hooks, no UI components
197
+ export {
198
+ BUBBLE_SURFACE,
199
+ ANCHOR,
200
+ TOGGLE,
201
+ DESTRUCTIVE_SURFACE,
202
+ TOOL_CALL,
203
+ useChatBubbleStyles,
204
+ useChatRoleStyles,
205
+ useChatDestructiveStyles,
206
+ type ChatBubbleSurface,
207
+ type ChatBubbleStyles,
208
+ type ChatRoleStyles,
209
+ type ChatDestructiveStyles,
210
+ } from './styles';
211
+
212
+ // Provider / context — needed by callers wiring custom chat shells
213
+ export {
214
+ ChatProvider,
215
+ useChatContext,
216
+ useChatContextOptional,
217
+ type ChatContextValue,
218
+ type ChatProviderProps,
219
+ } from './context';
220
+
221
+ // ============================================================================
222
+ // Composable surface — synchronously re-exported components and their prop
223
+ // types. These are needed by consumers that build custom chat shells
224
+ // (e.g. `@djangocfg/crm`'s AdminChat / PublicChat) rather than rendering
225
+ // `LazyChat` directly.
226
+ //
227
+ // They DO transitively pull in MarkdownMessage / Sources / StreamingIndicator
228
+ // when actually rendered. Importing the types alone is free. Importing the
229
+ // component costs the dependency graph — that's the price of a custom shell.
230
+ //
231
+ // Apps that want maximum laziness should use `LazyChat` / `LazyChatLauncher`
232
+ // above and skip these.
233
+ // ============================================================================
234
+
235
+ export {
236
+ ChatLauncher,
237
+ ChatFAB,
238
+ ChatDock,
239
+ ChatHeader,
240
+ ChatHeaderActionButton,
241
+ ChatHeaderAudioToggle,
242
+ ChatHeaderLanguageButton,
243
+ ChatHeaderModeToggle,
244
+ ChatHeaderResetButton,
245
+ ChatGreeting,
246
+ ChatUnreadPreview,
247
+ } from './launcher';
248
+
249
+ export {
250
+ ChatRoot,
251
+ MessageList,
252
+ MessageBubble,
253
+ MessageActions,
254
+ Composer,
255
+ Sources,
256
+ ToolCalls,
257
+ Attachments,
258
+ AttachmentsGrid,
259
+ AttachmentsList,
260
+ EmptyState,
261
+ ErrorBanner,
262
+ JumpToLatest,
263
+ StreamingIndicator,
264
+ AudioToggle,
265
+ } from './components';
266
+
267
+ // Heavy-component prop types
268
+ export type {
269
+ ChatLauncherProps,
270
+ ChatLauncherHotkey,
271
+ ChatLauncherGreeting,
272
+ ChatFABProps,
273
+ ChatFABPosition,
274
+ ChatFABVariant,
275
+ ChatFABSize,
276
+ ChatDockProps,
277
+ ChatDockMode,
278
+ ChatDockSide,
279
+ ChatHeaderProps,
280
+ ChatHeaderActionButtonProps,
281
+ ChatHeaderModeToggleProps,
282
+ ChatHeaderAudioToggleProps,
283
+ ChatHeaderResetButtonProps,
284
+ ChatHeaderLanguageButtonProps,
285
+ ChatGreetingProps,
286
+ ChatUnreadPreviewProps,
287
+ ChatPresencePhase,
288
+ } from './launcher';
289
+
290
+ export { useChatPresence } from './launcher';
291
+
292
+ export type {
293
+ ChatRootProps,
294
+ MessageListProps,
295
+ MessageListHandle,
296
+ MessageBubbleProps,
297
+ MessageActionsProps,
298
+ ComposerProps,
299
+ SourcesProps,
300
+ ToolCallsProps,
301
+ ToolPayloadKind,
302
+ AttachmentsProps,
303
+ AttachmentsGridProps,
304
+ AttachmentsListProps,
305
+ AttachmentRenderer,
306
+ AttachmentRendererArgs,
307
+ AttachmentRendererMap,
308
+ EmptyStateProps,
309
+ ErrorBannerProps,
310
+ JumpToLatestProps,
311
+ StreamingIndicatorProps,
312
+ AudioToggleProps,
313
+ } from './components';
@@ -0,0 +1,70 @@
1
+ 'use client';
2
+
3
+ /**
4
+ * `@djangocfg/ui-tools/code-editor` subpath entrypoint.
5
+ *
6
+ * Monaco itself weighs ~550 KB minified — we must never pay that cost
7
+ * unless the editor actually mounts. The `Lazy*` wrappers here dynamically
8
+ * import the `Editor` and `DiffEditor` components, which transitively pull
9
+ * in `monaco-editor` and the worker setup.
10
+ *
11
+ * The rest of the surface (types, EditorProvider, useMonaco hook, helpers)
12
+ * is light:
13
+ * - `monaco-editor` is referenced only as a TS type (`type *`) — erased
14
+ * at compile time.
15
+ * - `useMonaco` performs a dynamic `import('monaco-editor')` itself, so
16
+ * importing the hook is free until the caller actually invokes it.
17
+ *
18
+ * That means consumers can wire up keyboard shortcuts, theme toggles, or
19
+ * file-state hooks at the top of their tree without paying Monaco's cost,
20
+ * and only render `<LazyEditor>` lower in the tree where it's needed.
21
+ */
22
+
23
+ import { createLazyComponent, LoadingFallback } from '../../components';
24
+ import type { EditorProps, DiffEditorProps } from './types';
25
+
26
+ // ============================================================================
27
+ // Lazy components
28
+ // ============================================================================
29
+
30
+ export const LazyEditor = createLazyComponent<EditorProps>(
31
+ () => import('./components/Editor').then((m) => ({ default: m.Editor })),
32
+ {
33
+ displayName: 'LazyEditor',
34
+ fallback: <LoadingFallback minHeight={320} text="Loading editor…" />,
35
+ },
36
+ );
37
+
38
+ export const LazyDiffEditor = createLazyComponent<DiffEditorProps>(
39
+ () => import('./components/DiffEditor').then((m) => ({ default: m.DiffEditor })),
40
+ {
41
+ displayName: 'LazyDiffEditor',
42
+ fallback: <LoadingFallback minHeight={320} text="Loading diff editor…" />,
43
+ },
44
+ );
45
+
46
+ // ============================================================================
47
+ // Light surface
48
+ // ============================================================================
49
+
50
+ // Hooks — `useMonaco` does its own dynamic import; the others are pure.
51
+ export {
52
+ useMonaco,
53
+ useEditor,
54
+ useLanguage,
55
+ useEditorTheme,
56
+ } from './hooks';
57
+
58
+ // Provider + context hook
59
+ export { EditorProvider, useEditorContext } from './context';
60
+
61
+ // All types
62
+ export type {
63
+ EditorFile,
64
+ EditorOptions,
65
+ EditorProps,
66
+ EditorContextValue,
67
+ UseEditorReturn,
68
+ UseMonacoReturn,
69
+ DiffEditorProps,
70
+ } from './types';
@@ -42,11 +42,48 @@ export const LazyMapView = createLazyComponent(
42
42
  );
43
43
 
44
44
  // ============================================================================
45
- // Re-export types for convenience
45
+ // Light primitives direct re-exports
46
+ //
47
+ // MapMarker / MapPopup / MapCluster / MapSource / MapLayer / MapControls
48
+ // etc. are thin wrappers around `react-map-gl/maplibre`. They don't import
49
+ // `maplibre-gl` at module scope (only types, which are erased), so exporting
50
+ // them synchronously here costs ~tens of KB at most — not the ~800KB of
51
+ // MapLibre GL itself.
52
+ //
53
+ // The heavy library only loads when `LazyMapContainer` actually mounts,
54
+ // because `MapContainer.tsx` (the only module that imports `maplibre-gl`
55
+ // at runtime) is reached exclusively via the dynamic import above.
56
+ //
57
+ // This means consumers can write:
58
+ //
59
+ // import { LazyMapContainer, MapMarker, MapPopup } from '@djangocfg/ui-tools/map'
60
+ //
61
+ // …and still get correct code-splitting.
46
62
  // ============================================================================
47
63
 
64
+ export {
65
+ MapMarker,
66
+ MapPopup,
67
+ MapCluster,
68
+ MapSource,
69
+ MapLayer,
70
+ MapControls,
71
+ CustomOverlay,
72
+ MapLegend,
73
+ LayerSwitcher,
74
+ } from './components';
75
+
76
+ export { MapProvider, useMapContext, MapContext } from './context';
77
+ export type { MapProviderProps } from './context';
78
+
48
79
  export type {
49
80
  MapContainerProps,
81
+ MapMarkerProps,
82
+ MapPopupProps,
83
+ MapClusterProps,
84
+ MapSourceProps,
85
+ MapLayerProps,
86
+ MapControlsProps,
50
87
  } from './components';
51
88
 
52
89
  export type {
@@ -0,0 +1,42 @@
1
+ 'use client';
2
+
3
+ /**
4
+ * `@djangocfg/ui-tools/markdown-editor` subpath entrypoint.
5
+ *
6
+ * `MarkdownEditor` is a TipTap WYSIWYG with the full ProseMirror + TipTap
7
+ * extension stack — `@tiptap/react`, `starter-kit`, `markdown`, `mention`,
8
+ * `placeholder`, plus our `createMentionSuggestion` (floating-ui anchored
9
+ * dropdown). Together that's ~200 KB minified — wrap it in React.lazy so
10
+ * pages that don't render an editor don't pay.
11
+ *
12
+ * Light surface kept here:
13
+ * - All public types (erased at compile time).
14
+ * - `mentionPresets` — pure data describing how to render mentions to
15
+ * markdown. No TipTap imports at module scope.
16
+ */
17
+
18
+ import { createLazyComponent, LoadingFallback } from '../../components';
19
+ import type { MarkdownEditorProps } from './MarkdownEditor';
20
+
21
+ export const LazyMarkdownEditor = createLazyComponent<MarkdownEditorProps>(
22
+ () => import('./MarkdownEditor').then((m) => ({ default: m.MarkdownEditor })),
23
+ {
24
+ displayName: 'LazyMarkdownEditor',
25
+ fallback: <LoadingFallback minHeight={140} text="Loading editor…" />,
26
+ },
27
+ );
28
+
29
+ // Light surface — pure helpers + types.
30
+ export { mentionPresets } from './mentionPresets';
31
+
32
+ export type {
33
+ MarkdownEditorProps,
34
+ MarkdownEditorHandle,
35
+ } from './MarkdownEditor';
36
+
37
+ export type {
38
+ MentionItem,
39
+ MentionConfig,
40
+ MentionAttrs,
41
+ MentionMarkdownRenderer,
42
+ } from './types';
@@ -1,7 +1,7 @@
1
1
  import React, { memo } from 'react';
2
2
  import { CopyButton } from '@djangocfg/ui-core/components';
3
3
  import { useResolvedTheme } from '@djangocfg/ui-core/hooks';
4
- import PrettyCode from '../../../tools/PrettyCode';
4
+ import PrettyCode from '../PrettyCode';
5
5
 
6
6
  interface CodeBlockProps {
7
7
  code: string;
@@ -1,6 +1,6 @@
1
1
  import React, { memo } from 'react';
2
2
 
3
- import { TOGGLE } from '../../../tools/Chat/styles/bubbleTokens';
3
+ import { TOGGLE } from '../Chat/styles/bubbleTokens';
4
4
 
5
5
  interface CollapseToggleProps {
6
6
  isCollapsed: boolean;
@@ -11,7 +11,7 @@ import remarkGfm from 'remark-gfm';
11
11
  import remarkSmartypants from 'remark-smartypants';
12
12
  import type { Components } from 'react-markdown';
13
13
 
14
- import { useCollapsibleContent } from '../useCollapsibleContent';
14
+ import { useCollapsibleContent } from '../../hooks/useCollapsibleContent';
15
15
  import type { MarkdownMessageProps } from './types';
16
16
  import { buildSchema, buildUrlTransform } from './sanitize';
17
17
  import { looksLikePlainProse } from './plainText';
@@ -1,7 +1,7 @@
1
1
  import React from 'react';
2
2
  import type { Components } from 'react-markdown';
3
- import Mermaid from '../../../tools/Mermaid';
4
- import { ANCHOR } from '../../../tools/Chat/styles/bubbleTokens';
3
+ import Mermaid from '../Mermaid';
4
+ import { ANCHOR } from '../Chat/styles/bubbleTokens';
5
5
  import { CodeBlock, CodeBlockFallback } from './CodeBlock';
6
6
  import { extractTextFromChildren } from './plainText';
7
7
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  import React from 'react';
4
4
 
5
- import { MarkdownMessage } from '../../../../components/markdown';
5
+ import { MarkdownMessage } from '../../../MarkdownMessage';
6
6
  import type { ApiEndpoint, OpenApiInfo, OpenApiSchema } from '../../types';
7
7
  import { SchemaCopyMenu } from './SchemaCopyMenu';
8
8
 
@@ -5,7 +5,7 @@ import React, { useCallback, useMemo, useState } from 'react';
5
5
 
6
6
  import { Button } from '@djangocfg/ui-core/components';
7
7
 
8
- import { MarkdownMessage } from '../../../../../../components/markdown';
8
+ import { MarkdownMessage } from '../../../../../MarkdownMessage';
9
9
  import type { ApiEndpoint } from '../../../../types';
10
10
  import { endpointToMarkdown } from '../../../../utils/schemaExport';
11
11
  import type { SectionId } from '../types';
@@ -278,6 +278,54 @@ const unsubscribe = useSpeechPrefs.subscribe((state) => {
278
278
 
279
279
  ---
280
280
 
281
+ ## Debug logger
282
+
283
+ Scoped, namespaced [consola](https://github.com/unjs/consola) wrapper that silences itself in production by default. Mirrors `getChatLogger()` in the Chat tool so both surfaces feel the same in DevTools.
284
+
285
+ ```ts
286
+ import { getSpeechLogger } from '@djangocfg/ui-tools';
287
+
288
+ const log = getSpeechLogger();
289
+ log.dictation.info('final merged', { len: 42 });
290
+ log.engine.debug('state', 'listening');
291
+ log.error.error('engine threw', err);
292
+ ```
293
+
294
+ Sub-loggers: `engine`, `dictation`, `slot`, `composer`, `mic`, `push`, `error`. `error` always emits; everything else is gated.
295
+
296
+ **Opt-in (any one is enough):**
297
+
298
+ 1. **Dev mode** — `NODE_ENV === 'development'` auto-enables everything.
299
+ 2. **Runtime toggle** — paste this in DevTools to enable without a rebuild:
300
+ ```js
301
+ localStorage.setItem('djangocfg:speech-debug', '1');
302
+ location.reload();
303
+ ```
304
+ `'0'` (or `removeItem`) turns it back off.
305
+ 3. **Explicit** — `getSpeechLogger(true)` from a host component (analogous to `<ChatRoot debug />`).
306
+
307
+ **What you'll see when on**, in order of a typical dictation session:
308
+
309
+ ```
310
+ [speech][slot] mount { supported: true, hasComposerHandle: true, … }
311
+ [speech][engine] subscribe { engineId: 'webspeech' }
312
+ [speech][engine] state 'listening'
313
+ [speech][engine] partial { len: 6, segmentId: 's1' }
314
+ [speech][composer] setValue → composer handle { len: 12 }
315
+ [speech][engine] final { len: 42, confidence: 0.91 }
316
+ [speech][dictation] final merged { len: 42, totalLen: 54 }
317
+ [speech][engine] autoStop silence detected
318
+ [speech][engine] state 'closed'
319
+ ```
320
+
321
+ If text never appears in your composer, look for:
322
+
323
+ - `[speech][slot] mount { hasComposerHandle: false, … }` → `<VoiceComposerSlot>` is outside a `<ChatProvider>` and no `value`/`onChange` props were given — text is going nowhere.
324
+ - `[speech][composer] warn setValue called but no composer handle is registered …` → the composer never called `useRegisterComposer(...)`. Built-in `<Composer>` and `MarkdownEditor` do this automatically; custom composers must opt in.
325
+ - `[speech][engine] final` arrives but no `[speech][dictation] final merged` follows → check `normaliseFinal` filtered the text (empty / whitespace only).
326
+
327
+ ---
328
+
281
329
  ## Public surface
282
330
 
283
331
  ### Hooks
@@ -1,5 +0,0 @@
1
- export { ChatRoot } from './chunk-PEKBT75W.mjs';
2
- import './chunk-HIK6BPL7.mjs';
3
- import './chunk-N2XQF2OL.mjs';
4
- //# sourceMappingURL=ChatRoot-JVR3M3H2.mjs.map
5
- //# sourceMappingURL=ChatRoot-JVR3M3H2.mjs.map
@@ -1,14 +0,0 @@
1
- 'use strict';
2
-
3
- var chunkHPK3EWBF_cjs = require('./chunk-HPK3EWBF.cjs');
4
- require('./chunk-FIRK5CEH.cjs');
5
- require('./chunk-OLISEQHS.cjs');
6
-
7
-
8
-
9
- Object.defineProperty(exports, "ChatRoot", {
10
- enumerable: true,
11
- get: function () { return chunkHPK3EWBF_cjs.ChatRoot; }
12
- });
13
- //# sourceMappingURL=ChatRoot-LXIUBOXF.cjs.map
14
- //# sourceMappingURL=ChatRoot-LXIUBOXF.cjs.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/tools/Map/context/MapContext.tsx","../src/tools/Map/styles/index.ts","../src/tools/Map/components/MapContainer.tsx"],"names":["createContext","useRef","useState","useCallback","useMemo","jsx","__name","useContext","useEffect","jsxs","Fragment","Map","cn","RotateCcw","ExternalLink"],"mappings":"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","file":"MapContainer-76YL2JXL.cjs","sourcesContent":["'use client'\n\nimport {\n createContext,\n useContext,\n useState,\n useRef,\n useMemo,\n useCallback,\n type ReactNode,\n} from 'react'\nimport type { MapRef } from 'react-map-gl/maplibre'\nimport type { MapContextValue, MapViewport, MarkerData } from '../types'\n\nconst MapContext = createContext<MapContextValue | null>(null)\n\nexport interface MapProviderProps {\n children: ReactNode\n initialViewport?: Partial<MapViewport>\n}\n\nconst DEFAULT_VIEWPORT: MapViewport = {\n longitude: 115.1889,\n latitude: -8.4095,\n zoom: 10,\n bearing: 0,\n pitch: 0,\n}\n\nexport function MapProvider({ children, initialViewport }: MapProviderProps) {\n const mapRef = useRef<MapRef | null>(null)\n const initialViewportRef = useRef<MapViewport>({\n ...DEFAULT_VIEWPORT,\n ...initialViewport,\n })\n const [viewport, setViewportState] = useState<MapViewport>(initialViewportRef.current)\n const [markers, setMarkers] = useState<MarkerData[]>([])\n const [selectedMarker, setSelectedMarker] = useState<MarkerData | null>(null)\n const [hoveredFeature, setHoveredFeature] = useState<GeoJSON.Feature | null>(null)\n const [isLoaded, setIsLoaded] = useState(false)\n\n const setViewport = useCallback((newViewport: Partial<MapViewport>) => {\n setViewportState((prev) => ({ ...prev, ...newViewport }))\n }, [])\n\n const resetToInitial = useCallback(() => {\n const map = mapRef.current\n if (map) {\n map.flyTo({\n center: [initialViewportRef.current.longitude, initialViewportRef.current.latitude],\n zoom: initialViewportRef.current.zoom,\n bearing: initialViewportRef.current.bearing ?? 0,\n pitch: initialViewportRef.current.pitch ?? 0,\n duration: 1000,\n })\n }\n }, [])\n\n const value = useMemo<MapContextValue>(\n () => ({\n mapRef,\n viewport,\n setViewport,\n initialViewport: initialViewportRef.current,\n resetToInitial,\n markers,\n setMarkers,\n selectedMarker,\n setSelectedMarker,\n hoveredFeature,\n setHoveredFeature,\n isLoaded,\n setIsLoaded,\n }),\n [viewport, setViewport, resetToInitial, markers, selectedMarker, hoveredFeature, isLoaded]\n )\n\n return <MapContext.Provider value={value}>{children}</MapContext.Provider>\n}\n\nexport function useMapContext(): MapContextValue {\n const context = useContext(MapContext)\n if (!context) {\n throw new Error('useMapContext must be used within a MapProvider')\n }\n return context\n}\n\nexport { MapContext }\n","export const MAP_STYLES = {\n light: 'https://basemaps.cartocdn.com/gl/positron-gl-style/style.json',\n dark: 'https://basemaps.cartocdn.com/gl/dark-matter-gl-style/style.json',\n streets: 'https://basemaps.cartocdn.com/gl/voyager-gl-style/style.json',\n satellite: 'https://api.maptiler.com/maps/satellite/style.json',\n} as const\n\nexport type MapStyleKey = keyof typeof MAP_STYLES\n\nexport function getMapStyle(key: MapStyleKey | string): string {\n if (key in MAP_STYLES) {\n return MAP_STYLES[key as MapStyleKey]\n }\n return key\n}\n","'use client'\n\nimport { useCallback, useEffect, useRef, type ReactNode } from 'react'\nimport Map, { type ViewStateChangeEvent } from 'react-map-gl/maplibre'\nimport { ExternalLink, RotateCcw } from 'lucide-react'\nimport { cn } from '@djangocfg/ui-core/lib'\nimport 'maplibre-gl/dist/maplibre-gl.css'\n\nimport { MapProvider, useMapContext } from '../context'\nimport { MAP_STYLES } from '../styles'\nimport type { MapViewport, MapStyleKey } from '../types'\n\nexport interface MapContainerProps {\n children?: ReactNode\n initialViewport?: Partial<MapViewport>\n mapStyle?: MapStyleKey | string\n interactiveLayerIds?: string[]\n className?: string\n style?: React.CSSProperties\n cursor?: string\n attributionControl?: boolean\n reuseMaps?: boolean\n /** URL to open in external maps app (shows \"Open in Maps\" button if provided) */\n openInMapsUrl?: string\n /** Label for the open in maps button */\n openInMapsLabel?: string\n /** Auto-reset to initial viewport after N ms of inactivity (0 = disabled) */\n autoResetDelay?: number\n /** Show reset button */\n showResetButton?: boolean\n}\n\ninterface MapInnerProps extends Omit<MapContainerProps, 'initialViewport'> {}\n\nfunction MapInner({\n children,\n mapStyle = 'light',\n interactiveLayerIds,\n style,\n cursor,\n attributionControl = true,\n reuseMaps = true,\n openInMapsUrl,\n openInMapsLabel = 'Open in Maps',\n autoResetDelay = 0,\n showResetButton = false,\n}: MapInnerProps) {\n const { mapRef, viewport, setViewport, setIsLoaded, resetToInitial, initialViewport } = useMapContext()\n const resetTimerRef = useRef<NodeJS.Timeout | null>(null)\n const isInteractingRef = useRef(false)\n\n // Check if viewport has changed from initial\n const hasViewportChanged =\n Math.abs(viewport.longitude - initialViewport.longitude) > 0.0001 ||\n Math.abs(viewport.latitude - initialViewport.latitude) > 0.0001 ||\n Math.abs(viewport.zoom - initialViewport.zoom) > 0.1\n\n const handleMove = useCallback(\n (evt: ViewStateChangeEvent) => {\n setViewport({\n longitude: evt.viewState.longitude,\n latitude: evt.viewState.latitude,\n zoom: evt.viewState.zoom,\n bearing: evt.viewState.bearing,\n pitch: evt.viewState.pitch,\n })\n },\n [setViewport]\n )\n\n const handleMoveStart = useCallback(() => {\n isInteractingRef.current = true\n // Clear any pending reset timer\n if (resetTimerRef.current) {\n clearTimeout(resetTimerRef.current)\n resetTimerRef.current = null\n }\n }, [])\n\n const handleMoveEnd = useCallback(() => {\n isInteractingRef.current = false\n // Start auto-reset timer if enabled\n if (autoResetDelay > 0) {\n resetTimerRef.current = setTimeout(() => {\n if (!isInteractingRef.current) {\n resetToInitial()\n }\n }, autoResetDelay)\n }\n }, [autoResetDelay, resetToInitial])\n\n const handleLoad = useCallback(() => {\n setIsLoaded(true)\n }, [setIsLoaded])\n\n // Cleanup timer on unmount\n useEffect(() => {\n return () => {\n if (resetTimerRef.current) {\n clearTimeout(resetTimerRef.current)\n }\n }\n }, [])\n\n const resolvedStyle = mapStyle in MAP_STYLES\n ? MAP_STYLES[mapStyle as MapStyleKey]\n : mapStyle\n\n return (\n <>\n <Map\n ref={mapRef}\n {...viewport}\n onMove={handleMove}\n onMoveStart={handleMoveStart}\n onMoveEnd={handleMoveEnd}\n onLoad={handleLoad}\n mapStyle={resolvedStyle}\n interactiveLayerIds={interactiveLayerIds}\n attributionControl={attributionControl ? {} : false}\n reuseMaps={reuseMaps}\n style={{\n width: '100%',\n height: '100%',\n ...style,\n }}\n cursor={cursor}\n >\n {children}\n </Map>\n\n {/* Map overlay buttons */}\n <div className=\"absolute bottom-3 right-3 flex items-center gap-2\">\n {/* Reset button */}\n {showResetButton && hasViewportChanged && (\n <button\n type=\"button\"\n onClick={resetToInitial}\n className={cn(\n 'inline-flex items-center gap-2 px-3 py-2 rounded-lg',\n 'bg-background/95 backdrop-blur-sm text-foreground text-sm font-medium',\n 'hover:bg-background transition-colors shadow-sm border border-border'\n )}\n >\n <RotateCcw className=\"w-4 h-4\" />\n Reset\n </button>\n )}\n\n {/* Open in Maps button */}\n {openInMapsUrl && (\n <a\n href={openInMapsUrl}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className={cn(\n 'inline-flex items-center gap-2 px-4 py-2 rounded-lg',\n 'bg-background/95 backdrop-blur-sm text-foreground text-sm font-medium',\n 'hover:bg-background transition-colors shadow-sm border border-border'\n )}\n >\n <ExternalLink className=\"w-4 h-4\" />\n {openInMapsLabel}\n </a>\n )}\n </div>\n </>\n )\n}\n\nexport function MapContainer({\n children,\n initialViewport,\n className,\n ...props\n}: MapContainerProps) {\n return (\n <div className={className} style={{ width: '100%', height: '100%', position: 'relative' }}>\n <MapProvider initialViewport={initialViewport}>\n <MapInner {...props}>{children}</MapInner>\n </MapProvider>\n </div>\n )\n}\n\n/**\n * Use this when you need the map inside an existing MapProvider\n */\nexport function MapView(props: Omit<MapContainerProps, 'initialViewport'>) {\n return <MapInner {...props} />\n}\n"]}