dsh-custom-mode 1.7.1 → 1.9.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 +258 -38
  2. package/locales.mjs +32 -0
  3. package/package.json +1 -1
package/client.js CHANGED
@@ -98,6 +98,22 @@ try {
98
98
  "api.alreadyLast": "「{name}」已经在最后面。",
99
99
  "api.badVariableName": "变量引用的写法不合法:{variable} 里的名字只能用小写字母、数字、下划线,且以字母开头。要写字面量花括号,请用单个左花括号,或不闭合的双左花括号。",
100
100
  "api.unknownVariable": "{variable} 不是已注册的变量,渲染会报错并让本模式每个请求都失败。可用:{known}。",
101
+ "assistant.short": "每个助手是一个独立模式:自己的系统提示词、基础模式与插件开关。",
102
+ "name.short": "改名只影响显示,内部标识与已有会话不受影响。",
103
+ "mode.short": "底子决定「行集合」与工具能力;persona 行始终由本模式替换。",
104
+ "rows.short": "逐行控制挂载哪些插件;没拨过的行保持官方默认。",
105
+ "prompt.short": "这段文本就是本模式的系统提示词,保存后下一步生效。",
106
+ "aria.expandHint": "展开完整说明",
107
+ "aria.collapseHint": "收起完整说明",
108
+ "aria.expand": "展开详情",
109
+ "aria.collapse": "收起详情",
110
+ "detail.id": "行 id",
111
+ "detail.note": "说明",
112
+ "detail.state": "开关状态",
113
+ "detail.explicitOn": "已手动启用",
114
+ "detail.explicitOff": "已手动停用",
115
+ "detail.untouched": "未改动(跟随官方默认)",
116
+ "detail.platform": "平台条件",
101
117
  "api.saved": "已保存({name},基础模式 {mode})。新建会话即生效,当前会话保持原配置。",
102
118
  "api.created": "已创建「{name}」。现在可以为它写系统提示词。",
103
119
  "api.duplicated": "已复制自「{from}」。两份从此各改各的。",
@@ -272,6 +288,22 @@ try {
272
288
  "api.alreadyLast": "「{name}」 is already last.",
273
289
  "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
290
  "api.unknownVariable": "{variable} is not a registered variable — rendering would fail every request in this mode. Available: {known}.",
291
+ "assistant.short": "Each assistant is its own mode: its own system prompt, base mode and plugin switches.",
292
+ "name.short": "Renaming only changes what is displayed — not the internal id or existing sessions.",
293
+ "mode.short": "The base decides the row set and tool abilities; the persona row is always replaced by this mode.",
294
+ "rows.short": "Control which plugins this mode mounts, row by row; untouched rows keep the shipped default.",
295
+ "prompt.short": "Saving this text makes it the system prompt of this mode, and it takes effect on the next step.",
296
+ "aria.expandHint": "Show the full explanation",
297
+ "aria.collapseHint": "Hide the full explanation",
298
+ "aria.expand": "Show details",
299
+ "aria.collapse": "Hide details",
300
+ "detail.id": "Row id",
301
+ "detail.note": "Note",
302
+ "detail.state": "Switch",
303
+ "detail.explicitOn": "Set to on by you",
304
+ "detail.explicitOff": "Set to off by you",
305
+ "detail.untouched": "Untouched (follows the shipped default)",
306
+ "detail.platform": "Platform condition",
275
307
  "api.saved": "Saved ({name}, base mode {mode}). A new session picks it up; the current one keeps its configuration.",
276
308
  "api.created": "Created 「{name}」. You can write its system prompt now.",
277
309
  "api.duplicated": "Copied from 「{from}」. The two are independent from now on.",
@@ -560,6 +592,14 @@ try {
560
592
  ".cpfe{--g:8px;display:flex;flex-direction:column;gap:24px;width:100%;max-width:900px;box-sizing:border-box;padding-bottom:16px}",
561
593
  ".cpfe-h{margin:0 0 4px;font-size:15px;line-height:22px;color:var(--dsw-alias-label-primary)}",
562
594
  ".cpfe-sub{margin:0 0 12px;font-size:13px;line-height:20px;color:var(--dsw-alias-label-secondary)}",
595
+ // 一行提示 + 详情下拉(与插件行同一套语言)
596
+ ".cpfe-hint{display:flex;flex-wrap:wrap;align-items:center;gap:6px;margin:0 0 10px}",
597
+ ".cpfe-hint-line{flex:1;min-width:0;font-size:12px;line-height:17px;color:var(--dsw-alias-label-secondary);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}",
598
+ ".cpfe-hint-toggle{flex:0 0 auto;width:20px;height:20px;padding:0;border:0;border-radius:6px;background:transparent;color:var(--dsw-alias-label-secondary);cursor:pointer;font-size:11px;line-height:1}",
599
+ ".cpfe-hint-toggle:hover{background:var(--dsw-alias-bg-layer-2)}",
600
+ ".cpfe-hint-detail{flex:1 0 100%;margin:0;font-size:12px;line-height:18px;color:var(--dsw-alias-label-secondary)}",
601
+ // 描述:多行、自适应高度(没有多行输入组件,所以用 textarea + 同一批语义变量)
602
+ ".cpfe-desc{box-sizing:border-box;min-height:56px;max-height:200px;resize:vertical;padding:8px 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:inherit;font-size:13px;line-height:20px;margin-bottom:8px}",
563
603
  ".cpfe-note{display:block;font-size:11px;line-height:16px;color:var(--dsw-alias-label-secondary)}",
564
604
  ".cpfe-mono{display:block;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11px;line-height:16px;color:var(--dsw-alias-label-secondary);overflow-wrap:anywhere}",
565
605
  ".cpfe-pills{display:flex;flex-wrap:wrap;gap:6px;align-items:center}",
@@ -567,14 +607,25 @@ try {
567
607
  ".cpfe-actions{display:flex;flex-wrap:wrap;gap:var(--g);align-items:center;margin-top:4px}",
568
608
  ".cpfe-newrow{display:flex;gap:var(--g);align-items:center;flex-wrap:wrap;margin-top:10px}",
569
609
  ".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}",
610
+ ".cpfe-row-head{font-size:13px;line-height:18px;color:var(--dsw-alias-label-primary)}",
611
+ ".cpfe-row-switch{flex:0 0 auto}",
612
+ ".cpfe-row-line{display:flex;align-items:center;gap:8px;flex-wrap:wrap;min-width:0}",
613
+ ".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}",
614
+ ".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}",
615
+ ".cpfe-row-toggle:hover{background:var(--dsw-alias-bg-layer-2)}",
616
+ ".cpfe-row-open{border-color:var(--dsw-alias-border-l2)}",
617
+ ".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)}",
618
+ ".cpfe-detail-line{display:flex;gap:8px;min-width:0}",
619
+ ".cpfe-detail-key{flex:0 0 62px;color:var(--dsw-alias-label-secondary)}",
620
+ ".cpfe-detail-value{min-width:0;overflow-wrap:anywhere}",
572
621
  ".cpfe-sec-head{display:flex;align-items:center;justify-content:space-between;gap:var(--g);flex-wrap:wrap}",
573
622
  ".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)}",
623
+ // 单列、行高统一(对齐官方插件页的形态);之前的自适应多列网格会让行高参差不齐。
624
+ ".cpfe-rows{display:flex;flex-direction:column;gap:6px;margin-top:4px}",
625
+ ".cpfe-line{display:flex;flex-direction:column;gap:6px;min-width:0}",
626
+ ".cpfe-group{display:flex;flex-direction:column;gap:6px;margin-top:8px;min-width:0}",
627
+ ".cpfe-kids{display:flex;flex-direction:column;gap:6px;margin-left:14px;padding-left:14px;border-left:.5px solid var(--dsw-alias-border-l1)}",
628
+ ".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
629
  ".cpfe-row-meta{display:flex;flex-direction:column;gap:3px;min-width:0;flex:1}",
579
630
  ".cpfe-row-badges{display:flex;align-items:center;gap:6px;flex-wrap:wrap}",
580
631
  ".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 +755,52 @@ try {
704
755
  return String(template).replace("{name}", name)
705
756
  }
706
757
 
707
- /** Render one row's switch, recursing into a group's children. */
758
+ /**
759
+ * A section's one-line hint, with the full explanation behind a disclosure.
760
+ *
761
+ * The sections used to open with a paragraph each. On a real screen that is a wall of text above the
762
+ * controls the user came for, and the same "compact line + open on demand" language as the row list keeps
763
+ * the page scannable without dropping a word of the explanation.
764
+ */
765
+ function SectionHint(props) {
766
+ const { id, hint, detail, expanded, onToggleExpand, t } = props
767
+ const open = expanded[id] === true
768
+ const collapsible = typeof detail === "string" && detail !== "" && detail !== hint
769
+ return react.createElement(
770
+ "div",
771
+ { className: "cpfe-hint" },
772
+ react.createElement("span", { className: "cpfe-hint-line", title: hint }, hint),
773
+ collapsible === false
774
+ ? null
775
+ : react.createElement(
776
+ "button",
777
+ {
778
+ type: "button",
779
+ className: "cpfe-hint-toggle",
780
+ "aria-expanded": open,
781
+ "aria-label": t(open ? "aria.collapse" : "aria.expandHint"),
782
+ title: t(open ? "aria.collapse" : "aria.expandHint"),
783
+ onClick: () => onToggleExpand(id),
784
+ },
785
+ open ? "▾" : "▸",
786
+ ),
787
+ open === true && collapsible === true
788
+ ? react.createElement("p", { className: "cpfe-hint-detail" }, detail)
789
+ : null,
790
+ )
791
+ }
792
+
793
+ /**
794
+ * One row of the plugin switch list.
795
+ *
796
+ * Laid out like the shell's own plugin page: **one compact line per row** — title, status tags, a
797
+ * one-line truncated description and the switch on the right — with everything else (the row id, the
798
+ * full note, the tri-state detail, the platform condition) behind a per-row disclosure. Measured before
799
+ * this change: 33 rows at 274px wide and 84–117px tall with a bare `tool-bash` line each, i.e. uneven
800
+ * heights and a lot of vertical noise for information most users never read.
801
+ */
708
802
  function RowList(props) {
709
- const { rows, overrides, onToggle, depth, t } = props
803
+ const { rows, overrides, onToggle, expanded, onToggleExpand, depth, t } = props
710
804
  return react.createElement(
711
805
  "div",
712
806
  { className: depth === 0 ? "cpfe-rows" : "cpfe-kids" },
@@ -716,49 +810,109 @@ try {
716
810
  const effective = overrides[row.id] !== undefined ? overrides[row.id] : !row.disabled
717
811
  const changed = overrides[row.id] !== undefined
718
812
  const rowTitle = t("row." + row.id + ".label", row.label)
813
+ const note = row.note === null || row.note === undefined ? null : t("row." + row.id + ".note", row.note)
814
+ const open = expanded !== undefined && expanded[row.id] === true
719
815
  return react.createElement(
720
816
  "div",
721
- { key: row.id, className: row.children.length > 0 ? "cpfe-group" : undefined },
817
+ { key: row.id, className: row.children.length > 0 ? "cpfe-group" : "cpfe-line" },
722
818
  react.createElement(
723
819
  "div",
724
- { className: "cpfe-row" },
820
+ { className: "cpfe-row" + (open ? " cpfe-row-open" : "") },
821
+ react.createElement(
822
+ "div",
823
+ { className: "cpfe-row-meta" },
824
+ react.createElement(
825
+ "div",
826
+ { className: "cpfe-row-line" },
827
+ react.createElement("span", { className: "cpfe-row-head" }, rowTitle),
828
+ react.createElement(
829
+ "div",
830
+ { className: "cpfe-row-badges" },
831
+ react.createElement(
832
+ A.Tag,
833
+ { tone: effective ? "success" : "neutral" },
834
+ effective ? t("status.enabled") : t("status.disabled"),
835
+ ),
836
+ row.essential ? react.createElement(A.Tag, { tone: "warning" }, t("tag.essential")) : null,
837
+ row.disabledExpression !== null && row.disabledExpression !== undefined
838
+ ? react.createElement(A.Tag, { tone: "outline" }, t("tag.followPlatform"))
839
+ : null,
840
+ changed ? react.createElement(A.Tag, { tone: "info" }, t("status.changed")) : null,
841
+ ),
842
+ ),
843
+ // 折叠时只留一行说明(超出截断,悬停给全文)。**没有说明就留空**:裸露的行 id
844
+ // 是开发者信息,已经在「详情」里 —— 之前它作为副标题占了每行一行。
845
+ react.createElement("span", { className: "cpfe-row-note", title: note ?? "" }, note ?? ""),
846
+ ),
847
+ react.createElement(
848
+ "button",
849
+ {
850
+ type: "button",
851
+ className: "cpfe-row-toggle",
852
+ "aria-expanded": open,
853
+ "aria-label": t(open ? "aria.collapse" : "aria.expand"),
854
+ onClick: () => onToggleExpand(row.id),
855
+ },
856
+ open ? "▾" : "▸",
857
+ ),
725
858
  react.createElement(A.Switch, {
726
859
  checked: effective,
727
860
  onChange: (next) => onToggle(row.id, next),
728
861
  // `Switch` renders NO text: its `label` is the accessible name only, so the
729
- // visible title is drawn below (an unlabelled toggle is unusable).
862
+ // visible title is drawn next to it (an unlabelled toggle is unusable).
730
863
  label: rowTitle,
731
864
  className: "cpfe-row-switch",
732
865
  }),
733
- react.createElement(
734
- "div",
735
- { className: "cpfe-row-meta" },
736
- react.createElement("span", { className: "cpfe-row-head" }, rowTitle),
737
- react.createElement(
866
+ ),
867
+ open
868
+ ? react.createElement(
738
869
  "div",
739
- { className: "cpfe-row-badges" },
870
+ { className: "cpfe-row-detail" },
740
871
  react.createElement(
741
- A.Tag,
742
- { tone: effective ? "success" : "neutral" },
743
- effective ? t("status.enabled") : t("status.disabled"),
872
+ "div",
873
+ { className: "cpfe-detail-line" },
874
+ react.createElement("span", { className: "cpfe-detail-key" }, t("detail.id")),
875
+ react.createElement("span", { className: "cpfe-mono" }, row.id),
876
+ ),
877
+ note === null
878
+ ? null
879
+ : react.createElement(
880
+ "div",
881
+ { className: "cpfe-detail-line" },
882
+ react.createElement("span", { className: "cpfe-detail-key" }, t("detail.note")),
883
+ react.createElement("span", { className: "cpfe-detail-value" }, note),
884
+ ),
885
+ react.createElement(
886
+ "div",
887
+ { className: "cpfe-detail-line" },
888
+ react.createElement("span", { className: "cpfe-detail-key" }, t("detail.state")),
889
+ react.createElement(
890
+ "span",
891
+ { className: "cpfe-detail-value" },
892
+ changed
893
+ ? effective
894
+ ? t("detail.explicitOn")
895
+ : t("detail.explicitOff")
896
+ : t("detail.untouched"),
897
+ ),
744
898
  ),
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"))
899
+ row.disabledExpression !== null && row.disabledExpression !== undefined
900
+ ? react.createElement(
901
+ "div",
902
+ { className: "cpfe-detail-line" },
903
+ react.createElement("span", { className: "cpfe-detail-key" }, t("detail.platform")),
904
+ react.createElement("span", { className: "cpfe-mono" }, String(row.disabledExpression)),
905
+ )
748
906
  : 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
- ),
907
+ )
908
+ : null,
757
909
  row.children.length > 0
758
910
  ? react.createElement(RowList, {
759
911
  rows: row.children,
760
912
  overrides: overrides,
761
913
  onToggle: onToggle,
914
+ expanded: expanded,
915
+ onToggleExpand: onToggleExpand,
762
916
  depth: depth + 1,
763
917
  t: t,
764
918
  })
@@ -818,11 +972,36 @@ try {
818
972
  const [newName, setNewName] = react.useState("")
819
973
  const [deleteOpen, setDeleteOpen] = react.useState(false)
820
974
  const [acknowledged, setAcknowledged] = react.useState(false)
975
+ /**
976
+ * Which rows have their detail open, by row id.
977
+ *
978
+ * Per-row and local to the section: the list is long (33 rows measured) and the interesting detail
979
+ * differs per user, so nothing is expanded by default and nothing is persisted.
980
+ */
981
+ const [expandedRows, setExpandedRows] = react.useState({})
982
+ const toggleRowExpanded = (id) =>
983
+ setExpandedRows((previous) => {
984
+ const next = { ...previous }
985
+ if (next[id] === true) delete next[id]
986
+ else next[id] = true
987
+ return next
988
+ })
821
989
  /** Hidden `<input type="file">` behind the 「导入」 button. */
822
990
  const fileInput = react.useRef(null)
823
991
 
824
992
  const entry = selected === "" ? undefined : entries[selected]
825
993
  const draft = entry === undefined ? null : entry.value
994
+
995
+ /** 描述框:随内容长高,避免双语描述被单行截断。draft 在未选中助手时是 null,依赖项要先取出来。 */
996
+ const descriptionRef = react.useRef(null)
997
+ const draftDescription = draft === null ? "" : draft.description
998
+ react.useEffect(() => {
999
+ const el = descriptionRef.current
1000
+ if (el === null || el === undefined) return
1001
+ el.style.height = "auto"
1002
+ el.style.height = String(Math.min(el.scrollHeight, 200)) + "px"
1003
+ }, [draftDescription, selected])
1004
+
826
1005
  const payload = entry === undefined ? null : entry.payload
827
1006
  const editorReady = draft !== null && payload !== null
828
1007
  const dirty = editorReady && !sameDraft(draft, entry.saved)
@@ -1216,7 +1395,14 @@ try {
1216
1395
  "section",
1217
1396
  null,
1218
1397
  react.createElement("h2", { className: "cpfe-h" }, t("assistant.heading")),
1219
- react.createElement("p", { className: "cpfe-sub" }, t("assistant.hint")),
1398
+ react.createElement(SectionHint, {
1399
+ id: "hint:assistant",
1400
+ hint: t("assistant.short", t("assistant.hint")),
1401
+ detail: t("assistant.hint"),
1402
+ expanded: expandedRows,
1403
+ onToggleExpand: toggleRowExpanded,
1404
+ t: t,
1405
+ }),
1220
1406
  list === null
1221
1407
  ? react.createElement("p", { className: "cpfe-sub" }, t("assistant.loadingList"))
1222
1408
  : assistants.length === 0
@@ -1284,7 +1470,14 @@ try {
1284
1470
  "section",
1285
1471
  { key: "name" },
1286
1472
  react.createElement("h2", { className: "cpfe-h" }, t("name.heading")),
1287
- react.createElement("p", { className: "cpfe-sub" }, t("name.hint")),
1473
+ react.createElement(SectionHint, {
1474
+ id: "hint:name",
1475
+ hint: t("name.short", t("name.hint")),
1476
+ detail: t("name.hint"),
1477
+ expanded: expandedRows,
1478
+ onToggleExpand: toggleRowExpanded,
1479
+ t: t,
1480
+ }),
1288
1481
  react.createElement(A.Input, {
1289
1482
  className: "cpfe-field",
1290
1483
  value: draft.name,
@@ -1292,9 +1485,13 @@ try {
1292
1485
  "aria-label": t("name.heading"),
1293
1486
  onChange: (event) => update({ name: event.target.value }),
1294
1487
  }),
1295
- react.createElement(A.Input, {
1296
- className: "cpfe-field",
1488
+ // 描述用 textarea 而不是单行 Input:shell 的组件里没有多行输入,而双语描述在单行框里
1489
+ // 会被截断(实测界面上只看到 "… / Ful")。高度随内容自适应,样式沿用同一批语义变量。
1490
+ react.createElement("textarea", {
1491
+ ref: descriptionRef,
1492
+ className: "cpfe-field cpfe-desc",
1297
1493
  value: draft.description,
1494
+ rows: 2,
1298
1495
  placeholder: t("name.descriptionPlaceholder"),
1299
1496
  "aria-label": t("name.descriptionPlaceholder"),
1300
1497
  onChange: (event) => update({ description: event.target.value }),
@@ -1353,7 +1550,14 @@ try {
1353
1550
  "section",
1354
1551
  { key: "mode" },
1355
1552
  react.createElement("h2", { className: "cpfe-h" }, t("mode.heading")),
1356
- react.createElement("p", { className: "cpfe-sub" }, t("mode.hint")),
1553
+ react.createElement(SectionHint, {
1554
+ id: "hint:mode",
1555
+ hint: t("mode.short", t("mode.hint")),
1556
+ detail: t("mode.hint"),
1557
+ expanded: expandedRows,
1558
+ onToggleExpand: toggleRowExpanded,
1559
+ t: t,
1560
+ }),
1357
1561
  react.createElement(
1358
1562
  "div",
1359
1563
  { className: "cpfe-pills" },
@@ -1375,8 +1579,17 @@ try {
1375
1579
  "section",
1376
1580
  { key: "rows" },
1377
1581
  react.createElement("h2", { className: "cpfe-h" }, t("rows.heading")),
1378
- react.createElement("p", { className: "cpfe-sub" }, t("rows.hint")),
1582
+ react.createElement(SectionHint, {
1583
+ id: "hint:rows",
1584
+ hint: t("rows.short", t("rows.hint")),
1585
+ detail: t("rows.hint"),
1586
+ expanded: expandedRows,
1587
+ onToggleExpand: toggleRowExpanded,
1588
+ t: t,
1589
+ }),
1379
1590
  react.createElement(RowList, {
1591
+ expanded: expandedRows,
1592
+ onToggleExpand: toggleRowExpanded,
1380
1593
  rows: payload.rows,
1381
1594
  overrides: draft.overrides,
1382
1595
  onToggle: (id, next) => update({ overrides: { ...draft.overrides, [id]: next } }),
@@ -1447,7 +1660,14 @@ try {
1447
1660
  }),
1448
1661
  ),
1449
1662
  ),
1450
- react.createElement("p", { className: "cpfe-sub" }, t("prompt.hint")),
1663
+ react.createElement(SectionHint, {
1664
+ id: "hint:prompt",
1665
+ hint: t("prompt.short", t("prompt.hint")),
1666
+ detail: t("prompt.hint"),
1667
+ expanded: expandedRows,
1668
+ onToggleExpand: toggleRowExpanded,
1669
+ t: t,
1670
+ }),
1451
1671
  react.createElement("textarea", {
1452
1672
  className: "cpfe-editor",
1453
1673
  value: draft.prompt,
package/locales.mjs CHANGED
@@ -57,6 +57,22 @@ export const zh = {
57
57
  'api.alreadyLast': '「{name}」已经在最后面。',
58
58
  'api.badVariableName': '变量引用的写法不合法:{variable} 里的名字只能用小写字母、数字、下划线,且以字母开头。要写字面量花括号,请用单个左花括号,或不闭合的双左花括号。',
59
59
  'api.unknownVariable': '{variable} 不是已注册的变量,渲染会报错并让本模式每个请求都失败。可用:{known}。',
60
+ 'assistant.short': '每个助手是一个独立模式:自己的系统提示词、基础模式与插件开关。',
61
+ 'name.short': '改名只影响显示,内部标识与已有会话不受影响。',
62
+ 'mode.short': '底子决定「行集合」与工具能力;persona 行始终由本模式替换。',
63
+ 'rows.short': '逐行控制挂载哪些插件;没拨过的行保持官方默认。',
64
+ 'prompt.short': '这段文本就是本模式的系统提示词,保存后下一步生效。',
65
+ 'aria.expandHint': '展开完整说明',
66
+ 'aria.collapseHint': '收起完整说明',
67
+ 'aria.expand': '展开详情',
68
+ 'aria.collapse': '收起详情',
69
+ 'detail.id': '行 id',
70
+ 'detail.note': '说明',
71
+ 'detail.state': '开关状态',
72
+ 'detail.explicitOn': '已手动启用',
73
+ 'detail.explicitOff': '已手动停用',
74
+ 'detail.untouched': '未改动(跟随官方默认)',
75
+ 'detail.platform': '平台条件',
60
76
  'api.saved': '已保存({name},基础模式 {mode})。新建会话即生效,当前会话保持原配置。',
61
77
  'api.created': '已创建「{name}」。现在可以为它写系统提示词。',
62
78
  'api.duplicated': '已复制自「{from}」。两份从此各改各的。',
@@ -247,6 +263,22 @@ export const en = {
247
263
  'api.alreadyLast': '「{name}」 is already last.',
248
264
  '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
265
  'api.unknownVariable': '{variable} is not a registered variable — rendering would fail every request in this mode. Available: {known}.',
266
+ 'assistant.short': 'Each assistant is its own mode: its own system prompt, base mode and plugin switches.',
267
+ 'name.short': 'Renaming only changes what is displayed — not the internal id or existing sessions.',
268
+ 'mode.short': 'The base decides the row set and tool abilities; the persona row is always replaced by this mode.',
269
+ 'rows.short': 'Control which plugins this mode mounts, row by row; untouched rows keep the shipped default.',
270
+ 'prompt.short': 'Saving this text makes it the system prompt of this mode, and it takes effect on the next step.',
271
+ 'aria.expandHint': 'Show the full explanation',
272
+ 'aria.collapseHint': 'Hide the full explanation',
273
+ 'aria.expand': 'Show details',
274
+ 'aria.collapse': 'Hide details',
275
+ 'detail.id': 'Row id',
276
+ 'detail.note': 'Note',
277
+ 'detail.state': 'Switch',
278
+ 'detail.explicitOn': 'Set to on by you',
279
+ 'detail.explicitOff': 'Set to off by you',
280
+ 'detail.untouched': 'Untouched (follows the shipped default)',
281
+ 'detail.platform': 'Platform condition',
250
282
  'api.saved': 'Saved ({name}, base mode {mode}). A new session picks it up; the current one keeps its configuration.',
251
283
  'api.created': 'Created 「{name}」. You can write its system prompt now.',
252
284
  '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.9.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",