@fgv/ts-app-shell 5.1.0-4 → 5.1.0-40

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 (225) hide show
  1. package/README.md +182 -0
  2. package/dist/index.browser.js.map +1 -0
  3. package/dist/index.js.map +1 -0
  4. package/dist/packlets/ai-assist/index.js.map +1 -0
  5. package/dist/packlets/ai-assist/useAiAssist.js +225 -18
  6. package/dist/packlets/ai-assist/useAiAssist.js.map +1 -0
  7. package/dist/packlets/badge/index.js +23 -0
  8. package/dist/packlets/badge/index.js.map +1 -0
  9. package/dist/packlets/badge/renderStatusBadge.js +44 -0
  10. package/dist/packlets/badge/renderStatusBadge.js.map +1 -0
  11. package/dist/packlets/cascade/CascadeContainer.js.map +1 -0
  12. package/dist/packlets/cascade/ComparisonView.js.map +1 -0
  13. package/dist/packlets/cascade/EntityTabLayout.js.map +1 -0
  14. package/dist/packlets/cascade/MobileCascadeStack.js.map +1 -0
  15. package/dist/packlets/cascade/index.js.map +1 -0
  16. package/dist/packlets/cascade/model.js.map +1 -0
  17. package/dist/packlets/cascade/useCascadeOps.js.map +1 -0
  18. package/dist/packlets/cascade/useCascadeTransitions.js.map +1 -0
  19. package/dist/packlets/detail/DetailHelpers.js.map +1 -0
  20. package/dist/packlets/detail/index.js.map +1 -0
  21. package/dist/packlets/drop-zone/JsonDropZone.js.map +1 -0
  22. package/dist/packlets/drop-zone/index.js.map +1 -0
  23. package/dist/packlets/editing/EditFieldHelpers.js +4 -4
  24. package/dist/packlets/editing/EditFieldHelpers.js.map +1 -0
  25. package/dist/packlets/editing/MultiActionButton.js.map +1 -0
  26. package/dist/packlets/editing/NumericInput.js.map +1 -0
  27. package/dist/packlets/editing/TypeaheadInput.js.map +1 -0
  28. package/dist/packlets/editing/index.js.map +1 -0
  29. package/dist/packlets/editing/useTypeaheadMatch.js.map +1 -0
  30. package/dist/packlets/keyboard/index.js.map +1 -0
  31. package/dist/packlets/keyboard/registry.js.map +1 -0
  32. package/dist/packlets/keyboard/useKeyboardShortcuts.js.map +1 -0
  33. package/dist/packlets/messages/MessagesContext.js +79 -3
  34. package/dist/packlets/messages/MessagesContext.js.map +1 -0
  35. package/dist/packlets/messages/MessagesLogger.js +10 -31
  36. package/dist/packlets/messages/MessagesLogger.js.map +1 -0
  37. package/dist/packlets/messages/StatusBar.js +53 -28
  38. package/dist/packlets/messages/StatusBar.js.map +1 -0
  39. package/dist/packlets/messages/Toast.js +7 -4
  40. package/dist/packlets/messages/Toast.js.map +1 -0
  41. package/dist/packlets/messages/index.js +2 -2
  42. package/dist/packlets/messages/index.js.map +1 -0
  43. package/dist/packlets/messages/model.js +31 -4
  44. package/dist/packlets/messages/model.js.map +1 -0
  45. package/dist/packlets/messages/useLogReporter.js.map +1 -0
  46. package/dist/packlets/modal/ConfirmDialog.js.map +1 -0
  47. package/dist/packlets/modal/Modal.js.map +1 -0
  48. package/dist/packlets/modal/index.js.map +1 -0
  49. package/dist/packlets/print/PrintEnclosure.js.map +1 -0
  50. package/dist/packlets/print/index.js.map +1 -0
  51. package/dist/packlets/print/openPrintWindow.js.map +1 -0
  52. package/dist/packlets/responsive/ResponsiveProvider.js.map +1 -0
  53. package/dist/packlets/responsive/index.js.map +1 -0
  54. package/dist/packlets/responsive/useResponsiveLayout.js.map +1 -0
  55. package/dist/packlets/selectors/EntityRow.js.map +1 -0
  56. package/dist/packlets/selectors/PreferredSelector.js.map +1 -0
  57. package/dist/packlets/selectors/index.js.map +1 -0
  58. package/dist/packlets/sidebar/CollectionSection.js +158 -43
  59. package/dist/packlets/sidebar/CollectionSection.js.map +1 -0
  60. package/dist/packlets/sidebar/EntityList.js.map +1 -0
  61. package/dist/packlets/sidebar/FilterBar.js.map +1 -0
  62. package/dist/packlets/sidebar/FilterRow.js.map +1 -0
  63. package/dist/packlets/sidebar/GroupedEntityList.js.map +1 -0
  64. package/dist/packlets/sidebar/SearchBar.js.map +1 -0
  65. package/dist/packlets/sidebar/SidebarLayout.js.map +1 -0
  66. package/dist/packlets/sidebar/index.js.map +1 -0
  67. package/dist/packlets/theme/ThemeProvider.js.map +1 -0
  68. package/dist/packlets/theme/index.js.map +1 -0
  69. package/dist/packlets/top-bar/ModeSelector.js.map +1 -0
  70. package/dist/packlets/top-bar/TabBar.js +4 -1
  71. package/dist/packlets/top-bar/TabBar.js.map +1 -0
  72. package/dist/packlets/top-bar/index.js.map +1 -0
  73. package/dist/packlets/url-sync/index.js.map +1 -0
  74. package/dist/packlets/url-sync/useUrlSync.js.map +1 -0
  75. package/dist/styles/theme.css +194 -0
  76. package/lib/index.browser.d.ts.map +1 -0
  77. package/lib/index.browser.js.map +1 -0
  78. package/lib/index.d.ts.map +1 -0
  79. package/lib/index.js.map +1 -0
  80. package/lib/packlets/ai-assist/index.d.ts.map +1 -0
  81. package/lib/packlets/ai-assist/index.js.map +1 -0
  82. package/lib/packlets/ai-assist/useAiAssist.d.ts +26 -0
  83. package/lib/packlets/ai-assist/useAiAssist.d.ts.map +1 -0
  84. package/lib/packlets/ai-assist/useAiAssist.js +225 -18
  85. package/lib/packlets/ai-assist/useAiAssist.js.map +1 -0
  86. package/lib/packlets/badge/index.d.ts +2 -0
  87. package/lib/packlets/badge/index.d.ts.map +1 -0
  88. package/lib/packlets/badge/index.js +27 -0
  89. package/lib/packlets/badge/index.js.map +1 -0
  90. package/lib/packlets/badge/renderStatusBadge.d.ts +11 -0
  91. package/lib/packlets/badge/renderStatusBadge.d.ts.map +1 -0
  92. package/lib/packlets/badge/renderStatusBadge.js +50 -0
  93. package/lib/packlets/badge/renderStatusBadge.js.map +1 -0
  94. package/lib/packlets/cascade/CascadeContainer.d.ts.map +1 -0
  95. package/lib/packlets/cascade/CascadeContainer.js.map +1 -0
  96. package/lib/packlets/cascade/ComparisonView.d.ts.map +1 -0
  97. package/lib/packlets/cascade/ComparisonView.js.map +1 -0
  98. package/lib/packlets/cascade/EntityTabLayout.d.ts.map +1 -0
  99. package/lib/packlets/cascade/EntityTabLayout.js.map +1 -0
  100. package/lib/packlets/cascade/MobileCascadeStack.d.ts.map +1 -0
  101. package/lib/packlets/cascade/MobileCascadeStack.js.map +1 -0
  102. package/lib/packlets/cascade/index.d.ts.map +1 -0
  103. package/lib/packlets/cascade/index.js.map +1 -0
  104. package/lib/packlets/cascade/model.d.ts.map +1 -0
  105. package/lib/packlets/cascade/model.js.map +1 -0
  106. package/lib/packlets/cascade/useCascadeOps.d.ts.map +1 -0
  107. package/lib/packlets/cascade/useCascadeOps.js.map +1 -0
  108. package/lib/packlets/cascade/useCascadeTransitions.d.ts.map +1 -0
  109. package/lib/packlets/cascade/useCascadeTransitions.js.map +1 -0
  110. package/lib/packlets/detail/DetailHelpers.d.ts.map +1 -0
  111. package/lib/packlets/detail/DetailHelpers.js.map +1 -0
  112. package/lib/packlets/detail/index.d.ts.map +1 -0
  113. package/lib/packlets/detail/index.js.map +1 -0
  114. package/lib/packlets/drop-zone/JsonDropZone.d.ts.map +1 -0
  115. package/lib/packlets/drop-zone/JsonDropZone.js.map +1 -0
  116. package/lib/packlets/drop-zone/index.d.ts.map +1 -0
  117. package/lib/packlets/drop-zone/index.js.map +1 -0
  118. package/lib/packlets/editing/EditFieldHelpers.d.ts +6 -2
  119. package/lib/packlets/editing/EditFieldHelpers.d.ts.map +1 -0
  120. package/lib/packlets/editing/EditFieldHelpers.js +4 -4
  121. package/lib/packlets/editing/EditFieldHelpers.js.map +1 -0
  122. package/lib/packlets/editing/MultiActionButton.d.ts.map +1 -0
  123. package/lib/packlets/editing/MultiActionButton.js.map +1 -0
  124. package/lib/packlets/editing/NumericInput.d.ts.map +1 -0
  125. package/lib/packlets/editing/NumericInput.js.map +1 -0
  126. package/lib/packlets/editing/TypeaheadInput.d.ts.map +1 -0
  127. package/lib/packlets/editing/TypeaheadInput.js.map +1 -0
  128. package/lib/packlets/editing/index.d.ts.map +1 -0
  129. package/lib/packlets/editing/index.js.map +1 -0
  130. package/lib/packlets/editing/useTypeaheadMatch.d.ts.map +1 -0
  131. package/lib/packlets/editing/useTypeaheadMatch.js.map +1 -0
  132. package/lib/packlets/keyboard/index.d.ts.map +1 -0
  133. package/lib/packlets/keyboard/index.js.map +1 -0
  134. package/lib/packlets/keyboard/registry.d.ts.map +1 -0
  135. package/lib/packlets/keyboard/registry.js.map +1 -0
  136. package/lib/packlets/keyboard/useKeyboardShortcuts.d.ts.map +1 -0
  137. package/lib/packlets/keyboard/useKeyboardShortcuts.js.map +1 -0
  138. package/lib/packlets/messages/MessagesContext.d.ts +47 -3
  139. package/lib/packlets/messages/MessagesContext.d.ts.map +1 -0
  140. package/lib/packlets/messages/MessagesContext.js +79 -2
  141. package/lib/packlets/messages/MessagesContext.js.map +1 -0
  142. package/lib/packlets/messages/MessagesLogger.d.ts +8 -3
  143. package/lib/packlets/messages/MessagesLogger.d.ts.map +1 -0
  144. package/lib/packlets/messages/MessagesLogger.js +10 -31
  145. package/lib/packlets/messages/MessagesLogger.js.map +1 -0
  146. package/lib/packlets/messages/StatusBar.d.ts +10 -1
  147. package/lib/packlets/messages/StatusBar.d.ts.map +1 -0
  148. package/lib/packlets/messages/StatusBar.js +52 -27
  149. package/lib/packlets/messages/StatusBar.js.map +1 -0
  150. package/lib/packlets/messages/Toast.d.ts.map +1 -0
  151. package/lib/packlets/messages/Toast.js +6 -3
  152. package/lib/packlets/messages/Toast.js.map +1 -0
  153. package/lib/packlets/messages/index.d.ts +2 -2
  154. package/lib/packlets/messages/index.d.ts.map +1 -0
  155. package/lib/packlets/messages/index.js +6 -1
  156. package/lib/packlets/messages/index.js.map +1 -0
  157. package/lib/packlets/messages/model.d.ts +74 -8
  158. package/lib/packlets/messages/model.d.ts.map +1 -0
  159. package/lib/packlets/messages/model.js +32 -4
  160. package/lib/packlets/messages/model.js.map +1 -0
  161. package/lib/packlets/messages/useLogReporter.d.ts.map +1 -0
  162. package/lib/packlets/messages/useLogReporter.js.map +1 -0
  163. package/lib/packlets/modal/ConfirmDialog.d.ts.map +1 -0
  164. package/lib/packlets/modal/ConfirmDialog.js.map +1 -0
  165. package/lib/packlets/modal/Modal.d.ts.map +1 -0
  166. package/lib/packlets/modal/Modal.js.map +1 -0
  167. package/lib/packlets/modal/index.d.ts.map +1 -0
  168. package/lib/packlets/modal/index.js.map +1 -0
  169. package/lib/packlets/print/PrintEnclosure.d.ts.map +1 -0
  170. package/lib/packlets/print/PrintEnclosure.js.map +1 -0
  171. package/lib/packlets/print/index.d.ts.map +1 -0
  172. package/lib/packlets/print/index.js.map +1 -0
  173. package/lib/packlets/print/openPrintWindow.d.ts.map +1 -0
  174. package/lib/packlets/print/openPrintWindow.js.map +1 -0
  175. package/lib/packlets/responsive/ResponsiveProvider.d.ts.map +1 -0
  176. package/lib/packlets/responsive/ResponsiveProvider.js.map +1 -0
  177. package/lib/packlets/responsive/index.d.ts.map +1 -0
  178. package/lib/packlets/responsive/index.js.map +1 -0
  179. package/lib/packlets/responsive/useResponsiveLayout.d.ts.map +1 -0
  180. package/lib/packlets/responsive/useResponsiveLayout.js.map +1 -0
  181. package/lib/packlets/selectors/EntityRow.d.ts.map +1 -0
  182. package/lib/packlets/selectors/EntityRow.js.map +1 -0
  183. package/lib/packlets/selectors/PreferredSelector.d.ts.map +1 -0
  184. package/lib/packlets/selectors/PreferredSelector.js.map +1 -0
  185. package/lib/packlets/selectors/index.d.ts.map +1 -0
  186. package/lib/packlets/selectors/index.js.map +1 -0
  187. package/lib/packlets/sidebar/CollectionSection.d.ts +40 -2
  188. package/lib/packlets/sidebar/CollectionSection.d.ts.map +1 -0
  189. package/lib/packlets/sidebar/CollectionSection.js +157 -42
  190. package/lib/packlets/sidebar/CollectionSection.js.map +1 -0
  191. package/lib/packlets/sidebar/EntityList.d.ts.map +1 -0
  192. package/lib/packlets/sidebar/EntityList.js.map +1 -0
  193. package/lib/packlets/sidebar/FilterBar.d.ts.map +1 -0
  194. package/lib/packlets/sidebar/FilterBar.js.map +1 -0
  195. package/lib/packlets/sidebar/FilterRow.d.ts.map +1 -0
  196. package/lib/packlets/sidebar/FilterRow.js.map +1 -0
  197. package/lib/packlets/sidebar/GroupedEntityList.d.ts.map +1 -0
  198. package/lib/packlets/sidebar/GroupedEntityList.js.map +1 -0
  199. package/lib/packlets/sidebar/SearchBar.d.ts.map +1 -0
  200. package/lib/packlets/sidebar/SearchBar.js.map +1 -0
  201. package/lib/packlets/sidebar/SidebarLayout.d.ts.map +1 -0
  202. package/lib/packlets/sidebar/SidebarLayout.js.map +1 -0
  203. package/lib/packlets/sidebar/index.d.ts +1 -1
  204. package/lib/packlets/sidebar/index.d.ts.map +1 -0
  205. package/lib/packlets/sidebar/index.js.map +1 -0
  206. package/lib/packlets/theme/ThemeProvider.d.ts.map +1 -0
  207. package/lib/packlets/theme/ThemeProvider.js.map +1 -0
  208. package/lib/packlets/theme/index.d.ts.map +1 -0
  209. package/lib/packlets/theme/index.js.map +1 -0
  210. package/lib/packlets/top-bar/ModeSelector.d.ts.map +1 -0
  211. package/lib/packlets/top-bar/ModeSelector.js.map +1 -0
  212. package/lib/packlets/top-bar/TabBar.d.ts +12 -0
  213. package/lib/packlets/top-bar/TabBar.d.ts.map +1 -0
  214. package/lib/packlets/top-bar/TabBar.js +4 -1
  215. package/lib/packlets/top-bar/TabBar.js.map +1 -0
  216. package/lib/packlets/top-bar/index.d.ts +1 -1
  217. package/lib/packlets/top-bar/index.d.ts.map +1 -0
  218. package/lib/packlets/top-bar/index.js.map +1 -0
  219. package/lib/packlets/url-sync/index.d.ts.map +1 -0
  220. package/lib/packlets/url-sync/index.js.map +1 -0
  221. package/lib/packlets/url-sync/useUrlSync.d.ts.map +1 -0
  222. package/lib/packlets/url-sync/useUrlSync.js.map +1 -0
  223. package/lib/styles/theme.css +194 -0
  224. package/package.json +11 -7
  225. package/tailwind-preset.cjs +102 -0
