@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 +21 -0
- package/README.md +18 -0
- package/dist/index.d.ts +52 -0
- package/dist/index.js +631 -0
- package/dist/index.js.map +1 -0
- package/package.json +68 -0
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`.
|
package/dist/index.d.ts
ADDED
|
@@ -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
|
+
}
|