@djangocfg/ui-tools 2.1.381 → 2.1.382

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 (183) hide show
  1. package/README.md +132 -899
  2. package/dist/ChatRoot-6IZFM5HM.mjs +5 -0
  3. package/dist/{ChatRoot-EJC5Y2YM.cjs.map → ChatRoot-6IZFM5HM.mjs.map} +1 -1
  4. package/dist/ChatRoot-LW4XNIKP.cjs +14 -0
  5. package/dist/{ChatRoot-QOSKJPM6.mjs.map → ChatRoot-LW4XNIKP.cjs.map} +1 -1
  6. package/dist/DictationField-2ZLQWLYV.mjs +4 -0
  7. package/dist/DictationField-2ZLQWLYV.mjs.map +1 -0
  8. package/dist/DictationField-IPPJ54CU.cjs +13 -0
  9. package/dist/DictationField-IPPJ54CU.cjs.map +1 -0
  10. package/dist/{DocsLayout-2YKPXZYO.mjs → DocsLayout-2P3ONDWJ.mjs} +3 -3
  11. package/dist/{DocsLayout-2YKPXZYO.mjs.map → DocsLayout-2P3ONDWJ.mjs.map} +1 -1
  12. package/dist/{DocsLayout-Q4KS3QWW.cjs → DocsLayout-2YZNS5VK.cjs} +8 -8
  13. package/dist/{DocsLayout-Q4KS3QWW.cjs.map → DocsLayout-2YZNS5VK.cjs.map} +1 -1
  14. package/dist/chunk-4LXG3NBV.mjs +833 -0
  15. package/dist/chunk-4LXG3NBV.mjs.map +1 -0
  16. package/dist/{chunk-XACCHZH2.cjs → chunk-FIRK5CEH.cjs} +42 -4
  17. package/dist/chunk-FIRK5CEH.cjs.map +1 -0
  18. package/dist/{chunk-NWUT327A.mjs → chunk-HIK6BPL7.mjs} +38 -5
  19. package/dist/chunk-HIK6BPL7.mjs.map +1 -0
  20. package/dist/chunk-KMSBGNVC.cjs +835 -0
  21. package/dist/chunk-KMSBGNVC.cjs.map +1 -0
  22. package/dist/chunk-OZAU3QWD.cjs +2493 -0
  23. package/dist/chunk-OZAU3QWD.cjs.map +1 -0
  24. package/dist/chunk-UWVP6LCW.mjs +2447 -0
  25. package/dist/chunk-UWVP6LCW.mjs.map +1 -0
  26. package/dist/index.cjs +1532 -100
  27. package/dist/index.cjs.map +1 -1
  28. package/dist/index.d.cts +1148 -107
  29. package/dist/index.d.ts +1148 -107
  30. package/dist/index.mjs +1421 -51
  31. package/dist/index.mjs.map +1 -1
  32. package/package.json +16 -8
  33. package/src/audio-assets.d.ts +8 -0
  34. package/src/components/markdown/MarkdownMessage/CollapseToggle.tsx +3 -1
  35. package/src/components/markdown/MarkdownMessage/components.tsx +2 -5
  36. package/src/stories/index.ts +32 -2
  37. package/src/tools/Chat/README.md +347 -530
  38. package/src/tools/Chat/components/Attachments.tsx +6 -1
  39. package/src/tools/Chat/components/ChatRoot.tsx +30 -2
  40. package/src/tools/Chat/components/Composer.tsx +20 -3
  41. package/src/tools/Chat/components/ErrorBanner.tsx +7 -3
  42. package/src/tools/Chat/components/MessageActions.tsx +3 -1
  43. package/src/tools/Chat/components/MessageBubble.tsx +6 -5
  44. package/src/tools/Chat/components/MessageList.tsx +87 -1
  45. package/src/tools/Chat/components/ToolCalls.tsx +21 -3
  46. package/src/tools/Chat/context/ChatProvider.tsx +21 -3
  47. package/src/tools/Chat/core/audio/audioBus.ts +10 -163
  48. package/src/tools/Chat/core/audio/defaults.ts +43 -0
  49. package/src/tools/Chat/core/audio/index.ts +1 -0
  50. package/src/tools/Chat/core/audio/preferences.ts +5 -59
  51. package/src/tools/Chat/core/audio/sounds/error.mp3 +0 -0
  52. package/src/tools/Chat/core/audio/sounds/mention.mp3 +0 -0
  53. package/src/tools/Chat/core/audio/sounds/notification.mp3 +0 -0
  54. package/src/tools/Chat/core/audio/sounds/received.mp3 +0 -0
  55. package/src/tools/Chat/core/audio/sounds/sent.mp3 +0 -0
  56. package/src/tools/Chat/core/audio/sounds/start.mp3 +0 -0
  57. package/src/tools/Chat/core/audio/types.ts +28 -0
  58. package/src/tools/Chat/core/reducer.ts +33 -0
  59. package/src/tools/Chat/core/transport/index.ts +13 -0
  60. package/src/tools/Chat/core/transport/mappers/index.ts +6 -0
  61. package/src/tools/Chat/core/transport/mappers/pydantic-ai.ts +142 -0
  62. package/src/tools/Chat/core/transport/pydantic-ai-transport.ts +208 -0
  63. package/src/tools/Chat/core/transport/sse.ts +18 -5
  64. package/src/tools/Chat/hooks/index.ts +25 -0
  65. package/src/tools/Chat/hooks/useAutoFocusOnStreamEnd.ts +5 -3
  66. package/src/tools/Chat/hooks/useChat.ts +28 -0
  67. package/src/tools/Chat/hooks/useChatAudio.ts +59 -180
  68. package/src/tools/Chat/hooks/useChatDockPrefs.ts +74 -0
  69. package/src/tools/Chat/hooks/useChatReset.ts +70 -0
  70. package/src/tools/Chat/hooks/useChatUnread.ts +87 -0
  71. package/src/tools/Chat/hooks/useFocusOnEmptyClick.ts +111 -0
  72. package/src/tools/Chat/hooks/useVisitorFingerprint.ts +48 -0
  73. package/src/tools/Chat/index.ts +69 -1
  74. package/src/tools/Chat/launcher/ChatDock.tsx +263 -0
  75. package/src/tools/Chat/launcher/ChatFAB.tsx +349 -0
  76. package/src/tools/Chat/launcher/ChatGreeting.tsx +200 -0
  77. package/src/tools/Chat/launcher/ChatHeader.tsx +76 -0
  78. package/src/tools/Chat/launcher/ChatHeaderActionButton.tsx +87 -0
  79. package/src/tools/Chat/launcher/ChatHeaderAudioToggle.tsx +47 -0
  80. package/src/tools/Chat/launcher/ChatHeaderLanguageButton.tsx +179 -0
  81. package/src/tools/Chat/launcher/ChatHeaderModeToggle.tsx +57 -0
  82. package/src/tools/Chat/launcher/ChatHeaderResetButton.tsx +93 -0
  83. package/src/tools/Chat/launcher/ChatLauncher.tsx +321 -0
  84. package/src/tools/Chat/launcher/ChatUnreadPreview.tsx +197 -0
  85. package/src/tools/Chat/launcher/index.ts +46 -0
  86. package/src/tools/Chat/launcher/useChatPresence.ts +44 -0
  87. package/src/tools/Chat/stories/01-basic.story.tsx +64 -0
  88. package/src/tools/Chat/stories/02-bubbles.story.tsx +21 -0
  89. package/src/tools/Chat/stories/03-tool-calls.story.tsx +59 -0
  90. package/src/tools/Chat/stories/04-personas.story.tsx +78 -0
  91. package/src/tools/Chat/stories/05-launcher.story.tsx +321 -0
  92. package/src/tools/Chat/stories/06-header.story.tsx +147 -0
  93. package/src/tools/Chat/stories/07-audio-actions.story.tsx +112 -0
  94. package/src/tools/Chat/stories/shared/Frame.tsx +21 -0
  95. package/src/tools/Chat/stories/shared/index.ts +5 -0
  96. package/src/tools/Chat/stories/shared/messages.ts +39 -0
  97. package/src/tools/Chat/stories/shared/personas.ts +13 -0
  98. package/src/tools/Chat/stories/shared/seeds.ts +92 -0
  99. package/src/tools/Chat/stories/shared/transports.ts +36 -0
  100. package/src/tools/Chat/styles/bubbleTokens.ts +71 -0
  101. package/src/tools/Chat/styles/index.ts +16 -0
  102. package/src/tools/Chat/styles/useChatStyles.ts +101 -0
  103. package/src/tools/Chat/types/attachment.ts +25 -0
  104. package/src/tools/Chat/types/config.ts +48 -0
  105. package/src/tools/Chat/types/events.ts +35 -0
  106. package/src/tools/Chat/types/index.ts +34 -0
  107. package/src/tools/Chat/types/labels.ts +38 -0
  108. package/src/tools/Chat/types/message.ts +32 -0
  109. package/src/tools/Chat/types/persona.ts +31 -0
  110. package/src/tools/Chat/types/session.ts +43 -0
  111. package/src/tools/Chat/types/tool-call.ts +17 -0
  112. package/src/tools/Chat/types/transport.ts +28 -0
  113. package/src/tools/Chat/types.ts +5 -240
  114. package/src/tools/MarkdownEditor/MarkdownEditor.tsx +50 -14
  115. package/src/tools/MarkdownEditor/index.ts +1 -1
  116. package/src/tools/SpeechRecognition/README.md +336 -0
  117. package/src/tools/SpeechRecognition/__tests__/ids.test.ts +15 -0
  118. package/src/tools/SpeechRecognition/__tests__/language.test.ts +59 -0
  119. package/src/tools/SpeechRecognition/__tests__/reducer.test.ts +71 -0
  120. package/src/tools/SpeechRecognition/__tests__/transcript.test.ts +52 -0
  121. package/src/tools/SpeechRecognition/components/DevicePicker.tsx +49 -0
  122. package/src/tools/SpeechRecognition/components/DictationButton.tsx +93 -0
  123. package/src/tools/SpeechRecognition/components/EngineBadge.tsx +30 -0
  124. package/src/tools/SpeechRecognition/components/ErrorBanner.tsx +52 -0
  125. package/src/tools/SpeechRecognition/components/LanguagePicker.tsx +63 -0
  126. package/src/tools/SpeechRecognition/components/MicMeter.tsx +63 -0
  127. package/src/tools/SpeechRecognition/components/PushToTalkHint.tsx +51 -0
  128. package/src/tools/SpeechRecognition/components/TranscriptView.tsx +55 -0
  129. package/src/tools/SpeechRecognition/components/index.ts +16 -0
  130. package/src/tools/SpeechRecognition/context/SpeechRecognitionProvider.tsx +47 -0
  131. package/src/tools/SpeechRecognition/context/index.ts +6 -0
  132. package/src/tools/SpeechRecognition/core/audio/defaults.ts +24 -0
  133. package/src/tools/SpeechRecognition/core/engine/external.ts +222 -0
  134. package/src/tools/SpeechRecognition/core/engine/http.ts +147 -0
  135. package/src/tools/SpeechRecognition/core/engine/index.ts +52 -0
  136. package/src/tools/SpeechRecognition/core/engine/mediarecorder.ts +105 -0
  137. package/src/tools/SpeechRecognition/core/engine/websocket.ts +211 -0
  138. package/src/tools/SpeechRecognition/core/engine/webspeech.ts +188 -0
  139. package/src/tools/SpeechRecognition/core/ids.ts +11 -0
  140. package/src/tools/SpeechRecognition/core/index.ts +14 -0
  141. package/src/tools/SpeechRecognition/core/language.ts +78 -0
  142. package/src/tools/SpeechRecognition/core/languages-catalog.ts +229 -0
  143. package/src/tools/SpeechRecognition/core/logger.ts +3 -0
  144. package/src/tools/SpeechRecognition/core/reducer.ts +105 -0
  145. package/src/tools/SpeechRecognition/core/transcript.ts +36 -0
  146. package/src/tools/SpeechRecognition/hooks/index.ts +14 -0
  147. package/src/tools/SpeechRecognition/hooks/useDictation.ts +59 -0
  148. package/src/tools/SpeechRecognition/hooks/useEnginePrefs.ts +15 -0
  149. package/src/tools/SpeechRecognition/hooks/useMicDevices.ts +57 -0
  150. package/src/tools/SpeechRecognition/hooks/useMicLevel.ts +52 -0
  151. package/src/tools/SpeechRecognition/hooks/usePushToTalk.ts +85 -0
  152. package/src/tools/SpeechRecognition/hooks/useResolvedLanguage.ts +28 -0
  153. package/src/tools/SpeechRecognition/hooks/useSpeechLanguageInfo.ts +108 -0
  154. package/src/tools/SpeechRecognition/hooks/useSpeechRecognition.ts +188 -0
  155. package/src/tools/SpeechRecognition/hooks/useVoiceSupport.ts +78 -0
  156. package/src/tools/SpeechRecognition/index.ts +82 -0
  157. package/src/tools/SpeechRecognition/lazy.tsx +19 -0
  158. package/src/tools/SpeechRecognition/store/index.ts +2 -0
  159. package/src/tools/SpeechRecognition/store/prefsStore.ts +54 -0
  160. package/src/tools/SpeechRecognition/stories/01-basic.story.tsx +32 -0
  161. package/src/tools/SpeechRecognition/stories/02-dictation-field.story.tsx +32 -0
  162. package/src/tools/SpeechRecognition/stories/03-push-to-talk.story.tsx +27 -0
  163. package/src/tools/SpeechRecognition/stories/04-mic-meter.story.tsx +35 -0
  164. package/src/tools/SpeechRecognition/stories/05-custom-engine-http.story.tsx +40 -0
  165. package/src/tools/SpeechRecognition/stories/06-custom-engine-ws.story.tsx +48 -0
  166. package/src/tools/SpeechRecognition/stories/07-language-device.story.tsx +57 -0
  167. package/src/tools/SpeechRecognition/stories/08-errors-permissions.story.tsx +25 -0
  168. package/src/tools/SpeechRecognition/stories/09-chat-voice.story.tsx +90 -0
  169. package/src/tools/SpeechRecognition/stories/shared.tsx +123 -0
  170. package/src/tools/SpeechRecognition/types.ts +133 -0
  171. package/src/tools/SpeechRecognition/widgets/DictationField.tsx +105 -0
  172. package/src/tools/SpeechRecognition/widgets/VoiceComposerSlot.tsx +305 -0
  173. package/src/tools/SpeechRecognition/widgets/VoiceMessageRecorder.tsx +88 -0
  174. package/src/tools/SpeechRecognition/widgets/index.ts +6 -0
  175. package/dist/ChatRoot-EJC5Y2YM.cjs +0 -14
  176. package/dist/ChatRoot-QOSKJPM6.mjs +0 -5
  177. package/dist/chunk-NWUT327A.mjs.map +0 -1
  178. package/dist/chunk-QLMKCSR6.mjs +0 -2420
  179. package/dist/chunk-QLMKCSR6.mjs.map +0 -1
  180. package/dist/chunk-SI5RD2GD.cjs +0 -2460
  181. package/dist/chunk-SI5RD2GD.cjs.map +0 -1
  182. package/dist/chunk-XACCHZH2.cjs.map +0 -1
  183. package/src/tools/Chat/Chat.story.tsx +0 -1457
