@mp-consulting/homebridge-ui-kit 1.1.1 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -8,8 +8,11 @@ Provides shared CSS tokens, Bootstrap 5 overrides, and vanilla JS UI components
8
8
 
9
9
  | File | Description |
10
10
  |------|-------------|
11
- | `dist/kit.css` | CSS tokens, Bootstrap overrides, and shared component styles |
12
- | `dist/kit.js` | Vanilla JS helpers exposed as `window.MpKit` |
11
+ | `dist/kit.css` | CSS tokens, Bootstrap overrides, shared component styles and the Assistant (AI) components |
12
+ | `dist/kit.js` | Vanilla JS helpers exposed as `window.MpKit` (including `MpKit.ai`) |
13
+ | `dist/ai.css` | Standalone build: tokens + Assistant components only, no Bootstrap overrides (for non-plugin apps such as the Glass UI) |
14
+
15
+ Package entry points: `@mp-consulting/homebridge-ui-kit/dist/*` (and the shortcuts `/kit.css`, `/kit.js`, `/ai.css`).
13
16
 
14
17
  ## Integration
15
18
 
@@ -21,21 +24,33 @@ npm install --save-dev @mp-consulting/homebridge-ui-kit
21
24
 
22
25
  ### 2. Add the copy script to `package.json`
23
26
 
27
+ The package ships a zero-dependency CLI, `mp-ui-kit-copy`, that copies `dist/` into
28
+ `homebridge-ui/public/lib/` of the plugin it runs in:
29
+
24
30
  ```json
25
31
  {
26
32
  "scripts": {
27
- "copy:ui-kit": "cp node_modules/@mp-consulting/homebridge-ui-kit/dist/kit.css homebridge-ui/public/ && cp node_modules/@mp-consulting/homebridge-ui-kit/dist/kit.js homebridge-ui/public/",
33
+ "copy:ui-kit": "mp-ui-kit-copy --vendor",
28
34
  "build": "npm run copy:ui-kit && ..."
29
35
  }
30
36
  }
31
37
  ```
32
38
 
33
- > **Note:** Copying to the `homebridge-ui/public/` root is the simplest approach and is recommended.
34
- > `homebridge-config-ui-x` does support subdirectories (it resolves `dirname` dynamically per request), but keeping assets at the root avoids extra path nesting in HTML references.
39
+ | Option | Effect |
40
+ |--------|--------|
41
+ | *(none)* | Copies `kit.css`, `kit.js` and `ai.css` into `homebridge-ui/public/lib/` |
42
+ | `--dest <dir>` | Copies into `<dir>` instead (relative to the current directory) |
43
+ | `--vendor` | Also copies `bootstrap.min.css`, `bootstrap.bundle.min.js`, `bootstrap-icons.min.css` and `fonts/bootstrap-icons.woff(2)` from the plugin's own `node_modules` (packages that are not installed are skipped) and strips their `sourceMappingURL` comments, producing the same `lib/` layout as the plugins' previous `copy:ui-assets` scripts |
44
+
45
+ Without the CLI, the equivalent is:
46
+
47
+ ```bash
48
+ mkdir -p homebridge-ui/public/lib && cp node_modules/@mp-consulting/homebridge-ui-kit/dist/* homebridge-ui/public/lib/
49
+ ```
35
50
 
36
51
  ### 3. Reference in `index.html`
37
52
 
38
- The plugin's `homebridge-ui/public/index.html` must be a full HTML document. Load Bootstrap 5.3 from CDN, then `kit.css` and your styles in `<head>`, and Bootstrap JS + `kit.js` + your app script at the end of `<body>`:
53
+ The plugin's `homebridge-ui/public/index.html` must be a full HTML document. Load Bootstrap 5.3 (from CDN as below, or `lib/bootstrap.min.css` when you use `--vendor`), then `lib/kit.css` and your styles in `<head>`, and Bootstrap JS + `kit.js` + your app script at the end of `<body>`:
39
54
 
