@deepseek-ai/dsh-client-ui-settings-models 0.2.0-rc.2 → 0.2.1-alpha.2

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.i18n.yaml CHANGED
@@ -15,8 +15,8 @@
15
15
  en: d152484eb41ac6b4
16
16
  zh: 09388d293f9be9cb
17
17
  /deepseek-ai-dsh-client-ui-settings-models/use-this-package:
18
- en: 9ad8f5243c87c5b9
19
- zh: f3b01e25c28ebba2
18
+ en: db5aaf7f702e1f0c
19
+ zh: 409a78c14fb2b6e3
20
20
  /deepseek-ai-dsh-client-ui-settings-models/use-this-package/api-keys:
21
21
  en: c2f2a5c06ef20d5b
22
22
  zh: 49a7eb0b66b4ed53
@@ -39,8 +39,8 @@
39
39
  en: 74948a88fe1f9aac
40
40
  zh: 8ad2200fa74c12c9
41
41
  /deepseek-ai-dsh-client-ui-settings-models/understand-the-implementation/concurrency-and-credentials:
42
- en: 670b8c066aed9b7a
43
- zh: 2bedba9a2e0e3fee
42
+ en: 6e04c00818f014cd
43
+ zh: 2d57f8d8944e67ed
44
44
  /deepseek-ai-dsh-client-ui-settings-models/understand-the-implementation/onboarding-coordinator:
45
45
  en: 46dea666dffe8813
46
46
  zh: 6f56fdc365fa3642
@@ -57,5 +57,5 @@
57
57
  en: cc69c9934bf22302
58
58
  zh: 4ea896f4b54aa203
59
59
  /deepseek-ai-dsh-client-ui-settings-models/known-limitations-and-deferred-work/dev-note:
60
- en: 4f6749870e661a48
61
- zh: 4953f3ea814546a4
60
+ en: e67f633dc6fe7773
61
+ zh: 3bb0c20c93f16f98
package/README.md CHANGED
@@ -35,6 +35,8 @@ Open the Models page from the Settings navigation to see every configured provid
35
35
 
36
36
  A provider with a stored catalog error remains visible with its diagnostic and edit/delete actions. Add actions are offered only for registered settings namespaces, so an unavailable namespace cannot leave a button that opens no editor. A rejected save leaves the editor open and displays the Host diagnostic.
37
37
 
