dsh-per-message-model 0.1.5 → 0.1.7

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.
Files changed (2) hide show
  1. package/lib/client.js +33 -81
  2. package/package.json +1 -1
package/lib/client.js CHANGED
@@ -67,48 +67,6 @@ window.__ModuleLoader__.load({
67
67
  })
68
68
  ]);
69
69
  }
70
- /**
71
- * Per-message model badge as a small icon button. Missing projections
72
- * degrade to the same icon with an unknown title (fail-soft, verifiable).
73
- * @param value - a wire projection entry ({ provider, model, reasoningEffort, reasoningSource }).
74
- * @param t - locale seat bound to this plugin's namespace.
75
- */
76
- function modelBadge(value, t) {
77
- const known = value !== null && typeof value === "object";
78
- const provider = known && typeof value.provider === "string" ? value.provider : null;
79
- const model = known && typeof value.model === "string" ? value.model : null;
80
- const hasModel = provider !== null && model !== null;
81
- const label = hasModel
82
- ? `${provider} / ${model}${t("badge.separator")}${t("badge.effort")} ${known && typeof value.reasoningEffort === "string" && value.reasoningEffort.length > 0 ? value.reasoningEffort : t("badge.effort.default")}`
83
- : t("badge.unknown");
84
- const title = hasModel && known && value.reasoningSource === "adapter-default"
85
- ? `${label}(${t("badge.effort.adapterDefault")})`
86
- : hasModel ? label : t("badge.unknown.title");
87
- return createElement("span", {
88
- className: BADGE_PREFIX + "icon",
89
- "data-dsh-per-message-model": "true",
90
- "aria-label": label,
91
- title
92
- }, chipIcon());
93
- }
94
- /** Session-header occupant: current model badge (latest folded assistant message). */
95
- function SessionModelBadge({ useProjection, t }) {
96
- const value = useProjection("perMessageModel");
97
- const current = value === void 0 || value === null || typeof value !== "object" || value.current === void 0
98
- ? void 0
99
- : value.current;
100
- return modelBadge(current, t);
101
- }
102
- /** Assistant-actions occupant: per-message model icon, keyed by messageId
103
- * (renders inside the official per-message action row, reordered after the
104
- * branch button and before the time clock via CSS flex order). */
105
- function PerMessageModelBadge({ messageId, useProjection, t }) {
106
- const value = useProjection("perMessageModel");
107
- const entry = value === void 0 || value === null || typeof value !== "object" || value.messages === void 0
108
- ? void 0
109
- : value.messages[messageId];
110
- return modelBadge(entry, t);
111
- }
112
70
  //#endregion
113
71
  //#region client footer
114
72
  /**
@@ -118,26 +76,37 @@ window.__ModuleLoader__.load({
118
76
  * existing chip behavior (color change + title); a single click rolls the
119
77
  * model name out from left to right like a scroll (icon rotates by a random
120
78
  * angle); clicking again rolls it back (icon rotates back).
79
+ *
80
+ * Implemented WITHOUT React state on purpose: the DSH slot renderer mounts
81
+ * registrants through an error boundary and some hosts do not tolerate
82
+ * hook-based state in dock seats; the unfold/collapse is a pure DOM class
83
+ * toggle plus an inline transform on the icon, so it works in any host.
121
84
  */
