@mp-consulting/homebridge-ai-kit 2.0.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 +273 -0
- package/config.schema.json +131 -0
- package/dist/agent/run-agent.d.ts +61 -0
- package/dist/agent/run-agent.d.ts.map +1 -0
- package/dist/agent/run-agent.js +92 -0
- package/dist/agent/run-agent.js.map +1 -0
- package/dist/bin/homebridge-ai-kit.d.ts +3 -0
- package/dist/bin/homebridge-ai-kit.d.ts.map +1 -0
- package/dist/bin/homebridge-ai-kit.js +61 -0
- package/dist/bin/homebridge-ai-kit.js.map +1 -0
- package/dist/bin/homebridge-mcp-server.d.ts +3 -0
- package/dist/bin/homebridge-mcp-server.d.ts.map +1 -0
- package/dist/bin/homebridge-mcp-server.js +5 -0
- package/dist/bin/homebridge-mcp-server.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp/create-server.d.ts +17 -0
- package/dist/mcp/create-server.d.ts.map +1 -0
- package/dist/mcp/create-server.js +42 -0
- package/dist/mcp/create-server.js.map +1 -0
- package/dist/mcp/homebridge-client.d.ts +105 -0
- package/dist/mcp/homebridge-client.d.ts.map +1 -0
- package/dist/mcp/homebridge-client.js +341 -0
- package/dist/mcp/homebridge-client.js.map +1 -0
- package/dist/mcp/http.d.ts +26 -0
- package/dist/mcp/http.d.ts.map +1 -0
- package/dist/mcp/http.js +124 -0
- package/dist/mcp/http.js.map +1 -0
- package/dist/mcp/index.d.ts +12 -0
- package/dist/mcp/index.d.ts.map +1 -0
- package/dist/mcp/index.js +7 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/mcp/live.d.ts +46 -0
- package/dist/mcp/live.d.ts.map +1 -0
- package/dist/mcp/live.js +105 -0
- package/dist/mcp/live.js.map +1 -0
- package/dist/mcp/prompts.d.ts +4 -0
- package/dist/mcp/prompts.d.ts.map +1 -0
- package/dist/mcp/prompts.js +21 -0
- package/dist/mcp/prompts.js.map +1 -0
- package/dist/mcp/regex-search.d.ts +11 -0
- package/dist/mcp/regex-search.d.ts.map +1 -0
- package/dist/mcp/regex-search.js +53 -0
- package/dist/mcp/regex-search.js.map +1 -0
- package/dist/mcp/resources.d.ts +16 -0
- package/dist/mcp/resources.d.ts.map +1 -0
- package/dist/mcp/resources.js +65 -0
- package/dist/mcp/resources.js.map +1 -0
- package/dist/mcp/stdio.d.ts +11 -0
- package/dist/mcp/stdio.d.ts.map +1 -0
- package/dist/mcp/stdio.js +56 -0
- package/dist/mcp/stdio.js.map +1 -0
- package/dist/mcp/tools/accessories.d.ts +15 -0
- package/dist/mcp/tools/accessories.d.ts.map +1 -0
- package/dist/mcp/tools/accessories.js +179 -0
- package/dist/mcp/tools/accessories.js.map +1 -0
- package/dist/mcp/tools/child-bridges.d.ts +3 -0
- package/dist/mcp/tools/child-bridges.d.ts.map +1 -0
- package/dist/mcp/tools/child-bridges.js +28 -0
- package/dist/mcp/tools/child-bridges.js.map +1 -0
- package/dist/mcp/tools/config.d.ts +7 -0
- package/dist/mcp/tools/config.d.ts.map +1 -0
- package/dist/mcp/tools/config.js +111 -0
- package/dist/mcp/tools/config.js.map +1 -0
- package/dist/mcp/tools/helpers.d.ts +31 -0
- package/dist/mcp/tools/helpers.d.ts.map +1 -0
- package/dist/mcp/tools/helpers.js +49 -0
- package/dist/mcp/tools/helpers.js.map +1 -0
- package/dist/mcp/tools/logs.d.ts +7 -0
- package/dist/mcp/tools/logs.d.ts.map +1 -0
- package/dist/mcp/tools/logs.js +122 -0
- package/dist/mcp/tools/logs.js.map +1 -0
- package/dist/mcp/tools/plugin-jobs.d.ts +11 -0
- package/dist/mcp/tools/plugin-jobs.d.ts.map +1 -0
- package/dist/mcp/tools/plugin-jobs.js +75 -0
- package/dist/mcp/tools/plugin-jobs.js.map +1 -0
- package/dist/mcp/tools/plugins.d.ts +3 -0
- package/dist/mcp/tools/plugins.d.ts.map +1 -0
- package/dist/mcp/tools/plugins.js +74 -0
- package/dist/mcp/tools/plugins.js.map +1 -0
- package/dist/mcp/tools/server.d.ts +3 -0
- package/dist/mcp/tools/server.d.ts.map +1 -0
- package/dist/mcp/tools/server.js +69 -0
- package/dist/mcp/tools/server.js.map +1 -0
- package/dist/mcp/tools/system.d.ts +3 -0
- package/dist/mcp/tools/system.d.ts.map +1 -0
- package/dist/mcp/tools/system.js +9 -0
- package/dist/mcp/tools/system.js.map +1 -0
- package/dist/mcp/types.d.ts +75 -0
- package/dist/mcp/types.d.ts.map +1 -0
- package/dist/mcp/types.js +2 -0
- package/dist/mcp/types.js.map +1 -0
- package/dist/plugin/index.d.ts +7 -0
- package/dist/plugin/index.d.ts.map +1 -0
- package/dist/plugin/index.js +5 -0
- package/dist/plugin/index.js.map +1 -0
- package/dist/plugin/platform.d.ts +28 -0
- package/dist/plugin/platform.d.ts.map +1 -0
- package/dist/plugin/platform.js +76 -0
- package/dist/plugin/platform.js.map +1 -0
- package/dist/plugin/snippets.d.ts +27 -0
- package/dist/plugin/snippets.d.ts.map +1 -0
- package/dist/plugin/snippets.js +33 -0
- package/dist/plugin/snippets.js.map +1 -0
- package/homebridge-ui/public/index.html +164 -0
- package/homebridge-ui/public/js/app.js +200 -0
- package/homebridge-ui/public/lib/ai.css +738 -0
- package/homebridge-ui/public/lib/bootstrap-icons.min.css +5 -0
- package/homebridge-ui/public/lib/bootstrap.bundle.min.js +6 -0
- package/homebridge-ui/public/lib/bootstrap.min.css +5 -0
- package/homebridge-ui/public/lib/fonts/bootstrap-icons.woff +0 -0
- package/homebridge-ui/public/lib/fonts/bootstrap-icons.woff2 +0 -0
- package/homebridge-ui/public/lib/kit.css +994 -0
- package/homebridge-ui/public/lib/kit.js +794 -0
- package/homebridge-ui/server.js +28 -0
- package/package.json +94 -0
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en" data-bs-theme="dark">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
+
<title>AI Kit</title>
|
|
7
|
+
<script>
|
|
8
|
+
(function () {
|
|
9
|
+
var dark = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
|
|
10
|
+
document.documentElement.dataset.bsTheme = dark ? 'dark' : 'light';
|
|
11
|
+
}());
|
|
12
|
+
</script>
|
|
13
|
+
<!--
|
|
14
|
+
Served from the plugin, not a CDN: the Homebridge UI's content-security policy only
|
|
15
|
+
allows same-origin styles and scripts. `npm run copy:ui-assets` fills lib/.
|
|
16
|
+
-->
|
|
17
|
+
<link rel="stylesheet" href="lib/bootstrap.min.css">
|
|
18
|
+
<link rel="stylesheet" href="lib/bootstrap-icons.min.css">
|
|
19
|
+
<link rel="stylesheet" href="lib/kit.css">
|
|
20
|
+
<link rel="stylesheet" href="lib/ai.css">
|
|
21
|
+
<style>
|
|
22
|
+
.snippet { white-space: pre; font-size: .8rem; max-height: 18rem; overflow: auto; }
|
|
23
|
+
.form-text code { font-size: .8em; }
|
|
24
|
+
</style>
|
|
25
|
+
</head>
|
|
26
|
+
<body>
|
|
27
|
+
<div class="container py-4">
|
|
28
|
+
|
|
29
|
+
<div class="mp-header mb-4">
|
|
30
|
+
<span class="mp-header-icon"><i class="bi bi-stars"></i></span>
|
|
31
|
+
<div>
|
|
32
|
+
<h1 class="mp-header-title">AI Kit <span id="assistant-badge"></span></h1>
|
|
33
|
+
<p class="mp-header-subtitle">Choose the model behind the Assistant, and let Claude Desktop, Claude Code or Cursor reach Homebridge.</p>
|
|
34
|
+
</div>
|
|
35
|
+
</div>
|
|
36
|
+
|
|
37
|
+
<!-- Provider -->
|
|
38
|
+
<div class="card border-0 shadow-sm mb-4 mp-settings-card">
|
|
39
|
+
<div class="card-body">
|
|
40
|
+
<h2 class="h5 mb-3">Assistant</h2>
|
|
41
|
+
|
|
42
|
+
<div class="form-check form-switch mb-3">
|
|
43
|
+
<input class="form-check-input" type="checkbox" role="switch" id="enabled">
|
|
44
|
+
<label class="form-check-label" for="enabled">Enable the Assistant</label>
|
|
45
|
+
</div>
|
|
46
|
+
|
|
47
|
+
<div class="mb-3">
|
|
48
|
+
<label class="form-label mp-label" for="provider">Provider</label>
|
|
49
|
+
<select class="form-select" id="provider">
|
|
50
|
+
<option value="anthropic">Claude (Anthropic)</option>
|
|
51
|
+
<option value="openai">OpenAI</option>
|
|
52
|
+
<option value="gemini">Gemini (Google)</option>
|
|
53
|
+
<option value="openai-compatible">OpenAI-compatible server (Ollama, LM Studio)</option>
|
|
54
|
+
</select>
|
|
55
|
+
</div>
|
|
56
|
+
|
|
57
|
+
<div class="mb-3">
|
|
58
|
+
<label class="form-label mp-label" for="model">Model</label>
|
|
59
|
+
<input class="form-control" id="model" autocomplete="off" spellcheck="false">
|
|
60
|
+
<div class="form-text" id="model-help"></div>
|
|
61
|
+
</div>
|
|
62
|
+
|
|
63
|
+
<div class="mb-3" id="apiKey-group">
|
|
64
|
+
<label class="form-label mp-label" for="apiKey">API key</label>
|
|
65
|
+
<div class="input-group">
|
|
66
|
+
<input class="form-control" id="apiKey" type="password" autocomplete="off" spellcheck="false">
|
|
67
|
+
<button class="btn btn-outline-secondary" type="button" id="toggle-key" aria-label="Show or hide the key"><i class="bi bi-eye"></i></button>
|
|
68
|
+
</div>
|
|
69
|
+
<div class="form-text">Stored in config.json on this Homebridge server. Plugin pages never see it.</div>
|
|
70
|
+
</div>
|
|
71
|
+
|
|
72
|
+
<div class="mb-3 d-none" id="baseUrl-group">
|
|
73
|
+
<label class="form-label mp-label" for="baseUrl">Server URL</label>
|
|
74
|
+
<input class="form-control" id="baseUrl" placeholder="http://127.0.0.1:11434/v1" spellcheck="false">
|
|
75
|
+
<div class="form-text">Ollama: <code>http://127.0.0.1:11434/v1</code> · LM Studio: <code>http://127.0.0.1:1234/v1</code></div>
|
|
76
|
+
</div>
|
|
77
|
+
|
|
78
|
+
<div class="row g-3 mb-3">
|
|
79
|
+
<div class="col-sm-6">
|
|
80
|
+
<label class="form-label mp-label" for="maxOutputTokens">Maximum answer length (tokens)</label>
|
|
81
|
+
<input class="form-control" id="maxOutputTokens" type="number" min="64" step="64" placeholder="2048">
|
|
82
|
+
</div>
|
|
83
|
+
<div class="col-sm-6 d-none" id="contextTokens-group">
|
|
84
|
+
<label class="form-label mp-label" for="contextTokens">Context window (tokens)</label>
|
|
85
|
+
<input class="form-control" id="contextTokens" type="number" min="1024" step="1024" placeholder="8192">
|
|
86
|
+
</div>
|
|
87
|
+
</div>
|
|
88
|
+
|
|
89
|
+
<div class="d-flex flex-wrap gap-2 align-items-center">
|
|
90
|
+
<span id="test-button-slot"></span>
|
|
91
|
+
<span id="status" class="small text-body-secondary"></span>
|
|
92
|
+
</div>
|
|
93
|
+
<div id="test-result" class="mt-3"></div>
|
|
94
|
+
</div>
|
|
95
|
+
</div>
|
|
96
|
+
|
|
97
|
+
<!-- MCP -->
|
|
98
|
+
<div class="card border-0 shadow-sm mb-4 mp-settings-card">
|
|
99
|
+
<div class="card-body">
|
|
100
|
+
<h2 class="h5 mb-1">MCP clients</h2>
|
|
101
|
+
<p class="small text-body-secondary">Claude Desktop, Claude Code and Cursor can control Homebridge through the MCP server, either launched by the client (stdio) or served by this plugin over HTTP.</p>
|
|
102
|
+
|
|
103
|
+
<div class="form-check form-switch mb-3">
|
|
104
|
+
<input class="form-check-input" type="checkbox" role="switch" id="http-enabled">
|
|
105
|
+
<label class="form-check-label" for="http-enabled">Serve MCP over HTTP from this plugin</label>
|
|
106
|
+
</div>
|
|
107
|
+
|
|
108
|
+
<div id="http-settings" class="d-none">
|
|
109
|
+
<div class="row g-3 mb-3">
|
|
110
|
+
<div class="col-sm-8">
|
|
111
|
+
<label class="form-label mp-label" for="http-host">Bind address</label>
|
|
112
|
+
<input class="form-control" id="http-host" placeholder="127.0.0.1">
|
|
113
|
+
<div class="form-text">127.0.0.1 keeps it on this machine. 0.0.0.0 exposes it to your network.</div>
|
|
114
|
+
</div>
|
|
115
|
+
<div class="col-sm-4">
|
|
116
|
+
<label class="form-label mp-label" for="http-port">Port</label>
|
|
117
|
+
<input class="form-control" id="http-port" type="number" min="1" max="65535" placeholder="8582">
|
|
118
|
+
</div>
|
|
119
|
+
</div>
|
|
120
|
+
<div class="mb-3">
|
|
121
|
+
<label class="form-label mp-label" for="http-token">Client token</label>
|
|
122
|
+
<div class="input-group">
|
|
123
|
+
<input class="form-control" id="http-token" type="password" autocomplete="off" spellcheck="false">
|
|
124
|
+
<button class="btn btn-outline-secondary" type="button" id="generate-token">Generate</button>
|
|
125
|
+
</div>
|
|
126
|
+
</div>
|
|
127
|
+
<div class="row g-3 mb-3">
|
|
128
|
+
<div class="col-sm-6">
|
|
129
|
+
<label class="form-label mp-label" for="http-hb-url">Homebridge UI URL</label>
|
|
130
|
+
<input class="form-control" id="http-hb-url" placeholder="http://127.0.0.1:8581">
|
|
131
|
+
</div>
|
|
132
|
+
<div class="col-sm-6">
|
|
133
|
+
<label class="form-label mp-label" for="http-hb-token">Homebridge API token</label>
|
|
134
|
+
<input class="form-control" id="http-hb-token" type="password" autocomplete="off" spellcheck="false">
|
|
135
|
+
<div class="form-text">Create one in Glass UI. A read-only token blocks changes.</div>
|
|
136
|
+
</div>
|
|
137
|
+
</div>
|
|
138
|
+
</div>
|
|
139
|
+
|
|
140
|
+
<ul class="nav nav-tabs mp-tabs" role="tablist">
|
|
141
|
+
<li class="nav-item"><button class="nav-link active" data-snippet="claudeDesktop" type="button">Claude Desktop</button></li>
|
|
142
|
+
<li class="nav-item"><button class="nav-link" data-snippet="claudeCode" type="button">Claude Code</button></li>
|
|
143
|
+
<li class="nav-item"><button class="nav-link" data-snippet="cursor" type="button">Cursor</button></li>
|
|
144
|
+
</ul>
|
|
145
|
+
<div class="position-relative border border-top-0 rounded-bottom p-3">
|
|
146
|
+
<button class="btn btn-sm btn-outline-secondary position-absolute top-0 end-0 m-2" type="button" id="copy-snippet"><i class="bi bi-clipboard"></i> Copy</button>
|
|
147
|
+
<pre class="snippet mb-0" id="snippet"></pre>
|
|
148
|
+
</div>
|
|
149
|
+
<div class="form-check mt-2">
|
|
150
|
+
<input class="form-check-input" type="checkbox" id="include-token">
|
|
151
|
+
<label class="form-check-label small" for="include-token">Include the real token in the snippet</label>
|
|
152
|
+
</div>
|
|
153
|
+
</div>
|
|
154
|
+
</div>
|
|
155
|
+
|
|
156
|
+
<footer class="mp-footer">
|
|
157
|
+
<span>@mp-consulting/homebridge-ai-kit</span>
|
|
158
|
+
</footer>
|
|
159
|
+
</div>
|
|
160
|
+
|
|
161
|
+
<script src="lib/kit.js"></script>
|
|
162
|
+
<script src="js/app.js"></script>
|
|
163
|
+
</body>
|
|
164
|
+
</html>
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
(() => {
|
|
2
|
+
|
|
3
|
+
const PLATFORM = 'HomebridgeAiKit';
|
|
4
|
+
const DEFAULT_MODELS = {
|
|
5
|
+
anthropic: 'claude-sonnet-5-5',
|
|
6
|
+
openai: 'gpt-5',
|
|
7
|
+
gemini: 'gemini-2.5-pro',
|
|
8
|
+
'openai-compatible': 'llama3.1',
|
|
9
|
+
};
|
|
10
|
+
const MODEL_HELP = {
|
|
11
|
+
anthropic: 'e.g. claude-sonnet-5-5 (default), claude-haiku-4-5-20251001 (cheapest), claude-opus-5-5 (most capable).',
|
|
12
|
+
openai: 'e.g. gpt-5.',
|
|
13
|
+
gemini: 'e.g. gemini-2.5-pro or gemini-2.5-flash.',
|
|
14
|
+
'openai-compatible': 'The model name your server knows, e.g. llama3.1 or qwen3.',
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const $ = (id) => document.getElementById(id);
|
|
18
|
+
let block = { platform: PLATFORM, name: 'AI Kit' };
|
|
19
|
+
let snippetTab = 'claudeDesktop';
|
|
20
|
+
const ai = window.MpKit && window.MpKit.ai;
|
|
21
|
+
|
|
22
|
+
function num(value) {
|
|
23
|
+
const n = parseInt(value, 10);
|
|
24
|
+
return Number.isFinite(n) && n > 0 ? n : undefined;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function clean(obj) {
|
|
28
|
+
Object.keys(obj).forEach((k) => {
|
|
29
|
+
if (obj[k] === undefined || obj[k] === '') {
|
|
30
|
+
delete obj[k];
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
return obj;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** The block as edited in the form. */
|
|
37
|
+
function readForm() {
|
|
38
|
+
const http = clean({
|
|
39
|
+
enabled: $('http-enabled').checked,
|
|
40
|
+
host: $('http-host').value.trim(),
|
|
41
|
+
port: num($('http-port').value),
|
|
42
|
+
token: $('http-token').value.trim(),
|
|
43
|
+
homebridgeUrl: $('http-hb-url').value.trim(),
|
|
44
|
+
homebridgeToken: $('http-hb-token').value.trim(),
|
|
45
|
+
});
|
|
46
|
+
const provider = $('provider').value;
|
|
47
|
+
return clean(Object.assign({}, block, {
|
|
48
|
+
platform: PLATFORM,
|
|
49
|
+
enabled: $('enabled').checked,
|
|
50
|
+
provider: provider,
|
|
51
|
+
model: $('model').value.trim(),
|
|
52
|
+
apiKey: $('apiKey').value.trim(),
|
|
53
|
+
baseUrl: provider === 'openai-compatible' ? $('baseUrl').value.trim() : undefined,
|
|
54
|
+
contextTokens: provider === 'openai-compatible' ? num($('contextTokens').value) : undefined,
|
|
55
|
+
maxOutputTokens: num($('maxOutputTokens').value),
|
|
56
|
+
mcp: { http: http },
|
|
57
|
+
}));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function fillForm(b) {
|
|
61
|
+
const http = (b.mcp && b.mcp.http) || {};
|
|
62
|
+
$('enabled').checked = b.enabled !== false;
|
|
63
|
+
$('provider').value = b.provider || 'anthropic';
|
|
64
|
+
$('model').value = b.model || '';
|
|
65
|
+
$('apiKey').value = b.apiKey || '';
|
|
66
|
+
$('baseUrl').value = b.baseUrl || '';
|
|
67
|
+
$('contextTokens').value = b.contextTokens || '';
|
|
68
|
+
$('maxOutputTokens').value = b.maxOutputTokens || '';
|
|
69
|
+
$('http-enabled').checked = http.enabled === true;
|
|
70
|
+
$('http-host').value = http.host || '';
|
|
71
|
+
$('http-port').value = http.port || '';
|
|
72
|
+
$('http-token').value = http.token || '';
|
|
73
|
+
$('http-hb-url').value = http.homebridgeUrl || '';
|
|
74
|
+
$('http-hb-token').value = http.homebridgeToken || '';
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function refreshVisibility() {
|
|
78
|
+
const provider = $('provider').value;
|
|
79
|
+
const local = provider === 'openai-compatible';
|
|
80
|
+
$('baseUrl-group').classList.toggle('d-none', !local);
|
|
81
|
+
$('contextTokens-group').classList.toggle('d-none', !local);
|
|
82
|
+
$('model').placeholder = DEFAULT_MODELS[provider];
|
|
83
|
+
$('model-help').textContent = MODEL_HELP[provider] + ' Leave empty for the default.';
|
|
84
|
+
$('http-settings').classList.toggle('d-none', !$('http-enabled').checked);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
let saveTimer;
|
|
88
|
+
function save() {
|
|
89
|
+
block = readForm();
|
|
90
|
+
clearTimeout(saveTimer);
|
|
91
|
+
saveTimer = setTimeout(() => {
|
|
92
|
+
homebridge.updatePluginConfig([block]).catch((e) => {
|
|
93
|
+
homebridge.toast.error(e.message || String(e), 'Could not update the config');
|
|
94
|
+
});
|
|
95
|
+
}, 250);
|
|
96
|
+
refreshVisibility();
|
|
97
|
+
renderSnippet();
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function renderSnippet() {
|
|
101
|
+
const http = block.mcp && block.mcp.http;
|
|
102
|
+
const body = {
|
|
103
|
+
homebridgeUrl: window.location.origin,
|
|
104
|
+
includeToken: $('include-token').checked,
|
|
105
|
+
};
|
|
106
|
+
if (http && http.enabled) {
|
|
107
|
+
body.http = { host: http.host || '127.0.0.1', port: http.port || 8582, token: http.token };
|
|
108
|
+
}
|
|
109
|
+
homebridge.request('/mcp/snippets', body).then((snippets) => {
|
|
110
|
+
$('snippet').textContent = snippets[snippetTab];
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function testConnection() {
|
|
115
|
+
const result = $('test-result');
|
|
116
|
+
$('status').textContent = '';
|
|
117
|
+
const answer = ai ? ai.renderAnswer(result, { title: 'Connection test', note: '' }) : null;
|
|
118
|
+
if (!answer) {
|
|
119
|
+
result.textContent = 'Testing…';
|
|
120
|
+
}
|
|
121
|
+
homebridge.request('/ai/test', readForm()).then((res) => {
|
|
122
|
+
if (res.ok) {
|
|
123
|
+
const text = 'Connected to **' + res.provider + '** using `' + res.model + '` in ' + res.latencyMs + ' ms. Reply: ' + (res.reply || '(empty)');
|
|
124
|
+
if (answer) {
|
|
125
|
+
answer.done(text);
|
|
126
|
+
} else {
|
|
127
|
+
result.textContent = text;
|
|
128
|
+
}
|
|
129
|
+
} else if (answer) {
|
|
130
|
+
answer.error(res.message);
|
|
131
|
+
} else {
|
|
132
|
+
result.textContent = res.message;
|
|
133
|
+
}
|
|
134
|
+
}, (e) => {
|
|
135
|
+
if (answer) {
|
|
136
|
+
answer.error(e);
|
|
137
|
+
} else {
|
|
138
|
+
result.textContent = e.message || String(e);
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function init() {
|
|
144
|
+
const slot = $('test-button-slot');
|
|
145
|
+
slot.innerHTML = ai
|
|
146
|
+
? ai.renderButton({ id: 'test-connection', label: 'Test connection' })
|
|
147
|
+
: '<button type="button" class="btn btn-primary" id="test-connection">Test connection</button>';
|
|
148
|
+
if (ai) {
|
|
149
|
+
$('assistant-badge').innerHTML = ai.renderBadge('Assistant');
|
|
150
|
+
}
|
|
151
|
+
$('test-connection').addEventListener('click', testConnection);
|
|
152
|
+
|
|
153
|
+
$('toggle-key').addEventListener('click', () => {
|
|
154
|
+
$('apiKey').type = $('apiKey').type === 'password' ? 'text' : 'password';
|
|
155
|
+
});
|
|
156
|
+
$('generate-token').addEventListener('click', () => {
|
|
157
|
+
homebridge.request('/mcp/token').then((res) => {
|
|
158
|
+
$('http-token').value = res.token;
|
|
159
|
+
save();
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
document.querySelectorAll('[data-snippet]').forEach((button) => {
|
|
163
|
+
button.addEventListener('click', () => {
|
|
164
|
+
document.querySelectorAll('[data-snippet]').forEach((b) => {
|
|
165
|
+
b.classList.remove('active');
|
|
166
|
+
});
|
|
167
|
+
button.classList.add('active');
|
|
168
|
+
snippetTab = button.dataset.snippet;
|
|
169
|
+
renderSnippet();
|
|
170
|
+
});
|
|
171
|
+
});
|
|
172
|
+
$('copy-snippet').addEventListener('click', () => {
|
|
173
|
+
navigator.clipboard.writeText($('snippet').textContent).then(() => {
|
|
174
|
+
homebridge.toast.success('Copied to the clipboard.');
|
|
175
|
+
});
|
|
176
|
+
});
|
|
177
|
+
$('include-token').addEventListener('change', renderSnippet);
|
|
178
|
+
document.querySelectorAll('input, select').forEach((el) => {
|
|
179
|
+
if (el.id !== 'include-token') {
|
|
180
|
+
el.addEventListener('change', save);
|
|
181
|
+
el.addEventListener('input', save);
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
homebridge.getPluginConfig().then((blocks) => {
|
|
186
|
+
block = blocks.find((b) => {
|
|
187
|
+
return b.platform === PLATFORM;
|
|
188
|
+
}) || block;
|
|
189
|
+
fillForm(block);
|
|
190
|
+
refreshVisibility();
|
|
191
|
+
renderSnippet();
|
|
192
|
+
if (!blocks.length) {
|
|
193
|
+
save();
|
|
194
|
+
}
|
|
195
|
+
homebridge.hideSpinner();
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
homebridge.addEventListener('ready', init);
|
|
200
|
+
})();
|