38
+ After Apply closes an editor, its saved status appears when the current provider, settings, and credential snapshot is ready. Opening any row's editor, adding a provider, and opening a removal confirmation wait for that refresh. A newly opened Models section, or one retrying after a page-load failure, waits for a ready snapshot before mounting editors. A background refresh preserves an open draft while its editor remains mounted; a failed page load or provider removal can close it.
39
+
38
40
  Host configuration `credentialOnboarding` defaults to `true`. The Electron preload marker suppresses automatic credential onboarding and the Web welcome notice; Models settings and explicit API-key editing remain available. The [account plugin](../ui-settings-account/README.md#desktop-onboarding) owns the Desktop introduction. Other native shells can disable only the credential step with `credentialOnboarding: false`. Host publishes this public boolean through `webserver/index-inject`, and Client validates it before registering dialogs. It is page initialization data, not a durable completion marker.
39
41
 
40
42
  ### API keys
@@ -83,6 +85,8 @@ A typed API key is judged on its own field: after trimming, it must be non-empty
83
85
 
84
86
  Each settings write carries the card's current `revision`, so a concurrent write from another tab or an external `cordis.patch.yml` edit is refused as `settings/conflict`. After settings commit, the card adopts the returned redacted user subtree and revision before storing the credential, so a failed credential stage retries only that stage. Deletion removes a configured, writable credential only when the profile names the page's derived `<ROUTE>_API_KEY` target, then unsets the profile; both operations are idempotent. Once loaded, the page subscribes to forwarded `settings/document-updated`, `credentials/reference-updated`, and `llm/adapters-updated` owner events, plus local `connection/reset`, so external edits converge without polling.
85
87
 
88
+ Concurrent `ModelsSettingsStore.load()` callers resolve after the current refresh publishes its ready or error snapshot, including callers whose reads were superseded. Older responses cannot publish or settle callers; a refresh started synchronously by a snapshot subscriber owns the waiting callers. Expected Remote failures remain snapshot diagnostics, while an unexpected rejection of the current read rejects its waiting callers.
89
+
86
90
  ### Onboarding coordinator
87
91
 
88
92
  The notice step owns its exact copy in `src/client/locales.ts` and its acknowledgement version in `src/onboarding-copy.ts`; on loopback it compares and writes `ui-settings-general.welcomeNoticeVersion` through the shared configuration form, and only an explicit Continue records the current version. Users who acknowledged an earlier version see the current notice again. A non-loopback browser cannot use that Host-only namespace, so acknowledgement is process-local and the notice returns after reload. The DeepSeek step targets `deepseek-official` in `llm-deepseek` and renders the existing `ProviderEditor` in credential-only mode inside the shared onboarding modal; `credentials.set` stays the only secret write, and no provider settings are changed.
@@ -138,5 +142,3 @@ These limits define the editor's field coverage and the page's reach; they are c
138
142
  None.
139
143
 
140
144
  </details>
141
-
142
- **Runtime invariant:** No companion is published. A nav-entry-only section plugin rendering a fixed empty content column — it emits no cordis events and owns no cross-plugin mutable relation.
package/README.zh.md CHANGED
@@ -35,6 +35,8 @@ kind: "package-reference"
35
35
 
36
36
  存在已存储目录错误的提供商仍显示诊断以及编辑、删除入口。添加操作只面向已注册的 settings 命名空间,因此不可用的命名空间不会留下无法打开编辑器的按钮。保存被拒绝时,编辑器保持打开并展示 Host 诊断。
37
37
 
38
+ 应用设置并关闭编辑器后,当前提供商、settings 与凭据快照就绪时才显示保存状态。打开任一行的编辑器、添加提供商和打开移除确认都会等待该刷新。新打开的 Models 区域或页面加载失败后的重试会等到 ready 快照后才挂载编辑器。后台刷新期间,只要编辑器仍挂载,已打开的草稿就会保留用户的修改;页面加载失败或提供商被移除可能关闭编辑器。
39
+
38
40
  Host 配置 `credentialOnboarding` 默认为 `true`。Electron preload 标记会抑制自动凭证引导和 Web 欢迎须知;模型设置页与显式 API Key 编辑仍然可用。[账号插件](../ui-settings-account/README.zh.md#desktop-onboarding)负责 Desktop 引导。其他原生壳可以通过 `credentialOnboarding: false` 仅禁用凭证步骤。Host 通过 `webserver/index-inject` 发布这个公开的布尔值,Client 在注册弹窗前校验它。它是页面初始化数据,不是持久化的完成标记。
39
41
 
40
42
  ### API 密钥
@@ -83,6 +85,8 @@ Models 页面包含 **DeepSeek 账号**(`deepseek-account`,英文为 **DeepS
83
85
 
84
86
  每次 settings 写入都携带卡片当前的 `revision`,因此来自另一个标签页或外部 `cordis.patch.yml` 编辑的并发写入会以 `settings/conflict` 被拒绝。settings 提交后,卡片会在存储凭据前采纳返回的脱敏用户子树与 revision,因此失败的凭据阶段只重试该阶段。删除只会在 profile 指名本页派生的 `<ROUTE>_API_KEY` 目标时移除已配置且可写的凭据,然后 unset 该 profile;两个操作都幂等。加载完成后,页面订阅转发的 `settings/document-updated`、`credentials/reference-updated` 与 `llm/adapters-updated` 属主事件,以及本地 `connection/reset`,因此外部编辑无需轮询即可收敛。
85
87
 
88
+ 并发的 `ModelsSettingsStore.load()` 调用会在当前刷新发布 ready 或 error 快照后 resolve,包括读取已被后续刷新取代的调用。旧响应不能发布快照或完成等待中的调用;快照订阅者同步发起的刷新负责等待中的调用。预期的 Remote 失败仍作为快照诊断展示,当前读取的意外拒绝则会拒绝所有等待中的调用。
89
+
86
90
  ### 引导协调器
87
91
 
88
92
  预览版说明在 `src/client/locales.ts` 中持有精确文案,并在 `src/onboarding-copy.ts` 中持有确认版本;回环时它通过既有 settings API 比较并写入 `ui-settings-general.welcomeNoticeVersion`,且只有显式点击「继续」才会记录当前版本。已确认旧版的用户会再次看到当前说明。非回环浏览器无法使用这个仅限宿主的 namespace,因此确认只保留在进程内,刷新后说明会再次出现。DeepSeek 步骤面向 `llm-deepseek` 中的 `deepseek-official`,在共享引导模态框内以仅凭据模式渲染既有 `ProviderEditor`;`credentials.set` 仍是唯一的机密写入,且不改变任何提供商设置。
@@ -138,5 +142,3 @@ Models 页面包含 **DeepSeek 账号**(`deepseek-account`,英文为 **DeepS
138
142
  无。
139
143
 
140
144
  </details>
141
-
142
- **运行时不变式:** 不发布伴生入口。这是只贡献 nav entry 的 section 插件,渲染固定空 content column,不发出 Cordis 事件,也不持有跨插件可变关系。
package/lib/client.js CHANGED
@@ -981,6 +981,8 @@ window.__ModuleLoader__.load({
981
981
  });
982
982
  /** Latest load wins; an older response never overwrites a newer one. */
983
983
  generation = 0;
984
+ /** Callers follow the current refresh's outcome, including callers whose read was superseded. */
985
+ pendingLoads = /* @__PURE__ */ new Set();
984
986
  /**
985
987
  * @param ctx - the page plugin's context, whose `remote.llm` and
986
988
  * `remote.credentials` namespaces carry the directory and credential reads.
@@ -997,11 +999,28 @@ window.__ModuleLoader__.load({
997
999
  * settings answer in parallel, then one batched credential describe over
998
1000
  * every referenced ref. Provider failure or absence of an initial settings
999
1001
  * answer keeps the last good rows and surfaces an error; a failed settings
1000
- * refresh reuses the mirror's held view.
1001
- * @returns nothing; the snapshot carries the outcome.
1002
+ * refresh reuses the mirror's held view. Concurrent callers resolve only
1003
+ * after the current refresh publishes its ready or error snapshot.
1004
+ * @returns resolution after the current snapshot is published.
1005
+ * @throws the current read's unexpected rejection; superseded rejections are ignored.
1002
1006
  */
1003
- async load() {
1007
+ load() {
1008
+ const pending = Promise.withResolvers();
1009
+ this.pendingLoads.add(pending);
1004
1010
  const generation = ++this.generation;
1011
+ this.read(generation).then(() => {
1012
+ this.settleLoads(generation, (load) => {
1013
+ load.resolve();
1014
+ });
1015
+ }, (error) => {
1016
+ this.settleLoads(generation, (load) => {
1017
+ load.reject(error);
1018
+ });
1019
+ });
1020
+ return pending.promise;
1021
+ }
1022
+ /** Publish one joined answer; stale answers leave the current refresh in charge. */
1023
+ async read(generation) {
1005
1024
  this.store.update((s) => {
1006
1025
  s.status = "loading";
1007
1026
  s.error = null;
@@ -1011,17 +1030,18 @@ window.__ModuleLoader__.load({
1011
1030
  this.ctx.remote.llm.listConfigurableProviders(),
1012
1031
  this.describeFace.ensure()
1013
1032
  ]);
1033
+ if (generation !== this.generation) return;
1014
1034
  if (!registered.ok) {
1015
- this.failLoad(generation, registered.error.message);
1035
+ this.failLoad(registered.error.message);
1016
1036
  return;
1017
1037
  }
1018
1038
  if (!declared.ok) {
1019
- this.failLoad(generation, declared.error.message);
1039
+ this.failLoad(declared.error.message);
1020
1040
  return;
1021
1041
  }
1022
1042
  const mirrored = this.describeFace.getSnapshot();
1023
1043
  if (mirrored.view === void 0) {
1024
- this.failLoad(generation, mirrored.error ?? "settings are unavailable in this browser");
1044
+ this.failLoad(mirrored.error ?? "settings are unavailable in this browser");
1025
1045
  return;
1026
1046
  }
1027
1047
  const providers = joinProviderDirectory(registered.value, declared.value);
@@ -1069,9 +1089,15 @@ window.__ModuleLoader__.load({
1069
1089
  s.namespaces = namespaces;
1070
1090
  });
1071
1091
  }
1072
- /** Publish one load's failure text, unless a newer load already took over. */
1073
- failLoad(generation, message) {
1092
+ /** Settle callers only while this read still owns the current refresh. */
1093
+ settleLoads(generation, settle) {
1074
1094
  if (generation !== this.generation) return;
1095
+ const pending = [...this.pendingLoads];
1096
+ this.pendingLoads.clear();
1097
+ for (const load of pending) settle(load);
1098
+ }
1099
+ /** Publish the current joined read's failure text. */
1100
+ failLoad(message) {
1075
1101
  this.store.update((s) => {
1076
1102
  s.status = "error";
1077
1103
  s.error = message;
@@ -1973,6 +1999,10 @@ window.__ModuleLoader__.load({
1973
1999
  }
1974
2000
  } : row)
1975
2001
  };
2002
+ /** A new or failed surface waits for a current snapshot; background refreshes preserve its drafts. */
2003
+ const [hasReadySnapshot, setHasReadySnapshot] = (0, react.useState)(state.status === "ready");
2004
+ if (state.status === "ready" && !hasReadySnapshot) setHasReadySnapshot(true);
2005
+ if (state.status === "error" && hasReadySnapshot) setHasReadySnapshot(false);
1976
2006
  const [editing, setEditing] = (0, react.useState)(void 0);
1977
2007
  const [addOpen, setAddOpen] = (0, react.useState)(false);
1978
2008
  const [addMode, setAddMode] = (0, react.useState)("catalog");
@@ -2060,6 +2090,16 @@ window.__ModuleLoader__.load({
2060
2090
  })]
2061
2091
  });
2062
2092
  }
2093
+ if (!hasReadySnapshot && state.status !== "ready") return (0, react_jsx_runtime.jsxs)("div", {
2094
+ className: ModelsSection_module_css_default["section"],
2095
+ children: [(0, react_jsx_runtime.jsx)("h2", {
2096
+ className: ModelsSection_module_css_default["title"],
2097
+ children: t("title")
2098
+ }), (0, react_jsx_runtime.jsx)("p", {
2099
+ className: ModelsSection_module_css_default["intro"],
2100
+ children: t("intro")
2101
+ })]
2102
+ });
2063
2103
  const savedRow = savedTarget === void 0 ? void 0 : state.rows.find((row) => row.entry.provider === savedTarget.provider);
2064
2104
  const savedIdentity = savedRow === void 0 ? savedTarget : {
2065
2105
  provider: savedRow.entry.provider,
@@ -2191,7 +2231,9 @@ window.__ModuleLoader__.load({
2191
2231
  type: "button",
2192
2232
  className: ModelsSection_module_css_default["secondaryButton"],
2193
2233
  "aria-label": providerCopy(t("editProvider"), target),
2234
+ disabled: !open && state.status !== "ready",
2194
2235
  onClick: () => {
2236
+ if (!open && controller.store.getSnapshot().status !== "ready") return;
2195
2237
  setSavedTarget(void 0);
2196
2238
  setAddOpen(false);
2197
2239
  setEditing(open ? void 0 : target);
@@ -2201,8 +2243,9 @@ window.__ModuleLoader__.load({
2201
2243
  type: "button",
2202
2244
  className: ModelsSection_module_css_default["dangerButton"],
2203
2245
  "aria-label": providerCopy(t("removeProvider"), target),
2204
- disabled: !state.writable,
2246
+ disabled: !state.writable || state.status !== "ready",
2205
2247
  onClick: () => {
2248
+ if (controller.store.getSnapshot().status !== "ready") return;
2206
2249
  setSavedTarget(void 0);
2207
2250
  setDeleteFailure(void 0);
2208
2251
  setDeleteTarget(target);
@@ -2351,8 +2394,9 @@ window.__ModuleLoader__.load({
2351
2394
  children: (0, react_jsx_runtime.jsxs)("button", {
2352
2395
  type: "button",
2353
2396
  className: ModelsSection_module_css_default["addButton"],
2354
- disabled: !state.writable || !catalogEnabled && !customEnabled,
2397
+ disabled: !state.writable || state.status !== "ready" || !catalogEnabled && !customEnabled,
2355
2398
  onClick: () => {
2399
+ if (controller.store.getSnapshot().status !== "ready") return;
2356
2400
  const first = addable[0];
2357
2401
  const initial = catalogEnabled ? "catalog" : "custom";
2358
2402
  setSavedTarget(void 0);
@@ -91,6 +91,8 @@ export declare class ModelsSettingsStore {
91
91
  readonly store: SnapshotStore<ModelsSettingsState>;
92
92
  /** Latest load wins; an older response never overwrites a newer one. */
93
93
  private generation;
94
+ /** Callers follow the current refresh's outcome, including callers whose read was superseded. */
95
+ private readonly pendingLoads;
94
96
  /**
95
97
  * @param ctx - the page plugin's context, whose `remote.llm` and
96
98
  * `remote.credentials` namespaces carry the directory and credential reads.
@@ -103,11 +105,17 @@ export declare class ModelsSettingsStore {
103
105
  * settings answer in parallel, then one batched credential describe over
104
106
  * every referenced ref. Provider failure or absence of an initial settings
105
107
  * answer keeps the last good rows and surfaces an error; a failed settings
106
- * refresh reuses the mirror's held view.
107
- * @returns nothing; the snapshot carries the outcome.
108
+ * refresh reuses the mirror's held view. Concurrent callers resolve only
109
+ * after the current refresh publishes its ready or error snapshot.
110
+ * @returns resolution after the current snapshot is published.
111
+ * @throws the current read's unexpected rejection; superseded rejections are ignored.
108
112
  */
109
113
  load(): Promise<void>;
110
- /** Publish one load's failure text, unless a newer load already took over. */
114
+ /** Publish one joined answer; stale answers leave the current refresh in charge. */
115
+ private read;
116
+ /** Settle callers only while this read still owns the current refresh. */
117
+ private settleLoads;
118
+ /** Publish the current joined read's failure text. */
111
119
  private failLoad;
112
120
  }
113
121
  /**
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@deepseek-ai/dsh-client-ui-settings-models",
3
3
  "description": "Models settings and shared product-onboarding dialogs over existing settings and credential joins",
4
- "version": "0.2.0-rc.2",
4
+ "version": "0.2.1-alpha.2",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -37,27 +37,27 @@
37
37
  },
38
38
  "license": "MIT",
39
39
  "peerDependencies": {
40
- "@deepseek-ai/cordis": "~4.0.4"
40
+ "@deepseek-ai/cordis": "~4.0.5-alpha.1"
41
41
  },
42
42
  "devDependencies": {
43
+ "@deepseek-ai/dsh-host-webserver": "0.2.1-alpha.2",
44
+ "@deepseek-ai/dsh-api-remotes": "0.2.1-alpha.2",
45
+ "@deepseek-ai/dsh-client-locale": "0.2.1-alpha.2",
46
+ "@deepseek-ai/dsh-client-store": "0.2.1-alpha.2",
47
+ "@deepseek-ai/dsh-client-test-runtime": "0.2.1-alpha.2",
48
+ "@deepseek-ai/dsh-client-ui-primitives": "0.2.1-alpha.2",
49
+ "@deepseek-ai/dsh-client-ui-settings": "0.2.1-alpha.2",
50
+ "@deepseek-ai/dsh-client-ui-slots": "0.2.1-alpha.2",
43
51
  "@types/react": "~18.3.1",
52
+ "@deepseek-ai/cordis": "~4.0.5-alpha.1",
44
53
  "react": "^18.2.0",
45
- "@deepseek-ai/dsh-host-webserver": "0.2.0-rc.2",
46
- "@deepseek-ai/dsh-api-remotes": "0.2.0-rc.2",
47
- "@deepseek-ai/dsh-client-locale": "0.2.0-rc.2",
48
- "@deepseek-ai/dsh-client-store": "0.2.0-rc.2",
49
- "@deepseek-ai/dsh-client-test-runtime": "0.2.0-rc.2",
50
- "@deepseek-ai/dsh-client-ui-primitives": "0.2.0-rc.2",
51
- "@deepseek-ai/dsh-client-ui-settings": "0.2.0-rc.2",
52
- "@deepseek-ai/cordis": "~4.0.4",
53
- "@deepseek-ai/dsh-client-ui-renderer": "0.2.0-rc.2",
54
- "@deepseek-ai/dsh-util-values": "0.2.0-rc.2",
55
- "@deepseek-ai/dsh-remote-mock": "0.2.0-rc.2",
56
- "@deepseek-ai/dsh-client-ui-slots": "0.2.0-rc.2",
57
- "@deepseek-ai/dsh-client-product-analytics": "0.2.0-rc.2"
54
+ "@deepseek-ai/dsh-client-ui-renderer": "0.2.1-alpha.2",
55
+ "@deepseek-ai/dsh-util-values": "0.2.1-alpha.2",
56
+ "@deepseek-ai/dsh-remote-mock": "0.2.1-alpha.2",
57
+ "@deepseek-ai/dsh-client-product-analytics": "0.2.1-alpha.2"
58
58
  },
59
59
  "dependencies": {
60
- "@deepseek-ai/schemastery": "~3.18.4"
60
+ "@deepseek-ai/schemastery": "~3.18.5-alpha.1"
61
61
  },
62
62
  "files": [
63
63
  "lib/index.js",