package/README.md CHANGED
@@ -6,997 +6,230 @@
6
6
 
7
7
  # @djangocfg/ui-tools
8
8
 
9
- Heavy React tools with lazy loading (React.lazy + Suspense).
9
+ Heavy React tools with lazy loading. No Next.js dependency — works with Electron, Vite, CRA, any React environment.
10
10
 
11
- **No Next.js dependencies** — works with Electron, Vite, CRA, and any React environment.
12
-
13
- **Part of [DjangoCFG](https://djangocfg.com)** — modern Django framework for production-ready SaaS applications.
14
-
15
- ## Install
11
+ Part of [DjangoCFG](https://djangocfg.com).
16
12
 
17
13
  ```bash
18
14
  pnpm add @djangocfg/ui-tools
19
15
  ```
20
16
 
21
- ## Why ui-tools?
17
+ ---
22
18
 
23
- This package contains heavy components that are loaded lazily to keep your initial bundle small. Each tool is loaded only when used.
19
+ ## What's inside
24
20
 
25
- | Package | Use Case |
26
- |---------|----------|
27
- | `@djangocfg/ui-core` | Lightweight UI components (60+ components) |
28
- | `@djangocfg/ui-tools` | Heavy tools with lazy loading |
29
- | `@djangocfg/ui-nextjs` | Next.js apps (extends ui-core) |
21
+ Sixteen tools, each one lazy-loaded so it doesn't ship until used. Bundle size is the gzipped tool by itself; deps (mapbox, monaco, etc.) load on first render.
30
22
 
31
- ## Tools (16)
32
-
33
- | Tool | Bundle Size | Description |
34
- |------|-------------|-------------|
35
- | `Map` | ~800KB | MapLibre GL maps with markers, clusters, layers. See [Map/README.md](src/tools/Map/README.md). |
36
- | `Mermaid` | ~800KB | Diagram rendering with declarative builders |
37
- | `CodeEditor` | ~550KB | Monaco-based code editor with diff view |
38
- | `PrettyCode` | ~500KB | Code syntax highlighting (read-only) |
39
- | `OpenapiViewer` | ~400KB | OpenAPI schema viewer & playground |
40
- | `JsonForm` | ~300KB | JSON Schema form generator |
41
- | `MarkdownEditor` | ~200KB | WYSIWYG markdown editor with Tiptap, `@`-mentions (auto-flip popup), customizable markdown serialization via presets |
23
+ | Tool | Bundle | Docs |
24
+ |------|--------|------|
25
+ | `Map` | ~800KB | MapLibre maps, markers, clusters, layers. [README](src/tools/Map/README.md) |
26
+ | `Mermaid` | ~800KB | Diagrams + declarative builders |
27
+ | `CodeEditor` | ~550KB | Monaco editor + diff view |
28
+ | `PrettyCode` | ~500KB | Syntax-highlighted read-only code |
29
+ | `OpenapiViewer` | ~400KB | OpenAPI schema viewer + playground |
30
+ | `JsonForm` | ~300KB | JSON-Schema-driven form generator |
31
+ | `MarkdownEditor` | ~200KB | Tiptap WYSIWYG with `@`-mentions |
42
32
  | `LottiePlayer` | ~200KB | Lottie animation player |
43
- | `Chat` | ~150KB | Decomposed, transport-agnostic chat — streaming (SSE), tool calls, attachments, sources, mobile-ready. Reuses `MarkdownMessage`. See [Chat/README.md](src/tools/Chat/README.md). |
44
- | `AudioPlayer` | ~80KB | WebView-safe audio player (v6) static peaks waveform + clip-path playhead |
45
- | `VideoPlayer` | ~150KB | Professional video player with Vidstack |
46
- | `MarkdownMessage` | ~120KB | Read-only chat-tuned markdown renderer — GFM + soft line breaks + smart typography + emoji shortcodes + sanitized HTML + syntax-highlighted code fences + mermaid + declarative `linkRules` for custom URL schemes. See [MarkdownMessage/README.md](src/components/markdown/MarkdownMessage/README.md) for the plugin pipeline. |
47
- | `JsonTree` | ~100KB | JSON visualization with modes (full/compact/inline) |
48
- | `Gallery` | ~50KB | Image/video gallery with carousel, grid, lightbox |
49
- | `ImageViewer` | ~50KB | Image viewer with zoom/pan/rotate/flip and gallery navigation |
50
- | `CronScheduler` | ~15KB | Cron expression builder with intuitive UI |
33
+ | `Chat` | ~150KB | Streaming chat (SSE + tool calls + attachments). [README](src/tools/Chat/README.md) |
34
+ | `SpeechRecognition` | ~40KB | Mic capture + STT with pluggable engines (Web Speech / HTTP / WS). [README](src/tools/SpeechRecognition/README.md) |
35
+ | `VideoPlayer` | ~150KB | Vidstack-based pro player |
36
+ | `MarkdownMessage` | ~120KB | Read-only chat-tuned markdown. [README](src/components/markdown/MarkdownMessage/README.md) |
37
+ | `JsonTree` | ~100KB | JSON visualization (full/compact/inline modes) |
38
+ | `AudioPlayer` | ~80KB | WebView-safe waveform player |
39
+ | `Gallery` | ~50KB | Image/video gallery + lightbox |
40
+ | `ImageViewer` | ~50KB | Zoom/pan/rotate viewer |
41
+ | `CronScheduler` | ~15KB | Cron expression builder |
51
42
 
52
- ## Tree-Shakeable Imports
43
+ Plus utility primitives: `Tree`, `Tour`, `FileIcon`, `UploadDropzone`.
53
44
 
54
- For better bundle optimization, use subpath imports — each path loads only that tool:
45
+ ---
55
46
 
56
- ```tsx
57
- import { LazyJsonTree } from '@djangocfg/ui-tools/json-tree';
58
- import { Gallery, GalleryLightbox } from '@djangocfg/ui-tools/gallery';
59
- import { MapContainer, MapMarker } from '@djangocfg/ui-tools/map';
60
- import { FlowDiagram, SequenceDiagram } from '@djangocfg/ui-tools/mermaid';
61
- import { Editor, DiffEditor } from '@djangocfg/ui-tools/code-editor';
62
- import { LazyOpenapiViewer } from '@djangocfg/ui-tools/openapi-viewer';
63
- import { LazyAudioPlayer } from '@djangocfg/ui-tools/audio-player';
64
- import { LazyVideoPlayer } from '@djangocfg/ui-tools/video-player';
65
- import { LazyChat } from '@djangocfg/ui-tools/chat';
66
- import { LazyImageViewer } from '@djangocfg/ui-tools/image-viewer';
67
- import { LazyJsonSchemaForm } from '@djangocfg/ui-tools/json-form';
68
- import { LazyLottiePlayer } from '@djangocfg/ui-tools/lottie-player';
69
- import { LazyMermaid } from '@djangocfg/ui-tools/mermaid';
70
- import { LazyPrettyCode } from '@djangocfg/ui-tools/pretty-code';
71
- import { LazyCronScheduler } from '@djangocfg/ui-tools/cron-scheduler';
72
- import { MarkdownEditor } from '@djangocfg/ui-tools/markdown-editor';
73
- import { FileIcon } from '@djangocfg/ui-tools/file-icon';
74
- import { Tour } from '@djangocfg/ui-tools/tour';
75
- import { Tree } from '@djangocfg/ui-tools/tree';
76
- ```
47
+ ## Imports
77
48
 
78
- ## Exports
79
-
80
- | Path | Tool | Bundle |
81
- |------|------|--------|
82
- | `@djangocfg/ui-tools` | All tools (lazy) | full |
83
- | `@djangocfg/ui-tools/json-tree` | `JsonTree`, `LazyJsonTree` | ~100KB |
84
- | `@djangocfg/ui-tools/gallery` | `Gallery`, `GalleryLightbox`, hooks | ~50KB |
85
- | `@djangocfg/ui-tools/map` | `MapContainer`, markers, layers, utils | ~800KB |
86
- | `@djangocfg/ui-tools/mermaid` | `Mermaid`, declarative builders | ~800KB |
87
- | `@djangocfg/ui-tools/code-editor` | `Editor`, `DiffEditor`, hooks | ~550KB |
88
- | `@djangocfg/ui-tools/pretty-code` | `PrettyCode`, `LazyPrettyCode` | ~500KB |
89
- | `@djangocfg/ui-tools/openapi-viewer` | `OpenapiViewer`, `LazyOpenapiViewer` | ~400KB |
90
- | `@djangocfg/ui-tools/json-form` | `JsonSchemaForm`, `LazyJsonSchemaForm` | ~300KB |
91
- | `@djangocfg/ui-tools/markdown-editor` | `MarkdownEditor` | ~200KB |
92
- | `@djangocfg/ui-tools/lottie-player` | `LottiePlayer`, `LazyLottiePlayer` | ~200KB |
93
- | `@djangocfg/ui-tools/chat` | `ChatRoot`, `LazyChat`, transports | ~150KB |
94
- | `@djangocfg/ui-tools/video-player` | `VideoPlayer`, `LazyVideoPlayer` | ~150KB |
95
- | `@djangocfg/ui-tools/audio-player` | `AudioPlayer`, `LazyAudioPlayer` | ~80KB |
96
- | `@djangocfg/ui-tools/image-viewer` | `ImageViewer`, `LazyImageViewer` | ~50KB |
97
- | `@djangocfg/ui-tools/cron-scheduler` | `CronScheduler`, `LazyCronScheduler` | ~15KB |
98
- | `@djangocfg/ui-tools/tree` | `Tree` (file/folder tree) | ~20KB |
99
- | `@djangocfg/ui-tools/file-icon` | `FileIcon` | ~10KB |
100
- | `@djangocfg/ui-tools/tour` | `Tour` | ~10KB |
101
- | `@djangocfg/ui-tools/upload` | `UploadDropzone` | ~20KB |
102
- | `@djangocfg/ui-tools/styles` | Tailwind source CSS | — |
103
- | `@djangocfg/ui-tools/dist.css` | Pre-compiled CSS | — |
104
-
105
- ## Gallery
106
-
107
- Full-featured image/video gallery with carousel, grid view, and fullscreen lightbox.
49
+ Default barrel works for app-level imports:
108
50
 
109
51
  ```tsx
110
- import { Gallery } from '@djangocfg/ui-tools/gallery';
111
-
112
- const images = [
113
- { id: '1', src: '/photo1.jpg', alt: 'Photo 1' },
114
- { id: '2', src: '/photo2.jpg', alt: 'Photo 2' },
115
- { id: '3', src: '/video.mp4', alt: 'Video', type: 'video' },
116
- ];
117
-
118
- function PhotoGallery() {
119
- return (
120
- <Gallery
121
- images={images}
122
- previewMode="carousel" // or "grid"
123
- showThumbnails
124
- enableLightbox
125
- aspectRatio={16 / 9}
126
- />
127
- );
128
- }
52
+ import { LazyChat, LazyJsonTree, LazyMap, Gallery } from '@djangocfg/ui-tools';
129
53
  ```
130
54
 
131
- ### Gallery Components
132
-
133
- | Component | Description |
134
- |-----------|-------------|
135
- | `Gallery` | Complete gallery with carousel/grid + lightbox |
136
- | `GalleryCompact` | Minimal carousel for cards |
137
- | `GalleryGrid` | Grid layout with "show more" badge |
138
- | `GalleryLightbox` | Fullscreen lightbox viewer |
139
- | `GalleryCarousel` | Embla-based carousel |
140
- | `GalleryThumbnails` | Thumbnail strip navigation |
141
-
142
- ### Gallery Hooks
143
-
144
- | Hook | Description |
145
- |------|-------------|
146
- | `useGallery` | Gallery state management |
147
- | `useSwipe` | Touch swipe gestures |
148
- | `usePinchZoom` | Pinch-to-zoom for mobile |
149
- | `usePreloadImages` | Image preloading |
150
-
151
- ## Map
152
-
153
- MapLibre GL maps with React components for markers, clusters, popups, and custom layers.
55
+ For bundle splitting, import from the tool's subpath — only that tool ships:
154
56
 
155
57
  ```tsx
156
- import { MapContainer, MapMarker, MapPopup } from '@djangocfg/ui-tools/map';
157
-
158
- const markers = [
159
- { id: '1', lat: 37.7749, lng: -122.4194, title: 'San Francisco' },
160
- { id: '2', lat: 34.0522, lng: -118.2437, title: 'Los Angeles' },
161
- ];
162
-
163
- function LocationMap() {
164
- return (
165
- <MapContainer
166
- initialViewport={{ latitude: 36, longitude: -119, zoom: 5 }}
167
- style="streets"
168
- >
169
- {markers.map((m) => (
170
- <MapMarker key={m.id} latitude={m.lat} longitude={m.lng}>
171
- <MapPopup>{m.title}</MapPopup>
172
- </MapMarker>
173
- ))}
174
- </MapContainer>
175
- );
176
- }
177
- ```
178
-
179
- ### Map Components
180
-
181
- | Component | Description |
182
- |-----------|-------------|
183
- | `MapContainer` | Main map container with controls |
184
- | `MapMarker` | Custom marker with React children |
185
- | `MapPopup` | Popup attached to marker |
186
- | `MapCluster` | Clustered markers with spiderfy |
187
- | `MapSource` / `MapLayer` | Custom GeoJSON layers |
188
- | `MapControls` | Navigation controls |
189
- | `MapLegend` | Map legend component |
190
- | `LayerSwitcher` | Toggle map layers |
191
- | `DrawControl` | Drawing tools (optional) |
192
- | `GeocoderControl` | Search/geocoding (optional) |
193
-
194
- ### Overlapping Markers
195
-
196
- When multiple markers are at the same location, use `offsetOverlappingMarkers` to spread them out before rendering:
197
-
198
- ```tsx
199
- import { MapCluster } from '@djangocfg/ui-tools/map';
200
- import { offsetOverlappingMarkers } from '@djangocfg/ui-tools/map';
201
-
202
- // Pre-process data to offset overlapping points
203
- const processedData = useMemo(() => {
204
- const markers = geojson.features.map((f, i) => ({
205
- id: f.properties?.id || `point-${i}`,
206
- longitude: f.geometry.coordinates[0],
207
- latitude: f.geometry.coordinates[1],
208
- data: f.properties,
209
- }));
210
-
211
- const offsetMarkers = offsetOverlappingMarkers(markers, {
212
- spiralRadius: 0.0003, // ~30m spread
213
- });
214
-
215
- return {
216
- type: 'FeatureCollection',
217
- features: offsetMarkers.map((m) => ({
218
- type: 'Feature',
219
- properties: m.data,
220
- geometry: { type: 'Point', coordinates: [m.longitude, m.latitude] },
221
- })),
222
- };
223
- }, [geojson]);
224
-
225
- <MapCluster sourceId="points" data={processedData} />
226
- ```
227
-
228
- | Utility | Description |
229
- |---------|-------------|
230
- | `offsetOverlappingMarkers(markers, options)` | Spread overlapping markers using Fermat spiral |
231
- | `hasOverlappingMarkers(markers)` | Check if any markers overlap |
232
- | `getOverlapStats(markers)` | Get statistics about overlapping markers |
233
-
234
- ### Map Hooks
235
-
236
- | Hook | Description |
237
- |------|-------------|
238
- | `useMap` | Access map instance |
239
- | `useMapControl` | Programmatic map control |
240
- | `useMarkers` | Marker management |
241
- | `useMapEvents` | Map event handlers |
242
- | `useMapViewport` | Viewport state |
243
- | `useMapLayers` | Layer management |
244
-
245
- ### Map Styles
246
-
247
- ```tsx
248
- import { MAP_STYLES, getMapStyle } from '@djangocfg/ui-tools/map';
249
-
250
- // Available styles: streets, satellite, dark, light, terrain
251
- <MapContainer style="dark" />
252
- ```
253
-
254
- ### Layer Utilities
255
-
256
- ```tsx
257
- import {
258
- createClusterLayers,
259
- createPointLayer,
260
- createPolygonLayer,
261
- createLineLayer,
262
- } from '@djangocfg/ui-tools/map';
58
+ import { LazyJsonTree } from '@djangocfg/ui-tools/json-tree';
59
+ import { Gallery } from '@djangocfg/ui-tools/gallery';
60
+ import { MapContainer } from '@djangocfg/ui-tools/map';
61
+ import { Editor, DiffEditor } from '@djangocfg/ui-tools/code-editor';
62
+ import { LazyChat } from '@djangocfg/ui-tools/chat';
63
+ import { MarkdownEditor } from '@djangocfg/ui-tools/markdown-editor';
64
+ // …same pattern for every tool
263
65
  ```
264
66
 
265
- ## Video Player
67
+ | Subpath | Ships |
68
+ |---------|-------|
69
+ | `@djangocfg/ui-tools` | All tools (lazy) |
70
+ | `@djangocfg/ui-tools/<tool-name>` | One tool — `map`, `mermaid`, `chat`, `speech-recognition`, `json-tree`, `code-editor`, `pretty-code`, `openapi-viewer`, `json-form`, `markdown-editor`, `lottie-player`, `audio-player`, `video-player`, `image-viewer`, `cron-scheduler`, `gallery`, `tour`, `tree`, `file-icon`, `upload` |
71
+ | `@djangocfg/ui-tools/styles` | Tailwind source CSS |
72
+ | `@djangocfg/ui-tools/dist.css` | Pre-compiled CSS |
266
73
 
267
- ```tsx
268
- import { VideoPlayer } from '@djangocfg/ui-tools';
74
+ ---
269
75
 
270
- <VideoPlayer
271
- src="https://example.com/video.mp4"
272
- poster="/thumbnail.jpg"
273
- autoplay={false}
274
- />
275
- ```
76
+ ## Lazy loading
276
77
 
277
- ## Audio Player
78
+ Every heavy tool ships in two flavors:
278
79
 
279
- WebView-safe v6 player. Single component, two layouts (`default` / `compact`),
280
- container-query auto-switching, waveform doubles as the seek bar. Designed for
281
- 60 fps in WKWebView (Wails / Electron) and Tauri-style WebView2.
80
+ - `Lazy*` wrapped in `React.lazy` + `Suspense` + skeleton. Use by default.
81
+ - bare tool (`Mermaid`, `JsonTree`, …) for explicit loading control.
282
82
 
283
83
  ```tsx
284
- import { LazyAudioPlayer } from '@djangocfg/ui-tools';
285
-
286
- <LazyAudioPlayer
287
- src="/audio/track.mp3"
288
- title="Track Title"
289
- artist="Artist"
290
- cover="/cover.jpg"
291
- />
292
- ```
293
-
294
- Highlights:
295
-
296
- - **Static peaks waveform** by default (decoded once, cached per `src`); zero
297
- canvas paints during playback — playhead animates via `clip-path` + a single
298
- CSS variable on the GPU thread.
299
- - Five waveform modes: `peaks` (default), `live` (AnalyserNode), `bars`
300
- (CSS-only decoration), `progress` (plain scrubber, no animation), `none`.
301
- Decode failure quietly degrades `peaks` → `progress`.
302
- - **Slot composition**: drop `<PlayerProvider>` + import the parts you need
303
- (`Cover`, `Title`, `Waveform`, `PlayButton`, …) to build any custom layout.
304
- - **Click on the waveform = seek + play** (configurable via
305
- `seekStartsPlayback`); drag to scrub.
306
- - **One active player at a time** by default — `exclusive` prop, coordinated
307
- same-tab + cross-tab via `BroadcastChannel`.
308
- - **Persistent volume / mute** in `localStorage`, synced across all uncontrolled
309
- players (and tabs). Pass `initialVolume` / `muted` to opt out per-player.
310
- - **Mobile-aware**: auto-switches to `compact` on phone viewports, volume
311
- popover toggles on tap, hover affordances disabled on touch, iOS Safari
312
- hides the volume slider (read-only there).
313
- - **Keyboard**: Space/K toggle, ←/→ seek 5s, ↑/↓ volume, M mute, L loop.
314
- Active only when focus is in the player.
315
- - **Selector hooks** — `usePlayerState`, `usePlayerControls`, `usePlayerLevels`,
316
- `useActivePlayer`, `useLastActivePlayer`, `useIsActivePlayer`,
317
- `usePlayerPreferences` — for custom toolbars / "now playing" UIs.
318
-
319
- See [`AudioPlayer/README.md`](src/tools/AudioPlayer/README.md) for the full API
320
- and the docs at `@dev/@refactoring6-audioplayer/` for architecture / ADRs.
321
-
322
- ## Mermaid Diagrams
323
-
324
- Render Mermaid diagrams with fullscreen zoom support and type-safe declarative builders.
325
-
326
- ### Basic Usage
84
+ import { LazyChat } from '@djangocfg/ui-tools/chat';
327
85
 
328
- ```tsx
329
- import { LazyMermaid } from '@djangocfg/ui-tools';
330
-
331
- <LazyMermaid chart={`
332
- graph TD
333
- A[Start] --> B{Decision}
334
- B -->|Yes| C[Action]
335
- B -->|No| D[End]
336
- `} />
86
+ <LazyChat transport={transport} /> // loads on mount
87
+ <LazyChat fallback={<MySkeleton />} ... /> // custom loading UI
337
88
  ```
338
89
 
339
- ### Props
90
+ Skeletons match the tool's final layout so swap-in is jank-free.
340
91
 
341
- | Prop | Type | Default | Description |
342
- |------|------|---------|-------------|
343
- | `chart` | `string` | - | Mermaid diagram syntax |
344
- | `className` | `string` | `''` | Additional CSS classes |
345
- | `isCompact` | `boolean` | `false` | Compact rendering mode |
346
- | `fullscreen` | `boolean` | `true` | Enable fullscreen button with pinch-zoom |
92
+ ---
347
93
 
348
- ### Declarative Builders
94
+ ## Chat — quick start
349
95
 
350
- Type-safe builders for creating Mermaid diagrams programmatically:
96
+ The most-used tool. Headless reducer + composable UI: streaming SSE transport, pydantic-AI mapper, decomposed parts (`MessageBubble`, `Composer`, `MessageList`, `ToolCalls`, …), role-aware styling. Floating launcher with FAB, popover/side dock, proactive greeting, live-push notifications, audio mute toggle, reset-with-confirm, Linear-style hotkeys.
351
97
 
352
98
  ```tsx
353
- import { LazyMermaid } from '@djangocfg/ui-tools';
354
99
  import {
355
- FlowDiagram,
356
- SequenceDiagram,
357
- JourneyDiagram,
358
- useStylePresets,
359
- useBoxColors,
360
- } from '@djangocfg/ui-tools/mermaid';
361
-
362
- function MyDiagram() {
363
- const presets = useStylePresets();
364
- const boxes = useBoxColors();
365
-
366
- // Flow diagram with type-safe nodes
367
- type Nodes = 'start' | 'check' | 'success' | 'finish';
368
- const flow = FlowDiagram<Nodes>({ direction: 'TB' });
369
-
370
- flow.node('start').rect('Start');
371
- flow.node('check').rhombus('Is it working?');
372
- flow.node('success').rect('Great!');
373
- flow.node('finish').stadium('End');
374
-
375
- flow.edge('start').to('check').solid();
376
- flow.edge('check').to('success').solid('Yes');
377
- flow.edge('check').to('finish').solid('No');
378
- flow.edge('success').to('finish').solid();
379
-
380
- // Apply theme-aware styles
381
- flow.style.define('success', presets.success);
382
- flow.style.apply('success', 'success', 'finish');
383
-
384
- return <LazyMermaid chart={flow.toString()} />;
385
- }
386
- ```
387
-
388
- ### Available Builders
389
-
390
- | Builder | Description |
391
- |---------|-------------|
392
- | `FlowDiagram<Nodes>` | Flowcharts with nodes, edges, subgraphs |
393
- | `SequenceDiagram` | Sequence diagrams with participants, messages |
394
- | `JourneyDiagram` | User journey diagrams with sections, tasks |
395
-
396
- ### Theme Hooks
397
-
398
- | Hook | Description |
399
- |------|-------------|
400
- | `useThemePalette()` | Full palette from CSS variables |
401
- | `useStylePresets()` | Pre-built style configs (success, warning, etc.) |
402
- | `useBoxColors()` | Colors for sequence diagram boxes |
403
-
404
- ### FlowDiagram API
405
-
406
- ```tsx
407
- const flow = FlowDiagram<'A' | 'B' | 'C'>({ direction: 'TB' });
408
-
409
- // Nodes
410
- flow.node('A').rect('Rectangle');
411
- flow.node('B').round('Rounded');
412
- flow.node('C').rhombus('Diamond');
413
- flow.node('D').stadium('Stadium');
414
- flow.node('E').cylinder('Database');
415
- flow.node('F').hexagon('Hexagon');
416
-
417
- // Edges
418
- flow.edge('A').to('B').solid();
419
- flow.edge('A').to('B').solid('with label');
420
- flow.edge('A').to('B').dotted();
421
- flow.edge('A').to('B').thick();
422
-
423
- // Subgraphs
424
- flow.subgraph('Group Name', (sub) => {
425
- sub.direction('LR');
426
- sub.node('X').rect('Inside');
427
- });
428
-
429
- // Styles
430
- flow.style.define('myStyle', { fill: '#f00', stroke: '#000' });
431
- flow.style.apply('myStyle', 'A', 'B');
432
- ```
433
-
434
- ### SequenceDiagram API
435
-
436
- **Static API** (type-safe, for known participants):
437
-
438
- ```tsx
439
- const { d, rect, alt, loop, toString } = SequenceDiagram({
440
- User: 'actor',
441
- App: 'participant',
442
- API: 'participant',
443
- }, { autoNumber: true });
444
-
445
- // Messages (type-safe chain)
446
- d.User.sync.App.msg('Click button');
447
- d.App.async.API.msg('Fetch data');
448
- d.API.asyncReply.App.msg('Response');
449
-
450
- // Blocks
451
- rect('#rgba(0,100,200,0.2)', () => {
452
- d.User.sync.App.msg('Login');
453
- });
454
-
455
- alt('Success', () => {
456
- d.App.syncReply.User.msg('Welcome!');
457
- }).else('Failure', () => {
458
- d.App.syncReply.User.msg('Error');
459
- });
460
-
461
- loop('Every 5s', () => {
462
- d.App.async.API.msg('Heartbeat');
463
- });
464
-
465
- return toString();
466
- ```
467
-
468
- **Dynamic API** (for runtime participant names):
100
+ ChatLauncher,
101
+ ChatRoot,
102
+ createPydanticAIChatTransport,
103
+ useChatAudio,
104
+ } from '@djangocfg/ui-tools';
469
105
 
470
- ```tsx
471
- // Participants from API/database
472
- const characters = ['Alice', 'Bob', 'Charlie'];
473
- const participants: Record<string, 'participant'> = {};
474
- characters.forEach(c => { participants[c] = 'participant'; });
475
-
476
- const seq = SequenceDiagram(participants, { autoNumber: true });
477
-
478
- // Dynamic methods - no type assertions needed
479
- seq.message('Alice', 'Bob', 'Hello!');
480
- seq.message('Bob', 'Alice', 'Hi!', 'syncReply');
481
- seq.message('Alice', 'Charlie', 'Ping', 'async');
482
-
483
- // Notes
484
- seq.noteOver('Alice', 'Thinking...');
485
- seq.noteOverSpan('Alice', 'Bob', 'Discussion');
486
- seq.noteLeft('Charlie', 'Waiting');
487
- seq.noteRight('Charlie', 'Done');
488
-
489
- // Blocks work the same
490
- seq.rect('rgba(100,200,255,0.2)', () => {
491
- seq.message('Alice', 'Bob', 'Secret message');
106
+ const transport = createPydanticAIChatTransport({
107
+ buildStreamUrl: (sid, msg) => `${API}/chat/sessions/${sid}/stream?message=${encodeURIComponent(msg)}`,
108
+ streamMethod: 'GET',
109
+ buildHeaders: async () => ({ Authorization: `Bearer ${getToken()}` }),
492
110
  });
493
111
 
494
- return seq.toString();
112
+ function Chat() {
113
+ const audio = useChatAudio(); // zero setup — uses built-in sounds bundled into the lazy chunk
114
+ return (
115
+ <ChatLauncher
116
+ hotkey={{ key: '/', meta: true }}
117
+ fab={{ variant: 'animated' }} // size='responsive' default — phone/tablet/desktop
118
+ dock={{ title: 'Assistant', height: 600 }}
119
+ greeting="Hi 👋 Need help?"
120
+ audio={audio} // auto-injects mute toggle into header
121
+ >
122
+ <ChatRoot transport={transport} />
123
+ </ChatLauncher>
124
+ );
125
+ }
495
126
  ```
496
127
 
497
- | Dynamic Method | Description |
498
- |----------------|-------------|
499
- | `message(from, to, text, arrow?)` | Send message (arrow: sync, syncReply, async, asyncReply, solid, dotted, cross) |
500
- | `noteOver(participant, text)` | Note over one participant |
501
- | `noteOverSpan(p1, p2, text)` | Note spanning two participants |
502
- | `noteLeft(participant, text)` | Note left of participant |
503
- | `noteRight(participant, text)` | Note right of participant |
128
+ **What's wired by default:** desktop side-mode toggle (auto-hides on narrow screens), persisted dock prefs, two-step Escape, click-to-focus composer, mobile fullscreen with `dvh` heights, push-preview bubble for inbound messages while closed, **ChatGPT-style autoscroll** (sticky-to-bottom within 120 px, every user-send re-anchors the viewport), **bundled chat notification sounds** (sent/received/start/error/mention/notification, ~136KB inlined as `data:`-URLs inside the lazy chat chunk — zero host setup). Native hosts (cmdop_go / Tauri) pass `audio={{ silenced: true, onSoundEvent }}` to keep web silent while routing triggers to the backend.
504
129
 
505
- ### JourneyDiagram API
130
+ Drop `<VoiceComposerSlot />` from `@djangocfg/ui-tools/speech-recognition` into `composerToolbarEnd` for live mic-to-text — **zero props**, reads / writes the composer through `ComposerHandle` registered in chat context. Drop `<ChatHeaderLanguageButton />` into `dock.headerActions` for a flag-button language picker (66 BCP-47 tags). Both auto-hide on Firefox / in-app browsers / missing `getUserMedia`. See [`SpeechRecognition`](#speech-recognition--quick-start) below.
506
131
 
507
- ```tsx
508
- const journey = JourneyDiagram({ title: 'User Onboarding' });
509
-
510
- journey.section('Discovery')
511
- .task('Visit landing page', 5, 'User')
512
- .task('Read features', 4, 'User');
132
+ Full docs: [`Chat/README.md`](src/tools/Chat/README.md). Stories: `Tools/Chat/{Basic,Bubbles,ToolCalls,Personas,Launcher,Header,Audio & Actions,Voice composer}`.
513
133
 
514
- journey.section('Sign Up')
515
- .task('Click Sign Up', 5, 'User')
516
- .task('Fill form', 2, 'User')
517
- .task('Verify email', 4, ['User', 'System']);
134
+ ---
518
135
 
519
- return journey.toString();
520
- ```
136
+ <a id="speech-recognition--quick-start"></a>
521
137
 
522
- ## Code Editor
138
+ ## Speech Recognition — quick start
523
139
 
524
- Monaco-based code editor with full IDE features: syntax highlighting, IntelliSense, diff view, multi-file support.
525
-
526
- ```tsx
527
- import { Editor, DiffEditor } from '@djangocfg/ui-tools';
528
- // or tree-shakeable: import { Editor } from '@djangocfg/ui-tools/code-editor';
529
-
530
- // Basic editor
531
- <Editor
532
- value="const x = 42;"
533
- language="typescript"
534
- onChange={(value) => console.log(value)}
535
- options={{ fontSize: 14, minimap: false }}
536
- />
537
-
538
- // Diff view
539
- <DiffEditor
540
- original="const x = 1;"
541
- modified="const x = 42;"
542
- language="typescript"
543
- />
544
- ```
545
-
546
- ### Context & Hooks
140
+ Decomposed STT (Speech-to-Text) tool. Headless hook + composable UI + lazy bundle, same shape as `Chat` and `AudioPlayer`. Default backend is the browser Web Speech API (zero deps, zero network); custom engines plug in via a small interface — Deepgram, AssemblyAI, OpenAI Whisper REST, your own gateway, all without vendor SDKs on the critical path.
547
141
 
548
142
  ```tsx
549
143
  import {
550
- EditorProvider,
551
- useEditorContext,
552
- useMonaco,
553
- useEditor,
554
- useLanguage,
555
- } from '@djangocfg/ui-tools/code-editor';
556
-
557
- // Multi-file editor with shared state
558
- <EditorProvider onSave={async (path) => { /* save file */ }}>
559
- <FileTree />
560
- <Editor />
561
- </EditorProvider>
562
- ```
563
-
564
- ## Code Highlighting
565
-
566
- Read-only syntax highlighting (lighter than Monaco — use when editing not needed):
567
-
568
- ```tsx
569
- import { LazyPrettyCode } from '@djangocfg/ui-tools';
570
-
571
- // Default "card" variant — border, background, hover copy toolbar.
572
- <LazyPrettyCode
573
- data={`const hello = "world";`}
574
- language="typescript"
575
- />
576
-
577
- // "plain" variant — chrome-less, no internal scroll. Use when
578
- // embedding inside another scroll container so the parent surface
579
- // owns chrome and scroll (e.g. a panel that already has its own
580
- // ScrollArea and copy button).
581
- <LazyPrettyCode
582
- data={htmlResponseBody}
583
- language="markup"
584
- variant="plain"
585
- />
586
- ```
587
-
588
- ## JSON Form
589
-
590
- ```tsx
591
- import { JsonSchemaForm } from '@djangocfg/ui-tools';
592
-
593
- const schema = {
594
- type: 'object',
595
- properties: {
596
- name: { type: 'string', title: 'Name' },
597
- email: { type: 'string', format: 'email', title: 'Email' },
598
- },
599
- };
600
-
601
- <JsonSchemaForm
602
- schema={schema}
603
- onSubmit={(data) => console.log(data)}
604
- />
605
- ```
606
-
607
- ## Chat
608
-
609
- Decomposed, transport-agnostic chat. Streaming-aware, markdown-native, mobile-ready. Sits on top of `MarkdownMessage` (no second markdown stack).
610
-
611
- ```tsx
612
- import { ChatRoot, createHttpTransport } from '@djangocfg/ui-tools';
613
-
614
- const transport = createHttpTransport({
615
- baseUrl: '/api/chat',
616
- getAuthHeader: () => ({ Authorization: `Bearer ${getToken()}` }),
617
- });
144
+ DictationButton,
145
+ TranscriptView,
146
+ useSpeechRecognition,
147
+ } from '@djangocfg/ui-tools/speech-recognition';
618
148
 
619
- export function MyChat() {
149
+ function Dictate() {
150
+ const rec = useSpeechRecognition(); // Web Speech engine, browser language
620
151
  return (
621
- <div className="h-[600px]">
622
- <ChatRoot transport={transport} config={{ greeting: 'How can I help?' }} />
623
- </div>
152
+ <>
153
+ <DictationButton status={rec.status} onClick={() => rec.toggle()} />
154
+ <TranscriptView transcript={rec.transcript} />
155
+ </>
624
156
  );
625
157
  }
626
158
  ```
627
159
 
628
- Three usage levels — pick the smallest one that fits:
160
+ For the chat composer:
629
161
 
630
162
  ```tsx
631
- // 1) One-line preset
632
- <ChatRoot transport={transport} />
633
-
634
- // 2) Composition — bring your own layout, reuse the parts
635
- <ChatProvider transport={transport}>
636
- <MyHeader />
637
- <MessageList renderEmpty={() => <EmptyState greeting="Custom" />} />
638
- <Composer composer={composer} />
639
- </ChatProvider>
640
-
641
- // 3) Headless — just hooks
642
- const chat = useChat({ transport });
643
- const composer = useChatComposer({ onSubmit: chat.sendMessage });
644
- ```
645
-
646
- Built-in transports:
647
- - **`createHttpTransport`** — fetch + SSE streaming (default for web)
648
- - **`createMockTransport`** — scripted in-memory replies for tests/stories
649
-
650
- Custom hosts (Wails RPC, WebSocket, gRPC) implement the `ChatTransport` interface.
651
-
652
- What's exported: types (`ChatMessage`, `ChatToolCall`, `ChatAttachment`, `ChatSource`, `ChatTransport`, `ChatStreamEvent`, …), pure core (`reducer`, `createId`, `createTokenBuffer`), hooks (`useChat`, `useChatComposer`, `useChatScroll`, `useChatHistory`, `useChatLayout`), context (`ChatProvider`, `useChatContext`), and components (`ChatRoot`, `MessageList`, `MessageBubble`, `MessageActions`, `Composer`, `Sources`, `ToolCalls`, `Attachments`, `EmptyState`, `ErrorBanner`, `JumpToLatest`, `StreamingIndicator`).
653
-
654
- Highlights:
655
- - Token coalescing (~16ms) → ≤1 render per frame during fast streams.
656
- - Plain-text rendering during stream, full markdown on done.
657
- - Memoized bubbles by `(id, content, isStreaming, version, toolCalls, sources, attachments)`.
658
- - `100dvh`, safe-area inset, 16px textarea (no iOS focus-zoom).
659
- - `role="log"` + `aria-live="polite"`, `aria-busy` on streaming bubbles, `role="alert"` errors.
660
- - Cancel keeps partial text with `[cancelled]` marker.
661
- - Tool calls — auto-open while running, auto-close on completion. Pluggable payload renderers via `dispatchToolPayload(matchers, fallback)`.
662
- - Personas — `config.user` + `config.assistant` for avatar/name, `message.sender` for per-message overrides (multi-user chats).
663
- - Attachments — `AttachmentsGrid` (thumbs) + `AttachmentsList` (rich renderers); per-type registry `{ image, audio, video, file }` plus `onAttachmentOpen` for host-side lightbox.
664
- - Audio triggers — optional sounds on send / receive / stream start / error / mention. iOS unlock + cross-tab persisted volume / mute. Off by default.
665
- - Slots — `header`, `footer`, `banner`, `empty`, `composerToolbarStart/End`, `composerAttachmentTray`, `jumpToLatest` (plus render-prop variants).
666
- - Helpers — `useChatLightbox`, `collectImageAttachments` for swapping in `<LazyImageViewer>` host-side.
667
-
668
- Heavy renderers stay opt-in subpath imports — `ui-tools/Chat` doesn't pull `AudioPlayer`/`ImageViewer`/`Map`/`JsonTree` into its dep graph.
669
-
670
- Full docs in [`src/tools/Chat/README.md`](src/tools/Chat/README.md). Implementation plan in [`@dev/@refactoring7-chat/`](@dev/@refactoring7-chat/).
671
-
672
- ## Markdown Message
673
-
674
- Read-only markdown renderer tuned for chat / agent transcripts. Built
675
- on `react-markdown` + `remark-gfm` + `rehype-sanitize`, with a few
676
- chat-specific affordances on top:
677
-
678
- - Syntax-highlighted code blocks with a hover-revealed Copy button
679
- (delegates to `PrettyCode` from this package).
680
- - Mermaid diagram rendering for ` ```mermaid ` fences.
681
- - Plain-text fast path: when content has no markdown syntax we skip
682
- ReactMarkdown entirely and just render the string with
683
- `whitespace: pre-line`. Cheaper, identical visual.
684
- - Optional collapsible "Read more..." for long messages.
685
- - User vs assistant styling modes (`isUser` prop).
686
- - **`linkRules` API** — declarative handling of custom URL schemes
687
- (e.g. `cmdop://machine/<uuid>` → render as a chip; `obsidian://`
688
- → open in a viewer). One prop replaces the per-consumer
689
- custom-`a`/sanitize/urlTransform boilerplate.
690
-
691
- ```tsx
692
- import { MarkdownMessage } from '@djangocfg/ui-tools';
693
-
694
- <MarkdownMessage
695
- content="# Hello\n\nThis is **bold** text and `inline code`."
696
- isCompact={false}
697
- />
698
-
699
- // Chat user message — primary-tinted bubble styling
700
- <MarkdownMessage content={msg} isUser />
701
-
702
- // Long content with "Read more..."
703
- <MarkdownMessage
704
- content={longText}
705
- collapsible
706
- maxLength={300}
707
- maxLines={5}
708
- />
709
- ```
710
-
711
- ### Custom URL schemes — `linkRules`
712
-
713
- For any chat that emits its own URL schemes — `cmdop://machine/<uuid>`
714
- mention chips, `obsidian://open?path=…` deep-links, custom file
715
- viewers — the recommended approach is `linkRules`:
163
+ import {
164
+ VoiceComposerSlot,
165
+ ChatHeaderLanguageButton,
166
+ } from '@djangocfg/ui-tools/speech-recognition';
716
167
 
717
- ```tsx
718
- import { MarkdownMessage, type LinkRule } from '@djangocfg/ui-tools';
719
-
720
- const machineMention: LinkRule = {
721
- name: 'machine-mention',
722
- protocols: ['cmdop'],
723
-
724
- // Optional: rewrite the source markdown before render. Useful when
725
- // your composer adds a decorative `@` outside the link
726
- // (`@[label](href)`) — the chip itself reads as the mention
727
- // indicator, so rendering "@<chip>" looks like "@@label".
728
- preprocess: (source) =>
729
- source.replace(
730
- /(^|[^A-Za-z0-9_])@(\[[^\]]+\]\(cmdop:\/\/machine\/[^)\s]+\))/g,
731
- '$1$2',
732
- ),
733
-
734
- // Predicate against the resolved href.
735
- match: (href) => href.startsWith('cmdop://machine/'),
736
-
737
- // Render whatever you want. `children` is the link's React label.
738
- render: ({ href, children, isUser }) => {
739
- const id = href.slice('cmdop://machine/'.length);
740
- return <MentionChip id={id} isUser={isUser}>{children}</MentionChip>;
741
- },
742
- };
743
-
744
- <MarkdownMessage
745
- content="Talk to @[Vps-audi](cmdop://machine/abc-123) about deployment."
746
- linkRules={[machineMention]}
168
+ <ChatRoot
169
+ transport={transport}
170
+ composerToolbarEnd={<VoiceComposerSlot />}
747
171
  />
748
- ```
749
-
750
- #### Why `linkRules` and not just `customComponents.a`
751
-
752
- Three concerns have to be aligned for a custom URL scheme to make it
753
- intact to your renderer, and `customComponents` alone covers only one
754
- of them:
755
172
 
756
- | Concern | What goes wrong without help |
757
- |---|---|
758
- | **Sanitize whitelist** | `rehype-sanitize` strips href values for any protocol it doesn't recognise — your renderer receives `href={undefined}`. |
759
- | **`urlTransform`** | `react-markdown`'s default `urlTransform` runs *before* sanitize and blanks unrecognised schemes the same way — sanitize whitelist is moot if this layer already nuked the href. |
760
- | **Source preprocess** | Your composer might emit a shape like `@[label](cmdop://...)`; the leading `@` lives outside the link and needs to be stripped before render or the chip ends up next to a literal `@`. |
761
-
762
- `linkRules` collapses all three into a single declaration:
763
- - `protocols` is unioned into the sanitize schema.
764
- - The same protocol opts in the `urlTransform`.
765
- - `preprocess` runs ahead of render.
766
- - `match` + `render` replace the per-rule `<a>`.
767
-
768
- You can still pass `customComponents` and `extraHrefProtocols`
769
- alongside; rules win on URLs they `match`, everything else falls
770
- through to your `customComponents.a` (or the built-in chat anchor).
771
-
772
- ### Anatomy
773
-
774
- The component lives at `src/components/markdown/MarkdownMessage/`:
775
-
776
- ```
777
- MarkdownMessage/
778
- ├── MarkdownMessage.tsx # composition only
779
- ├── types.ts # MarkdownMessageProps + LinkRule
780
- ├── sanitize.ts # buildSchema + buildUrlTransform
781
- ├── components.tsx # createMarkdownComponents (h/p/ul/a/pre/...)
782
- ├── CodeBlock.tsx # code block with copy button + fallback
783
- ├── CollapseToggle.tsx # "Read more..." button
784
- ├── linkRules.ts # rule application helpers
785
- ├── plainText.ts # hasMarkdownSyntax + extractTextFromChildren
786
- └── index.ts # public re-exports
173
+ // Plus a flag-button language picker in the dock header:
174
+ <ChatLauncher dock={{ headerActions: <ChatHeaderLanguageButton /> }}>
787
175
  ```
788
176
 
789
- Decomposed in 2.1.299 to keep concerns flat and reviewable. Public
790
- contract (`MarkdownMessage`, `MarkdownMessageProps`, `LinkRule`,
791
- `extractTextFromChildren`) re-exported from
792
- `@djangocfg/ui-tools` directly.
177
+ **What's wired by default:** auto-hide on Firefox / in-app WebViews / missing `getUserMedia` (via `useVoiceSupport`); live interim+final stream into the composer via `ComposerHandle` (works transparently for the built-in textarea Composer and a TipTap MarkdownEditor); typed prefix anchored; focus + cursor-to-end on start and every partial; 90-second countdown + 2.5-second silence auto-stop; Esc cancels (without closing the chat) / Enter finishes (without submitting); persisted prefs (`djangocfg-stt:prefs`); `<SpeechRecognitionProvider>` for sharing engine state across the tree. Language picker shows 66 BCP-47 tags sourced from the Chrome Web Speech demo with country flags. Custom engines through `createHttpEngine` (REST/Whisper), `createWebSocketEngine` (Deepgram-style streaming), or `createExternalEngine` (Wails / Tauri / native sidecar — `onStart` / `onStop` / `subscribe` and you're done).
793
178
 