122
85
  function FooterModelBadge({ useProjection, t }) {
123
- const [open, setOpen] = React.useState(false);
124
- const [rot, setRot] = React.useState(0);
125
- const value = useProjection("perMessageModel");
126
- const current = value === void 0 || value === null || typeof value !== "object" || value.current === void 0
127
- ? void 0
128
- : value.current;
129
- const known = current !== void 0 && typeof current === "object";
130
- const provider = known && typeof current.provider === "string" ? current.provider : null;
131
- const model = known && typeof current.model === "string" ? current.model : null;
132
- const label = provider !== null && model !== null
133
- ? provider + " / " + model
134
- : t("badge.unknown");
135
- const toggle = () => {
136
- if (open) {
137
- setOpen(false);
86
+ let value = null;
87
+ try { value = useProjection("perMessageModel"); } catch (err) { value = null; }
88
+ const hasValue = value !== null && value !== void 0 && typeof value === "object";
89
+ const current = hasValue && typeof value.current === "object" && value.current !== null ? value.current : null;
90
+ const provider = current !== null && typeof current.provider === "string" ? current.provider : null;
91
+ const model = current !== null && typeof current.model === "string" ? current.model : null;
92
+ let label;
93
+ try { label = provider !== null && model !== null ? provider + " / " + model : (typeof t === "function" ? t("badge.unknown") : "model unknown"); }
94
+ catch (err) { label = provider !== null && model !== null ? provider + " / " + model : "model unknown"; }
95
+ const toggle = (event) => {
96
+ const btn = event && event.currentTarget ? event.currentTarget : null;
97
+ if (btn === null) return;
98
+ const labelEl = btn.querySelector('[class*="footer-label"]');
99
+ const iconEl = btn.querySelector('[class*="footer-icon"]');
100
+ const isOpen = btn.getAttribute("aria-expanded") === "true";
101
+ if (isOpen) {
102
+ btn.setAttribute("aria-expanded", "false");
103
+ if (labelEl !== null) labelEl.setAttribute("data-open", "false");
104
+ if (iconEl !== null) iconEl.style.transform = "rotate(0deg)";
138
105
  } else {
139
- setRot(Math.round(90 + Math.random() * 180));
140
- setOpen(true);
106
+ const deg = Math.round(90 + Math.random() * 180);
107
+ btn.setAttribute("aria-expanded", "true");
108
+ if (labelEl !== null) labelEl.setAttribute("data-open", "true");
109
+ if (iconEl !== null) iconEl.style.transform = "rotate(" + deg + "deg)";
141
110
  }
142
111
  };
143
112
  return createElement("button", {
@@ -146,22 +115,22 @@ window.__ModuleLoader__.load({
146
115
  "data-dsh-per-message-model-footer": "true",
147
116
  "aria-label": label,
148
117
  title: label,
149
- "aria-expanded": open ? "true" : "false",
118
+ "aria-expanded": "false",
150
119
  onClick: toggle
151
120
  }, [
152
121
  createElement("span", {
153
122
  key: "icon",
154
123
  className: BADGE_PREFIX + "footer-icon",
155
- style: { transform: open ? "rotate(" + rot + "deg)" : "rotate(0deg)" }
124
+ style: { transform: "rotate(0deg)" }
156
125
  }, chipIcon()),
157
126
  createElement("span", {
158
127
  key: "label",
159
128
  className: BADGE_PREFIX + "footer-label",
160
- "data-open": open ? "true" : "false"
129
+ "data-open": "false"
161
130
  }, label)
162
131
  ]);
163
132
  }
164
- //#endregion
133
+ //#endregion //#endregion
165
134
  //#region client divider
166
135
  /** Fixed Chinese copy for the model-switch divider (user-required). */
167
136
  const SWITCH_COPY = "模型切换:";
@@ -293,12 +262,7 @@ window.__ModuleLoader__.load({
293
262
  const badgeStyle = document.createElement("style");
294
263
  badgeStyle.dataset.dshPerMessageModel = BADGE_PREFIX;
295
264
  badgeStyle.textContent = [
296
- // Per-message icon: small chip, full value in title/aria.
297
- `[data-dsh-per-message-model]{display:inline-flex;align-items:center;justify-content:center;width:20px;height:20px;margin:0 2px;color:var(--dsw-alias-label-tertiary);border-radius:5px;cursor:default}`,
298
- `[data-dsh-per-message-model]:hover{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-base-2, var(--dsw-alias-bg-hover, rgba(128,128,128,.12)))}`,
299
- // Reorder inside the official action row: copy(0) branch(0) icon(3) clock(4)
300
- `[data-dsh-per-message-model]{order:3}`,
301
- `div[class*="actions"]:has([data-slot="conversation.chat.assistant-actions"]) [class*="timeEnd"]{order:4}`,
265
+
302
266
  // In-stream divider: full-width block, embeds in the message flow.
303
267
  // Label (模型切换:) and arrow are gray; model names are darker/bolder.
304
268
  `[data-dsh-model-switch]{display:flex;align-items:center;gap:8px;margin:10px 0 16px;padding:10px 14px;border:1.5px solid var(--dsw-alias-state-business-primary, #4f8cff);border-radius:10px;background:var(--dsw-alias-state-business-tertiary, rgba(79,140,255,.12));box-shadow:0 1px 3px rgba(0,0,0,.10);color:var(--dsw-alias-label-secondary);font-size:12px;line-height:18px;user-select:none}`,
@@ -315,18 +279,6 @@ window.__ModuleLoader__.load({
315
279
  document.head.appendChild(badgeStyle);
316
280
  ctx.effect(() => () => badgeStyle.remove(), "per-message-model: css");
317
281
  ctx.effect(() => ctx.locale.register(NS, { zh, en }), "per-message-model: dictionaries");
318
- ctx.slots.inject("conversation.session.header.actions", () => ctx.slots.register({
319
- name: "conversation.session.header.actions",
320
- id: "dsh-per-message-model-header",
321
- locale: NS,
322
- order: 80
323
- }, SessionModelBadge));
324
- ctx.slots.inject("conversation.chat.assistant-actions", () => ctx.slots.register({
325
- name: "conversation.chat.assistant-actions",
326
- id: "dsh-per-message-model-badge",
327
- locale: NS,
328
- order: 40
329
- }, PerMessageModelBadge));
330
282
  ctx.slots.inject("conversation.session.header.actions", () => ctx.slots.register({
331
283
  name: "conversation.session.header.actions",
332
284
  id: "dsh-per-message-model-switch",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-per-message-model",
3
- "version": "0.1.5",
3
+ "version": "0.1.7",
4
4
  "description": "在 DSH 主对话每条 assistant 回复旁展示该条回复实际使用的 provider / model 徽章(可选附带 reasoning effort)",
5
5
  "type": "module",
6
6
  "main": "./lib/index.js",