dsh-custom-mode 1.7.1 → 1.8.0

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 (3) hide show
  1. package/client.js +144 -31
  2. package/locales.mjs +18 -0
  3. package/package.json +1 -1
package/client.js CHANGED
@@ -98,6 +98,15 @@ try {
98
98
  "api.alreadyLast": "「{name}」已经在最后面。",
99
99
  "api.badVariableName": "变量引用的写法不合法:{variable} 里的名字只能用小写字母、数字、下划线,且以字母开头。要写字面量花括号,请用单个左花括号,或不闭合的双左花括号。",
100
100
  "api.unknownVariable": "{variable} 不是已注册的变量,渲染会报错并让本模式每个请求都失败。可用:{known}。",
101
+ "aria.expand": "展开详情",
102
+ "aria.collapse": "收起详情",
103
+ "detail.id": "行 id",
104
+ "detail.note": "说明",
105
+ "detail.state": "开关状态",
106
+ "detail.explicitOn": "已手动启用",
107
+ "detail.explicitOff": "已手动停用",
108
+ "detail.untouched": "未改动(跟随官方默认)",
109
+ "detail.platform": "平台条件",
101
110
  "api.saved": "已保存({name},基础模式 {mode})。新建会话即生效,当前会话保持原配置。",
102
111
  "api.created": "已创建「{name}」。现在可以为它写系统提示词。",
103
112
  "api.duplicated": "已复制自「{from}」。两份从此各改各的。",
@@ -272,6 +281,15 @@ try {
272
281
  "api.alreadyLast": "「{name}」 is already last.",
273
282
  "api.badVariableName": "{variable} is not a valid variable reference: names may use lower-case letters, digits and underscores, and must start with a letter. For a literal brace, use a single opening brace or an unclosed double brace.",
274
283
  "api.unknownVariable": "{variable} is not a registered variable — rendering would fail every request in this mode. Available: {known}.",
284
+ "aria.expand": "Show details",
285
+ "aria.collapse": "Hide details",
286
+ "detail.id": "Row id",
287
+ "detail.note": "Note",
288
+ "detail.state": "Switch",
289
+ "detail.explicitOn": "Set to on by you",
290
+ "detail.explicitOff": "Set to off by you",
291
+ "detail.untouched": "Untouched (follows the shipped default)",
292
+ "detail.platform": "Platform condition",
275
293
  "api.saved": "Saved ({name}, base mode {mode}). A new session picks it up; the current one keeps its configuration.",
276
294
  "api.created": "Created 「{name}」. You can write its system prompt now.",
277
295
  "api.duplicated": "Copied from 「{from}」. The two are independent from now on.",
@@ -567,14 +585,25 @@ try {
567
585
  ".cpfe-actions{display:flex;flex-wrap:wrap;gap:var(--g);align-items:center;margin-top:4px}",
568
586
  ".cpfe-newrow{display:flex;gap:var(--g);align-items:center;flex-wrap:wrap;margin-top:10px}",
569
587
  ".cpfe-field{display:flex;width:100%;margin-bottom:8px}",
570
- ".cpfe-row-head{display:block;font-size:13px;line-height:19px;color:var(--dsw-alias-label-primary)}",
571
- ".cpfe-row-switch{flex:0 0 auto;margin-top:2px}",
588
+ ".cpfe-row-head{font-size:13px;line-height:18px;color:var(--dsw-alias-label-primary)}",
589
+ ".cpfe-row-switch{flex:0 0 auto}",
590
+ ".cpfe-row-line{display:flex;align-items:center;gap:8px;flex-wrap:wrap;min-width:0}",
591
+ ".cpfe-row-note{min-height:16px;font-size:12px;line-height:16px;color:var(--dsw-alias-label-secondary);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}",
592
+ ".cpfe-row-toggle{flex:0 0 auto;width:22px;height:22px;padding:0;border:0;border-radius:6px;background:transparent;color:var(--dsw-alias-label-secondary);cursor:pointer;font-size:11px;line-height:1}",
593
+ ".cpfe-row-toggle:hover{background:var(--dsw-alias-bg-layer-2)}",
594
+ ".cpfe-row-open{border-color:var(--dsw-alias-border-l2)}",
595
+ ".cpfe-row-detail{display:flex;flex-direction:column;gap:4px;margin:0 0 2px 14px;padding:8px 12px;border-left:.5px solid var(--dsw-alias-border-l1);font-size:12px;line-height:17px;color:var(--dsw-alias-label-secondary)}",
596
+ ".cpfe-detail-line{display:flex;gap:8px;min-width:0}",
597
+ ".cpfe-detail-key{flex:0 0 62px;color:var(--dsw-alias-label-secondary)}",
598
+ ".cpfe-detail-value{min-width:0;overflow-wrap:anywhere}",
572
599
  ".cpfe-sec-head{display:flex;align-items:center;justify-content:space-between;gap:var(--g);flex-wrap:wrap}",
573
600
  ".cpfe-newinput{flex:1;min-width:200px}",
574
- ".cpfe-rows{display:grid;grid-template-columns:repeat(auto-fill,minmax(260px,1fr));gap:var(--g);align-items:stretch;margin-top:4px}",
575
- ".cpfe-group{grid-column:1/-1;display:flex;flex-direction:column;gap:var(--g)}",
576
- ".cpfe-kids{display:grid;grid-template-columns:repeat(auto-fill,minmax(240px,1fr));gap:var(--g);padding-left:16px;border-left:.5px solid var(--dsw-alias-border-l1)}",
577
- ".cpfe-row{display:flex;gap:12px;align-items:flex-start;box-sizing:border-box;padding:10px 12px;border-radius:10px;border:.5px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-layer-1)}",
601
+ // 单列、行高统一(对齐官方插件页的形态);之前的自适应多列网格会让行高参差不齐。
602
+ ".cpfe-rows{display:flex;flex-direction:column;gap:6px;margin-top:4px}",
603
+ ".cpfe-line{display:flex;flex-direction:column;gap:6px;min-width:0}",
604
+ ".cpfe-group{display:flex;flex-direction:column;gap:6px;margin-top:8px;min-width:0}",
605
+ ".cpfe-kids{display:flex;flex-direction:column;gap:6px;margin-left:14px;padding-left:14px;border-left:.5px solid var(--dsw-alias-border-l1)}",
606
+ ".cpfe-row{display:flex;gap:10px;align-items:center;box-sizing:border-box;min-height:52px;padding:8px 12px;border-radius:10px;border:.5px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-layer-1)}",
578
607
  ".cpfe-row-meta{display:flex;flex-direction:column;gap:3px;min-width:0;flex:1}",
579
608
  ".cpfe-row-badges{display:flex;align-items:center;gap:6px;flex-wrap:wrap}",
580
609
  ".cpfe-editor{box-sizing:border-box;width:100%;min-height:240px;resize:vertical;padding:12px;border-radius:10px;border:.5px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-base);color:var(--dsw-alias-label-primary);font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:13px;line-height:20px}",
@@ -704,9 +733,17 @@ try {
704
733
  return String(template).replace("{name}", name)
705
734
  }
706
735
 
707
- /** Render one row's switch, recursing into a group's children. */
736
+ /**
737
+ * One row of the plugin switch list.
738
+ *
739
+ * Laid out like the shell's own plugin page: **one compact line per row** — title, status tags, a
740
+ * one-line truncated description and the switch on the right — with everything else (the row id, the
741
+ * full note, the tri-state detail, the platform condition) behind a per-row disclosure. Measured before
742
+ * this change: 33 rows at 274px wide and 84–117px tall with a bare `tool-bash` line each, i.e. uneven
743
+ * heights and a lot of vertical noise for information most users never read.
744
+ */
708
745
  function RowList(props) {
709
- const { rows, overrides, onToggle, depth, t } = props
746
+ const { rows, overrides, onToggle, expanded, onToggleExpand, depth, t } = props
710
747
  return react.createElement(
711
748
  "div",
712
749
  { className: depth === 0 ? "cpfe-rows" : "cpfe-kids" },
@@ -716,49 +753,109 @@ try {
716
753
  const effective = overrides[row.id] !== undefined ? overrides[row.id] : !row.disabled
717
754
  const changed = overrides[row.id] !== undefined
718
755
  const rowTitle = t("row." + row.id + ".label", row.label)
756
+ const note = row.note === null || row.note === undefined ? null : t("row." + row.id + ".note", row.note)
757
+ const open = expanded !== undefined && expanded[row.id] === true
719
758
  return react.createElement(
720
759
  "div",
721
- { key: row.id, className: row.children.length > 0 ? "cpfe-group" : undefined },
760
+ { key: row.id, className: row.children.length > 0 ? "cpfe-group" : "cpfe-line" },
722
761
  react.createElement(
723
762
  "div",
724
- { className: "cpfe-row" },
763
+ { className: "cpfe-row" + (open ? " cpfe-row-open" : "") },
764
+ react.createElement(
765
+ "div",
766
+ { className: "cpfe-row-meta" },
767
+ react.createElement(
768
+ "div",
769
+ { className: "cpfe-row-line" },
770
+ react.createElement("span", { className: "cpfe-row-head" }, rowTitle),
771
+ react.createElement(
772
+ "div",
773
+ { className: "cpfe-row-badges" },
774
+ react.createElement(
775
+ A.Tag,
776
+ { tone: effective ? "success" : "neutral" },
777
+ effective ? t("status.enabled") : t("status.disabled"),
778
+ ),
779
+ row.essential ? react.createElement(A.Tag, { tone: "warning" }, t("tag.essential")) : null,
780
+ row.disabledExpression !== null && row.disabledExpression !== undefined
781
+ ? react.createElement(A.Tag, { tone: "outline" }, t("tag.followPlatform"))
782
+ : null,
783
+ changed ? react.createElement(A.Tag, { tone: "info" }, t("status.changed")) : null,
784
+ ),
785
+ ),
786
+ // 折叠时只留一行说明(超出截断,悬停给全文)。**没有说明就留空**:裸露的行 id
787
+ // 是开发者信息,已经在「详情」里 —— 之前它作为副标题占了每行一行。
788
+ react.createElement("span", { className: "cpfe-row-note", title: note ?? "" }, note ?? ""),
789
+ ),
790
+ react.createElement(
791
+ "button",
792
+ {
793
+ type: "button",
794
+ className: "cpfe-row-toggle",
795
+ "aria-expanded": open,
796
+ "aria-label": t(open ? "aria.collapse" : "aria.expand"),
797
+ onClick: () => onToggleExpand(row.id),
798
+ },
799
+ open ? "▾" : "▸",
800
+ ),
725
801
  react.createElement(A.Switch, {
726
802
  checked: effective,
727
803
  onChange: (next) => onToggle(row.id, next),
728
804
  // `Switch` renders NO text: its `label` is the accessible name only, so the
729
- // visible title is drawn below (an unlabelled toggle is unusable).
805
+ // visible title is drawn next to it (an unlabelled toggle is unusable).
730
806
  label: rowTitle,
731
807
  className: "cpfe-row-switch",
732
808
  }),
733
- react.createElement(
734
- "div",
735
- { className: "cpfe-row-meta" },
736
- react.createElement("span", { className: "cpfe-row-head" }, rowTitle),
737
- react.createElement(
809
+ ),
810
+ open
811
+ ? react.createElement(
738
812
  "div",
739
- { className: "cpfe-row-badges" },
813
+ { className: "cpfe-row-detail" },
740
814
  react.createElement(
741
- A.Tag,
742
- { tone: effective ? "success" : "neutral" },
743
- effective ? t("status.enabled") : t("status.disabled"),
815
+ "div",
816
+ { className: "cpfe-detail-line" },
817
+ react.createElement("span", { className: "cpfe-detail-key" }, t("detail.id")),
818
+ react.createElement("span", { className: "cpfe-mono" }, row.id),
744
819
  ),
745
- row.essential ? react.createElement(A.Tag, { tone: "warning" }, t("tag.essential")) : null,
746
- row.disabledExpression !== null
747
- ? react.createElement(A.Tag, { tone: "outline" }, t("tag.followPlatform"))
820
+ note === null
821
+ ? null
822
+ : react.createElement(
823
+ "div",
824
+ { className: "cpfe-detail-line" },
825
+ react.createElement("span", { className: "cpfe-detail-key" }, t("detail.note")),
826
+ react.createElement("span", { className: "cpfe-detail-value" }, note),
827
+ ),
828
+ react.createElement(
829
+ "div",
830
+ { className: "cpfe-detail-line" },
831
+ react.createElement("span", { className: "cpfe-detail-key" }, t("detail.state")),
832
+ react.createElement(
833
+ "span",
834
+ { className: "cpfe-detail-value" },
835
+ changed
836
+ ? effective
837
+ ? t("detail.explicitOn")
838
+ : t("detail.explicitOff")
839
+ : t("detail.untouched"),
840
+ ),
841
+ ),
842
+ row.disabledExpression !== null && row.disabledExpression !== undefined
843
+ ? react.createElement(
844
+ "div",
845
+ { className: "cpfe-detail-line" },
846
+ react.createElement("span", { className: "cpfe-detail-key" }, t("detail.platform")),
847
+ react.createElement("span", { className: "cpfe-mono" }, String(row.disabledExpression)),
848
+ )
748
849
  : null,
749
- changed ? react.createElement(A.Tag, { tone: "info" }, t("status.changed")) : null,
750
- ),
751
- react.createElement("span", { className: "cpfe-mono" }, row.id),
752
- row.note !== null
753
- ? react.createElement("span", { className: "cpfe-note" }, t("row." + row.id + ".note", row.note))
754
- : null,
755
- ),
756
- ),
850
+ )
851
+ : null,
757
852
  row.children.length > 0
758
853
  ? react.createElement(RowList, {
759
854
  rows: row.children,
760
855
  overrides: overrides,
761
856
  onToggle: onToggle,
857
+ expanded: expanded,
858
+ onToggleExpand: onToggleExpand,
762
859
  depth: depth + 1,
763
860
  t: t,
764
861
  })
@@ -818,6 +915,20 @@ try {
818
915
  const [newName, setNewName] = react.useState("")
819
916
  const [deleteOpen, setDeleteOpen] = react.useState(false)
820
917
  const [acknowledged, setAcknowledged] = react.useState(false)
918
+ /**
919
+ * Which rows have their detail open, by row id.
920
+ *
921
+ * Per-row and local to the section: the list is long (33 rows measured) and the interesting detail
922
+ * differs per user, so nothing is expanded by default and nothing is persisted.
923
+ */
924
+ const [expandedRows, setExpandedRows] = react.useState({})
925
+ const toggleRowExpanded = (id) =>
926
+ setExpandedRows((previous) => {
927
+ const next = { ...previous }
928
+ if (next[id] === true) delete next[id]
929
+ else next[id] = true
930
+ return next
931
+ })
821
932
  /** Hidden `<input type="file">` behind the 「导入」 button. */
822
933
  const fileInput = react.useRef(null)
823
934
 
@@ -1377,6 +1488,8 @@ try {
1377
1488
  react.createElement("h2", { className: "cpfe-h" }, t("rows.heading")),
1378
1489
  react.createElement("p", { className: "cpfe-sub" }, t("rows.hint")),
1379
1490
  react.createElement(RowList, {
1491
+ expanded: expandedRows,
1492
+ onToggleExpand: toggleRowExpanded,
1380
1493
  rows: payload.rows,
1381
1494
  overrides: draft.overrides,
1382
1495
  onToggle: (id, next) => update({ overrides: { ...draft.overrides, [id]: next } }),
package/locales.mjs CHANGED
@@ -57,6 +57,15 @@ export const zh = {
57
57
  'api.alreadyLast': '「{name}」已经在最后面。',
58
58
  'api.badVariableName': '变量引用的写法不合法:{variable} 里的名字只能用小写字母、数字、下划线,且以字母开头。要写字面量花括号,请用单个左花括号,或不闭合的双左花括号。',
59
59
  'api.unknownVariable': '{variable} 不是已注册的变量,渲染会报错并让本模式每个请求都失败。可用:{known}。',
60
+ 'aria.expand': '展开详情',
61
+ 'aria.collapse': '收起详情',
62
+ 'detail.id': '行 id',
63
+ 'detail.note': '说明',
64
+ 'detail.state': '开关状态',
65
+ 'detail.explicitOn': '已手动启用',
66
+ 'detail.explicitOff': '已手动停用',
67
+ 'detail.untouched': '未改动(跟随官方默认)',
68
+ 'detail.platform': '平台条件',
60
69
  'api.saved': '已保存({name},基础模式 {mode})。新建会话即生效,当前会话保持原配置。',
61
70
  'api.created': '已创建「{name}」。现在可以为它写系统提示词。',
62
71
  'api.duplicated': '已复制自「{from}」。两份从此各改各的。',
@@ -247,6 +256,15 @@ export const en = {
247
256
  'api.alreadyLast': '「{name}」 is already last.',
248
257
  'api.badVariableName': '{variable} is not a valid variable reference: names may use lower-case letters, digits and underscores, and must start with a letter. For a literal brace, use a single opening brace or an unclosed double brace.',
249
258
  'api.unknownVariable': '{variable} is not a registered variable — rendering would fail every request in this mode. Available: {known}.',
259
+ 'aria.expand': 'Show details',
260
+ 'aria.collapse': 'Hide details',
261
+ 'detail.id': 'Row id',
262
+ 'detail.note': 'Note',
263
+ 'detail.state': 'Switch',
264
+ 'detail.explicitOn': 'Set to on by you',
265
+ 'detail.explicitOff': 'Set to off by you',
266
+ 'detail.untouched': 'Untouched (follows the shipped default)',
267
+ 'detail.platform': 'Platform condition',
250
268
  'api.saved': 'Saved ({name}, base mode {mode}). A new session picks it up; the current one keeps its configuration.',
251
269
  'api.created': 'Created 「{name}」. You can write its system prompt now.',
252
270
  'api.duplicated': 'Copied from 「{from}」. The two are independent from now on.',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-custom-mode",
3
- "version": "1.7.1",
3
+ "version": "1.8.0",
4
4
  "description": "Custom modes and custom prompts for DeepSeek Harness (dsh): edit a mode's system prompt on the settings page (it takes effect on the next model step), choose its base mode, switch plugins row by row, and keep several assistants side by side.",
5
5
  "license": "MIT",
6
6
  "type": "module",