794
- ## Components
179
+ Full docs: [`SpeechRecognition/README.md`](src/tools/SpeechRecognition/README.md). Stories: `Tools/SpeechRecognition/{Basic, DictationField, PushToTalk, MicMeter, CustomEngine: HTTP, CustomEngine: WebSocket, Language & Device, Errors}` + `Tools/Chat/{Voice composer, Launcher}` (flag-button picker in the dock header). Unit-tested with vitest (`pnpm test`, 21 cases).
795
180
 
796
- | Component | Description |
797
- |-----------|-------------|
798
- | `MarkdownMessage` | Read-only markdown renderer with custom-URL-scheme support via `linkRules` (see above) |
799
- | `Markdown` | Generic markdown renderer with GFM support |
181
+ ---
800
182
 
801
- ## Stores
183
+ ## Markdown
802
184
 
803
- | Store | Description |
804
- |-------|-------------|
805
- | `useMediaCacheStore` | Media caching for video/audio players |
806
-
807
- ## Cron Scheduler
808
-
809
- Compact cron expression builder with intuitive UI. Supports Daily, Weekly, Monthly schedules and custom cron expressions.
810
-
811
- ```tsx
812
- import { CronScheduler } from '@djangocfg/ui-tools';
813
-
814
- <CronScheduler
815
- value="0 9 * * 1-5"
816
- onChange={(cron) => console.log(cron)}
817
- showPreview
818
- allowCopy
819
- />
820
- ```
821
-
822
- ### Props
823
-
824
- | Prop | Type | Default | Description |
825
- |------|------|---------|-------------|
826
- | `value` | `string` | - | Cron expression (Unix 5-field format) |
827
- | `onChange` | `(cron: string) => void` | - | Callback when schedule changes |
828
- | `defaultType` | `'daily' \| 'weekly' \| 'monthly' \| 'custom'` | `'daily'` | Initial schedule type |
829
- | `showPreview` | `boolean` | `true` | Show human-readable preview |
830
- | `showCronExpression` | `boolean` | `true` | Show cron expression in preview |
831
- | `allowCopy` | `boolean` | `false` | Enable copy to clipboard |
832
- | `timeFormat` | `'12h' \| '24h'` | `'24h'` | Time display format |
833
- | `disabled` | `boolean` | `false` | Disable all interactions |
834
-
835
- ### Context Hooks
836
-
837
- For custom compositions, use the context hooks:
838
-
839
- ```tsx
840
- import {
841
- CronSchedulerProvider,
842
- useCronType,
843
- useCronTime,
844
- useCronWeekDays,
845
- useCronMonthDays,
846
- useCronPreview,
847
- } from '@djangocfg/ui-tools';
848
- ```
849
-
850
- ### Utilities
851
-
852
- ```tsx
853
- import {
854
- buildCron, // State → Cron expression
855
- parseCron, // Cron → State
856
- humanizeCron, // Cron → Human description
857
- isValidCron, // Validate cron syntax
858
- } from '@djangocfg/ui-tools';
859
- ```
860
-
861
- ## Image Viewer
862
-
863
- Image viewer with zoom, pan, rotate, flip and gallery navigation.
185
+ Read-only `<MarkdownMessage>` powers every chat bubble. Stays useful standalone for docs, AI replies, system messages.
864
186
 
