@deepseek-ai/dsh-client-ui-model-selection 0.1.7-rc.2 → 0.2.0-rc.1

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
@@ -6,8 +6,8 @@
6
6
  en: f39813648aeef5a0
7
7
  zh: eea25094e8bfc031
8
8
  /deepseek-ai-dsh-client-ui-model-selection:
9
- en: 5e007d579a0e22f0
10
- zh: edfff0d021712077
9
+ en: 4cb6feb12e46f3a0
10
+ zh: eea39dd76fe1fa8b
11
11
  /deepseek-ai-dsh-client-ui-model-selection/summary:
12
12
  en: 2fb361ef704528fb
13
13
  zh: 2537d2afb4d2cb45
package/README.md CHANGED
@@ -7,6 +7,8 @@ kind: "package-reference"
7
7
 
8
8
  English | [中文](README.zh.md)
9
9
 
10
+ Desktop product events use the optional [product analytics service](../product-analytics/README.md); ordinary Web interactions are excluded.
11
+
10
12
  ## Summary
11
13
 
12
14
  The Web GUI lets users switch the model and reasoning effort for an existing session through either the `/model` popup or the composer's model control. Both surfaces present the same provider-grouped choices, and the selected model determines the available effort names and default. A complete selection applies to the next request; a running step keeps the model and effort it started with. If the selected model is unavailable, the composer stays disabled until the user selects an available model or that exact model becomes available again.
package/README.zh.md CHANGED
@@ -7,6 +7,8 @@ kind: "package-reference"
7
7
 
8
8
  [English](README.md) | 中文
9
9
 
10
+ 桌面端产品事件使用可选的[产品埋点服务](../product-analytics/README.zh.md),不包含普通 Web 交互。
11
+
10
12
  ## 概述
11
13
 
12
14
  Web GUI 允许用户通过 `/model` 弹窗或 composer 模型控件切换既有会话使用的模型与推理(reasoning)强度。两个界面呈现同一组按提供方分组的选择;所选模型决定可用的推理强度名称与默认值。完整选择从下一次请求开始生效;运行中的步骤保留其启动时的模型与推理强度。所选模型不可用时,composer 保持停用,直到用户选择可用模型或同一模型恢复可用。