40
55
  ```html
41
56
  <!DOCTYPE html>
@@ -54,14 +69,14 @@ The plugin's `homebridge-ui/public/index.html` must be a full HTML document. Loa
54
69
  </script>
55
70
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
56
71
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css" integrity="sha384-XGjxtQfXaH2tnPFa9x+ruJTuLE3Aa6LhHSWRr1XeTyhezb4abCG4ccI5AkVDxqC+" crossorigin="anonymous">
57
- <link rel="stylesheet" href="kit.css">
72
+ <link rel="stylesheet" href="lib/kit.css">
58
73
  <link rel="stylesheet" href="styles.css">
59
74
  </head>
60
75
  <body>
61
76
  <!-- your UI here -->
62
77
 
63
78
  <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous"></script>
64
- <script src="kit.js"></script>
79
+ <script src="lib/kit.js"></script>
65
80
  <script src="app.js"></script>
66
81
  </body>
67
82
  </html>
@@ -85,19 +100,18 @@ try {
85
100
  ### 4. Update `.gitignore`
86
101
 
87
102
  ```gitignore
88
- # Generated UI kit assets (copied from node_modules by copy:ui-kit script)
89
- homebridge-ui/public/kit.css
90
- homebridge-ui/public/kit.js
103
+ # Generated UI kit assets (copied from node_modules by the copy:ui-kit script)
104
+ homebridge-ui/public/lib/
91
105
  ```
92
106
 
93
107
  ### 5. Update `eslint.config.js`
94
108
 
95
- Add `kit.js` to the ignore list (it is a generated, vendored file) and expose `MpKit` as a browser global:
109
+ Add the vendored `lib/` folder to the ignore list (it is a generated, vendored file) and expose `MpKit` as a browser global:
96
110
 
97
111
  ```js