865
187
  ```tsx
866
- import { ImageViewer } from '@djangocfg/ui-tools';
867
-
868
- // Single image
869
- <div className="w-full h-[500px]">
870
- <ImageViewer
871
- images={[{ file: { name: 'photo.jpg', path: '/images/photo.jpg' }, src: '/images/photo.jpg' }]}
872
- />
873
- </div>
188
+ import { MarkdownMessage } from '@djangocfg/ui-tools';
874
189
 
875
- // Gallery pass multiple images, keyboard ←/→ navigation enabled automatically
876
- <div className="w-full h-[500px]">
877
- <ImageViewer
878
- images={[
879
- { file: { name: 'Photo 1', path: 'p1' }, src: 'https://example.com/1.jpg' },
880
- { file: { name: 'Photo 2', path: 'p2' }, src: 'https://example.com/2.jpg' },
881
- { file: { name: 'Photo 3', path: 'p3' }, src: 'https://example.com/3.jpg' },
882
- ]}
883
- initialIndex={0}
884
- />
885
- </div>
190
+ <MarkdownMessage content="# Hello\n\nGFM + emoji 😄 + code:\n\n```ts\nconst x = 1;\n```" />
886
191
  ```
887
192
 
888
- ### Props
889
-
890
- | Prop | Type | Default | Description |
891
- |------|------|---------|-------------|
892
- | `images` | `ImageItem[]` | required | Array of images to display |
893
- | `initialIndex` | `number` | `0` | Index of the image to show first |
894
- | `inDialog` | `boolean` | `false` | Hide expand button (for nested usage) |
895
-
896
- ### Keyboard Shortcuts
193
+ GFM, soft line breaks, smart typography, emoji shortcodes, sanitized HTML, mermaid blocks, syntax-highlighted code, declarative `linkRules` for custom URL schemes (e.g. `cmdop://`, `vehicle://`). Details: [`MarkdownMessage/README.md`](src/components/markdown/MarkdownMessage/README.md).
897
194
 
