@mp-consulting/homebridge-ui-kit 1.1.1 → 1.2.0
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 +125 -13
- package/dist/ai.css +738 -0
- package/dist/kit.css +703 -1
- package/dist/kit.js +618 -1
- package/package.json +13 -2
- package/scripts/copy-assets.js +152 -0
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,
|
|
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": "
|
|
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
|
-
|
|
34
|
-
|
|
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/
|
|
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 `
|
|
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/
|
|
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.
|