@animastor/web-local-ai 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Animastor
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,18 @@
1
+ # @animastor/web-local-ai
2
+
3
+ Local AI Connector section UI for Animastor web frontend.
4
+
5
+ Host capabilities (api, i18n, ui) arrive via the injected `LocalAiPorts` contract. The package never imports host stores, `api/client`, or app-level UI directly. Domain logic (validation, token rules, status keys, share status, error mapping) is consumed from `@animastor/web-settings` — a pure, side-effect-free Tier B package, which makes this the first package→package dependency in the web layer (a clean DAG).
6
+
7
+ ## Usage
8
+
9
+ ```tsx
10
+ import { LocalAISection } from '@animastor/web-local-ai';
11
+ import type { LocalAiPorts } from '@animastor/web-local-ai';
12
+
13
+ const ports: LocalAiPorts = { /* api, i18n, ui */ };
14
+
15
+ <LocalAISection ports={ports} />
16
+ ```
17
+
18
+ The host composition point lives in `frontends/app/src/app/localAiAdapters.ts`.
@@ -0,0 +1,52 @@
1
+ import * as preact from 'preact';
2
+ import { JSX } from 'preact';
3
+
4
+ /** Minimal API error contract the Local AI modules need. The host adapter
5
+ * bridges the real host ApiError; this is a structural subtype — the
6
+ * TypeScript compiler verifies the adapter at the seam. */
7
+ interface LocalAiApiError {
8
+ readonly name: string;
9
+ readonly message: string;
10
+ readonly status: number;
11
+ readonly code?: string;
12
+ }
13
+ /** API port — the HTTP surface the Local AI modules consume. */
14
+ interface LocalAiApiPort {
15
+ getJson: <T>(url: string) => Promise<T>;
16
+ postJson: <T>(url: string, body?: unknown) => Promise<T>;
17
+ putJson: <T>(url: string, body: unknown) => Promise<T>;
18
+ deleteJson: <T>(url: string) => Promise<T>;
19
+ ApiError: new (message: string, status: number, code?: string) => LocalAiApiError;
20
+ }
21
+ /** i18n keys the Local AI surface needs (the dictionary stays host-owned).
22
+ * Covers every literal call site PLUS the keys returned by the
23
+ * @animastor/web-settings helpers (statusKey, shareStatusKey,
24
+ * connectorErrorKey, validateCreateInput errors, RUNTIME_TYPE_OPTIONS
25
+ * labelKeys, REGISTRATION_STEP_KEYS, OFFLINE_TROUBLESHOOT_KEYS). */
26
+ type LocalAiI18nKey = 'ai_provider_last_tested' | 'ai_provider_model' | 'ai_provider_test' | 'ai_provider_test_ok' | 'dialog_cancel' | 'play_loading' | 'local_ai_add_hint' | 'local_ai_add_title' | 'local_ai_bind' | 'local_ai_binding_active' | 'local_ai_binding_none' | 'local_ai_bound' | 'local_ai_bound_badge' | 'local_ai_create' | 'local_ai_credential_warning' | 'local_ai_desc' | 'local_ai_empty' | 'local_ai_err_auth' | 'local_ai_err_forbidden' | 'local_ai_err_generic' | 'local_ai_err_no_models' | 'local_ai_err_not_found' | 'local_ai_err_rate_limited' | 'local_ai_list_title' | 'local_ai_model_auto' | 'local_ai_model_pick_hint' | 'local_ai_models_count' | 'local_ai_models_disclaimer' | 'local_ai_models_refreshed' | 'local_ai_name_hint' | 'local_ai_name_label' | 'local_ai_pending_hint' | 'local_ai_rebind' | 'local_ai_refresh_models' | 'local_ai_reg_expired' | 'local_ai_reg_title' | 'local_ai_reg_token_label' | 'local_ai_reg_ttl_hint' | 'local_ai_reissue_token' | 'local_ai_revoke_confirm' | 'local_ai_revoked' | 'local_ai_revoke_hint' | 'local_ai_rotate_hint' | 'local_ai_run_command_label' | 'local_ai_runtime_label' | 'local_ai_runtime_ok' | 'local_ai_runtime_unknown' | 'local_ai_setup_intro' | 'local_ai_setup_title' | 'local_ai_test_cold_warning' | 'local_ai_test_fail' | 'local_ai_title' | 'local_ai_unbind' | 'local_ai_unbound' | 'share_ai_concurrency_label' | 'share_ai_create_endpoint' | 'share_ai_create_endpoint_hint' | 'share_ai_disabled_notice' | 'share_ai_enable_confirm' | 'share_ai_enabled_notice' | 'share_ai_endpoint_created' | 'share_ai_endpoint_deleted' | 'share_ai_models_label' | 'share_ai_no_endpoint_hint' | 'share_ai_share_button' | 'share_ai_title' | 'share_ai_unshare_button' | 'worker_copied' | 'worker_copy' | 'worker_copy_failed' | 'worker_delete' | 'worker_done' | 'worker_last_seen' | 'worker_revoke' | 'worker_rotate' | 'worker_rotate_short' | 'worker_trouble_title' | 'local_ai_name_required' | 'local_ai_name_too_long' | 'local_ai_runtime_invalid' | 'local_ai_runtime_ollama' | 'local_ai_runtime_vllm' | 'local_ai_runtime_llamacpp' | 'local_ai_runtime_lmstudio' | 'local_ai_runtime_openai_compatible' | 'local_ai_status_pending' | 'local_ai_status_online' | 'local_ai_status_offline' | 'local_ai_setup_step_1' | 'local_ai_setup_step_2' | 'local_ai_setup_step_3' | 'local_ai_setup_step_4' | 'local_ai_trouble_process' | 'local_ai_trouble_runtime' | 'local_ai_trouble_network' | 'share_ai_status_private' | 'share_ai_status_shared' | 'share_ai_status_offline' | 'share_ai_status_runtime_unavailable' | 'local_ai_err_offline' | 'local_ai_err_timeout' | 'local_ai_err_runtime_unreachable' | 'local_ai_err_model_not_found' | 'local_ai_err_busy' | 'local_ai_err_context_length' | 'local_ai_err_bad_response' | 'local_ai_err_runtime_error' | 'local_ai_err_response_too_large' | 'local_ai_err_request_too_large' | 'local_ai_err_invalid_request' | 'local_ai_err_discovery_failed' | 'local_ai_err_registration_expired' | 'local_ai_err_registration_used';
27
+ interface LocalAiI18nPort {
28
+ t: (key: LocalAiI18nKey, fallback?: string) => string;
29
+ tf: (key: LocalAiI18nKey, ...params: unknown[]) => string;
30
+ }
31
+ interface LocalAiModalProps {
32
+ title?: string;
33
+ onClose: () => void;
34
+ footer?: JSX.Element | JSX.Element[];
35
+ children: JSX.Element | JSX.Element[];
36
+ }
37
+ interface LocalAiUiPort {
38
+ Modal: (props: LocalAiModalProps) => JSX.Element;
39
+ toast: (message: string, duration?: number) => void;
40
+ }
41
+ /** The complete host contract consumed by the Local AI modules. */
42
+ interface LocalAiPorts {
43
+ api: LocalAiApiPort;
44
+ i18n: LocalAiI18nPort;
45
+ ui: LocalAiUiPort;
46
+ }
47
+
48
+ declare function LocalAISection({ ports }: {
49
+ ports: LocalAiPorts;
50
+ }): preact.JSX.Element;
51
+
52
+ export { LocalAISection, type LocalAiApiError, type LocalAiApiPort, type LocalAiI18nKey, type LocalAiI18nPort, type LocalAiModalProps, type LocalAiPorts, type LocalAiUiPort };
package/dist/index.js ADDED
@@ -0,0 +1,631 @@
1
+ import { useState, useRef, useCallback, useEffect } from 'preact/hooks';
2
+ import { formatLastTested, RUNTIME_TYPE_OPTIONS, runtimeReachable, runtimeInfo, statusKey, statusClass, formatLastSeen, shareStatus, shareStatusKey, shareStatusClass, OFFLINE_TROUBLESHOOT_KEYS, validateCreateInput, looksLikeRegToken, connectorErrorKey, looksLikeConnectorCredential, buildRunCommand, regTokenExpired, REGISTRATION_STEP_KEYS } from '@animastor/web-settings';
3
+ import { jsxs, jsx, Fragment } from 'preact/jsx-runtime';
4
+
5
+ // src/LocalAISection.tsx
6
+ function OneTimeCredentialModal({ kind, token, connectorName, regExpiresAt, wsUrl, onClose, ports }) {
7
+ const [copied, setCopied] = useState(false);
8
+ const [copiedCmd, setCopiedCmd] = useState(false);
9
+ const { t, ui } = { t: ports.i18n.t, ui: ports.ui };
10
+ const origin = typeof location !== "undefined" ? location.origin : "";
11
+ const runCommand = kind === "register" && wsUrl ? buildRunCommand(token, wsUrl, origin) : null;
12
+ const onCopy = useCallback(async (text, mark) => {
13
+ try {
14
+ await navigator.clipboard.writeText(text);
15
+ mark(true);
16
+ setTimeout(() => mark(false), 1800);
17
+ } catch (_) {
18
+ ui.toast(t("worker_copy_failed"));
19
+ }
20
+ }, []);
21
+ return /* @__PURE__ */ jsx(
22
+ ui.Modal,
23
+ {
24
+ title: kind === "register" ? t("local_ai_reg_title") : `${t("worker_rotate")} \u2014 ${connectorName}`,
25
+ onClose,
26
+ children: /* @__PURE__ */ jsxs(Fragment, { children: [
27
+ /* @__PURE__ */ jsx("p", { class: "modal__notice worker__warn", children: t("local_ai_credential_warning") }),
28
+ kind === "register" && regTokenExpired(regExpiresAt) && /* @__PURE__ */ jsx("p", { class: "settings-page__error", children: t("local_ai_reg_expired") }),
29
+ /* @__PURE__ */ jsx("p", { class: "card__label", children: t("local_ai_reg_token_label") }),
30
+ /* @__PURE__ */ jsxs("div", { class: "worker__cred", children: [
31
+ /* @__PURE__ */ jsx("code", { class: "worker__token", children: token }),
32
+ /* @__PURE__ */ jsx("button", { class: "btn btn--outlined", onClick: () => void onCopy(token, setCopied), children: copied ? t("worker_copied") : t("worker_copy") })
33
+ ] }),
34
+ kind === "register" && /* @__PURE__ */ jsxs(Fragment, { children: [
35
+ /* @__PURE__ */ jsx("p", { class: "card__label", children: t("local_ai_setup_title") }),
36
+ /* @__PURE__ */ jsx("p", { class: "card__hint card__hint--wrap", children: t("local_ai_setup_intro") }),
37
+ /* @__PURE__ */ jsx("ol", { class: "worker__steps", children: REGISTRATION_STEP_KEYS.map((k) => /* @__PURE__ */ jsx("li", { children: t(k) }, k)) }),
38
+ runCommand && /* @__PURE__ */ jsxs(Fragment, { children: [
39
+ /* @__PURE__ */ jsx("p", { class: "card__label", children: t("local_ai_run_command_label") }),
40
+ /* @__PURE__ */ jsxs("div", { class: "worker__cred", children: [
41
+ /* @__PURE__ */ jsx("code", { class: "worker__token", children: runCommand }),
42
+ /* @__PURE__ */ jsx("button", { class: "btn btn--outlined", onClick: () => void onCopy(runCommand, setCopiedCmd), children: copiedCmd ? t("worker_copied") : t("worker_copy") })
43
+ ] })
44
+ ] }),
45
+ /* @__PURE__ */ jsx("p", { class: "card__hint card__hint--wrap", children: t("local_ai_reg_ttl_hint") })
46
+ ] }),
47
+ kind === "rotate" && /* @__PURE__ */ jsx("p", { class: "card__hint card__hint--wrap", children: t("local_ai_rotate_hint") }),
48
+ /* @__PURE__ */ jsx("div", { class: "modal__footer", children: /* @__PURE__ */ jsx("button", { class: "btn", onClick: onClose, children: t("worker_done") }) })
49
+ ] })
50
+ }
51
+ );
52
+ }
53
+ function LocalAISection({ ports }) {
54
+ const { api, t, tf, ui } = { api: ports.api, t: ports.i18n.t, tf: ports.i18n.tf, ui: ports.ui };
55
+ const [connectors, setConnectors] = useState(null);
56
+ const [modelsBy, setModelsBy] = useState({});
57
+ const [provider, setProvider] = useState(null);
58
+ const [name, setName] = useState("");
59
+ const [runtimeType, setRuntimeType] = useState("ollama");
60
+ const [busy, setBusy] = useState(false);
61
+ const [creating, setCreating] = useState(false);
62
+ const [refreshingId, setRefreshingId] = useState(null);
63
+ const [testingId, setTestingId] = useState(null);
64
+ const [error, setError] = useState("");
65
+ const [notice, setNotice] = useState("");
66
+ const [testOk, setTestOk] = useState(null);
67
+ const [loaded, setLoaded] = useState(false);
68
+ const [confirmRevoke, setConfirmRevoke] = useState(null);
69
+ const [disclosure, setDisclosure] = useState(null);
70
+ const [bindingModel, setBindingModel] = useState({});
71
+ const [endpointsBy, setEndpointsBy] = useState({});
72
+ const [confirmShare, setConfirmShare] = useState(null);
73
+ const busyRef = useRef(false);
74
+ const confirmRevokeRef = useRef(null);
75
+ const pollInFlight = useRef(false);
76
+ const loadStatus = useCallback(async () => {
77
+ if (pollInFlight.current) return;
78
+ pollInFlight.current = true;
79
+ try {
80
+ const [st, models, prov, eps] = await Promise.all([
81
+ api.getJson("/ai-connector/status"),
82
+ api.getJson("/ai-connector/models"),
83
+ api.getJson("/settings/ai/provider"),
84
+ // LLM Sharing Phase 1: owner's endpoint rows (401/404 silently
85
+ // ignored — an account without endpoints sees none).
86
+ api.getJson("/ai-endpoints").catch(() => ({ endpoints: [] }))
87
+ ]);
88
+ setConnectors(st.connectors);
89
+ const by = {};
90
+ for (const c of models.connectors) by[c.connector_id] = c.models;
91
+ setModelsBy(by);
92
+ setProvider(prov.provider && prov.provider.provider_type === "local-ai" ? prov.provider : null);
93
+ const eby = {};
94
+ for (const e of eps.endpoints || []) {
95
+ (eby[e.connector_id] = eby[e.connector_id] || []).push(e);
96
+ }
97
+ setEndpointsBy(eby);
98
+ } catch (e) {
99
+ setError(e.message);
100
+ } finally {
101
+ pollInFlight.current = false;
102
+ }
103
+ }, []);
104
+ useEffect(() => {
105
+ busyRef.current = busy;
106
+ confirmRevokeRef.current = confirmRevoke;
107
+ }, [busy, confirmRevoke]);
108
+ useEffect(() => {
109
+ let alive = true;
110
+ (async () => {
111
+ await loadStatus();
112
+ if (alive) setLoaded(true);
113
+ })();
114
+ const timer = setInterval(() => {
115
+ if (!busyRef.current && !confirmRevokeRef.current) void loadStatus();
116
+ }, 15e3);
117
+ return () => {
118
+ alive = false;
119
+ clearInterval(timer);
120
+ };
121
+ }, []);
122
+ const onCreate = async () => {
123
+ if (creating) return;
124
+ const v = validateCreateInput(name, runtimeType);
125
+ if (!v.ok) {
126
+ setError(t(v.error));
127
+ return;
128
+ }
129
+ setCreating(true);
130
+ setError("");
131
+ setNotice("");
132
+ setTestOk(null);
133
+ try {
134
+ const res = await api.postJson("/ai-connector/registrations", {
135
+ name: v.name,
136
+ runtime_type: v.runtime_type
137
+ });
138
+ if (looksLikeRegToken(res.reg_token)) {
139
+ setDisclosure({
140
+ kind: "register",
141
+ token: res.reg_token,
142
+ connectorName: res.connector.name,
143
+ regExpiresAt: res.reg_expires_at,
144
+ wsUrl: res.ws_url
145
+ });
146
+ }
147
+ setName("");
148
+ await loadStatus();
149
+ } catch (e) {
150
+ setError(humanError(ports, e));
151
+ } finally {
152
+ setCreating(false);
153
+ }
154
+ };
155
+ const onReissueToken = async (c) => {
156
+ if (busy) return;
157
+ setBusy(true);
158
+ setError("");
159
+ setNotice("");
160
+ setTestOk(null);
161
+ try {
162
+ const res = await api.getJson(
163
+ `/ai-connector/registrations/${encodeURIComponent(c.connector_id)}/token`
164
+ );
165
+ if (looksLikeRegToken(res.reg_token)) {
166
+ setDisclosure({
167
+ kind: "register",
168
+ token: res.reg_token,
169
+ connectorName: c.name,
170
+ regExpiresAt: res.reg_expires_at,
171
+ wsUrl: res.ws_url
172
+ });
173
+ }
174
+ } catch (e) {
175
+ setError(humanError(ports, e));
176
+ } finally {
177
+ setBusy(false);
178
+ }
179
+ };
180
+ const onRefreshModels = async (c) => {
181
+ if (refreshingId) return;
182
+ setRefreshingId(c.connector_id);
183
+ setError("");
184
+ setNotice("");
185
+ setTestOk(null);
186
+ try {
187
+ const res = await api.postJson(
188
+ `/ai-connector/connectors/${encodeURIComponent(c.connector_id)}/models/refresh`
189
+ );
190
+ if (res.ok && res.models) {
191
+ setModelsBy((m) => ({ ...m, [c.connector_id]: res.models }));
192
+ setNotice(tf("local_ai_models_refreshed", String(res.models.length)));
193
+ } else {
194
+ setError(t(connectorErrorKey(res.code || "")));
195
+ }
196
+ } catch (e) {
197
+ setError(humanError(ports, e));
198
+ } finally {
199
+ setRefreshingId(null);
200
+ }
201
+ };
202
+ const onBind = async (c) => {
203
+ if (busy) return;
204
+ const model = (bindingModel[c.connector_id] ?? "").trim();
205
+ if (!model && !(modelsBy[c.connector_id] ?? []).length) {
206
+ setError(t("local_ai_err_no_models"));
207
+ return;
208
+ }
209
+ setBusy(true);
210
+ setError("");
211
+ setNotice("");
212
+ setTestOk(null);
213
+ try {
214
+ await api.putJson("/settings/ai/provider", {
215
+ provider_type: "local-ai",
216
+ connector_id: c.connector_id,
217
+ model
218
+ });
219
+ setNotice(tf("local_ai_bound", c.name));
220
+ await loadStatus();
221
+ } catch (e) {
222
+ setError(humanError(ports, e));
223
+ } finally {
224
+ setBusy(false);
225
+ }
226
+ };
227
+ const onUnbind = async () => {
228
+ if (busy) return;
229
+ setBusy(true);
230
+ setError("");
231
+ setNotice("");
232
+ try {
233
+ await api.deleteJson("/settings/ai/provider");
234
+ setProvider(null);
235
+ setNotice(t("local_ai_unbound"));
236
+ } catch (e) {
237
+ setError(humanError(ports, e));
238
+ } finally {
239
+ setBusy(false);
240
+ }
241
+ };
242
+ const onTest = async (c) => {
243
+ if (testingId) return;
244
+ setTestingId(c.connector_id);
245
+ setError("");
246
+ setNotice("");
247
+ setTestOk(null);
248
+ try {
249
+ const body = { provider_type: "local-ai", connector_id: c.connector_id };
250
+ const model = (bindingModel[c.connector_id] ?? "").trim();
251
+ if (model) body.model = model;
252
+ const res = await api.postJson("/settings/ai/test", body);
253
+ if (res.ok) {
254
+ setTestOk(res.model || c.name);
255
+ } else {
256
+ setError(tf("local_ai_test_fail", t(connectorErrorKey(res.code || ""))));
257
+ }
258
+ await loadStatus();
259
+ } catch (e) {
260
+ setError(tf("local_ai_test_fail", humanError(ports, e)));
261
+ } finally {
262
+ setTestingId(null);
263
+ }
264
+ };
265
+ const onRotate = async (c) => {
266
+ if (busy) return;
267
+ setBusy(true);
268
+ setError("");
269
+ setNotice("");
270
+ setTestOk(null);
271
+ try {
272
+ const res = await api.postJson(
273
+ `/ai-connector/connectors/${encodeURIComponent(c.connector_id)}/rotate`
274
+ );
275
+ if (looksLikeConnectorCredential(res.token)) {
276
+ setDisclosure({ kind: "rotate", token: res.token, connectorName: c.name });
277
+ }
278
+ } catch (e) {
279
+ setError(humanError(ports, e));
280
+ } finally {
281
+ setBusy(false);
282
+ }
283
+ };
284
+ const onRevoke = async (c) => {
285
+ if (busy) return;
286
+ setBusy(true);
287
+ setError("");
288
+ setNotice("");
289
+ setTestOk(null);
290
+ try {
291
+ await api.deleteJson(`/ai-connector/connectors/${encodeURIComponent(c.connector_id)}`);
292
+ setConfirmRevoke(null);
293
+ setNotice(tf("local_ai_revoked", c.name));
294
+ await loadStatus();
295
+ } catch (e) {
296
+ setError(humanError(ports, e));
297
+ } finally {
298
+ setBusy(false);
299
+ }
300
+ };
301
+ const onCreateEndpoint = async (c) => {
302
+ if (busy) return;
303
+ setBusy(true);
304
+ setError("");
305
+ setNotice("");
306
+ setTestOk(null);
307
+ try {
308
+ const res = await api.postJson("/ai-endpoints", {
309
+ name: `${c.name} endpoint`,
310
+ connector_id: c.connector_id,
311
+ runtime_type: c.runtime_type
312
+ });
313
+ setNotice(tf("share_ai_endpoint_created", res.endpoint.name));
314
+ await loadStatus();
315
+ } catch (e) {
316
+ setError(humanError(ports, e));
317
+ } finally {
318
+ setBusy(false);
319
+ }
320
+ };
321
+ const onShare = async (e) => {
322
+ if (busy) return;
323
+ setBusy(true);
324
+ setError("");
325
+ setNotice("");
326
+ setTestOk(null);
327
+ try {
328
+ await api.postJson(`/ai-endpoints/${encodeURIComponent(e.endpoint_id)}/share`, {
329
+ confirm_share: true
330
+ });
331
+ setConfirmShare(null);
332
+ setNotice(t("share_ai_enabled_notice"));
333
+ await loadStatus();
334
+ } catch (err) {
335
+ setError(humanError(ports, err));
336
+ } finally {
337
+ setBusy(false);
338
+ }
339
+ };
340
+ const onUnshare = async (e) => {
341
+ if (busy) return;
342
+ setBusy(true);
343
+ setError("");
344
+ setNotice("");
345
+ setTestOk(null);
346
+ try {
347
+ await api.deleteJson(`/ai-endpoints/${encodeURIComponent(e.endpoint_id)}/share`);
348
+ setNotice(t("share_ai_disabled_notice"));
349
+ await loadStatus();
350
+ } catch (err) {
351
+ setError(humanError(ports, err));
352
+ } finally {
353
+ setBusy(false);
354
+ }
355
+ };
356
+ const onDeleteEndpoint = async (e) => {
357
+ if (busy) return;
358
+ setBusy(true);
359
+ setError("");
360
+ setNotice("");
361
+ setTestOk(null);
362
+ try {
363
+ await api.deleteJson(`/ai-endpoints/${encodeURIComponent(e.endpoint_id)}`);
364
+ setNotice(t("share_ai_endpoint_deleted"));
365
+ await loadStatus();
366
+ } catch (err) {
367
+ setError(humanError(ports, err));
368
+ } finally {
369
+ setBusy(false);
370
+ }
371
+ };
372
+ const boundConnectorId = provider?.connector_id ?? null;
373
+ return /* @__PURE__ */ jsxs("section", { class: "page settings-page", children: [
374
+ /* @__PURE__ */ jsxs("div", { class: "settings-page__scroll", children: [
375
+ /* @__PURE__ */ jsxs("div", { class: "card card--stack", children: [
376
+ /* @__PURE__ */ jsx("h3", { class: "card__title", children: t("local_ai_title") }),
377
+ /* @__PURE__ */ jsx("p", { class: "card__hint card__hint--wrap", children: t("local_ai_desc") }),
378
+ !loaded ? /* @__PURE__ */ jsx("p", { class: "card__hint", children: t("play_loading") }) : provider ? /* @__PURE__ */ jsxs(Fragment, { children: [
379
+ /* @__PURE__ */ jsxs("p", { class: "card__hint card__hint--wrap", children: [
380
+ t("local_ai_binding_active"),
381
+ ` \xB7 ${provider.model ?? t("local_ai_model_auto")}`,
382
+ ` \xB7 ${t("ai_provider_last_tested")}: ${formatLastTested(provider.last_tested_at)}`
383
+ ] }),
384
+ /* @__PURE__ */ jsx("div", { class: "settings__actions", children: /* @__PURE__ */ jsx("button", { class: "btn btn--outlined btn--error", onClick: () => void onUnbind(), disabled: busy, children: t("local_ai_unbind") }) })
385
+ ] }) : /* @__PURE__ */ jsx("p", { class: "card__hint card__hint--wrap", children: t("local_ai_binding_none") })
386
+ ] }),
387
+ /* @__PURE__ */ jsxs("div", { class: "card card--stack", children: [
388
+ /* @__PURE__ */ jsx("h3", { class: "card__title", children: t("local_ai_add_title") }),
389
+ /* @__PURE__ */ jsx("p", { class: "card__label", children: t("local_ai_name_label") }),
390
+ /* @__PURE__ */ jsx(
391
+ "input",
392
+ {
393
+ class: "settings__input",
394
+ value: name,
395
+ placeholder: t("local_ai_name_hint"),
396
+ "aria-label": t("local_ai_name_label"),
397
+ disabled: creating,
398
+ onInput: (e) => setName(e.target.value)
399
+ }
400
+ ),
401
+ /* @__PURE__ */ jsx("p", { class: "card__label", children: t("local_ai_runtime_label") }),
402
+ /* @__PURE__ */ jsx(
403
+ "select",
404
+ {
405
+ class: "select",
406
+ value: runtimeType,
407
+ "aria-label": t("local_ai_runtime_label"),
408
+ disabled: creating,
409
+ onChange: (e) => setRuntimeType(e.target.value),
410
+ children: RUNTIME_TYPE_OPTIONS.map(({ type, labelKey }) => /* @__PURE__ */ jsx("option", { value: type, children: t(labelKey) }, type))
411
+ }
412
+ ),
413
+ /* @__PURE__ */ jsx("p", { class: "card__hint card__hint--wrap", children: t("local_ai_add_hint") }),
414
+ /* @__PURE__ */ jsx("div", { class: "settings__actions", children: /* @__PURE__ */ jsx("button", { class: "btn btn--block", onClick: () => void onCreate(), disabled: creating, children: creating ? t("play_loading") : t("local_ai_create") }) })
415
+ ] }),
416
+ /* @__PURE__ */ jsxs("div", { class: "card card--stack", children: [
417
+ /* @__PURE__ */ jsx("h3", { class: "card__title", children: t("local_ai_list_title") }),
418
+ !loaded ? /* @__PURE__ */ jsx("p", { class: "card__hint", children: t("play_loading") }) : !connectors || connectors.length === 0 ? /* @__PURE__ */ jsx("p", { class: "card__hint card__hint--wrap", children: t("local_ai_empty") }) : /* @__PURE__ */ jsx("div", { class: "worker__list", children: connectors.map((c) => {
419
+ const models = modelsBy[c.connector_id] ?? [];
420
+ const isBound = boundConnectorId === c.connector_id;
421
+ const reachable = runtimeReachable(c);
422
+ const rtInfo = runtimeInfo(c);
423
+ const endpoints = endpointsBy[c.connector_id] ?? [];
424
+ return /* @__PURE__ */ jsxs("div", { class: "worker__row", children: [
425
+ /* @__PURE__ */ jsxs("div", { class: "worker__row-main", children: [
426
+ /* @__PURE__ */ jsx("span", { class: "worker__name", children: c.name }),
427
+ /* @__PURE__ */ jsxs("span", { class: "worker__row-badges", children: [
428
+ isBound && /* @__PURE__ */ jsx("span", { class: "worker__badge worker__badge--private", children: t("local_ai_bound_badge") }),
429
+ /* @__PURE__ */ jsx("span", { class: "worker__status " + statusClass(c), children: t(statusKey(c)) })
430
+ ] })
431
+ ] }),
432
+ /* @__PURE__ */ jsxs("div", { class: "worker__row-meta", children: [
433
+ /* @__PURE__ */ jsx("span", { children: t(RUNTIME_TYPE_OPTIONS.find((o) => o.type === c.runtime_type)?.labelKey ?? "local_ai_runtime_openai_compatible") }),
434
+ /* @__PURE__ */ jsx("span", { children: "\xB7" }),
435
+ /* @__PURE__ */ jsxs("span", { children: [
436
+ t("worker_last_seen"),
437
+ " ",
438
+ formatLastSeen(c.last_seen)
439
+ ] }),
440
+ /* @__PURE__ */ jsx("span", { children: "\xB7" }),
441
+ /* @__PURE__ */ jsx("span", { children: reachable ? t("local_ai_runtime_ok") : t("local_ai_runtime_unknown") }),
442
+ rtInfo.length > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
443
+ /* @__PURE__ */ jsx("span", { children: "\xB7" }),
444
+ /* @__PURE__ */ jsx("span", { children: rtInfo.join(" \xB7 ") })
445
+ ] })
446
+ ] }),
447
+ /* @__PURE__ */ jsxs("div", { class: "worker__row-meta", children: [
448
+ /* @__PURE__ */ jsx("span", { children: tf("local_ai_models_count", String(c.models_count)) }),
449
+ models.length > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
450
+ /* @__PURE__ */ jsx("span", { children: "\xB7" }),
451
+ /* @__PURE__ */ jsxs("span", { class: "local-ai__model-hint", children: [
452
+ models.slice(0, 4).join(", "),
453
+ models.length > 4 ? "\u2026" : ""
454
+ ] })
455
+ ] })
456
+ ] }),
457
+ /* @__PURE__ */ jsx("p", { class: "card__hint", children: t("local_ai_models_disclaimer") }),
458
+ /* @__PURE__ */ jsx("div", { class: "worker__row-meta", children: /* @__PURE__ */ jsx("span", { class: "card__label", children: t("share_ai_title") }) }),
459
+ endpoints.length === 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [
460
+ /* @__PURE__ */ jsx("p", { class: "card__hint card__hint--wrap", children: t("share_ai_no_endpoint_hint") }),
461
+ c.status !== "pending" && /* @__PURE__ */ jsx("div", { class: "settings__actions", children: /* @__PURE__ */ jsx(
462
+ "button",
463
+ {
464
+ class: "btn btn--outlined",
465
+ disabled: busy,
466
+ title: t("share_ai_create_endpoint_hint"),
467
+ onClick: () => void onCreateEndpoint(c),
468
+ children: t("share_ai_create_endpoint")
469
+ }
470
+ ) })
471
+ ] }) : endpoints.map((ep) => {
472
+ const ss = shareStatus(ep);
473
+ return /* @__PURE__ */ jsxs("div", { class: "worker__row-meta", children: [
474
+ /* @__PURE__ */ jsx("span", { children: ep.name }),
475
+ /* @__PURE__ */ jsx("span", { children: "\xB7" }),
476
+ /* @__PURE__ */ jsx("span", { class: "worker__status " + shareStatusClass(ss), children: t(shareStatusKey(ss)) }),
477
+ /* @__PURE__ */ jsx("span", { children: "\xB7" }),
478
+ /* @__PURE__ */ jsx("span", { children: tf("share_ai_concurrency_label", String(ep.concurrency_limit)) }),
479
+ /* @__PURE__ */ jsx("span", { children: "\xB7" }),
480
+ /* @__PURE__ */ jsx("span", { children: tf("local_ai_models_count", String(ep.models_discovered)) }),
481
+ ep.model && /* @__PURE__ */ jsxs(Fragment, { children: [
482
+ /* @__PURE__ */ jsx("span", { children: "\xB7" }),
483
+ /* @__PURE__ */ jsx("span", { children: tf("share_ai_models_label", ep.model) })
484
+ ] }),
485
+ /* @__PURE__ */ jsxs("div", { class: "settings__actions", children: [
486
+ !ep.sharing_enabled ? /* @__PURE__ */ jsx(
487
+ "button",
488
+ {
489
+ class: "btn btn--outlined",
490
+ disabled: busy,
491
+ onClick: () => setConfirmShare(ep),
492
+ children: t("share_ai_share_button")
493
+ }
494
+ ) : /* @__PURE__ */ jsx("button", { class: "btn btn--outlined", disabled: busy, onClick: () => void onUnshare(ep), children: t("share_ai_unshare_button") }),
495
+ /* @__PURE__ */ jsx(
496
+ "button",
497
+ {
498
+ class: "btn btn--outlined btn--error",
499
+ disabled: busy,
500
+ onClick: () => void onDeleteEndpoint(ep),
501
+ children: t("worker_delete")
502
+ }
503
+ )
504
+ ] })
505
+ ] }, ep.endpoint_id);
506
+ }),
507
+ /* @__PURE__ */ jsx("p", { class: "card__label", children: t("ai_provider_model") }),
508
+ /* @__PURE__ */ jsx(
509
+ "input",
510
+ {
511
+ class: "settings__input",
512
+ value: bindingModel[c.connector_id] ?? "",
513
+ placeholder: models.length ? models[0] : t("local_ai_model_pick_hint"),
514
+ "aria-label": t("ai_provider_model"),
515
+ disabled: busy,
516
+ onInput: (e) => setBindingModel((m) => ({
517
+ ...m,
518
+ [c.connector_id]: e.target.value
519
+ }))
520
+ }
521
+ ),
522
+ c.status === "pending" && /* @__PURE__ */ jsx("p", { class: "card__hint card__hint--wrap", children: t("local_ai_pending_hint") }),
523
+ statusKey(c) === "local_ai_status_offline" && /* @__PURE__ */ jsxs("details", { class: "worker__trouble", children: [
524
+ /* @__PURE__ */ jsx("summary", { children: t("worker_trouble_title") }),
525
+ /* @__PURE__ */ jsx("ul", { class: "worker__steps", children: OFFLINE_TROUBLESHOOT_KEYS.map((k) => /* @__PURE__ */ jsx("li", { children: t(k) }, k)) })
526
+ ] }),
527
+ /* @__PURE__ */ jsxs("div", { class: "worker__actions", children: [
528
+ c.status === "pending" && /* @__PURE__ */ jsx("button", { class: "btn btn--outlined", disabled: busy, onClick: () => void onReissueToken(c), children: t("local_ai_reissue_token") }),
529
+ /* @__PURE__ */ jsx(
530
+ "button",
531
+ {
532
+ class: "btn btn--outlined",
533
+ disabled: refreshingId === c.connector_id,
534
+ onClick: () => void onRefreshModels(c),
535
+ children: refreshingId === c.connector_id ? t("play_loading") : t("local_ai_refresh_models")
536
+ }
537
+ ),
538
+ /* @__PURE__ */ jsx(
539
+ "button",
540
+ {
541
+ class: "btn btn--outlined",
542
+ disabled: testingId === c.connector_id || c.status === "pending",
543
+ title: t("local_ai_test_cold_warning"),
544
+ onClick: () => void onTest(c),
545
+ children: testingId === c.connector_id ? t("play_loading") : t("ai_provider_test")
546
+ }
547
+ ),
548
+ /* @__PURE__ */ jsx("button", { class: "btn", disabled: busy, onClick: () => void onBind(c), children: isBound ? t("local_ai_rebind") : t("local_ai_bind") }),
549
+ c.status !== "pending" && /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsx("button", { class: "btn btn--outlined", disabled: busy, onClick: () => void onRotate(c), children: t("worker_rotate_short") }) }),
550
+ /* @__PURE__ */ jsx("button", { class: "btn btn--outlined btn--error", disabled: busy, onClick: () => setConfirmRevoke(c), children: t("worker_revoke") })
551
+ ] })
552
+ ] }, c.connector_id);
553
+ }) }),
554
+ (testOk || notice || error) && /* @__PURE__ */ jsxs("div", { class: "settings__feedback", children: [
555
+ testOk && /* @__PURE__ */ jsxs("p", { class: "settings-page__success", children: [
556
+ /* @__PURE__ */ jsx("svg", { class: "settings-page__success-icon", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2.5", width: "16", height: "16", children: /* @__PURE__ */ jsx("path", { d: "M20 6L9 17l-5-5" }) }),
557
+ t("ai_provider_test_ok"),
558
+ testOk ? ` \xB7 ${testOk}` : ""
559
+ ] }),
560
+ notice && /* @__PURE__ */ jsx("p", { class: "card__hint", children: notice }),
561
+ error && /* @__PURE__ */ jsx("p", { class: "settings-page__error", children: error })
562
+ ] })
563
+ ] })
564
+ ] }),
565
+ disclosure && /* @__PURE__ */ jsx(
566
+ OneTimeCredentialModal,
567
+ {
568
+ kind: disclosure.kind,
569
+ token: disclosure.token,
570
+ connectorName: disclosure.connectorName,
571
+ regExpiresAt: disclosure.regExpiresAt,
572
+ wsUrl: disclosure.wsUrl,
573
+ onClose: () => setDisclosure(null),
574
+ ports
575
+ }
576
+ ),
577
+ confirmRevoke && /* @__PURE__ */ jsx(
578
+ ui.Modal,
579
+ {
580
+ title: t("worker_revoke"),
581
+ onClose: () => {
582
+ if (!busy) setConfirmRevoke(null);
583
+ },
584
+ children: /* @__PURE__ */ jsxs(Fragment, { children: [
585
+ /* @__PURE__ */ jsx("p", { class: "modal__notice", children: tf("local_ai_revoke_confirm", confirmRevoke.name) }),
586
+ /* @__PURE__ */ jsx("p", { class: "card__hint card__hint--wrap", children: t("local_ai_revoke_hint") }),
587
+ /* @__PURE__ */ jsxs("div", { class: "modal__footer", children: [
588
+ /* @__PURE__ */ jsx("button", { class: "btn btn--outlined", onClick: () => {
589
+ if (!busy) setConfirmRevoke(null);
590
+ }, disabled: busy, children: t("dialog_cancel") }),
591
+ /* @__PURE__ */ jsx("button", { class: "btn btn--error", onClick: () => void onRevoke(confirmRevoke), disabled: busy, children: busy ? t("play_loading") : t("worker_revoke") })
592
+ ] })
593
+ ] })
594
+ }
595
+ ),
596
+ confirmShare && /* @__PURE__ */ jsx(
597
+ ui.Modal,
598
+ {
599
+ title: t("share_ai_share_button"),
600
+ onClose: () => {
601
+ if (!busy) setConfirmShare(null);
602
+ },
603
+ children: /* @__PURE__ */ jsxs(Fragment, { children: [
604
+ /* @__PURE__ */ jsx("p", { class: "modal__notice", children: t("share_ai_enable_confirm") }),
605
+ /* @__PURE__ */ jsx("p", { class: "card__hint card__hint--wrap", children: t("share_ai_create_endpoint_hint") }),
606
+ /* @__PURE__ */ jsxs("div", { class: "modal__footer", children: [
607
+ /* @__PURE__ */ jsx("button", { class: "btn btn--outlined", onClick: () => {
608
+ if (!busy) setConfirmShare(null);
609
+ }, disabled: busy, children: t("dialog_cancel") }),
610
+ /* @__PURE__ */ jsx("button", { class: "btn", onClick: () => void onShare(confirmShare), disabled: busy, children: busy ? t("play_loading") : t("share_ai_share_button") })
611
+ ] })
612
+ ] })
613
+ }
614
+ )
615
+ ] });
616
+ }
617
+ function humanError(ports, e) {
618
+ const { t } = ports.i18n;
619
+ if (e instanceof ports.api.ApiError) {
620
+ if (e.status === 404) return t("local_ai_err_not_found");
621
+ if (e.status === 401) return t("local_ai_err_auth");
622
+ if (e.status === 403) return t("local_ai_err_forbidden");
623
+ if (e.status === 429) return t("local_ai_err_rate_limited");
624
+ return e.message || t("local_ai_err_generic");
625
+ }
626
+ return e.message || t("local_ai_err_generic");
627
+ }
628
+
629
+ export { LocalAISection };
630
+ //# sourceMappingURL=index.js.map
631
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/LocalAISection.tsx"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["// ─────────────────────────────────────────────────────────────────────────\n// Local AI Connector section — /settings/local-ai (Local AI Connector V1, Phase 6)\n// ─────────────────────────────────────────────────────────────────────────\n// The USER-FACING flow for the Local AI Connector V1 (docs/04-planning/\n// local-ai-connector-v1.md §15 Phase 6):\n//\n// create connector → one-time registration token (+ CLI instruction)\n// → status (Pending / Online / Offline; runtime reachability distinct)\n// → Refresh Models (explicit discovery — never automatic, AD-7)\n// → bind as the workspace AI provider (existing PUT /settings/ai/provider\n// singleton — Connector ≠ Provider, AD-2)\n// → Test Connection (user-initiated max_tokens:1 probe, cold-load warning)\n// → rotate / revoke (existing atomic lifecycle).\n//\n// Model honesty (§7): \"discovered\" models are reported ids — NEVER shown as\n// loaded/warm. No inference probe runs on page open.\n//\n// SECURITY INVARIANT: llmcreg.*/llmc.* plaintext appears ONLY in the one-time\n// disclosure modal, held in transient `useState`, dropped on close — never\n// persisted, never re-displayable. The status/models responses never carry\n// credential material (not even the prefix mask).\n//\n// Package boundary (docs/architecture/web-local-ai-extraction-audit.md):\n// host capabilities (HTTP, i18n strings, Modal/toast) arrive ONLY through\n// the injected LocalAiPorts contract; domain logic comes from the pure\n// @animastor/web-settings package (the first package→package dependency).\n// ─────────────────────────────────────────────────────────────────────────\n\nimport { useState, useCallback, useEffect, useRef } from 'preact/hooks';\nimport type { LocalAiI18nKey, LocalAiPorts } from './ports';\nimport { formatLastTested } from '@animastor/web-settings';\nimport {\n RUNTIME_TYPE_OPTIONS,\n validateCreateInput,\n looksLikeRegToken,\n looksLikeConnectorCredential,\n statusKey,\n statusClass,\n runtimeReachable,\n runtimeInfo,\n formatLastSeen,\n connectorErrorKey,\n regTokenExpired,\n buildRunCommand,\n REGISTRATION_STEP_KEYS,\n OFFLINE_TROUBLESHOOT_KEYS,\n shareStatus,\n shareStatusKey,\n shareStatusClass,\n} from '@animastor/web-settings';\nimport type {\n AiConnectorStatus,\n AiConnectorModels,\n RegistrationResponse,\n RotateResponse,\n RefreshModelsResponse,\n ConnectorTestResponse,\n LocalProviderMeta,\n AiEndpoint,\n} from '@animastor/web-settings';\n\ninterface ProviderRead { provider: LocalProviderMeta | null; has_workspace_provider: boolean }\n\n/** One-time credential disclosure (registration token or rotated llmc.*).\n * The plaintext lives ONLY here, in transient component state. */\nfunction OneTimeCredentialModal({ kind, token, connectorName, regExpiresAt, wsUrl, onClose, ports }: {\n kind: 'register' | 'rotate';\n token: string;\n connectorName: string;\n regExpiresAt?: number | null;\n wsUrl?: string;\n onClose: () => void;\n ports: LocalAiPorts;\n}) {\n const [copied, setCopied] = useState(false);\n const [copiedCmd, setCopiedCmd] = useState(false);\n const { t, ui } = { t: ports.i18n.t, ui: ports.ui };\n const origin = typeof location !== 'undefined' ? location.origin : '';\n const runCommand = kind === 'register' && wsUrl ? buildRunCommand(token, wsUrl, origin) : null;\n\n const onCopy = useCallback(async (text: string, mark: (v: boolean) => void) => {\n try {\n await navigator.clipboard.writeText(text);\n mark(true);\n setTimeout(() => mark(false), 1800);\n } catch (_) {\n ui.toast(t('worker_copy_failed'));\n }\n }, []);\n\n return (\n <ui.Modal\n title={kind === 'register' ? t('local_ai_reg_title') : `${t('worker_rotate')} — ${connectorName}`}\n onClose={onClose}\n >\n <>\n <p class=\"modal__notice worker__warn\">{t('local_ai_credential_warning')}</p>\n {kind === 'register' && regTokenExpired(regExpiresAt) && (\n <p class=\"settings-page__error\">{t('local_ai_reg_expired')}</p>\n )}\n <p class=\"card__label\">{t('local_ai_reg_token_label')}</p>\n <div class=\"worker__cred\">\n <code class=\"worker__token\">{token}</code>\n <button class=\"btn btn--outlined\" onClick={() => void onCopy(token, setCopied)}>\n {copied ? t('worker_copied') : t('worker_copy')}\n </button>\n </div>\n\n {kind === 'register' && (\n <>\n <p class=\"card__label\">{t('local_ai_setup_title')}</p>\n <p class=\"card__hint card__hint--wrap\">{t('local_ai_setup_intro')}</p>\n <ol class=\"worker__steps\">\n {REGISTRATION_STEP_KEYS.map((k) => (\n <li key={k}>{t(k)}</li>\n ))}\n </ol>\n {runCommand && (\n <>\n <p class=\"card__label\">{t('local_ai_run_command_label')}</p>\n <div class=\"worker__cred\">\n <code class=\"worker__token\">{runCommand}</code>\n <button class=\"btn btn--outlined\" onClick={() => void onCopy(runCommand, setCopiedCmd)}>\n {copiedCmd ? t('worker_copied') : t('worker_copy')}\n </button>\n </div>\n </>\n )}\n <p class=\"card__hint card__hint--wrap\">{t('local_ai_reg_ttl_hint')}</p>\n </>\n )}\n {kind === 'rotate' && (\n <p class=\"card__hint card__hint--wrap\">{t('local_ai_rotate_hint')}</p>\n )}\n <div class=\"modal__footer\">\n <button class=\"btn\" onClick={onClose}>{t('worker_done')}</button>\n </div>\n </>\n </ui.Modal>\n );\n}\n\nexport function LocalAISection({ ports }: { ports: LocalAiPorts }) {\n const { api, t, tf, ui } = { api: ports.api, t: ports.i18n.t, tf: ports.i18n.tf, ui: ports.ui };\n const [connectors, setConnectors] = useState<AiConnectorStatus[] | null>(null);\n const [modelsBy, setModelsBy] = useState<Record<string, string[]>>({});\n const [provider, setProvider] = useState<LocalProviderMeta | null>(null);\n const [name, setName] = useState('');\n const [runtimeType, setRuntimeType] = useState<string>('ollama');\n const [busy, setBusy] = useState(false);\n const [creating, setCreating] = useState(false);\n const [refreshingId, setRefreshingId] = useState<string | null>(null);\n const [testingId, setTestingId] = useState<string | null>(null);\n const [error, setError] = useState('');\n const [notice, setNotice] = useState('');\n const [testOk, setTestOk] = useState<string | null>(null);\n const [loaded, setLoaded] = useState(false);\n const [confirmRevoke, setConfirmRevoke] = useState<AiConnectorStatus | null>(null);\n // One-time disclosure — transient state only (never persisted).\n const [disclosure, setDisclosure] = useState<\n { kind: 'register' | 'rotate'; token: string; connectorName: string; regExpiresAt?: number | null; wsUrl?: string } | null\n >(null);\n // Per-connector model selection for the provider binding (transient).\n const [bindingModel, setBindingModel] = useState<Record<string, string>>({});\n // LLM Sharing Phase 1 — endpoints by connector id (owner view).\n const [endpointsBy, setEndpointsBy] = useState<Record<string, AiEndpoint[]>>({});\n const [confirmShare, setConfirmShare] = useState<AiEndpoint | null>(null);\n\n // Fresh-value refs so the polling interval (mounted once) actually sees\n // the CURRENT busy/confirmRevoke state — a stale closure here would keep\n // polling during operations and let slow polls overlap.\n const busyRef = useRef(false);\n const confirmRevokeRef = useRef<AiConnectorStatus | null>(null);\n const pollInFlight = useRef(false);\n\n const loadStatus = useCallback(async () => {\n // In-flight dedup: a slow poll never stacks on top of a newer one.\n if (pollInFlight.current) return;\n pollInFlight.current = true;\n try {\n const [st, models, prov, eps] = await Promise.all([\n api.getJson<{ connectors: AiConnectorStatus[] }>('/ai-connector/status'),\n api.getJson<{ connectors: AiConnectorModels[] }>('/ai-connector/models'),\n api.getJson<ProviderRead>('/settings/ai/provider'),\n // LLM Sharing Phase 1: owner's endpoint rows (401/404 silently\n // ignored — an account without endpoints sees none).\n api.getJson<{ endpoints: AiEndpoint[] }>('/ai-endpoints').catch(() => ({ endpoints: [] as AiEndpoint[] })),\n ]);\n setConnectors(st.connectors);\n const by: Record<string, string[]> = {};\n for (const c of models.connectors) by[c.connector_id] = c.models;\n setModelsBy(by);\n setProvider(prov.provider && prov.provider.provider_type === 'local-ai' ? prov.provider : null);\n const eby: Record<string, AiEndpoint[]> = {};\n for (const e of eps.endpoints || []) {\n (eby[e.connector_id] = eby[e.connector_id] || []).push(e);\n }\n setEndpointsBy(eby);\n } catch (e) {\n setError((e as Error).message);\n } finally {\n pollInFlight.current = false;\n }\n }, []);\n\n useEffect(() => {\n // Keep the refs current for the mounted-once interval below.\n busyRef.current = busy;\n confirmRevokeRef.current = confirmRevoke;\n }, [busy, confirmRevoke]);\n\n useEffect(() => {\n let alive = true;\n (async () => {\n await loadStatus();\n if (alive) setLoaded(true);\n })();\n // Read-only status polling (PG + in-memory registry — no runtime probe,\n // no inference; AD-7 intact). Light cadence; paused while busy or while\n // a confirm/revoke dialog is open.\n const timer = setInterval(() => {\n if (!busyRef.current && !confirmRevokeRef.current) void loadStatus();\n }, 15_000);\n return () => { alive = false; clearInterval(timer); };\n }, []);\n\n const onCreate = async () => {\n if (creating) return;\n const v = validateCreateInput(name, runtimeType);\n if (!v.ok) { setError(t(v.error)); return; }\n setCreating(true); setError(''); setNotice(''); setTestOk(null);\n try {\n const res = await api.postJson<RegistrationResponse>('/ai-connector/registrations', {\n name: v.name,\n runtime_type: v.runtime_type,\n });\n // One-time disclosure — the ONLY place the plaintext token appears.\n if (looksLikeRegToken(res.reg_token)) {\n setDisclosure({\n kind: 'register',\n token: res.reg_token,\n connectorName: res.connector.name,\n regExpiresAt: res.reg_expires_at,\n wsUrl: res.ws_url,\n });\n }\n setName('');\n await loadStatus();\n } catch (e) {\n setError(humanError(ports, e));\n } finally {\n setCreating(false);\n }\n };\n\n const onReissueToken = async (c: AiConnectorStatus) => {\n if (busy) return;\n setBusy(true); setError(''); setNotice(''); setTestOk(null);\n try {\n const res = await api.getJson<RegistrationResponse>(\n `/ai-connector/registrations/${encodeURIComponent(c.connector_id)}/token`\n );\n if (looksLikeRegToken(res.reg_token)) {\n setDisclosure({\n kind: 'register',\n token: res.reg_token,\n connectorName: c.name,\n regExpiresAt: res.reg_expires_at,\n wsUrl: res.ws_url,\n });\n }\n } catch (e) {\n setError(humanError(ports, e));\n } finally {\n setBusy(false);\n }\n };\n\n const onRefreshModels = async (c: AiConnectorStatus) => {\n if (refreshingId) return;\n setRefreshingId(c.connector_id); setError(''); setNotice(''); setTestOk(null);\n try {\n const res = await api.postJson<RefreshModelsResponse>(\n `/ai-connector/connectors/${encodeURIComponent(c.connector_id)}/models/refresh`\n );\n if (res.ok && res.models) {\n setModelsBy((m) => ({ ...m, [c.connector_id]: res.models! }));\n setNotice(tf('local_ai_models_refreshed', String(res.models.length)));\n } else {\n setError(t(connectorErrorKey(res.code || '') as LocalAiI18nKey));\n }\n } catch (e) {\n setError(humanError(ports, e));\n } finally {\n setRefreshingId(null);\n }\n };\n\n const onBind = async (c: AiConnectorStatus) => {\n if (busy) return;\n const model = (bindingModel[c.connector_id] ?? '').trim();\n if (!model && !(modelsBy[c.connector_id] ?? []).length) {\n setError(t('local_ai_err_no_models'));\n return;\n }\n setBusy(true); setError(''); setNotice(''); setTestOk(null);\n try {\n await api.putJson<ProviderRead>('/settings/ai/provider', {\n provider_type: 'local-ai',\n connector_id: c.connector_id,\n model,\n });\n setNotice(tf('local_ai_bound', c.name));\n await loadStatus();\n } catch (e) {\n setError(humanError(ports, e));\n } finally {\n setBusy(false);\n }\n };\n\n const onUnbind = async () => {\n if (busy) return;\n setBusy(true); setError(''); setNotice('');\n try {\n await api.deleteJson('/settings/ai/provider');\n setProvider(null);\n setNotice(t('local_ai_unbound'));\n } catch (e) {\n setError(humanError(ports, e));\n } finally {\n setBusy(false);\n }\n };\n\n const onTest = async (c: AiConnectorStatus) => {\n if (testingId) return;\n setTestingId(c.connector_id); setError(''); setNotice(''); setTestOk(null);\n try {\n const body: Record<string, unknown> = { provider_type: 'local-ai', connector_id: c.connector_id };\n const model = (bindingModel[c.connector_id] ?? '').trim();\n if (model) body.model = model;\n const res = await api.postJson<ConnectorTestResponse>('/settings/ai/test', body);\n if (res.ok) {\n setTestOk(res.model || c.name);\n } else {\n setError(tf('local_ai_test_fail', t(connectorErrorKey(res.code || '') as LocalAiI18nKey)));\n }\n await loadStatus(); // status pill reflects the persisted test outcome\n } catch (e) {\n setError(tf('local_ai_test_fail', humanError(ports, e)));\n } finally {\n setTestingId(null);\n }\n };\n\n const onRotate = async (c: AiConnectorStatus) => {\n if (busy) return;\n setBusy(true); setError(''); setNotice(''); setTestOk(null);\n try {\n const res = await api.postJson<RotateResponse>(\n `/ai-connector/connectors/${encodeURIComponent(c.connector_id)}/rotate`\n );\n if (looksLikeConnectorCredential(res.token)) {\n setDisclosure({ kind: 'rotate', token: res.token, connectorName: c.name });\n }\n } catch (e) {\n setError(humanError(ports, e));\n } finally {\n setBusy(false);\n }\n };\n\n const onRevoke = async (c: AiConnectorStatus) => {\n if (busy) return;\n setBusy(true); setError(''); setNotice(''); setTestOk(null);\n try {\n await api.deleteJson(`/ai-connector/connectors/${encodeURIComponent(c.connector_id)}`);\n setConfirmRevoke(null);\n setNotice(tf('local_ai_revoked', c.name));\n await loadStatus();\n } catch (e) {\n setError(humanError(ports, e));\n } finally {\n setBusy(false);\n }\n };\n\n // ── LLM Sharing Phase 1 — Share this AI ─────────────────────────────────\n // Minimal owner-side control plane: create endpoint (Private), enable /\n // disable sharing, delete. NEVER shows credentials, local IP, runtime URL\n // or filesystem info — the backend endpoint shape has none of it.\n\n const onCreateEndpoint = async (c: AiConnectorStatus) => {\n if (busy) return;\n setBusy(true); setError(''); setNotice(''); setTestOk(null);\n try {\n const res = await api.postJson<{ endpoint: AiEndpoint }>('/ai-endpoints', {\n name: `${c.name} endpoint`,\n connector_id: c.connector_id,\n runtime_type: c.runtime_type,\n });\n setNotice(tf('share_ai_endpoint_created', res.endpoint.name));\n await loadStatus();\n } catch (e) {\n setError(humanError(ports, e));\n } finally {\n setBusy(false);\n }\n };\n\n const onShare = async (e: AiEndpoint) => {\n if (busy) return;\n setBusy(true); setError(''); setNotice(''); setTestOk(null);\n try {\n await api.postJson(`/ai-endpoints/${encodeURIComponent(e.endpoint_id)}/share`, {\n confirm_share: true,\n });\n setConfirmShare(null);\n setNotice(t('share_ai_enabled_notice'));\n await loadStatus();\n } catch (err) {\n setError(humanError(ports, err));\n } finally {\n setBusy(false);\n }\n };\n\n const onUnshare = async (e: AiEndpoint) => {\n if (busy) return;\n setBusy(true); setError(''); setNotice(''); setTestOk(null);\n try {\n await api.deleteJson(`/ai-endpoints/${encodeURIComponent(e.endpoint_id)}/share`);\n setNotice(t('share_ai_disabled_notice'));\n await loadStatus();\n } catch (err) {\n setError(humanError(ports, err));\n } finally {\n setBusy(false);\n }\n };\n\n const onDeleteEndpoint = async (e: AiEndpoint) => {\n if (busy) return;\n setBusy(true); setError(''); setNotice(''); setTestOk(null);\n try {\n await api.deleteJson(`/ai-endpoints/${encodeURIComponent(e.endpoint_id)}`);\n setNotice(t('share_ai_endpoint_deleted'));\n await loadStatus();\n } catch (err) {\n setError(humanError(ports, err));\n } finally {\n setBusy(false);\n }\n };\n\n const boundConnectorId = provider?.connector_id ?? null;\n\n return (\n <section class=\"page settings-page\">\n <div class=\"settings-page__scroll\">\n {/* ── Active provider binding (Connector ≠ Provider, AD-2) ── */}\n <div class=\"card card--stack\">\n <h3 class=\"card__title\">{t('local_ai_title')}</h3>\n <p class=\"card__hint card__hint--wrap\">{t('local_ai_desc')}</p>\n {!loaded ? (\n <p class=\"card__hint\">{t('play_loading')}</p>\n ) : provider ? (\n <>\n <p class=\"card__hint card__hint--wrap\">\n {t('local_ai_binding_active')}\n {` · ${provider.model ?? t('local_ai_model_auto')}`}\n {` · ${t('ai_provider_last_tested')}: ${formatLastTested(provider.last_tested_at)}`}\n </p>\n <div class=\"settings__actions\">\n <button class=\"btn btn--outlined btn--error\" onClick={() => void onUnbind()} disabled={busy}>\n {t('local_ai_unbind')}\n </button>\n </div>\n </>\n ) : (\n <p class=\"card__hint card__hint--wrap\">{t('local_ai_binding_none')}</p>\n )}\n </div>\n\n {/* ── Add connector ── */}\n <div class=\"card card--stack\">\n <h3 class=\"card__title\">{t('local_ai_add_title')}</h3>\n <p class=\"card__label\">{t('local_ai_name_label')}</p>\n <input\n class=\"settings__input\"\n value={name}\n placeholder={t('local_ai_name_hint')}\n aria-label={t('local_ai_name_label')}\n disabled={creating}\n onInput={(e) => setName((e.target as HTMLInputElement).value)}\n />\n <p class=\"card__label\">{t('local_ai_runtime_label')}</p>\n <select\n class=\"select\"\n value={runtimeType}\n aria-label={t('local_ai_runtime_label')}\n disabled={creating}\n onChange={(e) => setRuntimeType((e.target as HTMLSelectElement).value)}\n >\n {RUNTIME_TYPE_OPTIONS.map(({ type, labelKey }) => (\n <option key={type} value={type}>{t(labelKey)}</option>\n ))}\n </select>\n <p class=\"card__hint card__hint--wrap\">{t('local_ai_add_hint')}</p>\n <div class=\"settings__actions\">\n <button class=\"btn btn--block\" onClick={() => void onCreate()} disabled={creating}>\n {creating ? t('play_loading') : t('local_ai_create')}\n </button>\n </div>\n </div>\n\n {/* ── Connector list ── */}\n <div class=\"card card--stack\">\n <h3 class=\"card__title\">{t('local_ai_list_title')}</h3>\n {!loaded ? (\n <p class=\"card__hint\">{t('play_loading')}</p>\n ) : !connectors || connectors.length === 0 ? (\n <p class=\"card__hint card__hint--wrap\">{t('local_ai_empty')}</p>\n ) : (\n <div class=\"worker__list\">\n {connectors.map((c) => {\n const models = modelsBy[c.connector_id] ?? [];\n const isBound = boundConnectorId === c.connector_id;\n const reachable = runtimeReachable(c);\n const rtInfo = runtimeInfo(c);\n const endpoints = endpointsBy[c.connector_id] ?? [];\n return (\n <div class=\"worker__row\" key={c.connector_id}>\n <div class=\"worker__row-main\">\n <span class=\"worker__name\">{c.name}</span>\n <span class=\"worker__row-badges\">\n {isBound && (\n <span class=\"worker__badge worker__badge--private\">{t('local_ai_bound_badge')}</span>\n )}\n <span class={'worker__status ' + statusClass(c)}>{t(statusKey(c))}</span>\n </span>\n </div>\n <div class=\"worker__row-meta\">\n <span>{t(RUNTIME_TYPE_OPTIONS.find((o) => o.type === c.runtime_type)?.labelKey ?? 'local_ai_runtime_openai_compatible')}</span>\n <span>·</span>\n <span>{t('worker_last_seen')} {formatLastSeen(c.last_seen)}</span>\n <span>·</span>\n {/* Runtime reachability — distinct from Online (§7) */}\n <span>{reachable ? t('local_ai_runtime_ok') : t('local_ai_runtime_unknown')}</span>\n {rtInfo.length > 0 && (<><span>·</span><span>{rtInfo.join(' · ')}</span></>)}\n </div>\n\n {/* Discovered models — reported ids, NOT loaded/warm */}\n <div class=\"worker__row-meta\">\n <span>{tf('local_ai_models_count', String(c.models_count))}</span>\n {models.length > 0 && (\n <>\n <span>·</span>\n <span class=\"local-ai__model-hint\">{models.slice(0, 4).join(', ')}{models.length > 4 ? '…' : ''}</span>\n </>\n )}\n </div>\n <p class=\"card__hint\">{t('local_ai_models_disclaimer')}</p>\n\n {/* ── LLM Sharing Phase 1: Share this AI (minimal\n owner-side control plane — no marketplace) ── */}\n <div class=\"worker__row-meta\">\n <span class=\"card__label\">{t('share_ai_title')}</span>\n </div>\n {endpoints.length === 0 ? (\n <>\n <p class=\"card__hint card__hint--wrap\">{t('share_ai_no_endpoint_hint')}</p>\n {c.status !== 'pending' && (\n <div class=\"settings__actions\">\n <button\n class=\"btn btn--outlined\"\n disabled={busy}\n title={t('share_ai_create_endpoint_hint')}\n onClick={() => void onCreateEndpoint(c)}\n >\n {t('share_ai_create_endpoint')}\n </button>\n </div>\n )}\n </>\n ) : (\n endpoints.map((ep) => {\n const ss = shareStatus(ep);\n return (\n <div class=\"worker__row-meta\" key={ep.endpoint_id}>\n <span>{ep.name}</span>\n <span>·</span>\n <span class={'worker__status ' + shareStatusClass(ss)}>{t(shareStatusKey(ss) as LocalAiI18nKey)}</span>\n <span>·</span>\n <span>{tf('share_ai_concurrency_label', String(ep.concurrency_limit))}</span>\n <span>·</span>\n <span>{tf('local_ai_models_count', String(ep.models_discovered))}</span>\n {ep.model && (\n <>\n <span>·</span>\n <span>{tf('share_ai_models_label', ep.model)}</span>\n </>\n )}\n <div class=\"settings__actions\">\n {!ep.sharing_enabled ? (\n <button\n class=\"btn btn--outlined\"\n disabled={busy}\n onClick={() => setConfirmShare(ep)}\n >\n {t('share_ai_share_button')}\n </button>\n ) : (\n <button class=\"btn btn--outlined\" disabled={busy} onClick={() => void onUnshare(ep)}>\n {t('share_ai_unshare_button')}\n </button>\n )}\n <button\n class=\"btn btn--outlined btn--error\"\n disabled={busy}\n onClick={() => void onDeleteEndpoint(ep)}\n >\n {t('worker_delete')}\n </button>\n </div>\n </div>\n );\n })\n )}\n\n {/* Model picker for the provider binding (free text or a\n discovered id — the no-registry principle). */}\n <p class=\"card__label\">{t('ai_provider_model')}</p>\n <input\n class=\"settings__input\"\n value={bindingModel[c.connector_id] ?? ''}\n placeholder={models.length ? models[0] : t('local_ai_model_pick_hint')}\n aria-label={t('ai_provider_model')}\n disabled={busy}\n onInput={(e) => setBindingModel((m) => ({\n ...m, [c.connector_id]: (e.target as HTMLInputElement).value,\n }))}\n />\n\n {c.status === 'pending' && (\n <p class=\"card__hint card__hint--wrap\">{t('local_ai_pending_hint')}</p>\n )}\n {statusKey(c) === 'local_ai_status_offline' && (\n <details class=\"worker__trouble\">\n <summary>{t('worker_trouble_title')}</summary>\n <ul class=\"worker__steps\">\n {OFFLINE_TROUBLESHOOT_KEYS.map((k) => (\n <li key={k}>{t(k)}</li>\n ))}\n </ul>\n </details>\n )}\n\n <div class=\"worker__actions\">\n {c.status === 'pending' && (\n <button class=\"btn btn--outlined\" disabled={busy} onClick={() => void onReissueToken(c)}>\n {t('local_ai_reissue_token')}\n </button>\n )}\n <button\n class=\"btn btn--outlined\"\n disabled={refreshingId === c.connector_id}\n onClick={() => void onRefreshModels(c)}\n >\n {refreshingId === c.connector_id ? t('play_loading') : t('local_ai_refresh_models')}\n </button>\n <button\n class=\"btn btn--outlined\"\n disabled={testingId === c.connector_id || c.status === 'pending'}\n title={t('local_ai_test_cold_warning')}\n onClick={() => void onTest(c)}\n >\n {testingId === c.connector_id ? t('play_loading') : t('ai_provider_test')}\n </button>\n <button class=\"btn\" disabled={busy} onClick={() => void onBind(c)}>\n {isBound ? t('local_ai_rebind') : t('local_ai_bind')}\n </button>\n {c.status !== 'pending' && (\n <>\n <button class=\"btn btn--outlined\" disabled={busy} onClick={() => void onRotate(c)}>\n {t('worker_rotate_short')}\n </button>\n </>\n )}\n <button class=\"btn btn--outlined btn--error\" disabled={busy} onClick={() => setConfirmRevoke(c)}>\n {t('worker_revoke')}\n </button>\n </div>\n </div>\n );\n })}\n </div>\n )}\n\n {(testOk || notice || error) && (\n <div class=\"settings__feedback\">\n {testOk && (\n <p class=\"settings-page__success\">\n <svg class=\"settings-page__success-icon\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" width=\"16\" height=\"16\"><path d=\"M20 6L9 17l-5-5\" /></svg>\n {t('ai_provider_test_ok')}{testOk ? ` · ${testOk}` : ''}\n </p>\n )}\n {notice && <p class=\"card__hint\">{notice}</p>}\n {error && <p class=\"settings-page__error\">{error}</p>}\n </div>\n )}\n </div>\n </div>\n\n {/* One-time credential disclosure modal(s) */}\n {disclosure && (\n <OneTimeCredentialModal\n kind={disclosure.kind}\n token={disclosure.token}\n connectorName={disclosure.connectorName}\n regExpiresAt={disclosure.regExpiresAt}\n wsUrl={disclosure.wsUrl}\n onClose={() => setDisclosure(null)}\n ports={ports}\n />\n )}\n\n {/* Revoke confirmation */}\n {confirmRevoke && (\n <ui.Modal\n title={t('worker_revoke')}\n onClose={() => { if (!busy) setConfirmRevoke(null); }}\n >\n <>\n <p class=\"modal__notice\">{tf('local_ai_revoke_confirm', confirmRevoke.name)}</p>\n <p class=\"card__hint card__hint--wrap\">{t('local_ai_revoke_hint')}</p>\n <div class=\"modal__footer\">\n <button class=\"btn btn--outlined\" onClick={() => { if (!busy) setConfirmRevoke(null); }} disabled={busy}>\n {t('dialog_cancel')}\n </button>\n <button class=\"btn btn--error\" onClick={() => void onRevoke(confirmRevoke)} disabled={busy}>\n {busy ? t('play_loading') : t('worker_revoke')}\n </button>\n </div>\n </>\n </ui.Modal>\n )}\n\n {/* Share confirmation (LLM Sharing Phase 1 — explicit owner consent) */}\n {confirmShare && (\n <ui.Modal\n title={t('share_ai_share_button')}\n onClose={() => { if (!busy) setConfirmShare(null); }}\n >\n <>\n <p class=\"modal__notice\">{t('share_ai_enable_confirm')}</p>\n <p class=\"card__hint card__hint--wrap\">{t('share_ai_create_endpoint_hint')}</p>\n <div class=\"modal__footer\">\n <button class=\"btn btn--outlined\" onClick={() => { if (!busy) setConfirmShare(null); }} disabled={busy}>\n {t('dialog_cancel')}\n </button>\n <button class=\"btn\" onClick={() => void onShare(confirmShare)} disabled={busy}>\n {busy ? t('play_loading') : t('share_ai_share_button')}\n </button>\n </div>\n </>\n </ui.Modal>\n )}\n </section>\n );\n}\n\n/** Sanitized error surface: no raw stack traces, no URLs, no secrets. */\nfunction humanError(ports: LocalAiPorts, e: unknown): string {\n const { t } = ports.i18n;\n if (e instanceof ports.api.ApiError) {\n if (e.status === 404) return t('local_ai_err_not_found');\n if (e.status === 401) return t('local_ai_err_auth');\n if (e.status === 403) return t('local_ai_err_forbidden');\n if (e.status === 429) return t('local_ai_err_rate_limited');\n return e.message || t('local_ai_err_generic');\n }\n return (e as Error).message || t('local_ai_err_generic');\n}\n"]}
package/package.json ADDED
@@ -0,0 +1,68 @@
1
+ {
2
+ "name": "@animastor/web-local-ai",
3
+ "version": "0.1.0",
4
+ "description": "Local AI Connector section UI for Animastor web frontend — connector lifecycle (create/rotate/revoke), one-time token disclosure, model refresh, provider binding, test probe, and Share-this-AI endpoints. Host capabilities (api, i18n, ui) arrive via the injected LocalAiPorts contract; the package never imports host stores, api/client, or app-level UI directly. Domain logic is consumed from @animastor/web-settings (pure, side-effect-free) — the first package→package dependency in the web layer.",
5
+ "keywords": [
6
+ "animastor",
7
+ "local-ai",
8
+ "ai-connector",
9
+ "ollama",
10
+ "vllm",
11
+ "preact",
12
+ "web-ui",
13
+ "frontend",
14
+ "ports-and-adapters"
15
+ ],
16
+ "type": "module",
17
+ "main": "dist/index.js",
18
+ "module": "dist/index.js",
19
+ "types": "dist/index.d.ts",
20
+ "exports": {
21
+ ".": {
22
+ "types": "./dist/index.d.ts",
23
+ "import": "./dist/index.js",
24
+ "default": "./dist/index.js"
25
+ },
26
+ "./package.json": "./package.json"
27
+ },
28
+ "files": [
29
+ "dist",
30
+ "README.md",
31
+ "LICENSE"
32
+ ],
33
+ "sideEffects": false,
34
+ "scripts": {
35
+ "build": "tsup",
36
+ "typecheck": "tsc --noEmit",
37
+ "test": "vitest run",
38
+ "prepublishOnly": "npm run typecheck && npm run test && npm run build"
39
+ },
40
+ "peerDependencies": {
41
+ "preact": ">=10.5.0"
42
+ },
43
+ "devDependencies": {
44
+ "@testing-library/dom": "^10.4.1",
45
+ "@testing-library/preact": "^3.2.4",
46
+ "happy-dom": "^20.14.0",
47
+ "preact": "^10.24.0",
48
+ "tsup": "^8.3.5",
49
+ "typescript": "^5.6.0",
50
+ "vitest": "4.1.11"
51
+ },
52
+ "license": "MIT",
53
+ "publishConfig": {
54
+ "access": "public"
55
+ },
56
+ "repository": {
57
+ "type": "git",
58
+ "url": "git+https://github.com/Animastor/animastor.git",
59
+ "directory": "packages/animastor-web-local-ai"
60
+ },
61
+ "homepage": "https://github.com/Animastor/animastor/tree/main/packages/animastor-web-local-ai#readme",
62
+ "bugs": {
63
+ "url": "https://github.com/Animastor/animastor/issues"
64
+ },
65
+ "dependencies": {
66
+ "@animastor/web-settings": "^0.1.0"
67
+ }
68
+ }