898
- | Key | Action |
899
- |-----|--------|
900
- | `+` / `=` | Zoom in |
901
- | `-` | Zoom out |
902
- | `0` | Reset to fit |
903
- | `R` | Rotate 90° |
904
- | `←` | Previous image (gallery) |
905
- | `→` | Next image (gallery) |
195
+ ---
906
196
 
907
- ## JSON Tree
197
+ ## Map
908
198
 
909
- JSON visualization with three display modes:
199
+ MapLibre GL maps with markers, clusters, layers, drawing, geocoding. Subpath: `@djangocfg/ui-tools/map`. Details: [`Map/README.md`](src/tools/Map/README.md).
910
200
 
911
201
  ```tsx
912
- import { LazyJsonTree } from '@djangocfg/ui-tools';
913
-
914
- // Full mode (default) - with toolbar (Expand All, Copy, Download)
915
- <LazyJsonTree data={obj} mode="full" />
916
-
917
- // Compact mode - no toolbar, subtle border
918
- <LazyJsonTree data={obj} mode="compact" />
202
+ import { MapContainer, MapMarker } from '@djangocfg/ui-tools/map';
919
203
 
920
- // Inline mode - minimal, no border, for embedding
921
- <LazyJsonTree data={obj} mode="inline" />
204
+ <MapContainer style="streets" center={[37.6, 55.7]} zoom={10}>
205
+ <MapMarker position={[37.6, 55.7]} />
206
+ </MapContainer>
922
207
  ```
