@deepseek-ai/dsh-client-ui-conversation 0.1.2-alpha.2 → 0.1.2-alpha.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/lib/client.js CHANGED
@@ -568,12 +568,15 @@ window.__ModuleLoader__.load({
568
568
  contexts = /* @__PURE__ */ new Map();
569
569
  contextsByKind = /* @__PURE__ */ new Map();
570
570
  contextsBySeq = /* @__PURE__ */ new Map();
571
+ contextsByTarget = /* @__PURE__ */ new Map();
571
572
  inputs = /* @__PURE__ */ new Map();
572
573
  locationIndex = new ConversationLocationIndex();
573
574
  dirty = /* @__PURE__ */ new Set();
575
+ dirtyByTarget = /* @__PURE__ */ new Map();
574
576
  revised = /* @__PURE__ */ new Set();
575
577
  dependents = /* @__PURE__ */ new Map();
576
578
  views = /* @__PURE__ */ new Map();
579
+ activeTargets = /* @__PURE__ */ new Set();
577
580
  hasMore = false;
578
581
  replacePending = true;
579
582
  timelineDirty = true;
@@ -596,8 +599,10 @@ window.__ModuleLoader__.load({
596
599
  this.contexts.clear();
597
600
  this.contextsByKind.clear();
598
601
  this.contextsBySeq.clear();
602
+ this.contextsByTarget.clear();
599
603
  this.inputs.clear();
600
604
  this.dirty.clear();
605
+ this.dirtyByTarget.clear();
601
606
  this.revised.clear();
602
607
  this.dependents.clear();
603
608
  this.hasMore = hasMore;
@@ -608,7 +613,7 @@ window.__ModuleLoader__.load({
608
613
  for (const entry of sorted) this.matchInput(entry);
609
614
  this.replayDependencies();
610
615
  this.revised.clear();
611
- for (const context of this.contexts.values()) this.dirty.add(context);
616
+ for (const context of this.contexts.values()) this.markDirty(context);
612
617
  this.replacePending = true;
613
618
  return "immediate";
614
619
  }
@@ -673,60 +678,71 @@ window.__ModuleLoader__.load({
673
678
  return this.replaceWindow(this.sortedInputs(), this.hasMore);
674
679
  }
675
680
  /**
676
- * Materialize dirty Contexts and advance every registered view builder.
681
+ * Materialize dirty Contexts and advance every active view builder.
677
682
  * @returns whether any view snapshot was rebuilt or incrementally applied.
678
683
  */
679
684
  flush() {
680
685
  if (!this.replacePending && this.dirty.size === 0 && !this.timelineDirty) return false;
681
686
  if (this.replacePending) {
682
687
  this.replaceLocationData();
683
- const allByTarget = /* @__PURE__ */ new Map();
684
- for (const target of this.views.keys()) allByTarget.set(target, []);
685
- for (const context of this.contexts.values()) {
686
- const target = context.definition.target;
687
- if (target === void 0 || !this.views.has(target)) continue;
688
- const node = this.buildNode(context, target);
689
- context.current.set(target, node);
690
- if (node !== null) allByTarget.get(target)?.push(node);
691
- }
692
- for (const view of this.views.values()) view.snapshot = view.builder.replace({
693
- nodes: allByTarget.get(view.target) ?? [],
694
- timeline: this.locationIndex.snapshot()
695
- });
688
+ let published = false;
689
+ for (const target of this.activeTargets) {
690
+ const view = this.views.get(target);
691
+ if (view === void 0) continue;
692
+ const builder = view.builder ?? view.definition.create();
693
+ view.builder = builder;
694
+ view.snapshot = builder.replace({
695
+ nodes: this.buildTargetNodes(target, this.contextsByTarget.get(target)),
696
+ timeline: this.locationIndex.snapshot()
697
+ });
698
+ published = true;
699
+ }
696
700
  this.replacePending = false;
697
701
  this.dirty.clear();
702
+ this.dirtyByTarget.clear();
698
703
  this.timelineDirty = false;
699
- return true;
704
+ return published;
700
705
  }
701
- const upsertsByTarget = /* @__PURE__ */ new Map();
702
- for (const target of this.views.keys()) upsertsByTarget.set(target, []);
706
+ let published = false;
703
707
  if (this.applyDirtyLocationData()) this.timelineDirty = true;
704
- for (const context of this.dirty) {
705
- const target = context.definition.target;
706
- if (target === void 0 || !this.views.has(target)) continue;
707
- const previous = context.current.get(target) ?? null;
708
- const node = this.buildNode(context, target);
709
- if (node === null && previous !== null) throw new Error(`conversation Definition "${context.kind}" withdrew materialized target "${target}"; return the same key with hidden visibility instead`);
710
- context.current.set(target, node);
711
- if (node !== null) upsertsByTarget.get(target)?.push(node);
712
- }
713
- this.dirty.clear();
714
708
  const timelineDirty = this.timelineDirty;
715
- this.timelineDirty = false;
716
- for (const view of this.views.values()) {
717
- const upserts = upsertsByTarget.get(view.target) ?? [];
709
+ for (const target of this.activeTargets) {
710
+ const view = this.views.get(target);
711
+ if (view === void 0) continue;
712
+ const builder = view.builder;
713
+ if (builder === void 0) continue;
714
+ const upserts = this.buildTargetUpserts(target, this.dirtyByTarget.get(target));
718
715
  if (upserts.length === 0 && !timelineDirty) continue;
719
- view.snapshot = view.builder.apply({
716
+ view.snapshot = builder.apply({
720
717
  upserts,
721
718
  timeline: this.locationIndex.snapshot()
722
719
  });
720
+ published = true;
723
721
  }
722
+ this.dirty.clear();
723
+ this.dirtyByTarget.clear();
724
+ this.timelineDirty = false;
725
+ return published;
726
+ }
727
+ /**
728
+ * Add one target to the monotonic active set and materialize its current snapshot.
729
+ * Pending Context work is flushed before the first complete replacement.
730
+ * @param target - registered or subsequently registered view target.
731
+ * @returns whether any active target snapshot changed.
732
+ */
733
+ activateTarget(target) {
734
+ const view = this.views.get(target);
735
+ if (this.activeTargets.has(target)) return false;
736
+ const published = this.flush();
737
+ this.activeTargets.add(target);
738
+ if (view === void 0) return published;
739
+ this.replaceView(view);
724
740
  return true;
725
741
  }
726
742
  /**
727
743
  * Read the latest snapshot of a registered target.
728
744
  * @param target - registered view target.
729
- * @returns target snapshot, or undefined when no builder is registered.
745
+ * @returns target snapshot, or undefined before registration or activation.
730
746
  */
731
747
  snapshot(target) {
732
748
  return this.views.get(target)?.snapshot;
@@ -736,11 +752,15 @@ window.__ModuleLoader__.load({
736
752
  }
737
753
  /**
738
754
  * Read targets whose owners classify their latest snapshot as visible activity.
739
- * @returns active target ids.
755
+ * @returns target ids contributing visible activity.
740
756
  */
741
- activeTargets() {
757
+ activityTargets() {
742
758
  const active = /* @__PURE__ */ new Set();
743
- for (const view of this.views.values()) if (view.isActive?.(view.snapshot) === true) active.add(view.target);
759
+ for (const target of this.activeTargets) {
760
+ const view = this.views.get(target);
761
+ if (view === void 0) continue;
762
+ if (view.isActive?.(view.snapshot) === true) active.add(view.target);
763
+ }
744
764
  return active;
745
765
  }
746
766
  sortedInputs() {
@@ -781,27 +801,30 @@ window.__ModuleLoader__.load({
781
801
  }
782
802
  return publication;
783
803
  }
804
+ createContext(definition, id, key) {
805
+ const context = {
806
+ key,
807
+ kind: definition.kind,
808
+ id,
809
+ definition,
810
+ startSeq: void 0,
811
+ start: void 0,
812
+ matches: [],
813
+ state: void 0,
814
+ revision: 0,
815
+ current: /* @__PURE__ */ new Map(),
816
+ locationData: emptyLocationData(),
817
+ dependencies: /* @__PURE__ */ new Map()
818
+ };
819
+ this.contexts.set(key, context);
820
+ this.indexTargetContext(context);
821
+ return context;
822
+ }
784
823
  acceptMatch(definition, id, role, input) {
785
824
  const key = conversationContextKey(definition.kind, id);
786
825
  let context = this.contexts.get(key);
787
826
  if (role === "start" && context?.start !== void 0) throw new Error(`conversation Context ${key} received more than one start Match`);
788
- if (context === void 0) {
789
- context = {
790
- key,
791
- kind: definition.kind,
792
- id,
793
- definition,
794
- startSeq: void 0,
795
- start: void 0,
796
- matches: [],
797
- state: void 0,
798
- revision: 0,
799
- current: /* @__PURE__ */ new Map(),
800
- locationData: emptyLocationData(),
801
- dependencies: /* @__PURE__ */ new Map()
802
- };
803
- this.contexts.set(key, context);
804
- }
827
+ context ??= this.createContext(definition, id, key);
805
828
  const match = conversationMatch(key, input, role, this.locationIndex.locationOf(input.event));
806
829
  const previous = context.matches.at(-1);
807
830
  if (previous !== void 0 && previous.event.seq >= input.event.seq) throw new Error(`conversation Context ${key} received non-appended Match ${input.event.seq}`);
@@ -822,7 +845,7 @@ window.__ModuleLoader__.load({
822
845
  context.revision++;
823
846
  this.revised.add(context);
824
847
  }
825
- this.dirty.add(context);
848
+ this.markDirty(context);
826
849
  return definition.publication?.(match) ?? "immediate";
827
850
  }
828
851
  applyPendingMatches(pending, affected) {
@@ -831,23 +854,7 @@ window.__ModuleLoader__.load({
831
854
  const first = entries[0];
832
855
  if (first === void 0) continue;
833
856
  let context = this.contexts.get(key);
834
- if (context === void 0) {
835
- context = {
836
- key,
837
- kind: first.definition.kind,
838
- id: first.id,
839
- definition: first.definition,
840
- startSeq: void 0,
841
- start: void 0,
842
- matches: [],
843
- state: void 0,
844
- revision: 0,
845
- current: /* @__PURE__ */ new Map(),
846
- locationData: emptyLocationData(),
847
- dependencies: /* @__PURE__ */ new Map()
848
- };
849
- this.contexts.set(key, context);
850
- }
857
+ context ??= this.createContext(first.definition, first.id, key);
851
858
  let discoveredStart;
852
859
  const additions = entries.map((entry) => {
853
860
  if (entry.definition !== context.definition || entry.id !== context.id) throw new Error(`conversation Context ${key} received inconsistent Definition identity`);
@@ -870,7 +877,7 @@ window.__ModuleLoader__.load({
870
877
  }
871
878
  if (context.start !== void 0 && context.matches[0] !== context.start) throw new Error(`conversation Context ${context.key} received an update before its start Match`);
872
879
  affected.add(context);
873
- this.dirty.add(context);
880
+ this.markDirty(context);
874
881
  }
875
882
  for (const [kind, contexts] of startsByKind) this.indexStartedContexts(kind, contexts);
876
883
  }
@@ -879,7 +886,7 @@ window.__ModuleLoader__.load({
879
886
  for (const context of ordered) {
880
887
  if (context.start === void 0) {
881
888
  context.state = void 0;
882
- this.dirty.add(context);
889
+ this.markDirty(context);
883
890
  continue;
884
891
  }
885
892
  this.replayContext(context);
@@ -905,7 +912,22 @@ window.__ModuleLoader__.load({
905
912
  }
906
913
  context.revision++;
907
914
  this.revised.add(context);
915
+ this.markDirty(context);
916
+ }
917
+ indexTargetContext(context) {
918
+ const target = context.definition.target;
919
+ if (target === void 0) return;
920
+ const contexts = this.contextsByTarget.get(target) ?? /* @__PURE__ */ new Set();
921
+ contexts.add(context);
922
+ this.contextsByTarget.set(target, contexts);
923
+ }
924
+ markDirty(context) {
908
925
  this.dirty.add(context);
926
+ const target = context.definition.target;
927
+ if (target === void 0 || !this.activeTargets.has(target)) return;
928
+ const contexts = this.dirtyByTarget.get(target) ?? /* @__PURE__ */ new Set();
929
+ contexts.add(context);
930
+ this.dirtyByTarget.set(target, contexts);
909
931
  }
910
932
  replaceDependencies(context, dependencies) {
911
933
  for (const dependency of context.dependencies.values()) {
@@ -1053,6 +1075,34 @@ window.__ModuleLoader__.load({
1053
1075
  if (node.target !== target) throw new Error(`conversation Definition "${context.kind}" returned target "${node.target}" while building "${target}"`);
1054
1076
  return node;
1055
1077
  }
1078
+ replaceView(view) {
1079
+ const builder = view.builder ?? view.definition.create();
1080
+ view.builder = builder;
1081
+ view.snapshot = builder.replace({
1082
+ nodes: this.buildTargetNodes(view.target, this.contextsByTarget.get(view.target)),
1083
+ timeline: this.locationIndex.snapshot()
1084
+ });
1085
+ }
1086
+ buildTargetNodes(target, contexts) {
1087
+ const nodes = [];
1088
+ for (const context of contexts ?? []) {
1089
+ const node = this.buildNode(context, target);
1090
+ context.current.set(target, node);
1091
+ if (node !== null) nodes.push(node);
1092
+ }
1093
+ return nodes;
1094
+ }
1095
+ buildTargetUpserts(target, contexts) {
1096
+ const upserts = [];
1097
+ for (const context of contexts ?? []) {
1098
+ const previous = context.current.get(target) ?? null;
1099
+ const node = this.buildNode(context, target);
1100
+ if (node === null && previous !== null) throw new Error(`conversation Definition "${context.kind}" withdrew materialized target "${target}"; return the same key with hidden visibility instead`);
1101
+ context.current.set(target, node);
1102
+ if (node !== null) upserts.push(node);
1103
+ }
1104
+ return upserts;
1105
+ }
1056
1106
  buildLocationData(context, scope) {
1057
1107
  if (context.definition.buildLocationData === void 0) return null;
1058
1108
  const data = context.definition.buildLocationData(contextSnapshot(context), scope);
@@ -1098,13 +1148,14 @@ window.__ModuleLoader__.load({
1098
1148
  resetViewBuilders() {
1099
1149
  this.views.clear();
1100
1150
  for (const definition of this.viewDefinitions.entries()) {
1101
- const builder = definition.create();
1102
- this.views.set(definition.target, {
1151
+ const view = {
1103
1152
  target: definition.target,
1104
- builder,
1153
+ definition,
1105
1154
  isActive: definition.isActive === void 0 ? void 0 : (snapshot) => definition.isActive?.(snapshot) === true,
1106
- snapshot: builder.empty
1107
- });
1155
+ builder: void 0,
1156
+ snapshot: void 0
1157
+ };
1158
+ this.views.set(definition.target, view);
1108
1159
  }
1109
1160
  this.replacePending = true;
1110
1161
  }
@@ -1438,13 +1489,18 @@ window.__ModuleLoader__.load({
1438
1489
  source = {
1439
1490
  getSnapshot: () => views.get(target),
1440
1491
  subscribe: (listener) => {
1441
- return this.snapshot.subscribe(listener);
1492
+ const unsubscribe = this.snapshot.subscribe(listener);
1493
+ this.activate(target);
1494
+ return unsubscribe;
1442
1495
  }
1443
1496
  };
1444
1497
  this.targetSources.set(target, source);
1445
1498
  }
1446
1499
  return source;
1447
1500
  }
1501
+ activate(target) {
1502
+ if (this.assembler.activateTarget(target)) this.snapshot.set(this.currentSnapshot());
1503
+ }
1448
1504
  rebuild() {
1449
1505
  this.publish(this.assembler.rebuildRegistry());
1450
1506
  }
@@ -1496,7 +1552,7 @@ window.__ModuleLoader__.load({
1496
1552
  currentSnapshot() {
1497
1553
  return {
1498
1554
  views: this.viewStore,
1499
- activeTargets: this.assembler.activeTargets()
1555
+ activeTargets: this.assembler.activityTargets()
1500
1556
  };
1501
1557
  }
1502
1558
  };
@@ -1622,6 +1678,7 @@ window.__ModuleLoader__.load({
1622
1678
  //#endregion
1623
1679
  //#region lib/types/client/stores.js
1624
1680
  /** Per-session Conversation store shared by the shell body and header. */
1681
+ const CONVERSATION_STORE_KEY = "dsh.conversation";
1625
1682
  /**
1626
1683
  * Declare per-session draft persistence and View selection.
1627
1684
  * @returns the store handle.
@@ -1633,7 +1690,7 @@ window.__ModuleLoader__.load({
1633
1690
  view: null,
1634
1691
  viewRequest: null
1635
1692
  }),
1636
- persist: "dsh.conversation",
1693
+ persist: CONVERSATION_STORE_KEY,
1637
1694
  actions: {
1638
1695
  setDraft: (d, text) => {
1639
1696
  d.draft = text;
@@ -1654,6 +1711,23 @@ window.__ModuleLoader__.load({
1654
1711
  }
1655
1712
  });
1656
1713
  }
1714
+ /**
1715
+ * Read the persisted View preference before the Slot store is materialized.
1716
+ * @param sessionId - Session-scoped persistence suffix.
1717
+ * @returns the preferred View id, or null when storage has no usable value.
1718
+ */
1719
+ function readConversationViewPreference(sessionId) {
1720
+ if (typeof localStorage === "undefined") return null;
1721
+ try {
1722
+ const raw = localStorage.getItem(`${CONVERSATION_STORE_KEY}.${sessionId}`);
1723
+ if (raw === null) return null;
1724
+ const stored = JSON.parse(raw);
1725
+ if (typeof stored !== "object" || stored === null || !("view" in stored)) return null;
1726
+ return typeof stored.view === "string" ? stored.view : null;
1727
+ } catch {
1728
+ return null;
1729
+ }
1730
+ }
1657
1731
  //#endregion
1658
1732
  //#region lib/types/client/service.js
1659
1733
  /**
@@ -1801,6 +1875,7 @@ window.__ModuleLoader__.load({
1801
1875
  finishRetirement = resolve;
1802
1876
  });
1803
1877
  const submission = session.beginSubmission({
1878
+ mode,
1804
1879
  text,
1805
1880
  images: attachments.map((attachment) => ({
1806
1881
  previewUrl: attachment.previewUrl,
@@ -13177,7 +13252,6 @@ window.__ModuleLoader__.load({
13177
13252
  "image.tooManyPixels": "图片分辨率过大,请压缩后重试",
13178
13253
  "image.dimensionTooLarge": "图片宽高不能超过 {size}px,请缩小后重试",
13179
13254
  "image.modelUnsupported": "当前模型不支持图片,请切换支持图片的模型",
13180
- "image.subagentUnsupported": "子智能体会话暂不支持图片",
13181
13255
  "image.sendFailed": "图片发送失败({reason}),请重新添加图片后再试",
13182
13256
  "context.aria": "上下文已用 {percent}",
13183
13257
  "context.used": "上下文已用",
@@ -13189,7 +13263,7 @@ window.__ModuleLoader__.load({
13189
13263
  "settings.enter.queue": "排队发送",
13190
13264
  "settings.enter.steer": "插话发送",
13191
13265
  "access.preset.readOnly": "仅可查看",
13192
- "access.preset.workspaceWrite": "可写入工作区",
13266
+ "access.preset.workspaceWrite": "工作区内修改",
13193
13267
  "access.preset.fullAccess": "完全权限",
13194
13268
  "access.confirm.title": "确认启用完全权限?",
13195
13269
  "access.confirm.description": "启用完全权限后,智能体将减少确认步骤,并且可以直接执行更多操作,包括敏感操作、文件修改或外部命令。仅建议在你信任当前任务时使用。",
@@ -13263,6 +13337,7 @@ window.__ModuleLoader__.load({
13263
13337
  "web.contentTruncated": "内容已截断",
13264
13338
  "details.running": "运行中…",
13265
13339
  "queue.count": "{n} 条排队消息",
13340
+ "queue.image": "排队消息图片",
13266
13341
  "queue.edit": "编辑排队消息",
13267
13342
  "queue.edit.unsupported": "包含非文本内容,暂不支持编辑",
13268
13343
  "queue.save": "保存排队消息",
@@ -13323,7 +13398,6 @@ window.__ModuleLoader__.load({
13323
13398
  "image.tooManyPixels": "Image resolution is too high; compress it and try again",
13324
13399
  "image.dimensionTooLarge": "Image sides must be at most {size}px; downscale it and try again",
13325
13400
  "image.modelUnsupported": "The current model does not support images; switch to a model that does",
13326
- "image.subagentUnsupported": "Subagent sessions do not support images yet",
13327
13401
  "image.sendFailed": "Sending images failed ({reason}); re-add them and try again",
13328
13402
  "context.aria": "{percent} of context used",
13329
13403
  "context.used": "of context used",
@@ -13409,6 +13483,7 @@ window.__ModuleLoader__.load({
13409
13483
  "web.contentTruncated": "Content truncated",
13410
13484
  "details.running": "Running…",
13411
13485
  "queue.count": "{n} queued messages",
13486
+ "queue.image": "Queued message image",
13412
13487
  "queue.edit": "Edit queued message",
13413
13488
  "queue.edit.unsupported": "Contains non-text content; editing is not supported yet",
13414
13489
  "queue.save": "Save queued message",
@@ -13433,7 +13508,7 @@ window.__ModuleLoader__.load({
13433
13508
  };
13434
13509
  //#endregion
13435
13510
  //#region \0dsh-css:/home/runner/work/deepseek-harness/deepseek-harness/packages/client/ui-conversation/src/client/queue/QueueDock.module.css.mjs
13436
- const css$7 = "._7yHdaG_dock{box-sizing:border-box;width:calc(100% - var(--dsh-composer-side-clearance) - var(--dsh-composer-side-clearance) - var(--dsh-composer-dock-inset) - var(--dsh-composer-dock-inset));max-width:calc(var(--dsh-composer-card-max-width) - var(--dsh-composer-dock-inset) - var(--dsh-composer-dock-inset));margin:0 auto calc(0px - var(--dsh-composer-stack-gap) - 3px);padding:0 var(--dsh-composer-dock-inset);flex:none}._7yHdaG_panel{background:var(--dsw-specific-tip);--dsh-scrollbar-thumb:var(--dsw-alias-scrollbar-bg-l2);--dsh-scrollbar-thumb-hover:var(--dsw-alias-scrollbar-hover-l2);border-radius:12px 12px 0 0;width:100%;padding:2px 0;position:relative;overflow:hidden}._7yHdaG_panel:after{border:1px solid var(--dsw-alias-border-l1);border-radius:inherit;content:\"\";pointer-events:none;border-bottom:none;position:absolute;inset:0}._7yHdaG_header{box-sizing:border-box;width:100%;height:36px;color:var(--dsw-alias-label-primary);text-align:left;cursor:pointer;background:0 0;border:none;border-radius:8px;align-items:center;gap:10px;padding:4px 12px;display:flex}._7yHdaG_header:focus-visible{outline:2px solid var(--dsw-alias-label-tertiary);outline-offset:-2px}._7yHdaG_header:disabled{cursor:default}._7yHdaG_lead{color:var(--dsw-alias-label-tertiary);flex:none;place-items:center;display:grid}._7yHdaG_count{min-width:0;font-family:Inter, var(--dsw-font-family);flex:auto;font-size:13px;font-weight:500;line-height:24px}._7yHdaG_chevron{width:14px;height:14px;color:var(--dsw-alias-label-tertiary);flex:none;place-items:center;display:grid}._7yHdaG_list{max-height:180px;margin:0;padding:0;list-style:none;overflow-y:auto}._7yHdaG_row{box-sizing:border-box;border-radius:8px;align-items:center;gap:10px;width:100%;height:36px;padding:4px 5px 4px 12px;display:flex}._7yHdaG_row+._7yHdaG_row{box-shadow:inset 0 1px 0 var(--dsw-alias-border-l1)}._7yHdaG_preview,._7yHdaG_editor{min-width:0;font:var(--dsw-font-xs-13);font-family:Inter, var(--dsw-font-family);flex:auto}._7yHdaG_preview{color:var(--dsw-alias-label-primary-dimmed);text-overflow:ellipsis;white-space:nowrap;word-break:break-word;overflow:hidden}._7yHdaG_editor{box-sizing:border-box;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-base);height:28px;color:var(--dsw-alias-label-primary);border-radius:6px;outline:none;padding:0 8px}._7yHdaG_editor:focus{border-color:var(--dsw-alias-state-business-primary)}._7yHdaG_actions{flex:none;align-items:center;gap:10px;display:flex}._7yHdaG_action{width:28px;height:28px;color:var(--dsw-alias-label-tertiary);cursor:pointer;background:0 0;border:none;border-radius:999px;flex:none;place-items:center;padding:0;display:grid}._7yHdaG_action:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}._7yHdaG_action:focus-visible{outline:2px solid var(--dsw-alias-label-tertiary);outline-offset:-2px}._7yHdaG_action:disabled{cursor:default;opacity:.45}";
13511
+ const css$7 = "._7yHdaG_dock{box-sizing:border-box;width:calc(100% - var(--dsh-composer-side-clearance) - var(--dsh-composer-side-clearance) - var(--dsh-composer-dock-inset) - var(--dsh-composer-dock-inset));max-width:calc(var(--dsh-composer-card-max-width) - var(--dsh-composer-dock-inset) - var(--dsh-composer-dock-inset));margin:0 auto calc(0px - var(--dsh-composer-stack-gap) - 3px);padding:0 var(--dsh-composer-dock-inset);flex:none}._7yHdaG_panel{background:var(--dsw-specific-tip);--dsh-scrollbar-thumb:var(--dsw-alias-scrollbar-bg-l2);--dsh-scrollbar-thumb-hover:var(--dsw-alias-scrollbar-hover-l2);border-radius:12px 12px 0 0;width:100%;padding:2px 0;position:relative;overflow:hidden}._7yHdaG_panel:after{border:1px solid var(--dsw-alias-border-l1);border-radius:inherit;content:\"\";pointer-events:none;border-bottom:none;position:absolute;inset:0}._7yHdaG_header{box-sizing:border-box;width:100%;height:36px;color:var(--dsw-alias-label-primary);text-align:left;cursor:pointer;background:0 0;border:none;border-radius:8px;align-items:center;gap:10px;padding:4px 12px;display:flex}._7yHdaG_header:focus-visible{outline:2px solid var(--dsw-alias-label-tertiary);outline-offset:-2px}._7yHdaG_header:disabled{cursor:default}._7yHdaG_lead{color:var(--dsw-alias-label-tertiary);flex:none;place-items:center;display:grid}._7yHdaG_count{min-width:0;font-family:Inter, var(--dsw-font-family);flex:auto;font-size:13px;font-weight:500;line-height:24px}._7yHdaG_chevron{width:14px;height:14px;color:var(--dsw-alias-label-tertiary);flex:none;place-items:center;display:grid}._7yHdaG_list{max-height:180px;margin:0;padding:0;list-style:none;overflow-y:auto}._7yHdaG_row{box-sizing:border-box;border-radius:8px;align-items:center;gap:10px;width:100%;height:36px;padding:4px 5px 4px 12px;display:flex}._7yHdaG_row+._7yHdaG_row{box-shadow:inset 0 1px 0 var(--dsw-alias-border-l1)}._7yHdaG_thumbs{flex:none;gap:4px;display:flex}._7yHdaG_thumb{border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-base);object-fit:cover;border-radius:4px;width:24px;height:24px}._7yHdaG_preview,._7yHdaG_editor{min-width:0;font:var(--dsw-font-xs-13);font-family:Inter, var(--dsw-font-family);flex:auto}._7yHdaG_preview{color:var(--dsw-alias-label-primary-dimmed);text-overflow:ellipsis;white-space:nowrap;word-break:break-word;overflow:hidden}._7yHdaG_editor{box-sizing:border-box;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-base);height:28px;color:var(--dsw-alias-label-primary);border-radius:6px;outline:none;padding:0 8px}._7yHdaG_editor:focus{border-color:var(--dsw-alias-state-business-primary)}._7yHdaG_actions{flex:none;align-items:center;gap:10px;display:flex}._7yHdaG_action{width:28px;height:28px;color:var(--dsw-alias-label-tertiary);cursor:pointer;background:0 0;border:none;border-radius:999px;flex:none;place-items:center;padding:0;display:grid}._7yHdaG_action:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}._7yHdaG_action:focus-visible{outline:2px solid var(--dsw-alias-label-tertiary);outline-offset:-2px}._7yHdaG_action:disabled{cursor:default;opacity:.45}";
13437
13512
  const tagId$7 = "@deepseek-ai/dsh-client-ui-conversation/QueueDock.module.css";
13438
13513
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$7) + "]") === null) {
13439
13514
  const tag = document.createElement("style");
@@ -13454,17 +13529,60 @@ window.__ModuleLoader__.load({
13454
13529
  "list": "_7yHdaG_list",
13455
13530
  "panel": "_7yHdaG_panel",
13456
13531
  "preview": "_7yHdaG_preview",
13457
- "row": "_7yHdaG_row"
13532
+ "row": "_7yHdaG_row",
13533
+ "thumb": "_7yHdaG_thumb",
13534
+ "thumbs": "_7yHdaG_thumbs"
13458
13535
  };
13459
13536
  //#endregion
13460
13537
  //#region lib/types/client/queue/QueueDock.js
13461
13538
  /**
13539
+ * Durable references carried by one queued row. Queue frames are wire data
13540
+ * despite their typed face, so an image block without a reference is skipped
13541
+ * rather than trusted.
13542
+ * @param content - the row's wire content blocks.
13543
+ * @returns the row's durable image references in block order.
13544
+ */
13545
+ function queueImageRefs(content) {
13546
+ return content.flatMap((block) => {
13547
+ if (block.type !== "image") return [];
13548
+ const { attachment } = block;
13549
+ return attachment === void 0 ? [] : [attachment];
13550
+ });
13551
+ }
13552
+ /** One durable queued image as a fixed-size thumbnail; a load failure keeps the empty placeholder. */
13553
+ function QueueThumb({ attachment, loadImage, label }) {
13554
+ const [url, setUrl] = (0, react.useState)(null);
13555
+ (0, react.useEffect)(() => {
13556
+ let alive = true;
13557
+ loadImage(attachment).then((resolved) => {
13558
+ if (alive) setUrl(resolved);
13559
+ }, () => {});
13560
+ return () => {
13561
+ alive = false;
13562
+ };
13563
+ }, [attachment, loadImage]);
13564
+ return url === null ? (0, react_jsx_runtime.jsx)("span", {
13565
+ className: QueueDock_module_css_default.thumb,
13566
+ "aria-hidden": true
13567
+ }) : (0, react_jsx_runtime.jsx)("img", {
13568
+ className: QueueDock_module_css_default.thumb,
13569
+ src: url,
13570
+ alt: label
13571
+ });
13572
+ }
13573
+ /**
13462
13574
  * Queue strip: one item renders directly; multiple items default to a
13463
13575
  * collapsible count header; an empty queue renders nothing.
13464
13576
  */
13465
- function QueueDock({ useSession, updateQueue, notify, t }) {
13577
+ function QueueDock({ useSession, updateQueue, notify, loadImage, t }) {
13466
13578
  const inbox = useSession((s) => s.queue);
13467
13579
  const queue = (0, react.useMemo)(() => inbox.filter((row) => row.placement === "queued"), [inbox]);
13580
+ const pendingSubmissions = useSession((s) => s.pendingSubmissions);
13581
+ const pendingQueue = (0, react.useMemo)(() => {
13582
+ const admitted = new Set(queue.flatMap((row) => row.rpcId === void 0 ? [] : [row.rpcId]));
13583
+ return pendingSubmissions.filter((submission) => submission.placement === "queued" && !admitted.has(submission.requestId));
13584
+ }, [pendingSubmissions, queue]);
13585
+ const rowCount = queue.length + pendingQueue.length;
13468
13586
  const running = useSession((s) => s.running);
13469
13587
  const queueMutable = useSession((s) => s.subagent === null);
13470
13588
  const [editing, setEditing] = (0, react.useState)(null);
@@ -13472,18 +13590,19 @@ window.__ModuleLoader__.load({
13472
13590
  const [collapsed, setCollapsed] = (0, react.useState)(true);
13473
13591
  const listId = (0, react.useId)();
13474
13592
  (0, react.useEffect)(() => {
13475
- if (queue.length === 0 && !collapsed) setCollapsed(true);
13593
+ if (rowCount === 0 && !collapsed) setCollapsed(true);
13476
13594
  if (editing !== null && (!queueMutable || !queue.some((row) => row.id === editing.id))) setEditing(null);
13477
13595
  }, [
13478
13596
  collapsed,
13479
13597
  editing,
13480
13598
  queue,
13481
- queueMutable
13599
+ queueMutable,
13600
+ rowCount
13482
13601
  ]);
13483
- if (queue.length === 0) return null;
13602
+ if (rowCount === 0) return null;
13484
13603
  const interactionActive = queueMutable && (editing !== null || busy !== null);
13485
13604
  const expanded = !collapsed || interactionActive;
13486
- const listVisible = queue.length === 1 || expanded;
13605
+ const listVisible = rowCount === 1 || expanded;
13487
13606
  const applyAction = async (itemId, action, failure) => {
13488
13607
  setBusy(itemId);
13489
13608
  try {
@@ -13511,7 +13630,7 @@ window.__ModuleLoader__.load({
13511
13630
  "data-queue-dock": "",
13512
13631
  children: (0, react_jsx_runtime.jsxs)("div", {
13513
13632
  className: QueueDock_module_css_default.panel,
13514
- children: [queue.length > 1 && (0, react_jsx_runtime.jsxs)("button", {
13633
+ children: [rowCount > 1 && (0, react_jsx_runtime.jsxs)("button", {
13515
13634
  type: "button",
13516
13635
  className: QueueDock_module_css_default.header,
13517
13636
  "aria-controls": listId,
@@ -13528,7 +13647,7 @@ window.__ModuleLoader__.load({
13528
13647
  }),
13529
13648
  (0, react_jsx_runtime.jsx)("span", {
13530
13649
  className: QueueDock_module_css_default.count,
13531
- children: t("queue.count", { n: queue.length })
13650
+ children: t("queue.count", { n: rowCount })
13532
13651
  }),
13533
13652
  (0, react_jsx_runtime.jsx)("span", {
13534
13653
  className: QueueDock_module_css_default.chevron,
@@ -13536,130 +13655,162 @@ window.__ModuleLoader__.load({
13536
13655
  children: expanded ? (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutline14, {}) : (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronUpOutline14, {})
13537
13656
  })
13538
13657
  ]
13539
- }), (0, react_jsx_runtime.jsx)("ul", {
13658
+ }), (0, react_jsx_runtime.jsxs)("ul", {
13540
13659
  id: listId,
13541
13660
  className: QueueDock_module_css_default.list,
13542
13661
  hidden: !listVisible,
13543
- children: listVisible && queue.map((row) => (0, react_jsx_runtime.jsxs)("li", {
13544
- className: QueueDock_module_css_default.row,
13545
- children: [
13546
- queue.length === 1 && (0, react_jsx_runtime.jsx)("span", {
13547
- className: QueueDock_module_css_default.lead,
13548
- "aria-hidden": true,
13549
- children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconQueueOutline14, {})
13550
- }),
13551
- editing?.id === row.id ? (0, react_jsx_runtime.jsx)("input", {
13552
- autoFocus: true,
13553
- className: QueueDock_module_css_default.editor,
13554
- "aria-label": t("queue.edit"),
13555
- value: editing.text,
13556
- onChange: (event) => {
13557
- setEditing({
13558
- id: row.id,
13559
- text: event.currentTarget.value
13560
- });
13561
- },
13562
- onKeyDown: (event) => {
13563
- if (event.key === "Escape") {
13564
- setEditing(null);
13565
- return;
13566
- }
13567
- if (event.key === "Enter" && !event.nativeEvent.isComposing) {
13568
- event.preventDefault();
13569
- saveEdit();
13570
- }
13571
- }
13572
- }) : (0, react_jsx_runtime.jsx)("span", {
13573
- className: QueueDock_module_css_default.preview,
13574
- children: (0, _deepseek_ai_dsh_client_ui_primitives.projectUserText)(row.preview, [])
13575
- }),
13576
- queueMutable && (0, react_jsx_runtime.jsx)("div", {
13577
- className: QueueDock_module_css_default.actions,
13578
- children: editing?.id === row.id ? (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [(0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
13579
- label: t("queue.save"),
13580
- side: "bottom",
13581
- delayMs: 500,
13582
- children: (0, react_jsx_runtime.jsx)("button", {
13583
- type: "button",
13584
- className: QueueDock_module_css_default.action,
13585
- "aria-label": t("queue.save"),
13586
- disabled: busy !== null || editing.text.trim() === "",
13587
- onClick: () => {
13588
- saveEdit();
13589
- },
13590
- children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCheckOutline16, { size: 14 })
13591
- })
13592
- }), (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
13593
- label: t("queue.cancelEdit"),
13594
- side: "bottom",
13595
- delayMs: 500,
13596
- children: (0, react_jsx_runtime.jsx)("button", {
13597
- type: "button",
13598
- className: QueueDock_module_css_default.action,
13599
- "aria-label": t("queue.cancelEdit"),
13600
- disabled: busy !== null,
13601
- onClick: () => {
13662
+ children: [listVisible && queue.map((row) => {
13663
+ const imageRefs = queueImageRefs(row.content);
13664
+ return (0, react_jsx_runtime.jsxs)("li", {
13665
+ className: QueueDock_module_css_default.row,
13666
+ children: [
13667
+ rowCount === 1 && (0, react_jsx_runtime.jsx)("span", {
13668
+ className: QueueDock_module_css_default.lead,
13669
+ "aria-hidden": true,
13670
+ children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconQueueOutline14, {})
13671
+ }),
13672
+ editing?.id === row.id ? (0, react_jsx_runtime.jsx)("input", {
13673
+ autoFocus: true,
13674
+ className: QueueDock_module_css_default.editor,
13675
+ "aria-label": t("queue.edit"),
13676
+ value: editing.text,
13677
+ onChange: (event) => {
13678
+ setEditing({
13679
+ id: row.id,
13680
+ text: event.currentTarget.value
13681
+ });
13682
+ },
13683
+ onKeyDown: (event) => {
13684
+ if (event.key === "Escape") {
13602
13685
  setEditing(null);
13603
- },
13604
- children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCloseOutline16, { size: 14 })
13605
- })
13606
- })] }) : (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
13607
- (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
13608
- label: t("queue.edit"),
13686
+ return;
13687
+ }
13688
+ if (event.key === "Enter" && !event.nativeEvent.isComposing) {
13689
+ event.preventDefault();
13690
+ saveEdit();
13691
+ }
13692
+ }
13693
+ }) : (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [imageRefs.length > 0 && (0, react_jsx_runtime.jsx)("span", {
13694
+ className: QueueDock_module_css_default.thumbs,
13695
+ children: imageRefs.map((attachment, index) => (0, react_jsx_runtime.jsx)(QueueThumb, {
13696
+ attachment,
13697
+ loadImage,
13698
+ label: t("queue.image")
13699
+ }, `${attachment.attachmentId}:${index}`))
13700
+ }), (0, react_jsx_runtime.jsx)("span", {
13701
+ className: QueueDock_module_css_default.preview,
13702
+ children: (0, _deepseek_ai_dsh_client_ui_primitives.projectUserText)(row.preview, [])
13703
+ })] }),
13704
+ queueMutable && (0, react_jsx_runtime.jsx)("div", {
13705
+ className: QueueDock_module_css_default.actions,
13706
+ children: editing?.id === row.id ? (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [(0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
13707
+ label: t("queue.save"),
13609
13708
  side: "bottom",
13610
13709
  delayMs: 500,
13611
- disabled: row.text === null,
13612
13710
  children: (0, react_jsx_runtime.jsx)("button", {
13613
13711
  type: "button",
13614
13712
  className: QueueDock_module_css_default.action,
13615
- "aria-label": t("queue.edit"),
13616
- title: row.text === null ? t("queue.edit.unsupported") : void 0,
13617
- disabled: busy !== null || row.text === null,
13713
+ "aria-label": t("queue.save"),
13714
+ disabled: busy !== null || editing.text.trim() === "",
13618
13715
  onClick: () => {
13619
- if (row.text !== null) setEditing({
13620
- id: row.id,
13621
- text: row.text
13622
- });
13716
+ saveEdit();
13623
13717
  },
13624
- children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconEditOutline16, { size: 14 })
13718
+ children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCheckOutline16, { size: 14 })
13625
13719
  })
13626
- }),
13627
- (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
13628
- label: t("queue.remove"),
13720
+ }), (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
13721
+ label: t("queue.cancelEdit"),
13629
13722
  side: "bottom",
13630
13723
  delayMs: 500,
13631
13724
  children: (0, react_jsx_runtime.jsx)("button", {
13632
13725
  type: "button",
13633
13726
  className: QueueDock_module_css_default.action,
13634
- "aria-label": t("queue.remove"),
13727
+ "aria-label": t("queue.cancelEdit"),
13635
13728
  disabled: busy !== null,
13636
13729
  onClick: () => {
13637
- applyAction(row.id, { kind: "remove" }, t("queue.removeFailed"));
13730
+ setEditing(null);
13638
13731
  },
13639
- children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconTrashOutline16, { size: 14 })
13732
+ children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCloseOutline16, { size: 14 })
13640
13733
  })
13641
- }),
13642
- (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
13643
- label: t("queue.steer"),
13644
- side: "bottom",
13645
- delayMs: 500,
13646
- disabled: !running,
13647
- children: (0, react_jsx_runtime.jsx)("button", {
13648
- type: "button",
13649
- className: QueueDock_module_css_default.action,
13650
- "aria-label": t("queue.steer"),
13651
- title: running ? void 0 : t("queue.steer.unavailable"),
13652
- disabled: busy !== null || !running,
13653
- onClick: () => {
13654
- applyAction(row.id, { kind: "steer" }, t("queue.steerFailed"));
13655
- },
13656
- children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconSendOutline14, {})
13734
+ })] }) : (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
13735
+ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
13736
+ label: t("queue.edit"),
13737
+ side: "bottom",
13738
+ delayMs: 500,
13739
+ disabled: row.text === null,
13740
+ children: (0, react_jsx_runtime.jsx)("button", {
13741
+ type: "button",
13742
+ className: QueueDock_module_css_default.action,
13743
+ "aria-label": t("queue.edit"),
13744
+ title: row.text === null ? t("queue.edit.unsupported") : void 0,
13745
+ disabled: busy !== null || row.text === null,
13746
+ onClick: () => {
13747
+ if (row.text !== null) setEditing({
13748
+ id: row.id,
13749
+ text: row.text
13750
+ });
13751
+ },
13752
+ children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconEditOutline16, { size: 14 })
13753
+ })
13754
+ }),
13755
+ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
13756
+ label: t("queue.remove"),
13757
+ side: "bottom",
13758
+ delayMs: 500,
13759
+ children: (0, react_jsx_runtime.jsx)("button", {
13760
+ type: "button",
13761
+ className: QueueDock_module_css_default.action,
13762
+ "aria-label": t("queue.remove"),
13763
+ disabled: busy !== null,
13764
+ onClick: () => {
13765
+ applyAction(row.id, { kind: "remove" }, t("queue.removeFailed"));
13766
+ },
13767
+ children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconTrashOutline16, { size: 14 })
13768
+ })
13769
+ }),
13770
+ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
13771
+ label: t("queue.steer"),
13772
+ side: "bottom",
13773
+ delayMs: 500,
13774
+ disabled: !running,
13775
+ children: (0, react_jsx_runtime.jsx)("button", {
13776
+ type: "button",
13777
+ className: QueueDock_module_css_default.action,
13778
+ "aria-label": t("queue.steer"),
13779
+ title: running ? void 0 : t("queue.steer.unavailable"),
13780
+ disabled: busy !== null || !running,
13781
+ onClick: () => {
13782
+ applyAction(row.id, { kind: "steer" }, t("queue.steerFailed"));
13783
+ },
13784
+ children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconSendOutline14, {})
13785
+ })
13657
13786
  })
13658
- })
13659
- ] })
13787
+ ] })
13788
+ })
13789
+ ]
13790
+ }, row.id);
13791
+ }), listVisible && pendingQueue.map((submission) => (0, react_jsx_runtime.jsxs)("li", {
13792
+ className: QueueDock_module_css_default.row,
13793
+ "data-submission-echo": "",
13794
+ children: [
13795
+ rowCount === 1 && (0, react_jsx_runtime.jsx)("span", {
13796
+ className: QueueDock_module_css_default.lead,
13797
+ "aria-hidden": true,
13798
+ children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconQueueOutline14, {})
13799
+ }),
13800
+ submission.images.length > 0 && (0, react_jsx_runtime.jsx)("span", {
13801
+ className: QueueDock_module_css_default.thumbs,
13802
+ children: submission.images.map((image, index) => (0, react_jsx_runtime.jsx)("img", {
13803
+ className: QueueDock_module_css_default.thumb,
13804
+ src: image.previewUrl,
13805
+ alt: t("queue.image")
13806
+ }, `${image.previewUrl}:${index}`))
13807
+ }),
13808
+ (0, react_jsx_runtime.jsx)("span", {
13809
+ className: QueueDock_module_css_default.preview,
13810
+ children: (0, _deepseek_ai_dsh_client_ui_primitives.projectUserText)(submission.text, [])
13660
13811
  })
13661
13812
  ]
13662
- }, row.id))
13813
+ }, submission.requestId))]
13663
13814
  })]
13664
13815
  })
13665
13816
  });
@@ -13670,7 +13821,8 @@ window.__ModuleLoader__.load({
13670
13821
  inject: [
13671
13822
  "slots",
13672
13823
  "conversation",
13673
- "sessions"
13824
+ "sessions",
13825
+ "uiConversation"
13674
13826
  ],
13675
13827
  apply(ctx) {
13676
13828
  ctx.slots.inject("conversation.input.dock", () => ctx.slots.register({
@@ -13687,7 +13839,8 @@ window.__ModuleLoader__.load({
13687
13839
  updateQueue: (itemId, action) => conversation.updateQueue(itemId, action),
13688
13840
  notify: (level, text) => {
13689
13841
  conversation.input.for(actx).notify(level, text);
13690
- }
13842
+ },
13843
+ loadImage: (attachment) => ctx.uiConversation.imageUrl(sessionId, attachment)
13691
13844
  };
13692
13845
  }
13693
13846
  }, QueueDock));
@@ -14278,13 +14431,20 @@ window.__ModuleLoader__.load({
14278
14431
  });
14279
14432
  }
14280
14433
  //#endregion
14281
- //#region lib/types/client/skeleton/ConversationSession.js
14282
- /** Strict per-session header/body content inserted into the resident conversation layout. */
14434
+ //#region lib/types/client/view-selection.js
14283
14435
  const DEFAULT_VIEW_ID = "chat";
14284
- /** Resolve a persisted selection, then registered Chat, without choosing another View. */
14436
+ /**
14437
+ * Resolve a preferred registered View, then Chat, without choosing another View.
14438
+ * @param tabs - currently registered Views.
14439
+ * @param selectedId - preferred View identity, when one is stored.
14440
+ * @returns the selected View, Chat fallback, or undefined when neither is registered.
14441
+ */
14285
14442
  function resolveActiveView(tabs, selectedId) {
14286
14443
  return (selectedId === null ? void 0 : tabs.find((view) => view.id === selectedId)) ?? tabs.find((view) => view.id === DEFAULT_VIEW_ID);
14287
14444
  }
14445
+ //#endregion
14446
+ //#region lib/types/client/skeleton/ConversationSession.js
14447
+ /** Strict per-session header/body content inserted into the resident conversation layout. */
14288
14448
  function deriveAncestry(list, id) {
14289
14449
  const chain = [];
14290
14450
  const seen = /* @__PURE__ */ new Set();
@@ -14315,7 +14475,7 @@ window.__ModuleLoader__.load({
14315
14475
  * @param props - Strict Session store, view ledger, navigation, render, and locale shares.
14316
14476
  * @returns the hidden blank-session header or visible title and tabs.
14317
14477
  */
14318
- function ConversationSessionHeader({ sessionId, useSession, useSessions, useConversation, useConversationViews, useStore, actions, renderSlot, open, t }) {
14478
+ function ConversationSessionHeader({ sessionId, useSession, useSessions, useConversation, useConversationViews, useStore, renderSlot, open, selectView, t }) {
14319
14479
  const tabs = useConversationViews((value) => value);
14320
14480
  const active = resolveActiveView(tabs, useStore((s) => s.view));
14321
14481
  const ancestry = useSessions((s) => deriveAncestry(s, sessionId), equalBreadcrumbs);
@@ -14379,7 +14539,7 @@ window.__ModuleLoader__.load({
14379
14539
  "aria-selected": viewTab.id === active?.id,
14380
14540
  className: clsx(ConversationRoot_module_css_default.tab, viewTab.id === active?.id && ConversationRoot_module_css_default.tabActive),
14381
14541
  onClick: () => {
14382
- actions.setView(viewTab.id);
14542
+ selectView(viewTab.id);
14383
14543
  },
14384
14544
  children: viewTab.label
14385
14545
  }, viewTab.id))
@@ -14392,7 +14552,7 @@ window.__ModuleLoader__.load({
14392
14552
  * @param props - Strict Session input/store, view ledger, and render shares.
14393
14553
  * @returns the active view area, or null while the Session remains blank.
14394
14554
  */
14395
- function ConversationSession({ useSession, useConversation, useConversationViews, useInput, inputActions, useStore, actions, renderSlot, bindDraftMirror }) {
14555
+ function ConversationSession({ useSession, useConversation, useConversationViews, useInput, inputActions, useStore, actions, renderSlot, bindDraftMirror, openView }) {
14396
14556
  const active = resolveActiveView(useConversationViews((value) => value), useStore((s) => s.view));
14397
14557
  const session = useSession((s) => s);
14398
14558
  const conversation = useConversation((s) => s);
@@ -14411,7 +14571,7 @@ window.__ModuleLoader__.load({
14411
14571
  className: ConversationRoot_module_css_default.viewArea,
14412
14572
  children: active !== void 0 && renderSlot("conversation.view", {
14413
14573
  viewRequest,
14414
- openView: actions.openView,
14574
+ openView,
14415
14575
  completeViewRequest: actions.completeViewRequest
14416
14576
  }, { only: active.id })
14417
14577
  });
@@ -14507,7 +14667,7 @@ window.__ModuleLoader__.load({
14507
14667
  const recentlyComposing = () => composing || Date.now() < composingUntil;
14508
14668
  const arrow = (key) => (event) => {
14509
14669
  const inComposition = event !== null && isComposingEvent(event, recentlyComposing);
14510
- if (handlers.arbitrate(key, inComposition) === "consumed") {
14670
+ if (handlers.arbitrate(key, inComposition) !== "pass") {
14511
14671
  event?.preventDefault();
14512
14672
  return true;
14513
14673
  }
@@ -14574,7 +14734,7 @@ window.__ModuleLoader__.load({
14574
14734
  }
14575
14735
  /**
14576
14736
  * Product copy for a host attachment rejection (the `details.reason` of
14577
- * `session/attachment-invalid` or `subagent/attachment-unsupported`).
14737
+ * `session/attachment-invalid` or `subagent/attachment-invalid`).
14578
14738
  * User-solvable reasons name the limit and the way out;
14579
14739
  * reasons the user cannot act on fold into one send-failed line carrying the
14580
14740
  * reason code for a bug report.
@@ -14586,7 +14746,6 @@ window.__ModuleLoader__.load({
14586
14746
  function attachmentErrorText(t, reason, limits) {
14587
14747
  switch (reason) {
14588
14748
  case "MODEL_DOES_NOT_SUPPORT_IMAGES": return t("image.modelUnsupported");
14589
- case "SUBAGENT_IMAGE_UNSUPPORTED": return t("image.subagentUnsupported");
14590
14749
  case "IMAGE_TOO_MANY_PIXELS": return t("image.tooManyPixels");
14591
14750
  case "IMAGE_DIMENSION_TOO_LARGE":
14592
14751
  if (limits !== void 0) return t("image.dimensionTooLarge", { size: limits.maxImageDimension });
@@ -15122,7 +15281,7 @@ window.__ModuleLoader__.load({
15122
15281
  (0, react.useEffect)(() => {
15123
15282
  if (promptError === null) return;
15124
15283
  const { error } = promptError;
15125
- showToast(error.code === "session/attachment-invalid" || error.code === "subagent/attachment-unsupported" ? attachmentErrorText(t, error.details.reason, imageLimits) : `${error.message} (${error.code})`);
15284
+ showToast(error.code === "session/attachment-invalid" || error.code === "subagent/attachment-invalid" ? attachmentErrorText(t, error.details.reason, imageLimits) : `${error.message} (${error.code})`);
15126
15285
  }, [
15127
15286
  promptError,
15128
15287
  showToast,
@@ -15774,24 +15933,43 @@ window.__ModuleLoader__.load({
15774
15933
  }
15775
15934
  return tabs;
15776
15935
  };
15936
+ const activateView = (sessionId, preferred) => {
15937
+ const active = resolveActiveView(viewTabs(), preferred);
15938
+ if (active !== void 0) uiConversation.binding(sessionId).activate(active.id);
15939
+ };
15940
+ const restoreView = (sessionId) => {
15941
+ activateView(sessionId, readConversationViewPreference(sessionId));
15942
+ };
15943
+ const restoreCurrentView = () => {
15944
+ const sessionId = sessions.list.getSnapshot().current;
15945
+ if (sessionId !== void 0 && sessions.binding(sessionId) !== void 0) restoreView(sessionId);
15946
+ };
15777
15947
  const conversationViews = (0, _deepseek_ai_dsh_client_store.createSnapshotStore)(viewTabs());
15778
15948
  const refreshViews = () => {
15779
15949
  const current = conversationViews.getSnapshot();
15780
15950
  const next = viewTabs();
15781
- if (current.length === next.length && current.every((tab, index) => {
15951
+ if (!(current.length === next.length && current.every((tab, index) => {
15782
15952
  const candidate = next.at(index);
15783
15953
  return candidate !== void 0 && tab.id === candidate.id && tab.label === candidate.label;
15784
- })) return;
15785
- conversationViews.set(next);
15954
+ }))) conversationViews.set(next);
15955
+ restoreCurrentView();
15786
15956
  };
15787
15957
  ctx.effect(() => {
15958
+ let currentSessionId = sessions.list.getSnapshot().current;
15788
15959
  const disposeViews = slots.subscribe("conversation.view", refreshViews);
15789
15960
  const disposeLocale = ctx.locale.subscribe(refreshViews);
15961
+ const disposeCurrent = sessions.list.subscribe(() => {
15962
+ const nextSessionId = sessions.list.getSnapshot().current;
15963
+ if (nextSessionId === currentSessionId) return;
15964
+ currentSessionId = nextSessionId;
15965
+ restoreCurrentView();
15966
+ });
15790
15967
  return () => {
15968
+ disposeCurrent();
15791
15969
  disposeLocale();
15792
15970
  disposeViews();
15793
15971
  };
15794
- }, "ui-conversation: View roster");
15972
+ }, "ui-conversation: View selection");
15795
15973
  const inputHub = new InputHub(ctx, t);
15796
15974
  const composerBlocks = new ComposerBlockRegistry();
15797
15975
  ctx.uiSession.provide({
@@ -15799,9 +15977,11 @@ window.__ModuleLoader__.load({
15799
15977
  props: ["inputActions"],
15800
15978
  resolve: (binding) => {
15801
15979
  const shell = inputHub.shellFor(binding);
15980
+ const conversation = uiConversation.binding(binding);
15981
+ restoreView(binding.sessionId);
15802
15982
  return {
15803
15983
  hooks: {
15804
- conversation: uiConversation.binding(binding).snapshot,
15984
+ conversation: conversation.snapshot,
15805
15985
  input: shell.state
15806
15986
  },
15807
15987
  props: { inputActions: shell.actions }
@@ -15889,9 +16069,13 @@ window.__ModuleLoader__.load({
15889
16069
  scope: "session"
15890
16070
  } },
15891
16071
  store: conversationStore,
15892
- inject: (sessionId, _actions) => ({
16072
+ inject: (sessionId, actions) => ({
15893
16073
  hooks: { conversationViews },
15894
- bindDraftMirror: (write) => inputHub.shell(sessionId).bindMirror(write)
16074
+ bindDraftMirror: (write) => inputHub.shell(sessionId).bindMirror(write),
16075
+ openView: (view, focus) => {
16076
+ activateView(sessionId, view);
16077
+ actions.openView(view, focus);
16078
+ }
15895
16079
  })
15896
16080
  }, ConversationSession);
15897
16081
  const registerConversationHeader = () => slots.register({
@@ -15912,10 +16096,14 @@ window.__ModuleLoader__.load({
15912
16096
  }
15913
16097
  },
15914
16098
  store: conversationStore,
15915
- inject: () => ({
16099
+ inject: (sessionId, actions) => ({
15916
16100
  hooks: { conversationViews },
15917
16101
  open: (id) => {
15918
16102
  sessions.open(id);
16103
+ },
16104
+ selectView: (view) => {
16105
+ activateView(sessionId, view);
16106
+ actions.setView(view);
15919
16107
  }
15920
16108
  })
15921
16109
  }, ConversationSessionHeader);