98
112
  export default tseslint.config(
99
113
  {
100
- ignores: ['dist/**', 'node_modules/**', 'homebridge-ui/public/kit.js'],
114
+ ignores: ['dist/**', 'node_modules/**', 'homebridge-ui/public/lib/**'],
101
115
  },
102
116
  // ...
103
117
  {
@@ -141,6 +155,104 @@ MpKit.escapeHtml(device.name)
141
155
  All text passed to the helpers is HTML-escaped, so it is safe to pass device names or other
142
156
  values that came from the network. To include markup, build the element yourself.
143
157
 
158
+ ## Assistant (AI)
159
+
160
+ The Assistant components give AI features one recognisable look: a rotating
161
+ halo gradient used **only** for rings, glows and dots. Text always sits on a
162
+ neutral surface, so contrast is the normal body contrast. With
163
+ `prefers-reduced-motion` the gradient is static and nothing pulses.
164
+
165
+ ### CSS classes
166
+
167
+ | Class | Use |
168
+ |-------|-----|
169
+ | `.mp-ai-halo` | Container with a rotating gradient ring and soft glow |
170
+ | `.mp-ai-button` (`-sm`, `-lg`) | Pill button; ring spins and glows on hover/focus or with `aria-busy="true"` |
171
+ | `.mp-ai-badge` | Small "Assistant" pill |
172
+ | `.mp-ai-thinking` | Pulsing orb + label; on a halo/button/panel it pulses the glow |
173
+ | `.mp-ai-panel` | Answer card (`.is-streaming` spins the ring); `.mp-ai-prose` styles rendered text; `.mp-ai-caret` is the streaming caret |
174
+ | `.mp-ai-chat` | Chat log, bubbles and input |
175
+ | `.mp-ai-diff` | Line diff (`.is-add` / `.is-del`) with Apply / Reject actions |
176
+ | `.mp-ai-edge-glow` | Fixed, click-through glow around the viewport |
177
+
178
+ Tokens (`src/tokens.css`): `--mp-ai-1` … `--mp-ai-6` (halo palette), `--mp-ai-stops`,
179
+ `--mp-ai-gradient`, `--mp-ai-angle` (registered with `@property`), `--mp-ai-text`
180
+ (accessible accent text), `--mp-ai-surface`, `--mp-ai-border-width`,
181
+ `--mp-ai-glow-blur`, `--mp-ai-glow-opacity`, `--mp-ai-edge-glow-opacity`,
182
+ `--mp-ai-duration`, `--mp-ai-edge-width` and the `--mp-ai-diff-*` colours. Glows are
183
+ stronger under `[data-bs-theme="dark"]`.
184
+
185
+ ### `MpKit.ai`
186
+
187
+ `MpKit.ai` calls the routes that `registerAiRoutes(server)` from `@mp-consulting/homebridge-ai-core/plugin`
188
+ (also re-exported by `@mp-consulting/homebridge-ai-kit`) adds to the plugin's `homebridge-ui/server.js`, through the
189
+ `homebridge` global of `@homebridge/plugin-ui-utils`. Each request gets a generated
190
+ `requestId`; the server's `ai:chunk` / `ai:done` / `ai:error` events for that id are
191
+ forwarded to your callbacks while it runs.
192
+
193
+ ```js
194
+ // Is the Assistant configured? → { enabled, provider, model, capabilities }
195
+ const status = await MpKit.ai.status();
196
+
197
+ // Requests (all accept { onChunk(delta), onDone(), onError(message) })
198
+ await MpKit.ai.explain({ error, context, device }, { onChunk }); // → { text, usage }
199
+ await MpKit.ai.ask({ prompt, context }, { onChunk }); // → { text, usage }
200
+ await MpKit.ai.config({ schema, request, current }, { onChunk }); // → { config, explanation, usage }
201
+ ```
202
+
203
+ Render helpers (all text is HTML-escaped; answers support a safe markdown subset:
204
+ paragraphs, `**bold**`, `` `code` ``, fenced code blocks, lists and `#` headings — no
205
+ links or raw HTML):
206
+
207
+ ```js
208
+ // Button / badge / thinking indicator → HTML strings
209
+ el.innerHTML = MpKit.ai.renderButton({ label: 'Explain', id: 'explainBtn', size: 'sm' });
210
+ MpKit.ai.renderBadge(); // "Assistant" pill
211
+ MpKit.ai.renderThinking('Thinking…');
212
+
213
+ // Streaming answer panel
214
+ const answer = MpKit.ai.renderAnswer(document.getElementById('answer'), {
215
+ title: 'Why is the device offline?', // optional
216
+ note: 'Generated by the Assistant…', // optional footnote ('' hides it)
217
+ });
218
+ MpKit.ai.edgeGlow(); // optional page-edge glow while working
219
+ try {
220
+ const res = await MpKit.ai.explain({ error: err.message }, { onChunk: answer.append });
221
+ answer.done(res); // final text replaces the streamed text
222
+ } catch (e) {
223
+ answer.error(e); // shown as escaped text
224
+ } finally {
225
+ MpKit.ai.edgeGlow(false);
226
+ }
227
+
228
+ // Suggested config change
229
+ const { config } = await MpKit.ai.config({ schema, request: 'Add my kitchen lamp', current });
230
+ MpKit.ai.renderDiff(document.getElementById('diff'), {
231
+ before: current, // strings or objects (pretty-printed JSON)
232
+ after: config,
233
+ onApply: async ({ after }) => { // a rejected promise re-enables the buttons
234
+ await homebridge.updatePluginConfig([after]);
235
+ await homebridge.savePluginConfig();
236
+ },
237
+ onReject: () => {},
238
+ });
239
+
240
+ // Chat (uses MpKit.ai.ask with the recent conversation as context)
241
+ const chat = MpKit.ai.renderChat(document.getElementById('chat'), {
242
+ context: 'Plugin: homebridge-example',
243
+ placeholder: 'Ask about your devices…',
244
+ // onSend: (prompt, { onChunk, history }) => myTransport(prompt), // optional
245
+ });
246
+ ```
247
+
248
+ `MpKit.ai.markdown(text)` and `MpKit.ai.diffLines(before, after)` are exported too.
249
+ Outside Homebridge (e.g. the Glass UI) load only `dist/ai.css` and use the CSS
250
+ classes directly.
251
+
252
+ `examples/ai-preview.html` (repository only) previews every component in light and
253
+ dark mode with a mocked `homebridge` object: run `npm run build` and serve the repo
254
+ root with any static server.
255
+
144
256
  ## Dark Mode
145
257
 
146
258
  Dark mode is handled entirely by Bootstrap's `data-bs-theme="dark"` attribute on `<html>`. Do **not** use `@media (prefers-color-scheme: dark)` blocks, `.dark-mode` CSS classes, or custom CSS variable overrides — Bootstrap handles all of this automatically. Use Bootstrap CSS variables (`var(--bs-body-bg)`, `var(--bs-primary)`, etc.) in your custom CSS instead of hardcoded hex values.