923
208
 
924
- | Mode | Toolbar | Border | Use Case |
925
- |------|---------|--------|----------|
926
- | `full` | Yes | Yes | Standalone viewer |
927
- | `compact` | No | Subtle | Cards, panels |
928
- | `inline` | No | No | Embedded in lists, logs |
929
-
930
- ## Lazy Loading
931
-
932
- All heavy tools have unified lazy-loaded versions with built-in Suspense fallbacks:
209
+ Optional drawing/geocoding peers: `pnpm add mapbox-gl-draw geocoder-control`.
933
210
 
934
- ```tsx
935
- import {
936
- LazyMapContainer, // ~800KB
937
- LazyMermaid, // ~800KB
938
- LazyPrettyCode, // ~500KB
939
- LazyOpenapiViewer, // ~400KB
940
- LazyJsonSchemaForm, // ~300KB
941
- LazyLottiePlayer, // ~200KB
942
- LazyAudioPlayer, // ~80KB — WebView-safe v6 player
943
- LazyVideoPlayer, // ~150KB
944
- LazyJsonTree, // ~100KB
945
- LazyImageViewer, // ~50KB
946
- LazyCronScheduler, // ~15KB
947
- } from '@djangocfg/ui-tools';
211
+ ---
948
212
 
949
- // Just use them - no Suspense wrapper needed!
950
- <LazyMermaid chart={diagram} />
951
- <LazyMapContainer initialViewport={viewport} />
952
- <LazyVideoPlayer src="/video.mp4" />
953
- ```
954
-
955
- ### Custom Lazy Components
213
+ ## Storybook
956
214
 