package/README.md CHANGED
@@ -2,6 +2,29 @@
2
2
 
3
3
  Shared React UI primitives for application shells in the `@fgv` monorepo.
4
4
 
5
+ > ⚠️ **Required setup** — this package ships its components as Tailwind-classed JSX. You
6
+ > must add the `@fgv/ts-app-shell` content path to your Tailwind config, or icons and
7
+ > overlays in the messages panel and other primitives will render unsized (invisible
8
+ > buttons, viewport-overflowing absolute overlays). See [Setup](#setup) below; the
9
+ > `MessagesProvider` self-diagnoses this configuration gap on mount and surfaces a
10
+ > warning message linking back here.
11
+
12
+ ## Setup
13
+
14
+ See the [Theming](#theming) section for the canonical setup steps:
15
+
16
+ 1. Add the Tailwind preset and include `./node_modules/@fgv/ts-app-shell/lib/**/*.{js,jsx}`
17
+ in your `content` array.
18
+ 2. Import `@fgv/ts-app-shell/theme.css` once in your application entry point.
19
+
20
+ ## Troubleshooting
21
+
22
+ **Icons in the messages panel are invisible or oversized, or you see an
23
+ "@fgv/ts-app-shell styles are not fully loaded" warning message.** Your Tailwind config
24
+ is missing the `@fgv/ts-app-shell` content path. See [Setup](#setup) above and make sure
25
+ `./node_modules/@fgv/ts-app-shell/lib/**/*.{js,jsx}` appears in your `tailwind.config.js`
26
+ `content` array.
27
+
5
28
  ## Features
6
29
 
7
30
  - **Column Cascade** — Master-detail drill-down with horizontal scrolling, breadcrumb navigation, and min-width columns
@@ -10,6 +33,7 @@ Shared React UI primitives for application shells in the `@fgv` monorepo.
10
33
  - **Log Message Panel** — Collapsible bottom panel with severity-filtered message history
11
34
  - **Command Palette** — Cmd+K quick navigation overlay
12
35
  - **Keybinding Registry** — Infrastructure for registering and managing keyboard shortcuts
36
+ - **Default Theme** — CSS-variable tokens + Tailwind preset for all semantic colors (light + dark)
13
37
 
14
38
  ## Usage
15
39
 
@@ -17,6 +41,164 @@ Shared React UI primitives for application shells in the `@fgv` monorepo.
17
41
  import { /* components */ } from '@fgv/ts-app-shell';
18
42
  ```
19
43
 
44
+ ## Theming
45
+
46
+ All visual components use custom Tailwind semantic tokens (e.g. `bg-surface`, `text-primary`,
47
+ `bg-status-error-bg`). To make them render correctly in your application, add the Tailwind
48
+ preset and import the base CSS once.
49
+
50
+ ### 1. Add the Tailwind preset
51
+
52
+ In your `tailwind.config.js` (or `tailwind.config.ts`):
53
+
54
+ ```js
55
+ const appShellPreset = require('@fgv/ts-app-shell/tailwind-preset');
56
+
57
+ module.exports = {
58
+ presets: [appShellPreset],
59
+ // Make sure Tailwind scans the package's components for class names:
60
+ content: [
61
+ './src/**/*.{ts,tsx}',
62
+ './node_modules/@fgv/ts-app-shell/lib/**/*.{js,jsx}',
63
+ ],
64
+ // ...rest of your config
65
+ };
66
+ ```
67
+
68
+ ### 2. Import the base CSS
69
+
70
+ In your application entry point (e.g. `main.tsx` or `index.css`):
71
+
72
+ ```ts
73
+ import '@fgv/ts-app-shell/theme.css';
74
+ ```
75
+
76
+ ### 3. Toggle dark mode
77
+
78
+ The preset uses Tailwind's `darkMode: 'class'` strategy. Add the class `dark` to any ancestor
79
+ element (typically `<html>`) to activate dark mode. The `ThemeProvider` component does this
80
+ automatically when the user selects `'dark'` or `'system'` (if the OS prefers dark):
81
+
82
+ ```tsx
83
+ import { ThemeProvider } from '@fgv/ts-app-shell';
84
+
85
+ <ThemeProvider initialTheme="system" onThemeChange={saveTheme}>
86
+ <App />
87
+ </ThemeProvider>
88
+ ```
89
+
90
+ ### Overriding tokens
91
+
92
+ Every token is a CSS custom property under the `--fgv-` prefix. Override any subset in your
93
+ own CSS after importing `theme.css`:
94
+
95
+ ```css
96
+ :root {
97
+ --fgv-brand-primary: #7c3aed; /* purple brand instead of blue */
98
+ --fgv-brand-accent: #a78bfa;
99
+ }
100
+ .dark {
101
+ --fgv-brand-primary: #8b5cf6;
102
+ --fgv-brand-accent: #c4b5fd;
103
+ }
104
+ ```
105
+
106
+ ## Complete Token Reference
107
+
108
+ ### Surfaces
109
+
110
+ | Token name | CSS variable | Light default | Dark default | Purpose |
111
+ |---|---|---|---|---|
112
+ | `surface` | `--fgv-surface` | `#ffffff` | `#1f2937` | Base page / panel background |
113
+ | `surface-raised` | `--fgv-surface-raised` | `#f3f4f6` | `#374151` | Elevated cards, filter toolbars |
114
+ | `surface-alt` | `--fgv-surface-alt` | `#f9fafb` | `#111827` | Alternate / zebra row background |
115
+ | `backdrop` | `--fgv-backdrop` | `rgba(0,0,0,0.45)` | `rgba(0,0,0,0.65)` | Modal/dialog overlay |
116
+ | `hover` | `--fgv-hover` | `#f3f4f6` | `#374151` | Row hover highlight |
117
+ | `selected` | `--fgv-selected` | `#eff6ff` | `#1e3a5f` | Selected list item background |
118
+
119
+ ### Text
120
+
121
+ | Token name | CSS variable | Light | Dark | Purpose |
122
+ |---|---|---|---|---|
123
+ | `primary` | `--fgv-text-primary` | `#111827` | `#f9fafb` | High-emphasis body text (WCAG AA+) |
124
+ | `secondary` | `--fgv-text-secondary` | `#374151` | `#d1d5db` | Medium-emphasis labels |
125
+ | `muted` | `--fgv-text-muted` | `#6b7280` | `#9ca3af` | Supporting / helper text |
126
+ | `faint` | `--fgv-text-faint` | `#9ca3af` | `#6b7280` | Ghost / ornamental text |
127
+ | `star` | `--fgv-text-star` | `#d97706` | `#fbbf24` | Star / favorite icon color |
128
+
129
+ ### Borders & Focus
130
+
131
+ | Token name | CSS variable | Light | Dark | Purpose |
132
+ |---|---|---|---|---|
133
+ | `border` | `--fgv-border` | `#d1d5db` | `#4b5563` | Default input / divider border |
134
+ | `border-subtle` | `--fgv-border-subtle` | `#e5e7eb` | `#374151` | Subtle row separator |
135
+ | `focus-ring` | `--fgv-focus-ring` | `#3b82f6` | `#60a5fa` | Keyboard focus ring |
136
+ | `selected-border` | `--fgv-selected-border` | `#2563eb` | `#3b82f6` | Selected item left-border accent |
137
+
138
+ ### Brand
139
+
140
+ | Token name | CSS variable | Light | Dark | Purpose |
141
+ |---|---|---|---|---|
142
+ | `brand-primary` | `--fgv-brand-primary` | `#2563eb` | `#3b82f6` | Primary action / active nav |
143
+ | `brand-secondary` | `--fgv-brand-secondary` | `#1d4ed8` | `#1e3a5f` | Pressed / dark top-bar variant |
144
+ | `brand-accent` | `--fgv-brand-accent` | `#3b82f6` | `#60a5fa` | Hover text, checkmarks, accents |
145
+
146
+ ### Status: Error
147
+
148
+ | Token name | CSS variable | Purpose |
149
+ |---|---|---|
150
+ | `status-error-bg` | `--fgv-status-error-bg` | Error row / badge background |
151
+ | `status-error-surface` | `--fgv-status-error-surface` | Hover background on error items |
152
+ | `status-error-btn` | `--fgv-status-error-btn` | Danger action button fill |
153
+ | `status-error-btn-hover` | `--fgv-status-error-btn-hover` | Danger button hover fill |
154
+ | `status-error-icon` | `--fgv-status-error-icon` | Error icon / dot color |
155
+ | `status-error-border` | `--fgv-status-error-border` | Error section border |
156
+ | `status-error-accent` | `--fgv-status-error-accent` | Error focus ring |
157
+ | `status-error-text` | `--fgv-status-error-text` | Error message text |
158
+ | `status-error-strong` | `--fgv-status-error-strong` | High-emphasis error text |
159
+
160
+ ### Status: Warning
161
+
162
+ | Token name | CSS variable | Purpose |
163
+ |---|---|---|
164
+ | `status-warning-bg` | `--fgv-status-warning-bg` | Warning row / badge background |
165
+ | `status-warning-surface` | `--fgv-status-warning-surface` | Hover background on warning items |
166
+ | `status-warning-btn` | `--fgv-status-warning-btn` | Warning action button fill |
167
+ | `status-warning-btn-hover` | `--fgv-status-warning-btn-hover` | Warning button hover fill |
168
+ | `status-warning-icon` | `--fgv-status-warning-icon` | Warning icon / dot color |
169
+ | `status-warning-border` | `--fgv-status-warning-border` | Warning section border |
170
+ | `status-warning-accent` | `--fgv-status-warning-accent` | Warning focus ring |
171
+ | `status-warning-text` | `--fgv-status-warning-text` | Warning message text |
172
+ | `status-warning-strong` | `--fgv-status-warning-strong` | High-emphasis warning text |
173
+
174
+ ### Status: Info
175
+
176
+ | Token name | CSS variable | Purpose |
177
+ |---|---|---|
178
+ | `status-info-bg` | `--fgv-status-info-bg` | Info row / badge background |
179
+ | `status-info-surface` | `--fgv-status-info-surface` | Hover background on info items |
180
+ | `status-info-btn` | `--fgv-status-info-btn` | Info action button fill |
181
+ | `status-info-btn-hover` | `--fgv-status-info-btn-hover` | Info button hover fill |
182
+ | `status-info-icon` | `--fgv-status-info-icon` | Info icon / dot color |
183
+ | `status-info-border` | `--fgv-status-info-border` | Info section border |
184
+ | `status-info-accent` | `--fgv-status-info-accent` | Info focus ring |
185
+ | `status-info-text` | `--fgv-status-info-text` | Info message text |
186
+ | `status-info-strong` | `--fgv-status-info-strong` | High-emphasis info text |
187
+
188
+ ### Status: Success
189
+
190
+ | Token name | CSS variable | Purpose |
191
+ |---|---|---|
192
+ | `status-success-bg` | `--fgv-status-success-bg` | Success row / badge background |
193
+ | `status-success-surface` | `--fgv-status-success-surface` | Hover background on success items |
194
+ | `status-success-btn` | `--fgv-status-success-btn` | Success action button fill |
195
+ | `status-success-btn-hover` | `--fgv-status-success-btn-hover` | Success button hover fill |
196
+ | `status-success-icon` | `--fgv-status-success-icon` | Success icon / dot color |
197
+ | `status-success-border` | `--fgv-status-success-border` | Success section border |
198
+ | `status-success-accent` | `--fgv-status-success-accent` | Success focus ring |
199
+ | `status-success-text` | `--fgv-status-success-text` | Success message text |
200
+ | `status-success-strong` | `--fgv-status-success-strong` | High-emphasis success text |
201
+
20
202
  ## Development
21
203
 
22
204
  ```bash
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.browser.js","sourceRoot":"","sources":["../src/index.browser.ts"],"names":[],"mappings":"AAAA,8DAA8D;AAC9D,cAAc,SAAS,CAAC","sourcesContent":["// Browser entry point - re-exports everything from main index\nexport * from './index';\n"]}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,qBAAqB;AACrB,cAAc,oBAAoB,CAAC;AAEnC,wDAAwD;AACxD,cAAc,qBAAqB,CAAC;AAEpC,mDAAmD;AACnD,cAAc,qBAAqB,CAAC;AAEpC,eAAe;AACf,cAAc,kBAAkB,CAAC;AAEjC,6BAA6B;AAC7B,cAAc,qBAAqB,CAAC;AAEpC,mDAAmD;AACnD,cAAc,oBAAoB,CAAC;AAEnC,2BAA2B;AAC3B,cAAc,oBAAoB,CAAC;AAEnC,wCAAwC;AACxC,cAAc,sBAAsB,CAAC;AAErC,uEAAuE;AACvE,cAAc,sBAAsB,CAAC;AAErC,mDAAmD;AACnD,cAAc,oBAAoB,CAAC;AAEnC,2CAA2C;AAC3C,cAAc,mBAAmB,CAAC;AAElC,yCAAyC;AACzC,cAAc,sBAAsB,CAAC;AAErC,+BAA+B;AAC/B,cAAc,kBAAkB,CAAC;AAEjC,0BAA0B;AAC1B,cAAc,kBAAkB,CAAC;AAEjC,0CAA0C;AAC1C,cAAc,uBAAuB,CAAC","sourcesContent":["/**\n * \\@fgv/ts-app-shell - Shared React UI primitives for application shells.\n *\n * Provides reusable components for:\n * - Column cascade (master-detail drill-down)\n * - Compact sidebar with flyout filter selectors\n * - Toast notifications and log message panel\n * - Command palette\n * - Keybinding registry\n *\n * @packageDocumentation\n */\n\n// Top bar components\nexport * from './packlets/top-bar';\n\n// Messages infrastructure (context, toasts, status bar)\nexport * from './packlets/messages';\n\n// URL hash synchronization for mode/tab navigation\nexport * from './packlets/url-sync';\n\n// Modal dialog\nexport * from './packlets/modal';\n\n// Keyboard shortcut registry\nexport * from './packlets/keyboard';\n\n// Sidebar layout, search, filters, and entity list\nexport * from './packlets/sidebar';\n\n// Column cascade container\nexport * from './packlets/cascade';\n\n// Selectors (PreferredSelector popover)\nexport * from './packlets/selectors';\n\n// JSON drop zone (generic drop/paste target with converter validation)\nexport * from './packlets/drop-zone';\n\n// Generic edit field primitives for entity editors\nexport * from './packlets/editing';\n\n// Generic detail-view primitive components\nexport * from './packlets/detail';\n\n// AI assist hook (generic, context-free)\nexport * from './packlets/ai-assist';\n\n// Print popup window enclosure\nexport * from './packlets/print';\n\n// Theme provider and hook\nexport * from './packlets/theme';\n\n// Responsive layout detection and context\nexport * from './packlets/responsive';\n"]}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/packlets/ai-assist/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EACL,qBAAqB,EACrB,WAAW,EAKZ,MAAM,eAAe,CAAC","sourcesContent":["/**\n * AI assist packlet — generic hook for AI-assisted entity generation.\n * @packageDocumentation\n */\n\nexport {\n checkForAiErrorObject,\n useAiAssist,\n type IAiAssistAction,\n type IAiAssistResult,\n type IUseAiAssistParams,\n type IUseAiAssistResult\n} from './useAiAssist';\n"]}
@@ -19,6 +19,13 @@
19
19
  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20
20
  * SOFTWARE.
21
21
  */
22
+ var __asyncValues = (this && this.__asyncValues) || function (o) {
23
+ if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
24
+ var m = o[Symbol.asyncIterator], i;
25
+ return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i);
26
+ function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }
27
+ function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }
28
+ };
22
29
  /**
23
30
  * Generic AI assist hook — parameterized by settings and keystore, no app-specific context.
24
31
  *
@@ -33,6 +40,35 @@ import { fail, succeed } from '@fgv/ts-utils';
33
40
  // ============================================================================
34
41
  // Helpers
35
42
  // ============================================================================
43
+ /**
44
+ * Resolves the API key for a dispatcher call, honoring `descriptor.needsSecret`.
45
+ *
46
+ * - When `descriptor.needsSecret === false` (e.g. ollama, openai-compat),
47
+ * returns the configured key if one happens to be set in the keystore, or
48
+ * an empty string otherwise. ts-extras omits the `Authorization` header
49
+ * entirely for empty keys, which self-hosted servers expect.
50
+ * - When `descriptor.needsSecret !== false`, requires `secretName`, an
51
+ * unlocked keystore, and a successful key fetch.
52
+ *
53
+ * @internal
54
+ */
55
+ function resolveApiKey(descriptor, providerConfig, keyStore) {
56
+ var _a;
57
+ const { provider, secretName } = providerConfig;
58
+ if (descriptor.needsSecret === false) {
59
+ if (!secretName || !(keyStore === null || keyStore === void 0 ? void 0 : keyStore.isUnlocked)) {
60
+ return succeed('');
61
+ }
62
+ return succeed((_a = keyStore.getApiKey(secretName).orDefault('')) !== null && _a !== void 0 ? _a : '');
63
+ }
64
+ if (!secretName) {
65
+ return fail(`Provider "${provider}" has no secret name configured`);
66
+ }
67
+ if (!keyStore) {
68
+ return fail('No keystore available');
69
+ }
70
+ return keyStore.getApiKey(secretName).withErrorFormat((m) => `Failed to get API key: ${m}`);
71
+ }
36
72
  /**
37
73
  * Checks whether a parsed AI response is an error object (with an "error" field)
38
74
  * rather than a valid entity. AI prompts instruct the model to return
@@ -72,11 +108,15 @@ export function useAiAssist(params) {
72
108
  ? settings.defaultProvider
73
109
  : (_c = (_b = providers[0]) === null || _b === void 0 ? void 0 : _b.provider) !== null && _c !== void 0 ? _c : 'copy-paste';
74
110
  return providers.map((config) => {
75
- var _a, _b;
111
+ var _a;
76
112
  let isAvailable = true;
77
113
  let unavailableReason;
78
- if (config.provider !== 'copy-paste') {
79
- // API-based providers need a secret name and unlocked keystore with that secret
114
+ const descriptor = AiAssist.getProviderDescriptor(config.provider).orDefault();
115
+ // Self-hosted / no-secret providers (e.g. ollama, openai-compat) skip
116
+ // the keystore check entirely — they're available as long as the
117
+ // descriptor exists. Providers with `needsSecret: true` still require
118
+ // a configured secretName and an unlocked keystore.
119
+ if (config.provider !== 'copy-paste' && (descriptor === null || descriptor === void 0 ? void 0 : descriptor.needsSecret) !== false) {
80
120
  if (!config.secretName) {
81
121
  isAvailable = false;
82
122
  unavailableReason = 'No API key secret configured';
@@ -97,7 +137,7 @@ export function useAiAssist(params) {
97
137
  }
98
138
  }
99
139
  }
100
- const label = (_b = (_a = AiAssist.getProviderDescriptor(config.provider).orDefault()) === null || _a === void 0 ? void 0 : _a.buttonLabel) !== null && _b !== void 0 ? _b : config.provider;
140
+ const label = (_a = descriptor === null || descriptor === void 0 ? void 0 : descriptor.buttonLabel) !== null && _a !== void 0 ? _a : config.provider;
101
141
  return {
102
142
  provider: config.provider,
103
143
  label,
@@ -127,16 +167,9 @@ export function useAiAssist(params) {
127
167
  return fail(descriptorResult.message);
128
168
  }
129
169
  const descriptor = descriptorResult.value;
130
- if (!providerConfig.secretName) {
131
- return fail(`Provider "${provider}" has no secret name configured`);
132
- }
133
- if (!keyStore) {
134
- return fail('No keystore available');
135
- }
136
- // Get API key from keystore
137
- const apiKeyResult = keyStore.getApiKey(providerConfig.secretName);
170
+ const apiKeyResult = resolveApiKey(descriptor, providerConfig, keyStore);
138
171
  if (apiKeyResult.isFailure()) {
139
- return fail(`Failed to get API key: ${apiKeyResult.message}`);
172
+ return fail(apiKeyResult.message);
140
173
  }
141
174
  setIsWorking(true);
142
175
  try {
@@ -145,13 +178,17 @@ export function useAiAssist(params) {
145
178
  for (let attempt = 0; attempt < maxAttempts; attempt++) {
146
179
  // Resolve effective tools: per-call override > settings defaults > none
147
180
  const effectiveTools = AiAssist.resolveEffectiveTools(descriptor, providerConfig.tools, tools);
148
- // Call the API — through proxy if configured, otherwise direct
181
+ // Call the API — through proxy if configured, otherwise direct.
182
+ // The unified request orders history before the current turn; the
183
+ // correction-retry turns naturally append after the current user turn.
184
+ const base = prompt.toRequest();
149
185
  const completionParams = {
150
186
  descriptor,
151
187
  apiKey: apiKeyResult.value,
152
- prompt,
153
- additionalMessages: correctionMessages.length > 0 ? correctionMessages : undefined,
188
+ system: base.system,
189
+ messages: [...base.messages, ...correctionMessages],
154
190
  modelOverride: providerConfig.model,
191
+ endpoint: providerConfig.endpoint,
155
192
  logger,
156
193
  tools: effectiveTools.length > 0 ? effectiveTools : undefined
157
194
  };
@@ -207,13 +244,183 @@ export function useAiAssist(params) {
207
244
  return fail(`AI response validation failed after ${maxAttempts} attempts: ${entityResult.message}`);
208
245
  }
209
246
  }
210
- // Unreachable, but TypeScript needs it
247
+ /* c8 ignore next 2 - unreachable: every loop iteration returns; TypeScript needs the trailing return */
211
248
  return fail('AI generation failed');
212
249
  }
213
250
  finally {
214
251
  setIsWorking(false);
215
252
  }
216
253
  }, [settings, keyStore, logger]);
217
- return { actions, isWorking, copyPrompt, generateDirect };
254
+ const generateImages = useCallback(async (provider, params, signal) => {
255
+ const providerConfig = settings === null || settings === void 0 ? void 0 : settings.providers.find((p) => p.provider === provider);
256
+ if (!providerConfig) {
257
+ return fail(`Provider "${provider}" not configured`);
258
+ }
259
+ const descriptorResult = AiAssist.getProviderDescriptor(provider);
260
+ if (descriptorResult.isFailure()) {
261
+ return fail(descriptorResult.message);
262
+ }
263
+ const descriptor = descriptorResult.value;
264
+ if (!AiAssist.supportsImageGeneration(descriptor)) {
265
+ return fail(`Provider "${provider}" does not support image generation`);
266
+ }
267
+ const apiKeyResult = resolveApiKey(descriptor, providerConfig, keyStore);
268
+ if (apiKeyResult.isFailure()) {
269
+ return fail(apiKeyResult.message);
270
+ }
271
+ setIsWorking(true);
272
+ try {
273
+ const requestParams = {
274
+ descriptor,
275
+ apiKey: apiKeyResult.value,
276
+ params,
277
+ modelOverride: providerConfig.model,
278
+ endpoint: providerConfig.endpoint,
279
+ logger,
280
+ signal
281
+ };
282
+ const useProxy = !!(settings === null || settings === void 0 ? void 0 : settings.proxyUrl) && (settings.proxyAllProviders === true || descriptor.corsRestricted);
283
+ const result = useProxy
284
+ ? await AiAssist.callProxiedImageGeneration(settings.proxyUrl, requestParams)
285
+ : await AiAssist.callProviderImageGeneration(requestParams);
286
+ if (result.isFailure()) {
287
+ logger === null || logger === void 0 ? void 0 : logger.error(`AI image generation failed: ${result.message}`);
288
+ }
289
+ return result;
290
+ }
291
+ finally {
292
+ setIsWorking(false);
293
+ }
294
+ }, [settings, keyStore, logger]);
295
+ const listModels = useCallback(async (provider, capability, signal) => {
296
+ const providerConfig = settings === null || settings === void 0 ? void 0 : settings.providers.find((p) => p.provider === provider);
297
+ if (!providerConfig) {
298
+ return fail(`Provider "${provider}" not configured`);
299
+ }
300
+ const descriptorResult = AiAssist.getProviderDescriptor(provider);
301
+ if (descriptorResult.isFailure()) {
302
+ return fail(descriptorResult.message);
303
+ }
304
+ const descriptor = descriptorResult.value;
305
+ const apiKeyResult = resolveApiKey(descriptor, providerConfig, keyStore);
306
+ if (apiKeyResult.isFailure()) {
307
+ return fail(apiKeyResult.message);
308
+ }
309
+ const requestParams = Object.assign(Object.assign({ descriptor, apiKey: apiKeyResult.value }, (capability !== undefined ? { capability } : {})), { endpoint: providerConfig.endpoint, logger,
310
+ signal });
311
+ const useProxy = !!(settings === null || settings === void 0 ? void 0 : settings.proxyUrl) && (settings.proxyAllProviders === true || descriptor.corsRestricted);
312
+ const result = useProxy
313
+ ? await AiAssist.callProxiedListModels(settings.proxyUrl, requestParams)
314
+ : await AiAssist.callProviderListModels(requestParams);
315
+ if (result.isFailure()) {
316
+ logger === null || logger === void 0 ? void 0 : logger.error(`AI list-models failed: ${result.message}`);
317
+ }
318
+ return result;
319
+ }, [settings, keyStore, logger]);
320
+ const streamDirect = useCallback(async (provider, prompt, onEvent, options) => {
321
+ var _a, e_1, _b, _c;
322
+ var _d;
323
+ const providerConfig = settings === null || settings === void 0 ? void 0 : settings.providers.find((p) => p.provider === provider);
324
+ if (!providerConfig) {
325
+ return fail(`Provider "${provider}" not configured`);
326
+ }
327
+ const descriptorResult = AiAssist.getProviderDescriptor(provider);
328
+ if (descriptorResult.isFailure()) {
329
+ return fail(descriptorResult.message);
330
+ }
331
+ const descriptor = descriptorResult.value;
332
+ const apiKeyResult = resolveApiKey(descriptor, providerConfig, keyStore);
333
+ if (apiKeyResult.isFailure()) {
334
+ return fail(apiKeyResult.message);
335
+ }
336
+ const effectiveTools = AiAssist.resolveEffectiveTools(descriptor, providerConfig.tools, options === null || options === void 0 ? void 0 : options.tools);
337
+ // Order prior history before the current user turn (the unified request shape).
338
+ const base = prompt.toRequest();
339
+ const requestParams = {
340
+ descriptor,
341
+ apiKey: apiKeyResult.value,
342
+ system: base.system,
343
+ messages: [...((_d = options === null || options === void 0 ? void 0 : options.messagesBefore) !== null && _d !== void 0 ? _d : []), ...base.messages],
344
+ modelOverride: providerConfig.model,
345
+ endpoint: providerConfig.endpoint,
346
+ logger,
347
+ tools: effectiveTools.length > 0 ? effectiveTools : undefined,
348
+ signal: options === null || options === void 0 ? void 0 : options.signal
349
+ };
350
+ const useProxy = !!(settings === null || settings === void 0 ? void 0 : settings.proxyUrl) && (settings.proxyAllProviders === true || descriptor.streamingCorsRestricted);
351
+ const openResult = useProxy
352
+ ? await AiAssist.callProxiedCompletionStream(settings.proxyUrl, requestParams)
353
+ : await AiAssist.callProviderCompletionStream(requestParams);
354
+ if (openResult.isFailure()) {
355
+ logger === null || logger === void 0 ? void 0 : logger.error(`AI streaming failed to start: ${openResult.message}`);
356
+ return fail(openResult.message);
357
+ }
358
+ setIsWorking(true);
359
+ let fullText = '';
360
+ let truncated = false;
361
+ let terminalError;
362
+ let receivedTerminalEvent = false;
363
+ try {
364
+ try {
365
+ for (var _e = true, _f = __asyncValues(openResult.value), _g; _g = await _f.next(), _a = _g.done, !_a; _e = true) {
366
+ _c = _g.value;
367
+ _e = false;
368
+ const event = _c;
369
+ try {
370
+ onEvent(event);
371
+ }
372
+ catch (err) {
373
+ // Consumer callback threw (e.g. setState-after-unmount). Convert
374
+ // into a terminal failure so streamDirect always resolves to a
375
+ // Result instead of rejecting and breaking its contract.
376
+ terminalError = `AI stream event handler failed: ${err instanceof Error ? err.message : String(err)}`;
377
+ receivedTerminalEvent = true;
378
+ break;
379
+ }
380
+ if (event.type === 'text-delta') {
381
+ fullText += event.delta;
382
+ }
383
+ else if (event.type === 'done') {
384
+ fullText = event.fullText;
385
+ truncated = event.truncated;
386
+ receivedTerminalEvent = true;
387
+ }
388
+ else if (event.type === 'error') {
389
+ terminalError = event.message;
390
+ receivedTerminalEvent = true;
391
+ }
392
+ }
393
+ }
394
+ catch (e_1_1) { e_1 = { error: e_1_1 }; }
395
+ finally {
396
+ try {
397
+ if (!_e && !_a && (_b = _f.return)) await _b.call(_f);
398
+ }
399
+ finally { if (e_1) throw e_1.error; }
400
+ }
401
+ }
402
+ finally {
403
+ setIsWorking(false);
404
+ }
405
+ if (terminalError !== undefined) {
406
+ logger === null || logger === void 0 ? void 0 : logger.error(`AI streaming ended in error: ${terminalError}`);
407
+ return fail(terminalError);
408
+ }
409
+ if (!receivedTerminalEvent) {
410
+ const message = 'AI stream ended without a terminal done or error event';
411
+ logger === null || logger === void 0 ? void 0 : logger.error(message);
412
+ return fail(message);
413
+ }
414
+ return succeed({ fullText, truncated });
415
+ }, [settings, keyStore, logger]);
416
+ return {
417
+ actions,
418
+ isWorking,
419
+ copyPrompt,
420
+ generateDirect,
421
+ generateImages,
422
+ listModels,
423
+ streamDirect
424
+ };
218
425
  }
219
426
  //# sourceMappingURL=useAiAssist.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useAiAssist.js","sourceRoot":"","sources":["../../../src/packlets/ai-assist/useAiAssist.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * Copyright (c) 2026 Erik Fortune\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all\n * copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n * SOFTWARE.\n */\n\n/**\n * Generic AI assist hook — parameterized by settings and keystore, no app-specific context.\n *\n * Provides a list of available AI assist actions and functions for copy-paste\n * and direct API generation flows.\n *\n * @packageDocumentation\n */\n\nimport { useCallback, useMemo, useState } from 'react';\n\nimport { AiAssist } from '@fgv/ts-extras';\nimport { fail, type Logging, Result, succeed } from '@fgv/ts-utils';\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * Parameters for the generic useAiAssist hook.\n * @public\n */\nexport interface IUseAiAssistParams {\n /** Resolved AI assist settings (provider list + default). */\n readonly settings: AiAssist.IAiAssistSettings | undefined;\n /** Keystore for API key resolution (or undefined if no keystore). */\n readonly keyStore: AiAssist.IAiAssistKeyStore | undefined;\n /** Optional logger for request/response observability. */\n readonly logger?: Logging.ILogger;\n}\n\n/**\n * A single available AI assist action.\n * @public\n */\nexport interface IAiAssistAction {\n /** The provider identifier */\n readonly provider: AiAssist.AiProviderId;\n /** Display label (e.g. \"AI Assist | Copy\", \"AI Assist | Grok\") */\n readonly label: string;\n /** Whether this is the default (first) action */\n readonly isDefault: boolean;\n /** Whether the provider is currently available (keystore unlocked, secret present) */\n readonly isAvailable: boolean;\n /** Reason the provider is unavailable, if any */\n readonly unavailableReason?: string;\n}\n\n/**\n * Result of executing an AI assist action.\n * @public\n */\nexport interface IAiAssistResult<TEntity> {\n /** The generated entity */\n readonly entity: TEntity;\n /** Source indicator */\n readonly source: 'ai';\n}\n\n/**\n * Return type of the useAiAssist hook.\n * @public\n */\nexport interface IUseAiAssistResult {\n /** Available actions based on settings + keystore state */\n readonly actions: ReadonlyArray<IAiAssistAction>;\n /** Whether a direct assist call is in progress */\n readonly isWorking: boolean;\n /**\n * Execute a copy-paste action: copies the combined prompt to clipboard.\n * @returns Success with `'copied'`, or failure.\n */\n readonly copyPrompt: (prompt: AiAssist.AiPrompt) => Promise<Result<'copied'>>;\n /**\n * Execute a direct API action: calls the provider, validates the response, returns the entity.\n * @returns Success with the validated entity, or failure.\n */\n readonly generateDirect: <TEntity>(\n provider: AiAssist.AiProviderId,\n prompt: AiAssist.AiPrompt,\n convert: (from: unknown) => Result<TEntity>,\n tools?: ReadonlyArray<AiAssist.AiServerToolConfig>\n ) => Promise<Result<IAiAssistResult<TEntity>>>;\n /**\n * Execute an image-generation action: calls the provider's image API and\n * returns the generated images.\n * @returns Success with the image generation response, or failure.\n */\n readonly generateImages: (\n provider: AiAssist.AiProviderId,\n params: AiAssist.IAiImageGenerationParams,\n signal?: AbortSignal\n ) => Promise<Result<AiAssist.IAiImageGenerationResponse>>;\n /**\n * List the models available from a provider, optionally filtered by capability.\n * @returns Success with the resolved model list, or failure (no silent fallback).\n */\n readonly listModels: (\n provider: AiAssist.AiProviderId,\n capability?: AiAssist.AiModelCapability,\n signal?: AbortSignal\n ) => Promise<Result<ReadonlyArray<AiAssist.IAiModelInfo>>>;\n /**\n * Stream a chat completion from the provider. The `onEvent` callback fires\n * for every event (text deltas, tool progress, errors); the returned\n * promise resolves with the aggregated final text and truncation flag on\n * success, or with `Result.fail` when the stream couldn't be started or\n * ended in a terminal error event.\n */\n readonly streamDirect: (\n provider: AiAssist.AiProviderId,\n prompt: AiAssist.AiPrompt,\n onEvent: (event: AiAssist.IAiStreamEvent) => void,\n options?: {\n readonly tools?: ReadonlyArray<AiAssist.AiServerToolConfig>;\n readonly messagesBefore?: ReadonlyArray<AiAssist.IChatMessage>;\n readonly signal?: AbortSignal;\n }\n ) => Promise<Result<{ readonly fullText: string; readonly truncated: boolean }>>;\n}\n\n// ============================================================================\n// Helpers\n// ============================================================================\n\n/**\n * Resolves the API key for a dispatcher call, honoring `descriptor.needsSecret`.\n *\n * - When `descriptor.needsSecret === false` (e.g. ollama, openai-compat),\n * returns the configured key if one happens to be set in the keystore, or\n * an empty string otherwise. ts-extras omits the `Authorization` header\n * entirely for empty keys, which self-hosted servers expect.\n * - When `descriptor.needsSecret !== false`, requires `secretName`, an\n * unlocked keystore, and a successful key fetch.\n *\n * @internal\n */\nfunction resolveApiKey(\n descriptor: AiAssist.IAiProviderDescriptor,\n providerConfig: AiAssist.IAiAssistProviderConfig,\n keyStore: AiAssist.IAiAssistKeyStore | undefined\n): Result<string> {\n const { provider, secretName } = providerConfig;\n if (descriptor.needsSecret === false) {\n if (!secretName || !keyStore?.isUnlocked) {\n return succeed('');\n }\n return succeed(keyStore.getApiKey(secretName).orDefault('') ?? '');\n }\n if (!secretName) {\n return fail(`Provider \"${provider}\" has no secret name configured`);\n }\n if (!keyStore) {\n return fail('No keystore available');\n }\n return keyStore.getApiKey(secretName).withErrorFormat((m) => `Failed to get API key: ${m}`);\n}\n\n/**\n * Checks whether a parsed AI response is an error object (with an \"error\" field)\n * rather than a valid entity. AI prompts instruct the model to return\n * `{ \"error\": \"...\", \"term\": \"...\" }` when it cannot confidently generate the entity.\n *\n * @param parsed - The parsed JSON from the AI response\n * @returns A failure Result with the error message if it's an error object, or undefined if not\n */\nexport function checkForAiErrorObject(parsed: unknown): Result<never> | undefined {\n if (\n typeof parsed === 'object' &&\n parsed !== null &&\n 'error' in parsed &&\n typeof (parsed as Record<string, unknown>).error === 'string'\n ) {\n const errorObj = parsed as Record<string, unknown>;\n const term = typeof errorObj.term === 'string' ? ` (term: \"${errorObj.term}\")` : '';\n return fail(`AI declined to generate${term}: ${errorObj.error}`);\n }\n return undefined;\n}\n\n// ============================================================================\n// Hook\n// ============================================================================\n\n/**\n * Generic hook providing AI assist actions based on supplied settings and keystore.\n * @param params - Settings and keystore to use (typically from a workspace or app context)\n * @returns Available actions and execution functions\n * @public\n */\nexport function useAiAssist(params: IUseAiAssistParams): IUseAiAssistResult {\n const { settings, keyStore, logger } = params;\n const [isWorking, setIsWorking] = useState(false);\n\n const actions = useMemo((): ReadonlyArray<IAiAssistAction> => {\n const providers = settings?.providers ?? [{ provider: 'copy-paste' as AiAssist.AiProviderId }];\n const enabledSet = new Set(providers.map((p) => p.provider));\n const defaultProvider: AiAssist.AiProviderId =\n settings?.defaultProvider && enabledSet.has(settings.defaultProvider)\n ? settings.defaultProvider\n : providers[0]?.provider ?? ('copy-paste' as AiAssist.AiProviderId);\n\n return providers.map((config) => {\n let isAvailable = true;\n let unavailableReason: string | undefined;\n\n const descriptor = AiAssist.getProviderDescriptor(config.provider).orDefault();\n\n // Self-hosted / no-secret providers (e.g. ollama, openai-compat) skip\n // the keystore check entirely — they're available as long as the\n // descriptor exists. Providers with `needsSecret: true` still require\n // a configured secretName and an unlocked keystore.\n if (config.provider !== 'copy-paste' && descriptor?.needsSecret !== false) {\n if (!config.secretName) {\n isAvailable = false;\n unavailableReason = 'No API key secret configured';\n } else if (!keyStore) {\n isAvailable = false;\n unavailableReason = 'No keystore available';\n } else if (!keyStore.isUnlocked) {\n isAvailable = false;\n unavailableReason = 'Keystore is locked';\n } else {\n const hasSecret = keyStore.hasSecret(config.secretName);\n if (hasSecret.isFailure() || !hasSecret.value) {\n isAvailable = false;\n unavailableReason = `API key \"${config.secretName}\" not found in keystore`;\n }\n }\n }\n\n const label = descriptor?.buttonLabel ?? config.provider;\n return {\n provider: config.provider,\n label,\n isDefault: config.provider === defaultProvider,\n isAvailable,\n unavailableReason\n };\n });\n }, [settings, keyStore]);\n\n const copyPrompt = useCallback(async (prompt: AiAssist.AiPrompt): Promise<Result<'copied'>> => {\n try {\n await navigator.clipboard.writeText(prompt.combined);\n return succeed('copied' as const);\n } catch {\n return fail('Failed to copy prompt to clipboard');\n }\n }, []);\n\n const generateDirect = useCallback(\n async <TEntity>(\n provider: AiAssist.AiProviderId,\n prompt: AiAssist.AiPrompt,\n convert: (from: unknown) => Result<TEntity>,\n tools?: ReadonlyArray<AiAssist.AiServerToolConfig>\n ): Promise<Result<IAiAssistResult<TEntity>>> => {\n // Find the provider config and descriptor\n const providerConfig = settings?.providers.find((p) => p.provider === provider);\n\n if (!providerConfig) {\n return fail(`Provider \"${provider}\" not configured`);\n }\n\n const descriptorResult = AiAssist.getProviderDescriptor(provider);\n if (descriptorResult.isFailure()) {\n return fail(descriptorResult.message);\n }\n const descriptor = descriptorResult.value;\n\n const apiKeyResult = resolveApiKey(descriptor, providerConfig, keyStore);\n if (apiKeyResult.isFailure()) {\n return fail(apiKeyResult.message);\n }\n\n setIsWorking(true);\n try {\n const maxAttempts = 3;\n const correctionMessages: Array<AiAssist.IChatMessage> = [];\n\n for (let attempt = 0; attempt < maxAttempts; attempt++) {\n // Resolve effective tools: per-call override > settings defaults > none\n const effectiveTools = AiAssist.resolveEffectiveTools(descriptor, providerConfig.tools, tools);\n\n // Call the API — through proxy if configured, otherwise direct.\n // The unified request orders history before the current turn; the\n // correction-retry turns naturally append after the current user turn.\n const base = prompt.toRequest();\n const completionParams: AiAssist.IProviderCompletionParams = {\n descriptor,\n apiKey: apiKeyResult.value,\n system: base.system,\n messages: [...base.messages, ...correctionMessages],\n modelOverride: providerConfig.model,\n endpoint: providerConfig.endpoint,\n logger,\n tools: effectiveTools.length > 0 ? effectiveTools : undefined\n };\n const useProxy: boolean =\n !!settings?.proxyUrl && (settings.proxyAllProviders === true || descriptor.corsRestricted);\n const responseResult = useProxy\n ? await AiAssist.callProxiedCompletion(settings!.proxyUrl!, completionParams)\n : await AiAssist.callProviderCompletion(completionParams);\n if (responseResult.isFailure()) {\n logger?.error(`AI completion failed: ${responseResult.message}`);\n return fail(responseResult.message);\n }\n\n const { content: rawResponse, truncated } = responseResult.value;\n logger?.detail(`AI response received (${rawResponse.length} chars, truncated=${truncated})`);\n\n // Truncated responses are almost certainly malformed JSON — fail early\n if (truncated) {\n logger?.warn('AI response truncated due to token limits');\n return fail('AI response was truncated due to token limits — try a shorter prompt');\n }\n\n // Strip markdown code fences and parse JSON\n const stripped = rawResponse\n .trim()\n .replace(/^```(?:\\w+)?\\s*\\n?([\\s\\S]*?)\\n?\\s*```$/, '$1')\n .trim();\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(stripped);\n } catch (err: unknown) {\n const detail = err instanceof Error ? err.message : String(err);\n // JSON parse failures are not retryable — the model isn't producing valid JSON at all\n return fail(`AI returned invalid JSON: ${detail}`);\n }\n\n // Check for AI error object (model declined to generate)\n const aiError = checkForAiErrorObject(parsed);\n if (aiError !== undefined) {\n return aiError;\n }\n\n // Validate with the provided converter\n const entityResult = convert(parsed);\n if (entityResult.isSuccess()) {\n return succeed({ entity: entityResult.value, source: 'ai' as const });\n }\n\n // Validation failed — if we have retries left, send a correction\n logger?.warn(\n `AI response validation failed (attempt ${attempt + 1}/${maxAttempts}): ${entityResult.message}`\n );\n if (attempt < maxAttempts - 1) {\n correctionMessages.push(\n { role: 'assistant', content: rawResponse },\n {\n role: 'user',\n content: `The JSON you returned failed validation with the following error:\\n\\n${entityResult.message}\\n\\nPlease fix the JSON and return ONLY the corrected JSON object, nothing else.`\n }\n );\n } else {\n // Out of retries\n return fail(\n `AI response validation failed after ${maxAttempts} attempts: ${entityResult.message}`\n );\n }\n }\n\n /* c8 ignore next 2 - unreachable: every loop iteration returns; TypeScript needs the trailing return */\n return fail('AI generation failed');\n } finally {\n setIsWorking(false);\n }\n },\n [settings, keyStore, logger]\n );\n\n const generateImages = useCallback(\n async (\n provider: AiAssist.AiProviderId,\n params: AiAssist.IAiImageGenerationParams,\n signal?: AbortSignal\n ): Promise<Result<AiAssist.IAiImageGenerationResponse>> => {\n const providerConfig = settings?.providers.find((p) => p.provider === provider);\n if (!providerConfig) {\n return fail(`Provider \"${provider}\" not configured`);\n }\n\n const descriptorResult = AiAssist.getProviderDescriptor(provider);\n if (descriptorResult.isFailure()) {\n return fail(descriptorResult.message);\n }\n const descriptor = descriptorResult.value;\n\n if (!AiAssist.supportsImageGeneration(descriptor)) {\n return fail(`Provider \"${provider}\" does not support image generation`);\n }\n\n const apiKeyResult = resolveApiKey(descriptor, providerConfig, keyStore);\n if (apiKeyResult.isFailure()) {\n return fail(apiKeyResult.message);\n }\n\n setIsWorking(true);\n try {\n const requestParams: AiAssist.IProviderImageGenerationParams = {\n descriptor,\n apiKey: apiKeyResult.value,\n params,\n modelOverride: providerConfig.model,\n endpoint: providerConfig.endpoint,\n logger,\n signal\n };\n const useProxy: boolean =\n !!settings?.proxyUrl && (settings.proxyAllProviders === true || descriptor.corsRestricted);\n const result = useProxy\n ? await AiAssist.callProxiedImageGeneration(settings!.proxyUrl!, requestParams)\n : await AiAssist.callProviderImageGeneration(requestParams);\n if (result.isFailure()) {\n logger?.error(`AI image generation failed: ${result.message}`);\n }\n return result;\n } finally {\n setIsWorking(false);\n }\n },\n [settings, keyStore, logger]\n );\n\n const listModels = useCallback(\n async (\n provider: AiAssist.AiProviderId,\n capability?: AiAssist.AiModelCapability,\n signal?: AbortSignal\n ): Promise<Result<ReadonlyArray<AiAssist.IAiModelInfo>>> => {\n const providerConfig = settings?.providers.find((p) => p.provider === provider);\n if (!providerConfig) {\n return fail(`Provider \"${provider}\" not configured`);\n }\n\n const descriptorResult = AiAssist.getProviderDescriptor(provider);\n if (descriptorResult.isFailure()) {\n return fail(descriptorResult.message);\n }\n const descriptor = descriptorResult.value;\n\n const apiKeyResult = resolveApiKey(descriptor, providerConfig, keyStore);\n if (apiKeyResult.isFailure()) {\n return fail(apiKeyResult.message);\n }\n\n const requestParams: AiAssist.IProviderListModelsParams = {\n descriptor,\n apiKey: apiKeyResult.value,\n ...(capability !== undefined ? { capability } : {}),\n endpoint: providerConfig.endpoint,\n logger,\n signal\n };\n const useProxy: boolean =\n !!settings?.proxyUrl && (settings.proxyAllProviders === true || descriptor.corsRestricted);\n const result = useProxy\n ? await AiAssist.callProxiedListModels(settings!.proxyUrl!, requestParams)\n : await AiAssist.callProviderListModels(requestParams);\n if (result.isFailure()) {\n logger?.error(`AI list-models failed: ${result.message}`);\n }\n return result;\n },\n [settings, keyStore, logger]\n );\n\n const streamDirect = useCallback(\n async (\n provider: AiAssist.AiProviderId,\n prompt: AiAssist.AiPrompt,\n onEvent: (event: AiAssist.IAiStreamEvent) => void,\n options?: {\n readonly tools?: ReadonlyArray<AiAssist.AiServerToolConfig>;\n readonly messagesBefore?: ReadonlyArray<AiAssist.IChatMessage>;\n readonly signal?: AbortSignal;\n }\n ): Promise<Result<{ readonly fullText: string; readonly truncated: boolean }>> => {\n const providerConfig = settings?.providers.find((p) => p.provider === provider);\n if (!providerConfig) {\n return fail(`Provider \"${provider}\" not configured`);\n }\n\n const descriptorResult = AiAssist.getProviderDescriptor(provider);\n if (descriptorResult.isFailure()) {\n return fail(descriptorResult.message);\n }\n const descriptor = descriptorResult.value;\n\n const apiKeyResult = resolveApiKey(descriptor, providerConfig, keyStore);\n if (apiKeyResult.isFailure()) {\n return fail(apiKeyResult.message);\n }\n\n const effectiveTools = AiAssist.resolveEffectiveTools(descriptor, providerConfig.tools, options?.tools);\n // Order prior history before the current user turn (the unified request shape).\n const base = prompt.toRequest();\n const requestParams: AiAssist.IProviderCompletionStreamParams = {\n descriptor,\n apiKey: apiKeyResult.value,\n system: base.system,\n messages: [...(options?.messagesBefore ?? []), ...base.messages],\n modelOverride: providerConfig.model,\n endpoint: providerConfig.endpoint,\n logger,\n tools: effectiveTools.length > 0 ? effectiveTools : undefined,\n signal: options?.signal\n };\n const useProxy: boolean =\n !!settings?.proxyUrl && (settings.proxyAllProviders === true || descriptor.streamingCorsRestricted);\n const openResult = useProxy\n ? await AiAssist.callProxiedCompletionStream(settings!.proxyUrl!, requestParams)\n : await AiAssist.callProviderCompletionStream(requestParams);\n if (openResult.isFailure()) {\n logger?.error(`AI streaming failed to start: ${openResult.message}`);\n return fail(openResult.message);\n }\n\n setIsWorking(true);\n let fullText = '';\n let truncated = false;\n let terminalError: string | undefined;\n let receivedTerminalEvent = false;\n try {\n for await (const event of openResult.value) {\n try {\n onEvent(event);\n } catch (err: unknown) {\n // Consumer callback threw (e.g. setState-after-unmount). Convert\n // into a terminal failure so streamDirect always resolves to a\n // Result instead of rejecting and breaking its contract.\n terminalError = `AI stream event handler failed: ${\n err instanceof Error ? err.message : String(err)\n }`;\n receivedTerminalEvent = true;\n break;\n }\n if (event.type === 'text-delta') {\n fullText += event.delta;\n } else if (event.type === 'done') {\n fullText = event.fullText;\n truncated = event.truncated;\n receivedTerminalEvent = true;\n } else if (event.type === 'error') {\n terminalError = event.message;\n receivedTerminalEvent = true;\n }\n }\n } finally {\n setIsWorking(false);\n }\n if (terminalError !== undefined) {\n logger?.error(`AI streaming ended in error: ${terminalError}`);\n return fail(terminalError);\n }\n if (!receivedTerminalEvent) {\n const message = 'AI stream ended without a terminal done or error event';\n logger?.error(message);\n return fail(message);\n }\n return succeed({ fullText, truncated });\n },\n [settings, keyStore, logger]\n );\n\n return {\n actions,\n isWorking,\n copyPrompt,\n generateDirect,\n generateImages,\n listModels,\n streamDirect\n };\n}\n"]}
@@ -0,0 +1,23 @@
1
+ /*
2
+ * Copyright (c) 2026 Erik Fortune
3
+ *
4
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ * of this software and associated documentation files (the "Software"), to deal
6
+ * in the Software without restriction, including without limitation the rights
7
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ * copies of the Software, and to permit persons to whom the Software is
9
+ * furnished to do so, subject to the following conditions:
10
+ *
11
+ * The above copyright notice and this permission notice shall be included in all
12
+ * copies or substantial portions of the Software.
13
+ *
14
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20
+ * SOFTWARE.
21
+ */
22
+ export { renderStatusBadge } from './renderStatusBadge';
23
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/packlets/badge/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC","sourcesContent":["/*\n * Copyright (c) 2026 Erik Fortune\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all\n * copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n * SOFTWARE.\n */\n\nexport { renderStatusBadge } from './renderStatusBadge';\n"]}