@huaweidevtools/aishell-dsh-llm 0.0.2 → 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -0
- package/lib/client.js +170 -189
- package/lib/index.js +24 -36
- package/lib/types/client/index.d.ts +16 -11
- package/lib/types/client/provider-row-shadow.d.ts +67 -0
- package/lib/types/index.d.ts +4 -30
- package/lib/types/provider-name.d.ts +17 -0
- package/lib/types/types.d.ts +11 -0
- package/package.json +4 -14
- package/lib/types/client/ProviderCard.d.ts +0 -77
package/README.md
CHANGED
|
@@ -4,6 +4,10 @@ OpenAI-compatible LLM adapter for DSH. Registers the `aishell` provider route on
|
|
|
4
4
|
|
|
5
5
|
Besides the live route, the plugin declares `aishell` in the configurable-provider directory (`ctx.llm.registerConfigurableProviders`) — the directory the Models page joins with live routes, so the route shows up as a configurable provider row with its own settings address. The plugin also carries a Config schema; the settings mirror only lists namespaces whose plugin declares one, so without it the Models page would not show the row.
|
|
6
6
|
|
|
7
|
+
## The Models-page row
|
|
8
|
+
|
|
9
|
+
The row itself is read-only: the profile has no fields the host editor knows how to write, and credentials are managed at Settings → AI Shell (by `aishell-credentials`). The browser half therefore does not extend the row with a card — it shadows the row's header at runtime (`src/client/provider-row-shadow.ts`, the same approach `aishell-brand` uses for its digest-protected host residues): a status dot after the row's name (green when AK/SK are stored, warn when they are not, with the readiness copy as its hover title) and the header's Edit button hidden, because there is nothing to edit. A MutationObserver keeps the shadow applied across the page's re-renders; disabling the plugin restores the official row exactly.
|
|
10
|
+
|
|
7
11
|
## How it works
|
|
8
12
|
|
|
9
13
|
```
|
package/lib/client.js
CHANGED
|
@@ -1,221 +1,202 @@
|
|
|
1
1
|
window.__ModuleLoader__.load({ id: "@huaweidevtools/aishell-dsh-llm", factory: (require) => {
|
|
2
2
|
"use strict";
|
|
3
3
|
var module = { exports: {} }; var exports = module.exports;
|
|
4
|
-
//#region rolldown:runtime
|
|
5
|
-
var __create = Object.create;
|
|
6
|
-
var __defProp = Object.defineProperty;
|
|
7
|
-
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
8
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
9
|
-
var __getProtoOf = Object.getPrototypeOf;
|
|
10
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
11
|
-
var __copyProps = (to, from, except, desc) => {
|
|
12
|
-
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
13
|
-
key = keys[i];
|
|
14
|
-
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
|
|
15
|
-
get: ((k) => from[k]).bind(null, key),
|
|
16
|
-
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
17
|
-
});
|
|
18
|
-
}
|
|
19
|
-
return to;
|
|
20
|
-
};
|
|
21
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
22
|
-
value: mod,
|
|
23
|
-
enumerable: true
|
|
24
|
-
}) : target, mod));
|
|
25
4
|
|
|
26
|
-
//#
|
|
27
|
-
const __deepseek_ai_dsh_client_store = __toESM(require("@deepseek-ai/dsh-client-store"));
|
|
28
|
-
const react = __toESM(require("react"));
|
|
29
|
-
const react_jsx_runtime = __toESM(require("react/jsx-runtime"));
|
|
30
|
-
|
|
31
|
-
//#region src/client/ProviderCard.tsx
|
|
5
|
+
//#region src/provider-name.ts
|
|
32
6
|
/**
|
|
33
|
-
*
|
|
7
|
+
* The aishell provider's display name, shared by both plugin halves.
|
|
8
|
+
*
|
|
9
|
+
* The Host half registers it on the configurable-provider directory (the
|
|
10
|
+
* Models page renders it as the provider row's name), and the browser half's
|
|
11
|
+
* row shadow finds that row by matching the rendered name against this
|
|
12
|
+
* literal (see `client/provider-row-shadow.ts`). It must stay a single
|
|
13
|
+
* literal string — the shadow compares `textContent` with it.
|
|
34
14
|
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
* "can I use this provider", not "what are my credentials"); details and
|
|
41
|
-
* management of credentials belong to the AI Shell settings section, which
|
|
42
|
-
* the unconfigured hint names.
|
|
15
|
+
* It is also the name `providerInfo()` reports, so the picker and the Models
|
|
16
|
+
* row call the route the same thing: this package's whole surface is one
|
|
17
|
+
* product, and qualifying it by cloud ("AI Shell (Huawei Cloud)") added a
|
|
18
|
+
* distinction no reader needed — there is no second AI Shell to tell it
|
|
19
|
+
* apart from.
|
|
43
20
|
*/
|
|
21
|
+
const PROVIDER_DISPLAY_NAME = "AI Shell";
|
|
22
|
+
|
|
23
|
+
//#endregion
|
|
24
|
+
//#region src/client/provider-row-shadow.ts
|
|
25
|
+
/** Marker attribute the dot carries (exposed for the spec). */
|
|
26
|
+
const ROW_DOT_MARKER = "data-aishell-provider-dot";
|
|
27
|
+
/** Marker attribute a hidden Edit button carries (exposed for the spec). */
|
|
28
|
+
const ROW_EDIT_HIDDEN = "data-aishell-edit-hidden";
|
|
29
|
+
/** The dot's own CSS: the host credential dot's geometry and palette. */
|
|
30
|
+
const ROW_DOT_CSS = [
|
|
31
|
+
`[class*="rowIdentity"] [${ROW_DOT_MARKER}] {`,
|
|
32
|
+
" box-sizing: border-box;",
|
|
33
|
+
" border-radius: 50%;",
|
|
34
|
+
" flex: none;",
|
|
35
|
+
" width: 8px;",
|
|
36
|
+
" height: 8px;",
|
|
37
|
+
" display: inline-block;",
|
|
38
|
+
"}",
|
|
39
|
+
`[class*="rowIdentity"] [${ROW_DOT_MARKER}="ok"] {`,
|
|
40
|
+
" background: var(--dsw-alias-state-success-primary);",
|
|
41
|
+
"}",
|
|
42
|
+
`[class*="rowIdentity"] [${ROW_DOT_MARKER}="warn"] {`,
|
|
43
|
+
" background: var(--dsw-alias-state-warn-label);",
|
|
44
|
+
"}",
|
|
45
|
+
`[${ROW_EDIT_HIDDEN}] {`,
|
|
46
|
+
" display: none !important;",
|
|
47
|
+
"}"
|
|
48
|
+
].join("\n");
|
|
49
|
+
/** Title copy for the dot, by readiness. The warn state keeps the
|
|
50
|
+
* actionable half — where to save AK/SK — because that is the one thing a
|
|
51
|
+
* fresh user cannot guess; the ready state needs nothing beyond itself. */
|
|
52
|
+
const DOT_TITLE = {
|
|
53
|
+
ok: "已就绪",
|
|
54
|
+
warn: "未就绪 — 凭据未配置(在 设置 → AI Shell 中保存 AK/SK)"
|
|
55
|
+
};
|
|
56
|
+
/** Every Edit button an aishell row renders (there is ordinarily one; a
|
|
57
|
+
* re-render that leaves a second copy gets both, so none stays visible). */
|
|
58
|
+
function findEditButtons(row) {
|
|
59
|
+
const actions = row.querySelector("[class*=\"rowActions\"]");
|
|
60
|
+
if (actions === null) return [];
|
|
61
|
+
const found = [];
|
|
62
|
+
for (const button of actions.querySelectorAll("button")) {
|
|
63
|
+
const label = button.getAttribute("aria-label") ?? "";
|
|
64
|
+
if (label.includes(PROVIDER_DISPLAY_NAME) || button.textContent === "编辑") found.push(button);
|
|
65
|
+
}
|
|
66
|
+
return found;
|
|
67
|
+
}
|
|
44
68
|
/**
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
69
|
+
* Whether `node` is an Element (`querySelectorAll` roots can be non-element
|
|
70
|
+
* nodes like the Document, where the row check below simply answers false).
|
|
71
|
+
*/
|
|
72
|
+
function isElement(node) {
|
|
73
|
+
return node.nodeType === Node.ELEMENT_NODE;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Whether `node` is a provider row whose rendered name is ours.
|
|
51
77
|
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
78
|
+
* Duck-typed on the attribute surface rather than `instanceof Element` so
|
|
79
|
+
* the shadow works identically in the browser and under the specs' DOM
|
|
80
|
+
* stub, which provides the attribute methods without the constructor.
|
|
81
|
+
*/
|
|
82
|
+
function isProviderRow(node) {
|
|
83
|
+
if (node.nodeType !== Node.ELEMENT_NODE) return false;
|
|
84
|
+
const element = node;
|
|
85
|
+
const name = element.querySelector("[class*=\"rowName\"]");
|
|
86
|
+
return name !== null && name.textContent === PROVIDER_DISPLAY_NAME;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Apply the shadow to one row: add the dot (or repaint an existing one) and
|
|
90
|
+
* hide the Edit button. Idempotent — a second pass rewrites the dot's
|
|
91
|
+
* readiness instead of stacking another one.
|
|
54
92
|
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
93
|
+
* `target` is the owning document, passed in because the stub DOM the specs
|
|
94
|
+
* run on keeps no `ownerDocument` link.
|
|
57
95
|
*/
|
|
58
|
-
function
|
|
59
|
-
|
|
60
|
-
if (
|
|
61
|
-
|
|
96
|
+
function shadowRow(row, ready, target) {
|
|
97
|
+
const identity = row.querySelector("[class*=\"rowIdentity\"]");
|
|
98
|
+
if (identity === null) return;
|
|
99
|
+
const state = ready ? "ok" : "warn";
|
|
100
|
+
const existing = identity.querySelector(`[${ROW_DOT_MARKER}]`);
|
|
101
|
+
const dot = existing ?? target.createElement("span");
|
|
102
|
+
dot.setAttribute(ROW_DOT_MARKER, state);
|
|
103
|
+
dot.setAttribute("title", DOT_TITLE[state]);
|
|
104
|
+
dot.setAttribute("aria-label", DOT_TITLE[state]);
|
|
105
|
+
if (existing === null) {
|
|
106
|
+
dot.setAttribute("role", "img");
|
|
107
|
+
identity.appendChild(dot);
|
|
108
|
+
}
|
|
109
|
+
for (const edit of findEditButtons(row)) edit.setAttribute(ROW_EDIT_HIDDEN, "");
|
|
62
110
|
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
|
71
|
-
return response.json();
|
|
72
|
-
}).then((body) => {
|
|
73
|
-
if (!cancelled) {
|
|
74
|
-
setCatalog(body);
|
|
75
|
-
setCatalogError(void 0);
|
|
76
|
-
}
|
|
77
|
-
}).catch((error) => {
|
|
78
|
-
if (!cancelled) setCatalogError(error instanceof Error ? error.message : String(error));
|
|
79
|
-
});
|
|
80
|
-
return () => {
|
|
81
|
-
cancelled = true;
|
|
82
|
-
};
|
|
83
|
-
}, []);
|
|
84
|
-
const configured = status.credentialsStored;
|
|
85
|
-
const readinessStyle = {
|
|
86
|
-
display: "flex",
|
|
87
|
-
alignItems: "center",
|
|
88
|
-
gap: 8,
|
|
89
|
-
fontSize: 12,
|
|
90
|
-
lineHeight: "18px",
|
|
91
|
-
fontWeight: 500,
|
|
92
|
-
color: "var(--dsw-alias-label-secondary)"
|
|
93
|
-
};
|
|
94
|
-
const readinessTextStyle = {
|
|
95
|
-
fontWeight: 400,
|
|
96
|
-
color: configured ? "var(--dsw-alias-label-primary)" : "var(--dsw-alias-label-tertiary)"
|
|
97
|
-
};
|
|
98
|
-
const rowStyle = {
|
|
99
|
-
display: "flex",
|
|
100
|
-
flexDirection: "column",
|
|
101
|
-
gap: 2
|
|
102
|
-
};
|
|
103
|
-
const catalogLabelStyle = {
|
|
104
|
-
fontSize: 12,
|
|
105
|
-
lineHeight: "18px",
|
|
106
|
-
fontWeight: 500,
|
|
107
|
-
color: "var(--dsw-alias-label-secondary)"
|
|
108
|
-
};
|
|
109
|
-
const valueStyle = {
|
|
110
|
-
fontSize: 12,
|
|
111
|
-
lineHeight: "18px",
|
|
112
|
-
color: "var(--dsw-alias-label-tertiary)"
|
|
113
|
-
};
|
|
114
|
-
const sectionStyle = {
|
|
115
|
-
display: "flex",
|
|
116
|
-
flexDirection: "column",
|
|
117
|
-
gap: 10,
|
|
118
|
-
borderTop: "0.5px solid var(--dsw-alias-border-l4)",
|
|
119
|
-
marginTop: 12,
|
|
120
|
-
paddingTop: 12
|
|
121
|
-
};
|
|
122
|
-
const modelStyle = {
|
|
123
|
-
display: "flex",
|
|
124
|
-
alignItems: "center",
|
|
125
|
-
gap: 8,
|
|
126
|
-
fontSize: 13,
|
|
127
|
-
lineHeight: "20px",
|
|
128
|
-
color: "var(--dsw-alias-label-primary)"
|
|
129
|
-
};
|
|
130
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
131
|
-
style: sectionStyle,
|
|
132
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
133
|
-
style: readinessStyle,
|
|
134
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
135
|
-
style: {
|
|
136
|
-
width: 6,
|
|
137
|
-
height: 6,
|
|
138
|
-
borderRadius: "50%",
|
|
139
|
-
flex: "none",
|
|
140
|
-
background: configured ? "var(--dsw-alias-state-success-primary)" : "var(--dsw-alias-state-warn-label)"
|
|
141
|
-
},
|
|
142
|
-
"aria-hidden": "true"
|
|
143
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
144
|
-
style: readinessTextStyle,
|
|
145
|
-
children: configured ? "已就绪 — 凭据已验证,可直接对话" : "未就绪 — 凭据未配置(在 设置 → AI Shell 中保存 AK/SK)"
|
|
146
|
-
})]
|
|
147
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
148
|
-
style: rowStyle,
|
|
149
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
150
|
-
style: catalogLabelStyle,
|
|
151
|
-
children: "模型目录(来自 aishell 网关,只读)"
|
|
152
|
-
}), catalogError !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
153
|
-
style: valueStyle,
|
|
154
|
-
children: ["目录获取失败:", catalogError]
|
|
155
|
-
}) : catalog === void 0 || catalog.models.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
156
|
-
style: valueStyle,
|
|
157
|
-
children: catalogStatusText(catalog)
|
|
158
|
-
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
|
|
159
|
-
style: {
|
|
160
|
-
margin: 0,
|
|
161
|
-
padding: 0,
|
|
162
|
-
listStyle: "none",
|
|
163
|
-
display: "flex",
|
|
164
|
-
flexDirection: "column",
|
|
165
|
-
gap: 4
|
|
166
|
-
},
|
|
167
|
-
children: catalog.models.map((model) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
|
|
168
|
-
style: modelStyle,
|
|
169
|
-
children: [
|
|
170
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
171
|
-
"aria-hidden": "true",
|
|
172
|
-
children: "·"
|
|
173
|
-
}),
|
|
174
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: model.id }),
|
|
175
|
-
model.inputModalities.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
176
|
-
style: {
|
|
177
|
-
fontSize: 11,
|
|
178
|
-
color: "var(--dsw-alias-label-quaternary)"
|
|
179
|
-
},
|
|
180
|
-
children: [model.inputModalities.join(" / "), " 输入"]
|
|
181
|
-
}) : null
|
|
182
|
-
]
|
|
183
|
-
}, model.id))
|
|
184
|
-
})]
|
|
185
|
-
})]
|
|
111
|
+
/** Undo one row: remove the dot, restore the Edit button. */
|
|
112
|
+
function unshadowRow(row) {
|
|
113
|
+
row.querySelectorAll(`[${ROW_DOT_MARKER}]`).forEach((dot) => {
|
|
114
|
+
dot.remove();
|
|
115
|
+
});
|
|
116
|
+
row.querySelectorAll(`[${ROW_EDIT_HIDDEN}]`).forEach((button) => {
|
|
117
|
+
button.removeAttribute(ROW_EDIT_HIDDEN);
|
|
186
118
|
});
|
|
187
119
|
}
|
|
188
120
|
/**
|
|
189
|
-
*
|
|
190
|
-
*
|
|
191
|
-
*
|
|
192
|
-
*
|
|
121
|
+
* Install the provider-row shadow on a document.
|
|
122
|
+
*
|
|
123
|
+
* `ready` is the credential fact as the browser half derives it from the
|
|
124
|
+
* settings namespace `aishell-credentials` owns (see `client/index.ts`);
|
|
125
|
+
* the install does not read the store itself, so a later credential save is
|
|
126
|
+
* a reinstall with the new fact, not a re-derivation buried in here. Sweeps
|
|
127
|
+
* the page once, then observes the body so re-rendered rows are covered too.
|
|
128
|
+
* Returns the disposer that stops the observer and restores every row.
|
|
193
129
|
*/
|
|
194
|
-
|
|
130
|
+
function installProviderRowShadow(target, ready) {
|
|
131
|
+
if (typeof target.querySelector !== "function" || typeof MutationObserver !== "function") return () => {};
|
|
132
|
+
/** Run the shadow over every provider row at or below `root`. */
|
|
133
|
+
const sweep = (root) => {
|
|
134
|
+
if (isElement(root) && isProviderRow(root)) shadowRow(root, ready, target);
|
|
135
|
+
for (const row of root.querySelectorAll("[class*=\"rowCard\"], [class*=\"setupCard\"]")) if (isProviderRow(row)) shadowRow(row, ready, target);
|
|
136
|
+
};
|
|
137
|
+
const sweepIfRow = (node) => {
|
|
138
|
+
if (isProviderRow(node)) {
|
|
139
|
+
shadowRow(node, ready, target);
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
for (let parent = node.parentElement; parent !== null; parent = parent.parentElement) sweep(parent);
|
|
143
|
+
};
|
|
144
|
+
const style = target.createElement("style");
|
|
145
|
+
style.setAttribute(ROW_DOT_MARKER + "-css", "");
|
|
146
|
+
style.textContent = ROW_DOT_CSS;
|
|
147
|
+
target.head.appendChild(style);
|
|
148
|
+
sweep(target.body);
|
|
149
|
+
const observer = new MutationObserver((mutations) => {
|
|
150
|
+
for (const mutation of mutations) for (const node of mutation.addedNodes) if (node.nodeType === Node.ELEMENT_NODE) sweepIfRow(node);
|
|
151
|
+
});
|
|
152
|
+
observer.observe(target.body, {
|
|
153
|
+
childList: true,
|
|
154
|
+
subtree: true
|
|
155
|
+
});
|
|
156
|
+
return () => {
|
|
157
|
+
observer.disconnect();
|
|
158
|
+
style.remove();
|
|
159
|
+
for (const row of target.body.querySelectorAll("[class*=\"rowCard\"], [class*=\"setupCard\"]")) if (isProviderRow(row)) unshadowRow(row);
|
|
160
|
+
};
|
|
161
|
+
}
|
|
195
162
|
|
|
196
163
|
//#endregion
|
|
197
164
|
//#region src/client/index.ts
|
|
198
|
-
/** The settings namespace aishell-credentials owns (the
|
|
165
|
+
/** The settings namespace aishell-credentials owns (the shadow reads its status). */
|
|
199
166
|
const AISHELL_CREDENTIALS_NS = "aishell-client-credentials";
|
|
200
167
|
/** Required services. */
|
|
201
|
-
const inject = ["
|
|
168
|
+
const inject = ["configForms"];
|
|
202
169
|
function apply(ctx) {
|
|
170
|
+
if (typeof document === "undefined") return;
|
|
203
171
|
const form = ctx.configForms.get(AISHELL_CREDENTIALS_NS);
|
|
204
|
-
|
|
172
|
+
let readiness = form.getSnapshot().value?.credentialsStored === true;
|
|
173
|
+
let disposeShadow;
|
|
174
|
+
const sync = () => {
|
|
175
|
+
disposeShadow?.();
|
|
176
|
+
disposeShadow = installProviderRowShadow(document, readiness);
|
|
177
|
+
};
|
|
205
178
|
const derive = () => {
|
|
206
|
-
|
|
179
|
+
const next = form.getSnapshot().value?.credentialsStored === true;
|
|
180
|
+
if (next !== readiness) {
|
|
181
|
+
readiness = next;
|
|
182
|
+
sync();
|
|
183
|
+
}
|
|
207
184
|
};
|
|
208
|
-
ctx.effect(() =>
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
}
|
|
185
|
+
ctx.effect(() => ctx.configForms.whileServed([AISHELL_CREDENTIALS_NS], () => {
|
|
186
|
+
sync();
|
|
187
|
+
return () => {
|
|
188
|
+
disposeShadow?.();
|
|
189
|
+
disposeShadow = void 0;
|
|
190
|
+
};
|
|
191
|
+
}), "aishell-llm: models page provider row shadow");
|
|
192
|
+
ctx.effect(() => form.subscribe(derive), "aishell-llm: credential presence for the models row");
|
|
215
193
|
}
|
|
216
194
|
|
|
217
195
|
//#endregion
|
|
196
|
+
exports.ROW_DOT_MARKER = ROW_DOT_MARKER
|
|
197
|
+
exports.ROW_EDIT_HIDDEN = ROW_EDIT_HIDDEN
|
|
218
198
|
exports.apply = apply
|
|
219
199
|
exports.inject = inject
|
|
200
|
+
exports.installProviderRowShadow = installProviderRowShadow
|
|
220
201
|
return module.exports; } });
|
|
221
202
|
//# sourceMappingURL=client.js.map
|
package/lib/index.js
CHANGED
|
@@ -1404,6 +1404,25 @@ async function* translate(payloads) {
|
|
|
1404
1404
|
};
|
|
1405
1405
|
}
|
|
1406
1406
|
|
|
1407
|
+
//#endregion
|
|
1408
|
+
//#region src/provider-name.ts
|
|
1409
|
+
/**
|
|
1410
|
+
* The aishell provider's display name, shared by both plugin halves.
|
|
1411
|
+
*
|
|
1412
|
+
* The Host half registers it on the configurable-provider directory (the
|
|
1413
|
+
* Models page renders it as the provider row's name), and the browser half's
|
|
1414
|
+
* row shadow finds that row by matching the rendered name against this
|
|
1415
|
+
* literal (see `client/provider-row-shadow.ts`). It must stay a single
|
|
1416
|
+
* literal string — the shadow compares `textContent` with it.
|
|
1417
|
+
*
|
|
1418
|
+
* It is also the name `providerInfo()` reports, so the picker and the Models
|
|
1419
|
+
* row call the route the same thing: this package's whole surface is one
|
|
1420
|
+
* product, and qualifying it by cloud ("AI Shell (Huawei Cloud)") added a
|
|
1421
|
+
* distinction no reader needed — there is no second AI Shell to tell it
|
|
1422
|
+
* apart from.
|
|
1423
|
+
*/
|
|
1424
|
+
const PROVIDER_DISPLAY_NAME = "AI Shell";
|
|
1425
|
+
|
|
1407
1426
|
//#endregion
|
|
1408
1427
|
//#region src/adapter.ts
|
|
1409
1428
|
/** Credential store account names for Huawei Cloud credentials. */
|
|
@@ -1457,7 +1476,7 @@ var GatewayAdapter = class GatewayAdapter extends LlmAdapter {
|
|
|
1457
1476
|
providerInfo(provider) {
|
|
1458
1477
|
return {
|
|
1459
1478
|
id: provider,
|
|
1460
|
-
name:
|
|
1479
|
+
name: PROVIDER_DISPLAY_NAME
|
|
1461
1480
|
};
|
|
1462
1481
|
}
|
|
1463
1482
|
/**
|
|
@@ -1487,7 +1506,7 @@ var GatewayAdapter = class GatewayAdapter extends LlmAdapter {
|
|
|
1487
1506
|
return wireModels.map((m) => ({
|
|
1488
1507
|
provider,
|
|
1489
1508
|
id: m.id,
|
|
1490
|
-
name: m.id,
|
|
1509
|
+
name: m.name ?? m.id,
|
|
1491
1510
|
...mapModalities(m.modalities)
|
|
1492
1511
|
}));
|
|
1493
1512
|
}
|
|
@@ -1497,7 +1516,7 @@ var GatewayAdapter = class GatewayAdapter extends LlmAdapter {
|
|
|
1497
1516
|
const base = {
|
|
1498
1517
|
provider,
|
|
1499
1518
|
id: model,
|
|
1500
|
-
name: model,
|
|
1519
|
+
name: wire?.name ?? model,
|
|
1501
1520
|
...mapModalities(wire?.modalities)
|
|
1502
1521
|
};
|
|
1503
1522
|
if (wire === void 0) return base;
|
|
@@ -1669,35 +1688,10 @@ const Config = Schema.object({
|
|
|
1669
1688
|
requestImageMaxBytes: Schema.number().step(1).min(1).default(DEFAULT_REQUEST_IMAGE_MAX_BYTES).volatile(),
|
|
1670
1689
|
maxRequestImageBytes: Schema.number().step(1).min(1).default(DEFAULT_MAX_REQUEST_IMAGE_BYTES).volatile()
|
|
1671
1690
|
});
|
|
1672
|
-
/** Route the Models-page card reads the live gateway catalog from. */
|
|
1673
|
-
const MODELS_PATH = "/api/aishell/models";
|
|
1674
|
-
/**
|
|
1675
|
-
* Build the route body from the adapter's own catalog cache, so the card and
|
|
1676
|
-
* the pickers cannot disagree about what the gateway carries.
|
|
1677
|
-
*
|
|
1678
|
-
* `credentialsPresent` is read from the adapter rather than assumed: without
|
|
1679
|
-
* credentials `listModels` answers `[]` for a reason that is not "the gateway
|
|
1680
|
-
* has no models", and the card's empty state is the one place a fresh user
|
|
1681
|
-
* learns which of the two it is.
|
|
1682
|
-
*
|
|
1683
|
-
* Returned as a plain function of the adapter so a test can pin the mapping
|
|
1684
|
-
* without a live context; `apply` wires it to the fetch route.
|
|
1685
|
-
*/
|
|
1686
|
-
function createModelsRoute(adapter) {
|
|
1687
|
-
return () => adapter.listModels(PROVIDER).then((models) => Response.json({
|
|
1688
|
-
credentialsPresent: adapter.hasCredentials(),
|
|
1689
|
-
models: models.map((m) => ({
|
|
1690
|
-
id: m.id,
|
|
1691
|
-
inputModalities: [...m.inputModalities ?? []]
|
|
1692
|
-
}))
|
|
1693
|
-
}, { headers: { "cache-control": "no-store" } }));
|
|
1694
|
-
}
|
|
1695
1691
|
const name = "aishell-llm";
|
|
1696
|
-
const inject = ["llm"
|
|
1692
|
+
const inject = ["llm"];
|
|
1697
1693
|
/** The provider route this plugin owns. */
|
|
1698
1694
|
const PROVIDER = "aishell";
|
|
1699
|
-
/** Human-readable provider name the Models page and pickers show. */
|
|
1700
|
-
const PROVIDER_DISPLAY_NAME = "AI Shell (Huawei Cloud)";
|
|
1701
1695
|
function apply(ctx, config) {
|
|
1702
1696
|
const keyringService = config.keyringService.get();
|
|
1703
1697
|
const store = createCredentialsStore(keyringService === void 0 || keyringService === "" ? {} : { service: keyringService });
|
|
@@ -1719,14 +1713,8 @@ function apply(ctx, config) {
|
|
|
1719
1713
|
settingsNs: ctx.fiber.entry?.options.id ?? name,
|
|
1720
1714
|
settingsPath: []
|
|
1721
1715
|
}]);
|
|
1722
|
-
ctx.effect(() => ctx.connection.fetch.register({
|
|
1723
|
-
path: MODELS_PATH,
|
|
1724
|
-
methods: ["GET"],
|
|
1725
|
-
requestBody: "buffered",
|
|
1726
|
-
fetch: createModelsRoute(adapter)
|
|
1727
|
-
}), "aishell-llm: models catalog route");
|
|
1728
1716
|
}
|
|
1729
1717
|
|
|
1730
1718
|
//#endregion
|
|
1731
|
-
export { Config, PROVIDER, PROVIDER_DISPLAY_NAME, apply,
|
|
1719
|
+
export { Config, PROVIDER, PROVIDER_DISPLAY_NAME, apply, inject, name };
|
|
1732
1720
|
//# sourceMappingURL=index.js.map
|
|
@@ -1,20 +1,25 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* aishell LLM browser half —
|
|
2
|
+
* aishell LLM browser half — shadows the Models page's provider row.
|
|
3
3
|
*
|
|
4
|
-
* The
|
|
5
|
-
* the host
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
4
|
+
* The page's row for the aishell family is read-only: its profile has no
|
|
5
|
+
* fields the host editor knows how to write, and its credentials live in
|
|
6
|
+
* `aishell-credentials`' own settings section. What the row should tell the
|
|
7
|
+
* user is the one fact the old card extension distilled — ready or not — so
|
|
8
|
+
* the browser half paints it as a status dot beside the row's name and hides
|
|
9
|
+
* the header's Edit button (see `client/provider-row-shadow.ts`).
|
|
10
|
+
*
|
|
11
|
+
* The readiness fact is read from the `aishell-client-credentials` settings
|
|
12
|
+
* namespace (`aishell-credentials` owns it; the shadow never names accounts
|
|
13
|
+
* or manages anything). The shadow follows that namespace through
|
|
14
|
+
* `whileServed`, so a deployment without that plugin (this LLM package
|
|
15
|
+
* installed standalone) shows no trace of the dot either — the fact is
|
|
16
|
+
* unknown, not false.
|
|
13
17
|
*
|
|
14
18
|
* @module @huaweidevtools/aishell-dsh-llm/client
|
|
15
19
|
*/
|
|
16
20
|
import type { Context as ClientContext } from '@deepseek-ai/cordis';
|
|
17
|
-
export type {
|
|
21
|
+
export type { ProviderRowShadowDisposer, } from './provider-row-shadow.ts';
|
|
22
|
+
export { installProviderRowShadow, ROW_DOT_MARKER, ROW_EDIT_HIDDEN, } from './provider-row-shadow.ts';
|
|
18
23
|
/** Required services. */
|
|
19
24
|
export declare const inject: string[];
|
|
20
25
|
export declare function apply(ctx: ClientContext): void;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime shadow of the aishell provider's Models-page row.
|
|
3
|
+
*
|
|
4
|
+
* The Models page renders each configured provider as a row whose header
|
|
5
|
+
* (`rowHead`) shows the display name followed by an Edit button the aishell
|
|
6
|
+
* family has no use for: the profile has no fields the host editor knows how
|
|
7
|
+
* to write (the old card extension said so itself), and the row's directory
|
|
8
|
+
* entry names no api-key credential, so the host header also paints no status
|
|
9
|
+
* dot. What the user actually needs from this row is the one fact the old
|
|
10
|
+
* card distilled — ready or not — and that reads better beside the name than
|
|
11
|
+
* in a card below the header.
|
|
12
|
+
*
|
|
13
|
+
* The header lives in ui-settings-models, digest-protected with no slot for
|
|
14
|
+
* the identity half (the `settings.models.provider-card` seat extends the
|
|
15
|
+
* area BELOW the header, which is exactly the part this family no longer
|
|
16
|
+
* wants). The shadow therefore works the way aishell-brand's host residues
|
|
17
|
+
* do: find the row, patch the DOM. The row is located by its rendered name
|
|
18
|
+
* (the directory entry's displayName, `PROVIDER_DISPLAY_NAME` — a literal
|
|
19
|
+
* both halves share); the class names beside it are CSS-module hashes, so
|
|
20
|
+
* they are matched by the same `[class*="…"]` substring selectors the hero
|
|
21
|
+
* patch already relies on.
|
|
22
|
+
*
|
|
23
|
+
* What the shadow does to a found row:
|
|
24
|
+
*
|
|
25
|
+
* 1. a status dot after the name — the host credential dot's own 8px
|
|
26
|
+
* geometry and success/warn colors, green when AK/SK are stored, warn
|
|
27
|
+
* when they are not, with the readiness copy as its title (the row
|
|
28
|
+
* keeps saying what the old card said, just in one word of hover);
|
|
29
|
+
* 2. the row's Edit button is hidden — the row is read-only, and the only
|
|
30
|
+
* control the header keeps is whatever the host itself decides (Remove,
|
|
31
|
+
* for user-added rows; this family's row is not removable). The hide is
|
|
32
|
+
* a marker-keyed `display: none !important` rule, not the `hidden`
|
|
33
|
+
* attribute: the button's own class sets `display: inline-flex`, and an
|
|
34
|
+
* author declaration beats the UA sheet's `[hidden] { display: none }`,
|
|
35
|
+
* so the attribute alone leaves the button visible.
|
|
36
|
+
*
|
|
37
|
+
* The Models page re-renders the rows (credential saves, pushed
|
|
38
|
+
* invalidations), so a MutationObserver keeps the shadow applied for as long
|
|
39
|
+
* as the page exists. Shadowing is a default, not a force: install() and the
|
|
40
|
+
* disposer it returns are symmetric, so disabling the plugin restores the
|
|
41
|
+
* official row exactly — dot removed, Edit button back.
|
|
42
|
+
*
|
|
43
|
+
* @module @huaweidevtools/aishell-dsh-llm/client/provider-row-shadow
|
|
44
|
+
*/
|
|
45
|
+
/**
|
|
46
|
+
* The disposer stops the observer, removes every dot the install added, and
|
|
47
|
+
* restores every hidden Edit button; a no-op install returns this.
|
|
48
|
+
*/
|
|
49
|
+
export type ProviderRowShadowDisposer = () => void;
|
|
50
|
+
/** Marker attribute the dot carries (exposed for the spec). */
|
|
51
|
+
export declare const ROW_DOT_MARKER = "data-aishell-provider-dot";
|
|
52
|
+
/** Marker attribute a hidden Edit button carries (exposed for the spec). */
|
|
53
|
+
export declare const ROW_EDIT_HIDDEN = "data-aishell-edit-hidden";
|
|
54
|
+
/** The dot's own CSS: the host credential dot's geometry and palette. */
|
|
55
|
+
export declare const ROW_DOT_CSS: string;
|
|
56
|
+
/**
|
|
57
|
+
* Install the provider-row shadow on a document.
|
|
58
|
+
*
|
|
59
|
+
* `ready` is the credential fact as the browser half derives it from the
|
|
60
|
+
* settings namespace `aishell-credentials` owns (see `client/index.ts`);
|
|
61
|
+
* the install does not read the store itself, so a later credential save is
|
|
62
|
+
* a reinstall with the new fact, not a re-derivation buried in here. Sweeps
|
|
63
|
+
* the page once, then observes the body so re-rendered rows are covered too.
|
|
64
|
+
* Returns the disposer that stops the observer and restores every row.
|
|
65
|
+
*/
|
|
66
|
+
export declare function installProviderRowShadow(target: Document, ready: boolean): ProviderRowShadowDisposer;
|
|
67
|
+
//# sourceMappingURL=provider-row-shadow.d.ts.map
|
package/lib/types/index.d.ts
CHANGED
|
@@ -14,12 +14,14 @@
|
|
|
14
14
|
* directory (`registerConfigurableProviders`), which is what the Models page
|
|
15
15
|
* joins with live routes — and it carries a Config schema, without which the
|
|
16
16
|
* settings mirror (and therefore the Models row) never lists this namespace.
|
|
17
|
+
* The row itself carries no card: the browser half replaces the page's
|
|
18
|
+
* Edit-card extension with a status dot beside the row's name (see
|
|
19
|
+
* `client/provider-row-shadow.ts`).
|
|
17
20
|
*
|
|
18
21
|
* @module @huaweidevtools/aishell-dsh-llm
|
|
19
22
|
*/
|
|
20
23
|
import type { Context, Volatile } from '@deepseek-ai/cordis';
|
|
21
24
|
import z from '@deepseek-ai/schemastery';
|
|
22
|
-
import { GatewayAdapter } from './adapter.ts';
|
|
23
25
|
export type { GatewayAdapter, GatewayAdapterOptions } from './adapter.ts';
|
|
24
26
|
/** Plugin config. */
|
|
25
27
|
export interface Config {
|
|
@@ -56,38 +58,10 @@ export declare const Config: z<Schemastery.ObjectS<NoInfer<{
|
|
|
56
58
|
requestImageMaxBytes: z<number, number, "volatile-defined">;
|
|
57
59
|
maxRequestImageBytes: z<number, number, "volatile-defined">;
|
|
58
60
|
}>>, "plain">;
|
|
59
|
-
/**
|
|
60
|
-
* The catalog the route answers with: the gateway's `/models` entries, mapped
|
|
61
|
-
* to the display shape the browser card renders, plus the credential state
|
|
62
|
-
* that decides which of the two postures the card shows. Detached plain data —
|
|
63
|
-
* no secret rides on it, ever (presence flags only).
|
|
64
|
-
*/
|
|
65
|
-
export interface ModelsRouteBody {
|
|
66
|
-
/** Whether a usable AK/SK pair is stored — the fact that separates "empty catalog" from "no credentials". */
|
|
67
|
-
readonly credentialsPresent: boolean;
|
|
68
|
-
readonly models: ReadonlyArray<{
|
|
69
|
-
readonly id: string;
|
|
70
|
-
readonly inputModalities: readonly string[];
|
|
71
|
-
}>;
|
|
72
|
-
}
|
|
73
|
-
/**
|
|
74
|
-
* Build the route body from the adapter's own catalog cache, so the card and
|
|
75
|
-
* the pickers cannot disagree about what the gateway carries.
|
|
76
|
-
*
|
|
77
|
-
* `credentialsPresent` is read from the adapter rather than assumed: without
|
|
78
|
-
* credentials `listModels` answers `[]` for a reason that is not "the gateway
|
|
79
|
-
* has no models", and the card's empty state is the one place a fresh user
|
|
80
|
-
* learns which of the two it is.
|
|
81
|
-
*
|
|
82
|
-
* Returned as a plain function of the adapter so a test can pin the mapping
|
|
83
|
-
* without a live context; `apply` wires it to the fetch route.
|
|
84
|
-
*/
|
|
85
|
-
export declare function createModelsRoute(adapter: GatewayAdapter): () => Promise<Response>;
|
|
86
61
|
export declare const name = "aishell-llm";
|
|
87
62
|
export declare const inject: string[];
|
|
88
63
|
/** The provider route this plugin owns. */
|
|
89
64
|
export declare const PROVIDER = "aishell";
|
|
90
|
-
|
|
91
|
-
export declare const PROVIDER_DISPLAY_NAME = "AI Shell (Huawei Cloud)";
|
|
65
|
+
export { PROVIDER_DISPLAY_NAME } from './provider-name.ts';
|
|
92
66
|
export declare function apply(ctx: Context, config: Config): void;
|
|
93
67
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The aishell provider's display name, shared by both plugin halves.
|
|
3
|
+
*
|
|
4
|
+
* The Host half registers it on the configurable-provider directory (the
|
|
5
|
+
* Models page renders it as the provider row's name), and the browser half's
|
|
6
|
+
* row shadow finds that row by matching the rendered name against this
|
|
7
|
+
* literal (see `client/provider-row-shadow.ts`). It must stay a single
|
|
8
|
+
* literal string — the shadow compares `textContent` with it.
|
|
9
|
+
*
|
|
10
|
+
* It is also the name `providerInfo()` reports, so the picker and the Models
|
|
11
|
+
* row call the route the same thing: this package's whole surface is one
|
|
12
|
+
* product, and qualifying it by cloud ("AI Shell (Huawei Cloud)") added a
|
|
13
|
+
* distinction no reader needed — there is no second AI Shell to tell it
|
|
14
|
+
* apart from.
|
|
15
|
+
*/
|
|
16
|
+
export declare const PROVIDER_DISPLAY_NAME = "AI Shell";
|
|
17
|
+
//# sourceMappingURL=provider-name.d.ts.map
|
package/lib/types/types.d.ts
CHANGED
|
@@ -86,6 +86,17 @@ export interface WireChunk {
|
|
|
86
86
|
/** One model entry from the GET /models response (OpenAI-compatible). */
|
|
87
87
|
export interface WireModel {
|
|
88
88
|
id: string;
|
|
89
|
+
/**
|
|
90
|
+
* Display name, when the gateway sends one. Outside OpenAI's own model
|
|
91
|
+
* object (which carries `id` alone), so it is an extension like the
|
|
92
|
+
* metadata below, and the gateway omits it for a model that declares none.
|
|
93
|
+
*
|
|
94
|
+
* This is the field that lets an id stay a durable routing key while the
|
|
95
|
+
* label a person reads stays free to change — the split every official
|
|
96
|
+
* adapter's `name ?? id` expresses. Without it the two roles collapse onto
|
|
97
|
+
* the id, and a reserved name like `trial` has to read well in a picker.
|
|
98
|
+
*/
|
|
99
|
+
name?: string;
|
|
89
100
|
object?: string;
|
|
90
101
|
owned_by?: string;
|
|
91
102
|
created?: number;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@huaweidevtools/aishell-dsh-llm",
|
|
3
3
|
"description": "aishell LLM gateway adapter for DSH — OpenAI-compatible, Huawei Cloud AK/SK auth via credential store",
|
|
4
|
-
"version": "0.0.
|
|
4
|
+
"version": "0.0.3",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|
|
@@ -33,13 +33,12 @@
|
|
|
33
33
|
"license": "Apache-2.0",
|
|
34
34
|
"dependencies": {
|
|
35
35
|
"eventsource-parser": "^3.0.0",
|
|
36
|
-
"@huaweidevtools/aishell-dsh-credentials-store": "0.0.
|
|
36
|
+
"@huaweidevtools/aishell-dsh-credentials-store": "0.0.3"
|
|
37
37
|
},
|
|
38
38
|
"peerDependencies": {
|
|
39
39
|
"@deepseek-ai/cordis": "^4.0.4",
|
|
40
40
|
"@deepseek-ai/dsh-attachment": ">=0.2.0-0 <0.3.0-0",
|
|
41
41
|
"@deepseek-ai/dsh-brand": ">=0.2.0-0 <0.3.0-0",
|
|
42
|
-
"@deepseek-ai/dsh-client-connection": ">=0.2.0-0 <0.3.0-0",
|
|
43
42
|
"@deepseek-ai/dsh-fs": ">=0.2.0-0 <0.3.0-0",
|
|
44
43
|
"@deepseek-ai/dsh-llm": ">=0.2.0-0 <0.3.0-0"
|
|
45
44
|
},
|
|
@@ -48,24 +47,15 @@
|
|
|
48
47
|
"@deepseek-ai/cordis-plugin-loader": "^1.0.5",
|
|
49
48
|
"@deepseek-ai/dsh-attachment": "0.2.0-rc.1",
|
|
50
49
|
"@deepseek-ai/dsh-brand": ">=0.2.0-0 <0.3.0-0",
|
|
51
|
-
"@deepseek-ai/dsh-client-connection": "0.2.0-rc.1",
|
|
52
|
-
"@deepseek-ai/dsh-client-store": "0.2.0-rc.1",
|
|
53
|
-
"@deepseek-ai/dsh-client-ui-renderer": "0.2.0-rc.1",
|
|
54
50
|
"@deepseek-ai/dsh-client-ui-settings": "0.2.0-rc.1",
|
|
55
|
-
"@deepseek-ai/dsh-client-ui-settings-models": "0.2.0-rc.1",
|
|
56
|
-
"@deepseek-ai/dsh-client-ui-slots": "0.2.0-rc.1",
|
|
57
51
|
"@deepseek-ai/dsh-fs": "0.2.0-rc.1",
|
|
58
52
|
"@deepseek-ai/dsh-llm": ">=0.2.0-0 <0.3.0-0",
|
|
59
|
-
"@deepseek-ai/schemastery": "3.18.4"
|
|
60
|
-
"@types/react": "~18.3.1",
|
|
61
|
-
"react": "^18.2.0"
|
|
53
|
+
"@deepseek-ai/schemastery": "3.18.4"
|
|
62
54
|
},
|
|
63
55
|
"dsh": {
|
|
64
56
|
"client": {
|
|
65
57
|
"inject": [
|
|
66
|
-
"@deepseek-ai/dsh-client-ui-settings"
|
|
67
|
-
"@deepseek-ai/dsh-client-ui-settings-models",
|
|
68
|
-
"@deepseek-ai/dsh-api-remotes"
|
|
58
|
+
"@deepseek-ai/dsh-client-ui-settings"
|
|
69
59
|
],
|
|
70
60
|
"platform": "web"
|
|
71
61
|
},
|
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The aishell row's Edit-card extension: a one-line readiness status + the
|
|
3
|
-
* live gateway model catalog.
|
|
4
|
-
*
|
|
5
|
-
* The Models page's own editor for an adapter family it does not know renders
|
|
6
|
-
* the host's fallback hint ("other fields live in cordis.patch.yml") with a
|
|
7
|
-
* disabled Apply — nothing to edit, because the aishell profile has no fields
|
|
8
|
-
* the host editor knows how to write. What the user actually needs from this
|
|
9
|
-
* card is the other direction: to READ the family's state.
|
|
10
|
-
*
|
|
11
|
-
* Scope discipline: this page answers "can I use this provider, and what does
|
|
12
|
-
* it carry" — one readiness line whose credential fact comes from
|
|
13
|
-
* `aishell-credentials`' settings namespace (managed at Settings → AI Shell;
|
|
14
|
-
* the card never names accounts or manages anything). The model catalog comes
|
|
15
|
-
* from the gateway through the Host half's `api/aishell/models` route and is
|
|
16
|
-
* likewise read-only — the gateway owns it.
|
|
17
|
-
*
|
|
18
|
-
* @module @huaweidevtools/aishell-dsh-llm/client/ProviderCard
|
|
19
|
-
*/
|
|
20
|
-
import { type ReactNode } from 'react';
|
|
21
|
-
import type { SnapshotStore } from '@deepseek-ai/dsh-client-store';
|
|
22
|
-
import type { InjectFace, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots';
|
|
23
|
-
/** The route body the Host half answers (see its ModelsRouteBody). */
|
|
24
|
-
interface ModelsRouteBody {
|
|
25
|
-
readonly credentialsPresent: boolean;
|
|
26
|
-
readonly models: ReadonlyArray<{
|
|
27
|
-
readonly id: string;
|
|
28
|
-
readonly inputModalities: readonly string[];
|
|
29
|
-
}>;
|
|
30
|
-
}
|
|
31
|
-
/**
|
|
32
|
-
* Credential presence, read from the settings namespace `aishell-credentials`
|
|
33
|
-
* owns. The card only needs the presence flag for its readiness line; the
|
|
34
|
-
* account name stays in the credentials section that owns it.
|
|
35
|
-
*/
|
|
36
|
-
export interface CredentialSnapshot {
|
|
37
|
-
readonly credentialsStored: boolean;
|
|
38
|
-
}
|
|
39
|
-
/** Full component props: the seat's owner share + the inject face. */
|
|
40
|
-
export type AishellProviderCardProps = PropsRuntime<'settings.models.provider-card'> & InjectFace<AishellProviderCardInjected>;
|
|
41
|
-
/** Injected hooks bound in the client half's apply(). */
|
|
42
|
-
export interface AishellProviderCardInjected {
|
|
43
|
-
hooks: {
|
|
44
|
-
/** Credential presence from the aishell-client-credentials namespace. */
|
|
45
|
-
credentials: SnapshotStore<CredentialSnapshot>;
|
|
46
|
-
};
|
|
47
|
-
}
|
|
48
|
-
/**
|
|
49
|
-
* Render the aishell family's card extension.
|
|
50
|
-
*
|
|
51
|
-
* The parent renders this inside the host editor's card (below its fallback
|
|
52
|
-
* hint), so the visual style follows the surrounding card rather than opening
|
|
53
|
-
* a panel of its own: one readiness line and one catalog list, no buttons —
|
|
54
|
-
* the host card's Cancel stays the only control. Readiness folds the
|
|
55
|
-
* credential fact into a single conclusion (the question this page asks is
|
|
56
|
-
* "can I use this provider", not "what are my credentials"); details and
|
|
57
|
-
* management of credentials belong to the AI Shell settings section, which
|
|
58
|
-
* the unconfigured hint names.
|
|
59
|
-
*/
|
|
60
|
-
/**
|
|
61
|
-
* What an empty catalog means, and therefore what the card says about it.
|
|
62
|
-
*
|
|
63
|
-
* An empty model list has two unrelated causes that look identical on the
|
|
64
|
-
* wire: the gateway returned nothing, or the request was never signed because
|
|
65
|
-
* no credentials are stored. Only the second one is actionable, and it is the
|
|
66
|
-
* state every new user starts in — so it has to name the place to fix it.
|
|
67
|
-
*
|
|
68
|
-
* Extracted from the render so the branch is testable without a DOM: the
|
|
69
|
-
* repo's client-side tests cover pure logic, not components.
|
|
70
|
-
*
|
|
71
|
-
* @param catalog - the route body, or `undefined` while it is still loading.
|
|
72
|
-
* @returns the text to show, which the caller renders as-is.
|
|
73
|
-
*/
|
|
74
|
-
export declare function catalogStatusText(catalog: ModelsRouteBody | undefined): string;
|
|
75
|
-
export declare function AishellProviderCard(props: AishellProviderCardProps): ReactNode;
|
|
76
|
-
export {};
|
|
77
|
-
//# sourceMappingURL=ProviderCard.d.ts.map
|