package/lib/client.js CHANGED
@@ -173,6 +173,8 @@ window.__ModuleLoader__.load({
173
173
  available;
174
174
  catalog;
175
175
  projected;
176
+ isBlank;
177
+ track;
176
178
  /** The shared snapshot both entries render from (uSES-safe store). */
177
179
  store = (0, _deepseek_ai_dsh_client_store.createSnapshotStore)({
178
180
  current: null,
@@ -194,13 +196,17 @@ window.__ModuleLoader__.load({
194
196
  * @param available - whether this session may use Agent-bound model RPCs.
195
197
  * @param catalog - Host-generation catalog shared by every Session.
196
198
  * @param projected - durable model selection projected from Session history.
199
+ * @param isBlank - whether this Session has no first message yet.
200
+ * @param track - desktop-only callback after a successful user selection.
197
201
  */
198
- constructor(sessions, sessionId, available, catalog, projected) {
202
+ constructor(sessions, sessionId, available, catalog, projected, isBlank, track) {
199
203
  this.sessions = sessions;
200
204
  this.sessionId = sessionId;
201
205
  this.available = available;
202
206
  this.catalog = catalog;
203
207
  this.projected = projected;
208
+ this.isBlank = isBlank;
209
+ this.track = track;
204
210
  this.unsubscribeCatalog = catalog.store.subscribe(() => {
205
211
  this.syncInputs();
206
212
  });
@@ -228,6 +234,9 @@ window.__ModuleLoader__.load({
228
234
  */
229
235
  async select(selection) {
230
236
  this.assertAvailable();
237
+ const previous = this.store.getSnapshot().current;
238
+ const previousEffort = previous?.reasoningEffort ?? (previous === null ? void 0 : this.catalog.reasoningFor(previous)?.defaultEffort);
239
+ const nextEffort = selection.reasoningEffort ?? this.catalog.reasoningFor(selection)?.defaultEffort;
231
240
  const generation = ++this.generation;
232
241
  this.store.update((s) => {
233
242
  s.status = "selecting";
@@ -252,6 +261,21 @@ window.__ModuleLoader__.load({
252
261
  });
253
262
  return result;
254
263
  }
264
+ if (previous !== null) {
265
+ const from = `${previous.provider}/${previous.model}`;
266
+ const to = `${selection.provider}/${selection.model}`;
267
+ if (from !== to) this.track?.("model_switch", {
268
+ ...this.isBlank() ? {} : { session_id: this.sessionId },
269
+ switch_from: from,
270
+ switch_to: to
271
+ });
272
+ if (from === to && previousEffort !== nextEffort) this.track?.("thinking_level_switch", {
273
+ ...this.isBlank() ? {} : { session_id: this.sessionId },
274
+ model_name: to,
275
+ switch_from: previousEffort ?? "default",
276
+ switch_to: nextEffort ?? "default"
277
+ });
278
+ }
255
279
  this.store.update((s) => {
256
280
  s.status = "ready";
257
281
  s.pending = null;
@@ -325,20 +349,6 @@ window.__ModuleLoader__.load({
325
349
  }
326
350
  //#endregion
327
351
  //#region lib/types/client/service.js
328
- /**
329
- * ModelDirectoryResolver (`ctx.modelDirectories`): the root owner of per-session
330
- * {@link ModelDirectory} instances. Both selection entries (the /model popup
331
- * and the composer model seat) resolve their session's directory through
332
- * this service, which is what makes the dual entry one shared state.
333
- *
334
- * Per-session storage follows the client service pattern (InputTriggerService /
335
- * CommandUiRuntime): a lazy service-internal map whose entry is deleted by the
336
- * owning scope's disposer. The host `dsh-scope` ScopedLayers registry does
337
- * does not belong here: it derives scope from the host carrier mechanism
338
- * (object-keyed), while client scopes tag contexts with branded SessionId
339
- * strings, and it models global+shadow named registries — this is a
340
- * per-session singleton with no global layer to merge.
341
- */
342
352
  /** The `ctx.modelDirectories` session model-selection service. */
343
353
  var ModelDirectoryResolver = class extends _deepseek_ai_cordis.Service {
344
354
  static inject = [
@@ -387,7 +397,7 @@ window.__ModuleLoader__.load({
387
397
  if (binding === void 0) throw new Error(`ui-model-selection: session "${String(sessionId)}" resolved no binding`);
388
398
  const existing = live.directories.get(binding);
389
399
  if (existing !== void 0) return existing;
390
- const directory = new ModelDirectory(this.ctx.remote.session, sessionId, () => sessions.subagentAddress(sessionId) === void 0, this.catalog, binding.session.projections.faceOf("modelSelection"));
400
+ const directory = new ModelDirectory(this.ctx.remote.session, sessionId, () => sessions.subagentAddress(sessionId) === void 0, this.catalog, binding.session.projections.faceOf("modelSelection"), () => binding.session.getSnapshot().blank, (name, attributes) => this.ctx.get("productAnalytics")?.track(name, attributes));
391
401
  live.directories.set(binding, directory);
392
402
  actx.effect(() => () => {
393
403
  directory.dispose();
@@ -4,6 +4,7 @@
4
4
  * Session's durable selection projection, then submit through the same
5
5
  * selectModel call. A switch made in either entry updates this shared state.
6
6
  */
7
+ import type { TrackProductEvent } from '@deepseek-ai/dsh-client-product-analytics/client';
7
8
  import type { ModelCatalogFailure, ModelProviderGroup, ModelSelection } from '@deepseek-ai/dsh-api-session-controller/types';
8
9
  import type { SessionId } from '@deepseek-ai/dsh-api-remotes/client';
9
10
  import type { RemoteResult, TypertClientRemote } from '@deepseek-ai/dsh-typert-protocol';
@@ -35,6 +36,8 @@ export declare class ModelDirectory {
35
36
  private readonly available;
36
37
  private readonly catalog;
37
38
  private readonly projected;
39
+ private readonly isBlank;
40
+ private readonly track?;
38
41
  /** The shared snapshot both entries render from (uSES-safe store). */
39
42
  readonly store: SnapshotStore<ModelDirectoryState>;
40
43
  /** Latest selection operation wins; an older response never overwrites a newer one. */
@@ -48,8 +51,10 @@ export declare class ModelDirectory {
48
51
  * @param available - whether this session may use Agent-bound model RPCs.
49
52
  * @param catalog - Host-generation catalog shared by every Session.
50
53
  * @param projected - durable model selection projected from Session history.
54
+ * @param isBlank - whether this Session has no first message yet.
55
+ * @param track - desktop-only callback after a successful user selection.
51
56
  */
52
- constructor(sessions: Pick<TypertClientRemote['session'], 'selectModel'>, sessionId: SessionId, available: () => boolean, catalog: ModelCatalogDirectory, projected: ObservableSnapshot<unknown>);
57
+ constructor(sessions: Pick<TypertClientRemote['session'], 'selectModel'>, sessionId: SessionId, available: () => boolean, catalog: ModelCatalogDirectory, projected: ObservableSnapshot<unknown>, isBlank: () => boolean, track?: TrackProductEvent | undefined);
53
58
  /**
54
59
  * Ensure the Host generation's shared available catalog is loaded.
55
60
  * @returns the fresh directory value.
@@ -1,17 +1,3 @@
1
- /**
2
- * ModelDirectoryResolver (`ctx.modelDirectories`): the root owner of per-session
3
- * {@link ModelDirectory} instances. Both selection entries (the /model popup
4
- * and the composer model seat) resolve their session's directory through
5
- * this service, which is what makes the dual entry one shared state.
6
- *
7
- * Per-session storage follows the client service pattern (InputTriggerService /
8
- * CommandUiRuntime): a lazy service-internal map whose entry is deleted by the
9
- * owning scope's disposer. The host `dsh-scope` ScopedLayers registry does
10
- * does not belong here: it derives scope from the host carrier mechanism
11
- * (object-keyed), while client scopes tag contexts with branded SessionId
12
- * strings, and it models global+shadow named registries — this is a
13
- * per-session singleton with no global layer to merge.
14
- */
15
1
  import { Service } from '@deepseek-ai/cordis';
16
2
  import type { Context } from '@deepseek-ai/cordis';
17
3
  import type { SessionId } from '@deepseek-ai/dsh-session/types';
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@deepseek-ai/dsh-client-ui-model-selection",
3
3
  "description": "Model selection over the shared model catalog, Session projection, and session.selectModel",
4
- "version": "0.1.7-rc.2",
4
+ "version": "0.2.0-rc.1",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -46,22 +46,23 @@
46
46
  "react-dom": "^18.2.0",
47
47
  "@types/react-dom": "~18.3.0",
48
48
  "clsx": "^2.1.1",
49
- "@deepseek-ai/dsh-api-remotes": "0.1.7-rc.2",
50
- "@deepseek-ai/dsh-api-session-controller": "0.1.7-rc.2",
51
- "@deepseek-ai/dsh-client-locale": "0.1.7-rc.2",
52
- "@deepseek-ai/dsh-client-test-runtime": "0.1.7-rc.2",
53
- "@deepseek-ai/dsh-client-ui-commands": "0.1.7-rc.2",
54
- "@deepseek-ai/dsh-client-ui-conversation": "0.1.7-rc.2",
55
- "@deepseek-ai/dsh-client-ui-input-trigger": "0.1.7-rc.2",
56
- "@deepseek-ai/dsh-client-ui-primitives": "0.1.7-rc.2",
57
- "@deepseek-ai/dsh-client-ui-slots": "0.1.7-rc.2",
58
- "@deepseek-ai/dsh-typert-protocol": "0.1.7-rc.2",
49
+ "@deepseek-ai/dsh-api-remotes": "0.2.0-rc.1",
50
+ "@deepseek-ai/dsh-api-session-controller": "0.2.0-rc.1",
51
+ "@deepseek-ai/dsh-client-locale": "0.2.0-rc.1",
52
+ "@deepseek-ai/dsh-client-test-runtime": "0.2.0-rc.1",
53
+ "@deepseek-ai/dsh-client-ui-conversation": "0.2.0-rc.1",
54
+ "@deepseek-ai/dsh-client-ui-commands": "0.2.0-rc.1",
55
+ "@deepseek-ai/dsh-client-ui-primitives": "0.2.0-rc.1",
56
+ "@deepseek-ai/dsh-client-ui-slots": "0.2.0-rc.1",
57
+ "@deepseek-ai/dsh-typert-protocol": "0.2.0-rc.1",
59
58
  "@deepseek-ai/cordis": "~4.0.4",
60
- "@deepseek-ai/dsh-client-store": "0.1.7-rc.2",
61
- "@deepseek-ai/dsh-session": "0.1.7-rc.2",
62
- "@deepseek-ai/dsh-util-values": "0.1.7-rc.2",
63
- "@deepseek-ai/dsh-client-ui-renderer": "0.1.7-rc.2",
64
- "@deepseek-ai/dsh-client-ui-session": "0.1.7-rc.2"
59
+ "@deepseek-ai/dsh-client-store": "0.2.0-rc.1",
60
+ "@deepseek-ai/dsh-session": "0.2.0-rc.1",
61
+ "@deepseek-ai/dsh-util-values": "0.2.0-rc.1",
62
+ "@deepseek-ai/dsh-client-ui-input-trigger": "0.2.0-rc.1",
63
+ "@deepseek-ai/dsh-client-ui-renderer": "0.2.0-rc.1",
64
+ "@deepseek-ai/dsh-client-ui-session": "0.2.0-rc.1",
65
+ "@deepseek-ai/dsh-client-product-analytics": "0.2.0-rc.1"
65
66
  },
66
67
  "files": [
67
68
  "lib/index.js",