957
- Create your own lazy components with `createLazyComponent`:
215
+ Stories live next to each tool and are registered in [`src/stories/index.ts`](src/stories/index.ts):
958
216
 
959
- ```tsx
960
- import { createLazyComponent, CardLoadingFallback } from '@djangocfg/ui-tools';
961
-
962
- const LazyMyComponent = createLazyComponent(
963
- () => import('./MyHeavyComponent'),
964
- {
965
- displayName: 'LazyMyComponent',
966
- fallback: <CardLoadingFallback title="Loading..." minHeight={200} />,
967
- }
968
- );
217
+ ```bash
218
+ pnpm playground
969
219
  ```
970
220
 
971
- ### Loading Fallbacks
972
-
973
- Built-in fallback components for different use cases:
221
+ Open browser to the URL printed in the console. Categories: `Tools/<ToolName>` plus per-feature subsections for Chat and Map.
974
222
 
975
- | Component | Use Case |
976
- |-----------|----------|
977
- | `LoadingFallback` | Generic spinner with optional text |
978
- | `CardLoadingFallback` | Card-styled loading with title |
979
- | `MapLoadingFallback` | Map-specific with location icon |
980
- | `Spinner` | Simple spinning loader |
981
- | `LazyWrapper` | Suspense wrapper with configurable fallback |
223
+ ---
982
224
 
983
225
  ## Requirements
984
226
 
985
- - React >= 18 or >= 19
986
- - Tailwind CSS >= 4
987
- - Zustand >= 5
988
- - @djangocfg/ui-core (peer dependency)
989
-
990
- ### Optional Dependencies (for Map)
227
+ - React 18+ / React 19
228
+ - Tailwind CSS (or import `@djangocfg/ui-tools/dist.css`)
229
+ - Peer deps for individual tools auto-installed by your package manager.
991
230
 
992
- ```bash
993
- # For drawing tools
994
- pnpm add @mapbox/mapbox-gl-draw
995
-
996
- # For geocoding/search
997
- pnpm add @maplibre/maplibre-gl-geocoder
998
- ```
231
+ ---
999
232
 
1000
233
  ## License
1001
234
 
1002
- MIT
235
+ MIT — see [LICENSE](./LICENSE).