zcode-pro 0.17.2 → 0.19.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.
package/dist/inject.js CHANGED
@@ -2,13 +2,16 @@
2
2
  // src/inject/core.js
3
3
  var BOOT = typeof window !== "undefined" ? window.__ZCODEPRO__ || {} : {};
4
4
  var HELPER_URL = BOOT.helperUrl || "http://127.0.0.1:47889";
5
- var TOKEN = BOOT.token || "";
5
+ function liveBoot() {
6
+ return typeof window !== "undefined" && window.__ZCODEPRO__ || BOOT;
7
+ }
6
8
  function rpc(path, options = {}) {
7
- return fetch(HELPER_URL + path, {
9
+ const boot = liveBoot();
10
+ return fetch((boot.helperUrl || HELPER_URL) + path, {
8
11
  method: options.method || "GET",
9
12
  headers: {
10
13
  "Content-Type": "application/json",
11
- "X-ZCodePro-Token": TOKEN
14
+ "X-ZCodePro-Token": boot.token || ""
12
15
  },
13
16
  body: options.body ? JSON.stringify(options.body) : void 0
14
17
  }).then((r) => r.json()).catch((err) => ({ ok: false, error: String(err && err.message || err) }));
@@ -32,22 +35,31 @@
32
35
  tabFeatures: "\u529F\u80FD",
33
36
  tabStyles: "\u6837\u5F0F",
34
37
  tabProxy: "\u4EE3\u7406",
38
+ tabBehavior: "\u884C\u4E3A",
35
39
  tabAgents: "\u5168\u5C40\u63D0\u793A\u8BCD",
36
40
  agentsDesc: "\u5199\u5165 ~/.zcode/AGENTS.md\uFF0C\u4F5C\u4E3A\u9ED8\u8BA4\u6307\u4EE4\u7528\u4E8E\u6240\u6709\u9879\u76EE\u7684\u6BCF\u6B21\u4F1A\u8BDD\uFF1B\u4FDD\u5B58\u540E\u4ECE\u65B0\u4F1A\u8BDD\u8D77\u751F\u6548\uFF0C\u9879\u76EE\u5185\u7684 AGENTS.md \u53EF\u8865\u5145\u6216\u8986\u76D6\u3002\u5185\u5BB9\u8D85\u8FC7 100KB \u4F1A\u88AB\u5E94\u7528\u622A\u65AD\u3002",
37
41
  agentsPlaceholder: "\u586B\u5199\u5E0C\u671B\u6240\u6709\u9879\u76EE\u9ED8\u8BA4\u9075\u5FAA\u7684\u6307\u4EE4\uFF1B\u6E05\u7A7A\u5E76\u4FDD\u5B58\u5373\u79FB\u9664\u5168\u5C40\u63D0\u793A\u8BCD",
38
42
  agentsSave: "\u4FDD\u5B58",
39
43
  agentsSaved: "\u5168\u5C40\u63D0\u793A\u8BCD\u5DF2\u4FDD\u5B58",
40
44
  agentsLoadFailed: "\u8BFB\u53D6\u5168\u5C40\u63D0\u793A\u8BCD\u5931\u8D25",
41
- proxyDesc: "\u4E3A ZCode Pro \u672C\u5730\u52A9\u624B\u53D1\u8D77\u7684\u7F51\u7EDC\u8BBF\u95EE\u8BBE\u7F6E HTTP \u4EE3\u7406\uFF1A\u63D2\u4EF6\u5E02\u573A\u7684\u66F4\u65B0\u4E0E\u5B89\u88C5\u3001Headroom \u672C\u4F53\u7684\u68C0\u67E5\u66F4\u65B0\u4E0E\u5347\u7EA7\u90FD\u4F1A\u8D70\u8BE5\u4EE3\u7406\u3002\u4E0D\u5F71\u54CD ZCode \u5E94\u7528\u672C\u8EAB\u4E0E\u6A21\u578B\u8BF7\u6C42\uFF0C\u4FDD\u5B58\u540E\u7ACB\u5373\u751F\u6548\u3002",
45
+ proxyDesc: "\u4E3A ZCode Pro \u672C\u5730\u52A9\u624B\u53D1\u8D77\u7684\u7F51\u7EDC\u8BBF\u95EE\u8BBE\u7F6E HTTP \u4EE3\u7406\uFF1A\u63D2\u4EF6\u5E02\u573A\u7684\u66F4\u65B0\u4E0E\u5B89\u88C5\u3001\u63D2\u4EF6\u4F9D\u8D56\u8F6F\u4EF6\u7684\u5B89\u88C5\u4E0E\u5347\u7EA7\u3001WebDAV \u540C\u6B65\u90FD\u53EF\u8D70\u8BE5\u4EE3\u7406\uFF0C\u7528\u4E0B\u65B9\u5F00\u5173\u9009\u62E9\u751F\u6548\u8303\u56F4\u3002\u4E0D\u5F71\u54CD ZCode \u5E94\u7528\u672C\u8EAB\u4E0E\u6A21\u578B\u8BF7\u6C42\uFF0C\u4FDD\u5B58\u540E\u7ACB\u5373\u751F\u6548\u3002",
42
46
  proxyPlaceholder: "http://127.0.0.1:7890",
43
47
  proxySave: "\u4FDD\u5B58",
44
48
  proxySaved: "\u4EE3\u7406\u8BBE\u7F6E\u5DF2\u4FDD\u5B58",
45
49
  proxyHint: "\u7559\u7A7A\u4FDD\u5B58\u5373\u6E05\u9664\u4EE3\u7406\u3002",
46
50
  proxyTest: "\u68C0\u6D4B",
47
51
  proxyTesting: "\u68C0\u6D4B\u4E2D\u2026",
52
+ proxyTestEmpty: "\u8BF7\u5148\u8F93\u5165\u4EE3\u7406\u5730\u5740",
48
53
  proxyTestFailed: "\u68C0\u6D4B\u5931\u8D25",
54
+ proxyScopePlugins: "\u63D2\u4EF6\u5E02\u573A\u66F4\u65B0",
55
+ proxyScopePluginsDesc: "\u63D2\u4EF6\u5E02\u573A\u7684\u68C0\u67E5\u66F4\u65B0\u3001\u5B89\u88C5\u4E0E\u5378\u8F7D\uFF08zcode CLI \u7684 git \u64CD\u4F5C\uFF09\u8D70\u4EE3\u7406\u3002",
56
+ proxyScopeDeps: "\u63D2\u4EF6\u4F9D\u8D56\u8F6F\u4EF6",
57
+ proxyScopeDepsDesc: "headroom \u4E0E rtk \u7684\u5B89\u88C5\u3001\u68C0\u67E5\u66F4\u65B0\u4E0E\u5347\u7EA7\uFF08pip \u4E0E GitHub \u4E0B\u8F7D\uFF09\u8D70\u4EE3\u7406\u3002",
58
+ proxyScopeSync: "\u540C\u6B65",
59
+ proxyScopeSyncDesc: "WebDAV \u4E0A\u4F20\u4E0E\u4E0B\u8F7D\u8D70\u4EE3\u7406\u3002\u9ED8\u8BA4\u5173\u95ED\uFF1A\u575A\u679C\u4E91\u7B49\u56FD\u5185\u7F51\u76D8\u76F4\u8FDE\u5373\u53EF\uFF0C\u4EE3\u7406\u505C\u4E86\u540C\u6B65\u4F1A\u8DDF\u7740\u5931\u8D25\u3002",
49
60
  tabSync: "\u540C\u6B65",
50
- syncDesc: "\u624B\u52A8\u628A ZCode Pro \u8BBE\u7F6E\u4E0E\u6A21\u578B\u8BBE\u7F6E\u5907\u4EFD\u5230 WebDAV\uFF0C\u6216\u5728\u65B0\u7535\u8111\u4E0A\u6062\u590D\u3002\u4E24\u9879\u5404\u81EA\u72EC\u7ACB\u6210\u6587\u4EF6\uFF0C\u53EF\u53EA\u4E0A\u4F20\u6216\u53EA\u8986\u76D6\u5176\u4E2D\u4E00\u9879\uFF1B\u4EC5\u5728\u70B9\u51FB\u6309\u94AE\u65F6\u4F20\u8F93\uFF0C\u4E0D\u505A\u81EA\u52A8\u540C\u6B65\u3002",
61
+ tabDebug: "\u8C03\u8BD5",
62
+ syncDesc: "\u624B\u52A8\u628A ZCode Pro \u8BBE\u7F6E\u4E0E\u6A21\u578B\u8BBE\u7F6E\u5907\u4EFD\u5230 WebDAV\uFF0C\u6216\u5728\u65B0\u7535\u8111\u4E0A\u6062\u590D\u3002\u4E24\u9879\u5404\u81EA\u72EC\u7ACB\u6210\u6587\u4EF6\uFF0C\u53EF\u53EA\u4E0A\u4F20\u6216\u53EA\u8986\u76D6\u5176\u4E2D\u4E00\u9879\uFF1B\u6BCF\u6B21\u4E0A\u4F20\u4F1A\u989D\u5916\u4FDD\u7559\u4E00\u4EFD\u5E26\u65F6\u95F4\u6233\u7684\u5386\u53F2\u7248\u672C\uFF0C\u4E0B\u8F7D\u65F6\u4EFB\u9009\u4E00\u4E2A\u7248\u672C\u6062\u590D\uFF1B\u4EC5\u5728\u70B9\u51FB\u6309\u94AE\u65F6\u4F20\u8F93\uFF0C\u4E0D\u505A\u81EA\u52A8\u540C\u6B65\u3002",
51
63
  syncUrlPlaceholder: "https://dav.jianguoyun.com/dav/",
52
64
  syncDir: "\u4E0A\u4F20\u76EE\u5F55",
53
65
  syncDirPlaceholder: "zcode-pro",
@@ -55,7 +67,7 @@
55
67
  syncPassword: "\u5BC6\u7801",
56
68
  syncSave: "\u4FDD\u5B58",
57
69
  syncSaved: "\u540C\u6B65\u914D\u7F6E\u5DF2\u4FDD\u5B58",
58
- syncFormHint: "\u540C\u6B65\u6587\u4EF6\u5B58\u5230\u201C\u4E0A\u4F20\u76EE\u5F55\u201D\uFF08\u9ED8\u8BA4 zcode-pro\uFF0C\u53EF\u591A\u7EA7\uFF0C\u81EA\u52A8\u521B\u5EFA\uFF09\u4E0B\u7684 zcodepro.json \u4E0E model.json\u3002",
70
+ syncFormHint: "\u540C\u6B65\u6587\u4EF6\u5B58\u5230\u201C\u4E0A\u4F20\u76EE\u5F55\u201D\uFF08\u9ED8\u8BA4 zcode-pro\uFF0C\u53EF\u591A\u7EA7\uFF0C\u81EA\u52A8\u521B\u5EFA\uFF09\uFF1Azcodepro.json \u4E0E model.json \u59CB\u7EC8\u662F\u6700\u65B0\u4E00\u7248\uFF0C\u65C1\u8FB9\u53E6\u4FDD\u7559\u5E26\u65F6\u95F4\u6233\u7684\u5386\u53F2\u7248\u672C\u3002",
59
71
  syncRecLink: "\u575A\u679C\u4E91 WebDAV",
60
72
  syncRecSuffix: "\uFF1A\u767B\u5F55\u4E0E\u5BC6\u7801\u586B\u201C\u5E94\u7528\u5BC6\u7801\u201D\uFF0C\u4E0D\u662F\u8D26\u53F7\u5BC6\u7801\u3002",
61
73
  syncIncludeZcp: "ZCode Pro \u8BBE\u7F6E",
@@ -65,16 +77,44 @@
65
77
  syncUpload: "\u4E0A\u4F20\u5230\u4E91\u7AEF",
66
78
  syncDownload: "\u4ECE\u4E91\u7AEF\u4E0B\u8F7D",
67
79
  syncUploading: "\u4E0A\u4F20\u4E2D\u2026",
68
- syncDownloading: "\u4E0B\u8F7D\u4E2D\u2026",
69
80
  syncUploaded: "\u5DF2\u4E0A\u4F20\u5230\u4E91\u7AEF",
70
81
  syncDownloaded: "\u5DF2\u4ECE\u4E91\u7AEF\u6062\u590D",
71
82
  syncLastPush: "\u4E0A\u6B21\u4E0A\u4F20",
72
83
  syncLastPull: "\u4E0A\u6B21\u4E0B\u8F7D",
73
84
  syncNever: "\u65E0",
74
85
  syncConfirmTitle: "\u4ECE\u4E91\u7AEF\u4E0B\u8F7D",
75
- syncConfirmDesc: "\u5C06\u7528\u4E91\u7AEF\u6570\u636E\u8986\u76D6\u672C\u673A\u52FE\u9009\u7684\u540C\u6B65\u5185\u5BB9\uFF08\u8986\u76D6\u524D\u4F1A\u5728\u539F\u6587\u4EF6\u65C1\u7559\u4E0B .zcodepro-backup \u5907\u4EFD\uFF09\uFF1B\u672C\u673A\u7684\u540C\u6B65\u914D\u7F6E\u4E0E\u767B\u5F55\u51ED\u636E\u4E0D\u53D7\u5F71\u54CD\u3002\u6A21\u578B\u8BBE\u7F6E\u6062\u590D\u540E\u5EFA\u8BAE\u91CD\u542F ZCode \u751F\u6548\u3002\u786E\u8BA4\u4E0B\u8F7D\uFF1F",
76
- syncConfirmGo: "\u786E\u8BA4\u4E0B\u8F7D",
86
+ syncConfirmDesc: "\u5C06\u7528\u6240\u9009\u6587\u4EF6\u8986\u76D6\u672C\u673A\u5BF9\u5E94\u7684\u4E00\u9879\uFF08\u8986\u76D6\u524D\u4F1A\u5728\u539F\u6587\u4EF6\u65C1\u7559\u4E0B .zcodepro-backup \u5907\u4EFD\uFF09\uFF1B\u672C\u673A\u7684\u540C\u6B65\u914D\u7F6E\u4E0E\u767B\u5F55\u51ED\u636E\u4E0D\u53D7\u5F71\u54CD\u3002\u6A21\u578B\u8BBE\u7F6E\u6062\u590D\u540E\u5EFA\u8BAE\u91CD\u542F ZCode \u751F\u6548\u3002\u786E\u8BA4\u5BFC\u5165\uFF1F",
87
+ syncConfirmGo: "\u786E\u8BA4\u5BFC\u5165",
88
+ syncPickDesc: "\u56FA\u5B9A\u540D\u6587\u4EF6\u59CB\u7EC8\u662F\u6700\u65B0\u4E00\u7248\uFF0C\u5E26\u65F6\u95F4\u6233\u7684\u662F\u6BCF\u6B21\u4E0A\u4F20\u7559\u4E0B\u7684\u5386\u53F2\u7248\u672C\uFF1B\u300C\u5BFC\u5165\u300D\u7528\u6240\u9009\u6587\u4EF6\u8986\u76D6\u672C\u673A\u5BF9\u5E94\u7684\u4E00\u9879\u3002",
89
+ syncVerDeleteLatestNote: "\u6CE8\u610F\uFF1A\u8FD9\u662F\u201C\u6700\u65B0\u201D\u6587\u4EF6\uFF0C\u65E7\u7248 zcode-pro \u7684\u300C\u4ECE\u4E91\u7AEF\u4E0B\u8F7D\u300D\u4F9D\u8D56\u5B83\uFF0C\u5220\u9664\u540E\u65E7\u7248\u673A\u5668\u5C06\u65E0\u6CD5\u4E0B\u8F7D\uFF1B\u5E26\u65F6\u95F4\u6233\u7684\u5386\u53F2\u7248\u672C\u4E0D\u53D7\u5F71\u54CD\u3002",
77
90
  syncHint: "\u540C\u6B65\u767B\u5F55\u51ED\u636E\u4EC5\u4FDD\u5B58\u5728\u672C\u673A zcodepro.json\uFF1B\u6A21\u578B\u8BBE\u7F6E\u5305\u542B\u81EA\u5B9A\u4E49\u4F9B\u5E94\u5546\u7684 API Key\uFF0C\u53EA\u4F1A\u4E0A\u4F20\u5230\u4F60\u81EA\u5DF1\u7684 WebDAV\u3002",
91
+ syncTest: "\u6D4B\u8BD5",
92
+ syncTesting: "\u6D4B\u8BD5\u4E2D\u2026",
93
+ syncTestEmpty: "\u8BF7\u5148\u586B\u5199 URL",
94
+ syncTestFailed: "\u6D4B\u8BD5\u5931\u8D25",
95
+ syncTestStepConnect: "\u8FDE\u63A5",
96
+ syncTestStepDir: "\u76EE\u5F55",
97
+ syncTestStepList: "\u6587\u4EF6",
98
+ syncTestFilesFound: "\u4E91\u7AEF\u5DF2\u6709 {n} \u4E2A\u5907\u4EFD\u6587\u4EF6",
99
+ syncPickTitle: "\u8BF7\u9009\u62E9\u4E00\u4E2A\u6587\u4EF6",
100
+ syncPickLoading: "\u6B63\u5728\u8BFB\u53D6\u4E91\u7AEF\u6587\u4EF6\u5217\u8868\u2026",
101
+ syncPickEmpty: "\u4E91\u7AEF\u8FD8\u6CA1\u6709\u5907\u4EFD\u6587\u4EF6\uFF0C\u8BF7\u5148\u5728\u5176\u4ED6\u7535\u8111\u4E0A\u4F20",
102
+ syncVerImport: "\u5BFC\u5165",
103
+ syncVerImporting: "\u5BFC\u5165\u4E2D\u2026",
104
+ syncVerDelete: "\u5220\u9664",
105
+ syncVerSave: "\u4FDD\u5B58\u5230\u78C1\u76D8",
106
+ syncVerSaving: "\u4FDD\u5B58\u4E2D\u2026",
107
+ syncVerLatest: "\u6700\u65B0",
108
+ syncVerDeleteTitle: "\u5220\u9664\u4E91\u7AEF\u6587\u4EF6",
109
+ syncVerDeleteDesc: "\u5C06\u5220\u9664\u4E91\u7AEF\u7684\u8FD9\u4EFD\u5907\u4EFD\uFF0C\u4E0D\u53EF\u6062\u590D\uFF1A",
110
+ syncVerDeleted: "\u5DF2\u5220\u9664\u4E91\u7AEF\u5907\u4EFD",
111
+ syncVerSaved: "\u5DF2\u4FDD\u5B58\u5230",
112
+ devMode: "\u5F00\u53D1\u6A21\u5F0F",
113
+ devModeRowDesc: "\u5F00\u542F\u540E\u51FA\u73B0\u300C\u91CD\u8F7D ZCode Pro\u300D\u6309\u94AE\uFF1B\u4E5F\u53EF\u7ED9\u542F\u52A8\u5668\u8BBE\u73AF\u5883\u53D8\u91CF ZCODEPRO_DEV=1 \u542F\u7528\u3002\u6B64\u6807\u7B7E\u9875\u4EC5\u5728\u5F00\u53D1\u6A21\u5F0F\u751F\u6548\u65F6\u663E\u793A\u3002",
114
+ devModeDesc: "\u5F00\u53D1\u8C03\u8BD5\u7528\uFF1A\u6539\u5B8C zcode-pro \u4EE3\u7801\u540E\u4E0D\u5FC5\u91CD\u542F ZCode\uFF0C\u70B9\u300C\u91CD\u8F7D ZCode Pro\u300D\u5373\u53EF\u52A0\u8F7D\u65B0\u4EE3\u7801\u3002\u6D41\u7A0B\u4E3A\u81EA\u52A8\u6784\u5EFA\uFF08\u9700\u5728\u6E90\u7801\u76EE\u5F55\u8FD0\u884C\uFF09\u2192 \u4EE5\u65B0\u8FDB\u7A0B\u91CD\u542F ZCode Pro\uFF08\u672C\u5730\u52A9\u624B\u4E0E\u6CE8\u5165\u811A\u672C\u90FD\u6362\u65B0\uFF09\u2192 \u6240\u6709 ZCode \u7A97\u53E3\u81EA\u52A8\u5237\u65B0\u5E76\u91CD\u65B0\u6CE8\u5165\uFF1BZCode \u672C\u4F53\u4E0E\u5DF2\u6253\u5F00\u7684\u4F1A\u8BDD\u4E0D\u53D7\u5F71\u54CD\u3002",
115
+ devReload: "\u91CD\u8F7D ZCode Pro",
116
+ devReloading: "\u91CD\u8F7D\u4E2D\u2026",
117
+ devReloadDesc: "\u70B9\u51FB\u540E\u7EA6 2-3 \u79D2\u5185\u6240\u6709 ZCode \u7A97\u53E3\u81EA\u52A8\u5237\u65B0\u4E00\u6B21\u5E76\u52A0\u8F7D\u65B0\u811A\u672C\u3002",
78
118
  rowGapName: "\u6BB5\u843D\u95F4\u8DDD",
79
119
  rowGapDesc: "\u4F1A\u8BDD\u4E2D\u6BB5\u843D\u7B49\u6587\u672C\u5757\u4E4B\u95F4\u7684\u5782\u76F4\u95F4\u8DDD\uFF08\u56DE\u5408\u4E4B\u95F4\u3001\u7B54\u6848\u5185\u90E8\uFF09\u3002",
80
120
  listSpacingName: "\u5217\u8868\u4E0A\u4E0B\u8FB9\u8DDD",
@@ -91,6 +131,8 @@
91
131
  tableCellPaddingDesc: "\u8868\u683C\u5355\u5143\u683C\u5185\u7684\u7559\u767D\uFF1B\u5DE6\u8F93\u5165\u6846\u4E3A\u4E0A\u4E0B\u3001\u53F3\u4E3A\u5DE6\u53F3\u3002",
92
132
  lineHeightName: "\u56DE\u7B54\u884C\u9AD8",
93
133
  lineHeightDesc: "\u56DE\u7B54\u6B63\u6587\u7684\u884C\u9AD8\uFF08\u500D\u6570\uFF09\u3002",
134
+ thinkingLineHeightName: "\u601D\u8003\u884C\u9AD8",
135
+ thinkingLineHeightDesc: "\u300C\u5DF2\u5DE5\u4F5C/\u5DE5\u4F5C\u4E2D\u300D\u8FC7\u7A0B\u8BB0\u5F55\uFF08\u8BFB\u53D6/\u7F16\u8F91/\u67E5\u9605/\u7EC8\u7AEF\u6761\u76EE\u4E0E\u601D\u8003\u5185\u5BB9\uFF09\u7684\u884C\u9AD8\uFF08\u500D\u6570\uFF09\u3002",
94
136
  userLineHeightName: "\u63D0\u95EE\u884C\u9AD8",
95
137
  userLineHeightDesc: "\u63D0\u95EE\u5185\u5BB9\u7684\u884C\u9AD8\uFF08\u500D\u6570\uFF09\u3002",
96
138
  contentWidthName: "\u5185\u5BB9\u5BBD\u5EA6",
@@ -105,6 +147,30 @@
105
147
  featureSessionSwitchDesc: "alt+z \u5728\u5F53\u524D\u4E0E\u4E0A\u6B21\u4F1A\u8BDD\u95F4\u6765\u56DE\u5207\u6362\uFF1B\u6309\u4F4F alt \u518D\u6309 x/c \u5F39\u51FA\u6700\u8FD1\u4F1A\u8BDD\u5217\u8868\u524D\u540E\u9009\u62E9\uFF0C\u677E\u5F00 alt \u5207\u6362\uFF08\u7C7B alt+tab\uFF09\u3002",
106
148
  featureTitleLock: "\u91CD\u547D\u540D\u4F1A\u8BDD\u540D\u9501\u5B9A",
107
149
  featureTitleLockDesc: "\u624B\u52A8\u91CD\u547D\u540D\u8FC7\u7684\u4F1A\u8BDD\u4E0D\u518D\u88AB\u81EA\u52A8\u6807\u9898\u8986\u76D6\uFF1A\u4F1A\u8BDD\u6709\u65B0\u56DE\u7B54\u540E\u754C\u9762\u53EF\u80FD\u6539\u663E\u81EA\u52A8\u6807\u9898\uFF0C\u91CD\u542F\u540E\u53C8\u53D8\u56DE\uFF0C\u672C\u529F\u80FD\u8BA9\u4FA7\u680F\u59CB\u7EC8\u663E\u793A\u91CD\u547D\u540D\u540E\u7684\u540D\u5B57\u3002",
150
+ tabTitleGen: "\u4EFB\u52A1\u540D\u63D0\u793A\u8BCD",
151
+ titleGenShortDesc: "\u65B0\u4F1A\u8BDD\u5F00\u59CB\u4F5C\u7B54\u65F6\uFF0C\u6309\u6240\u9009\u4F9B\u5E94\u5546\u4E0E\u63D0\u793A\u8BCD\u751F\u6210\u4EFB\u52A1\u540D\u5E76\u6309\u624B\u52A8\u91CD\u547D\u540D\u4FDD\u5B58\uFF0C\u5B98\u65B9\u81EA\u52A8\u6807\u9898\u4E0D\u518D\u8986\u76D6\u3002",
152
+ titleGenEnabled: "\u542F\u7528",
153
+ titleGenProvider: "\u4F9B\u5E94\u5546",
154
+ titleGenProviderNone: "\u4E0D\u8DDF\u968F\uFF08\u81EA\u586B\u5730\u5740\uFF09",
155
+ titleGenProviderSession: "\u8DDF\u968F\u5F53\u524D\u4F1A\u8BDD",
156
+ titleGenProviderDeleted: "\uFF08\u5DF2\u5220\u9664\uFF09",
157
+ titleGenModel: "\u6A21\u578B",
158
+ titleGenModelHint: "\u4ECE\u5217\u8868\u9009\u62E9\uFF0C\u6216\u76F4\u63A5\u586B\u5199\u6A21\u578B id",
159
+ titleGenModelEmpty: "\uFF08\u672A\u9009\u62E9\uFF09",
160
+ titleGenModelSessionHint: "\u4ECE\u5217\u8868\u9009\u62E9\u6A21\u578B\uFF1B\u5217\u8868\u7B2C\u4E00\u9879\u4E3A\u8DDF\u968F\u4F1A\u8BDD\u5F53\u524D\u6A21\u578B",
161
+ titleGenModelFollowSession: "\u8DDF\u968F\u5F53\u524D\u4F1A\u8BDD\u5F53\u524D\u6A21\u578B",
162
+ titleGenModelManualHint: "\u8F93\u5165\u6A21\u578B id",
163
+ titleGenBaseUrl: "\u63A5\u53E3\u5730\u5740",
164
+ titleGenApiKey: "API Key",
165
+ titleGenFormat: "\u534F\u8BAE",
166
+ titleGenPrompt: "\u63D0\u793A\u8BCD",
167
+ titleGenPromptPlaceholder: "\u7559\u7A7A\u4F7F\u7528\u5185\u7F6E\u9ED8\u8BA4\u63D0\u793A\u8BCD\uFF08\u70B9\u51FB\u4E0B\u65B9\u6309\u94AE\u53EF\u67E5\u770B\uFF09",
168
+ titleGenPromptReset: "\u4F7F\u7528\u9ED8\u8BA4\u63D0\u793A\u8BCD",
169
+ titleGenSample: "\u6D4B\u8BD5\u6D88\u606F",
170
+ titleGenTest: "\u6D4B\u8BD5\u751F\u6210",
171
+ titleGenTesting: "\u751F\u6210\u4E2D\u2026",
172
+ titleGenTestOk: "\u751F\u6210\u7ED3\u679C\uFF1A",
173
+ titleGenTestFailed: "\u751F\u6210\u5931\u8D25",
108
174
  featureWsRunning: "\u6298\u53E0\u9879\u76EE\u8FD0\u884C\u63D0\u793A\uFF08\u5B9E\u9A8C\u6027\uFF09",
109
175
  featureWsRunningDesc: "\u6298\u53E0\u7684\u9879\u76EE\u91CC\u4ECD\u6709\u4F1A\u8BDD\u5728\u8FD0\u884C\u65F6\uFF0C\u9879\u76EE\u56FE\u6807\u65CB\u8F6C\u63D0\u793A\uFF1B\u5168\u90E8\u7ED3\u675F\u6216\u5C55\u5F00\u9879\u76EE\u540E\u6062\u590D\u3002\u671F\u95F4\u6BCF 60 \u79D2\u5728\u540E\u53F0\u77ED\u6682\u5C55\u5F00\u8BE5\u9879\u76EE\u590D\u6838\u8FD0\u884C\u72B6\u6001\u518D\u6536\u8D77\uFF0C\u4F1A\u8BDD\u884C\u4E0D\u663E\u793A\uFF0C\u4EC5\u9879\u76EE\u4F4D\u7F6E\u53EF\u80FD\u6709\u7EA6\u56DB\u5206\u4E4B\u4E00\u79D2\u7684\u8F7B\u5FAE\u9AD8\u5EA6\u8D77\u4F0F\u3002",
110
176
  tabPerformance: "\u6027\u80FD",
@@ -154,7 +220,7 @@
154
220
  featurePinnedExpand: "\u7F6E\u9876\u4F1A\u8BDD\u4FDD\u6301\u9879\u76EE\u6298\u53E0\uFF08\u5B9E\u9A8C\u6027\uFF09",
155
221
  featurePinnedExpandDesc: "\u70B9\u51FB\u6298\u53E0\u9879\u76EE\u7684\u7F6E\u9876\u4F1A\u8BDD\u540E\u5C06\u5176\u4FDD\u6301\u6298\u53E0\u3002\u53D7\u9650\u4E8E\u5E94\u7528\u673A\u5236\uFF0C\u9879\u76EE\u4F1A\u5148\u77ED\u6682\u5C55\u5F00\u518D\u7F29\u8D77\u3002",
156
222
  featureAutoUpdatePlugins: "\u81EA\u52A8\u66F4\u65B0 zcode-plugins \u63D2\u4EF6",
157
- featureAutoUpdatePluginsDesc: "\u542F\u52A8\u65F6\u81EA\u52A8\u68C0\u67E5\u5E76\u66F4\u65B0\u5DF2\u88C5\u7684 zcode-plugins \u63D2\u4EF6\uFF08\u542B\u5B89\u88C5\u5E02\u573A\u91CC\u65B0\u589E\u7684\u63D2\u4EF6\uFF09\uFF0C\u66F4\u65B0\u5728\u65B0\u4F1A\u8BDD\u751F\u6548\u3002",
223
+ featureAutoUpdatePluginsDesc: "\u542F\u52A8\u65F6\u81EA\u52A8\u68C0\u67E5\u5E76\u66F4\u65B0\u5DF2\u5B89\u88C5\u7684 zcode-plugins \u63D2\u4EF6\uFF08\u53EA\u5347\u7EA7\u4E0D\u5B89\u88C5\uFF0C\u5378\u8F7D\u4E0D\u4F1A\u88AB\u64A4\u9500\uFF09\uFF0C\u66F4\u65B0\u5728\u65B0\u4F1A\u8BDD\u751F\u6548\u3002",
158
224
  pluginsUpdateTitle: "\u63D2\u4EF6\u66F4\u65B0 \xB7 zcode-plugins",
159
225
  pluginsCheckNow: "\u68C0\u67E5\u5E76\u66F4\u65B0",
160
226
  pluginsUpdating: "\u6B63\u5728\u66F4\u65B0\u63D2\u4EF6\u2026",
@@ -166,7 +232,7 @@
166
232
  pluginsAutoUpdated: "zcode-plugins \u63D2\u4EF6\u5DF2\u66F4\u65B0",
167
233
  tabVision: "Vision",
168
234
  visionDesc: "\u4E3B\u6A21\u578B\u4E0D\u652F\u6301\u56FE\u7247\u8F93\u5165\u65F6\uFF08\u5982 glm-5.3\uFF09\uFF0C\u81EA\u52A8\u628A\u6D88\u606F\u91CC\u7684\u56FE\u7247\u4EA4\u7ED9\u4E0B\u9762\u7684\u89C6\u89C9\u6A21\u578B\u8BC6\u522B\uFF0C\u628A\u63CF\u8FF0\u5E26\u7ED9\u4E3B\u6A21\u578B\uFF08zcode-vision \u63D2\u4EF6\uFF1B/vision-* \u547D\u4EE4\u7F16\u8F91\u540C\u4E00\u914D\u7F6E\uFF09\u3002\u6539\u52A8\u81EA\u52A8\u4FDD\u5B58\u3002",
169
- visionEnabled: "\u542F\u7528\u56FE\u7247\u8BC6\u522B",
235
+ visionEnabled: "\u542F\u7528",
170
236
  visionForceIntercept: "\u4E3B\u6A21\u578B\u80FD\u770B\u56FE\u4E5F\u62E6\u622A",
171
237
  visionMode: "\u94FE\u6A21\u5F0F",
172
238
  visionModeFallback: "\u4F9D\u6B21\u5C1D\u8BD5",
@@ -179,10 +245,6 @@
179
245
  visionMoveDown: "\u4E0B\u79FB",
180
246
  visionName: "\u540D\u79F0",
181
247
  visionUseProvider: "\u4F9B\u5E94\u5546",
182
- visionUseProviderHint: "\u4E0B\u62C9\u9009\u62E9\u6216\u624B\u52A8\u8F93\u5165\uFF1Asession=\u8DDF\u968F\u5F53\u524D\u4F1A\u8BDD\u4F9B\u5E94\u5546\uFF0C\u6216\u4F9B\u5E94\u5546\u540D/ID\uFF1B\u8BBE\u7F6E\u540E baseUrl/API Key/\u683C\u5F0F\u81EA\u52A8\u53D6\u8BE5\u4F9B\u5E94\u5546",
183
- visionUseProviderNone: "\u4E0D\u8DDF\u968F\uFF08\u81EA\u586B\u5730\u5740\uFF09",
184
- visionUseProviderSession: "\u8DDF\u968F\u5F53\u524D\u4F1A\u8BDD\uFF08session\uFF09",
185
- visionUseProviderUnknown: "\u672A\u627E\u5230\u8BE5\u4F9B\u5E94\u5546\uFF0C\u8BC6\u522B\u65F6\u4F1A\u62A5\u9519\uFF1B\u8BF7\u4ECE\u4E0B\u62C9\u9009\u62E9\uFF0C\u6216\u8F93\u5165 session",
186
248
  visionSkipAfterFailures: "\u8FDE\u7EED\u5931\u8D25\u6B21\u6570",
187
249
  visionSkipAfterFailuresHint: "\u67D0\u4EE3\u7406\u8FDE\u7EED\u5931\u8D25\u6EE1\u6B21\u6570\u540E\u6682\u65F6\u8DF3\u8FC7\u5B83\uFF0C\u76F4\u63A5\u8BD5\u4E0B\u4E00\u4E2A\uFF1B0 = \u4E0D\u8DF3\u8FC7",
188
250
  visionSkipMinutes: "\u8DF3\u8FC7\u5206\u949F\u6570",
@@ -191,21 +253,21 @@
191
253
  visionResetChainConfirm: "\u5C06\u94FE\u6A21\u5F0F\u4E0E\u4EE3\u7406\u5217\u8868\u6062\u590D\u4E3A\u9ED8\u8BA4\u4E24\u7EA7\uFF08glm-session \u2192 glm-flash\uFF09\uFF0C\u5F53\u524D\u5BF9\u4EE3\u7406\u7684\u4FEE\u6539\u4F1A\u4E22\u5931\u3002\u7EE7\u7EED\uFF1F",
192
254
  visionBaseUrl: "\u63A5\u53E3\u5730\u5740 baseUrl",
193
255
  visionModel: "\u6A21\u578B",
194
- visionFormat: "\u683C\u5F0F",
195
256
  visionApiKey: "API Key",
196
257
  visionApiKeyHint: "\u7559\u7A7A\u81EA\u52A8\u53D6 GLM \u8BA2\u9605 key",
197
258
  visionPrompt: "\u8BC6\u522B\u63D0\u793A\u8BCD\uFF08pipeline \u540E\u7EED\u6B65\u53EF\u7528 {prev} \u5F15\u7528\u4E0A\u4E00\u6B65\uFF09",
198
259
  visionTest: "\u6D4B\u8BD5",
199
260
  visionTesting: "\u6D4B\u8BD5\u4E2D\u2026",
200
261
  visionTestFailed: "\u89C6\u89C9\u4EE3\u7406\u6D4B\u8BD5\u5931\u8D25",
201
- visionTestOutput: "\u6D4B\u8BD5\u8F93\u51FA",
202
262
  visionCompressKB: "\u538B\u7F29\u9608\u503C KB",
203
263
  visionCompressKBHint: "\u8D85\u8FC7\u8BE5\u5927\u5C0F\u7684\u56FE\u5148\u538B\u7F29\u518D\u8BC6\u522B\uFF08\u6700\u957F\u8FB9 2000\u3001JPEG\uFF09\uFF1B0 = \u4E0D\u538B\u7F29",
204
264
  visionLoadFailed: "\u8BFB\u53D6\u89C6\u89C9\u4EE3\u7406\u914D\u7F6E\u5931\u8D25",
205
265
  tabRtk: "rtk",
206
266
  rtkDesc: "rtk \u63D2\u4EF6\uFF1A\u628A\u5E38\u89C1\u5F00\u53D1\u547D\u4EE4\u7684\u8F93\u51FA\u538B\u7F29 60-90% \u518D\u8FDB\u5165\u4E0A\u4E0B\u6587\uFF08/rtk-* \u547D\u4EE4\u7F16\u8F91\u540C\u4E00\u914D\u7F6E\uFF09\u3002\u6539\u52A8\u81EA\u52A8\u4FDD\u5B58\u5E76\u7ACB\u5373\u751F\u6548\u3002",
207
- rtkEnabled: "\u542F\u7528 rtk \u538B\u7F29",
208
- rtkNotInstalled: "\u672A\u5B89\u88C5 rtk\uFF0C\u4EE5\u4E0B\u914D\u7F6E\u5C06\u5728\u5B89\u88C5\u540E\u751F\u6548\uFF08\u4F1A\u8BDD\u91CC\u53EF\u7528 /rtk-setup \u5B89\u88C5\uFF09",
267
+ rtkEnabled: "\u542F\u7528",
268
+ rtkNotInstalled: "\u672A\u5B89\u88C5 rtk \u672C\u4F53\uFF1B\u5B89\u88C5\u540E\u4EE5\u4E0B\u8BBE\u7F6E\u751F\u6548\uFF08\u4F1A\u8BDD\u91CC\u6267\u884C /rtk-setup \u4E5F\u53EF\u4EE5\uFF09",
269
+ rtkInstallBin: "\u5B89\u88C5 rtk \u672C\u4F53",
270
+ rtkBinInstalled: "rtk \u672C\u4F53\u5DF2\u5B89\u88C5\uFF1BPATH \u9700\u6CE8\u9500\u91CD\u767B\u540E\u5BF9\u4F1A\u8BDD\u751F\u6548\uFF0C\u63D2\u4EF6\u94A9\u5B50\u5373\u65F6\u751F\u6548",
209
271
  rtkWhitelistTitle: "\u767D\u540D\u5355\uFF08\u4E0D\u538B\u7F29\u76F4\u63A5\u653E\u884C\uFF09",
210
272
  rtkBuiltinLabel: "\u5185\u7F6E\u653E\u884C\uFF08\u53EA\u8BFB\uFF0C\u968F\u63D2\u4EF6\u66F4\u65B0\uFF09\uFF1A",
211
273
  rtkWhitelistDesc: "\u6BCF\u6761 name\uFF08\u5982 docker\uFF09\u6216 git:name\uFF08\u5982 git:clone\uFF09\u3002\u8F93\u51FA\u6781\u5C0F\u6216\u7EAF\u526F\u4F5C\u7528\u7684\u547D\u4EE4\u65E0\u9700\u538B\u7F29\uFF0C\u52A0\u5165\u540E\u4E0D\u518D\u538B\u7F29\uFF1B\u5185\u7F6E\u7684 git add/commit/push\u3001mkdir/cp \u7B49\u4E0D\u53EF\u79FB\u9664\u3002\u5220\u9664\u672C\u6587\u4EF6\u5373\u6062\u590D\u9ED8\u8BA4\u3002",
@@ -218,12 +280,25 @@
218
280
  tabHeadroom: "Headroom",
219
281
  headroomDesc: "Headroom \u63D2\u4EF6\uFF1A\u672C\u5730\u538B\u7F29\u4EE3\u7406\u9A7B\u7559 127.0.0.1\uFF08\u9ED8\u8BA4 8787 \u7AEF\u53E3\uFF09\uFF0C\u538B\u7F29\u53D1\u5F80\u6A21\u578B\u7684\u4E0A\u4E0B\u6587\u4EE5\u7701 token\uFF08/hr-* \u547D\u4EE4\u7F16\u8F91\u540C\u4E00\u914D\u7F6E\uFF09\u3002\u538B\u7F29\u8BBE\u5907\u4E0E\u7701\u7535\u5207\u6362\u4FDD\u5B58\u540E\u7ACB\u5373\u751F\u6548\uFF1B\u5207\u6362\u8BBE\u5907\u4F1A\u91CD\u542F\u4EE3\u7406\uFF0C\u6B63\u5728\u5904\u7406\u7684\u8BF7\u6C42\u53EF\u80FD\u4E2D\u65AD\u4E00\u6B21\u3002",
220
282
  headroomBinMissing: "\u672A\u627E\u5230 headroom \u7A0B\u5E8F\uFF0C\u4EE3\u7406\u65E0\u6CD5\u542F\u52A8\uFF1B\u5B89\u88C5 headroom \u540E\u4EE5\u4E0B\u8BBE\u7F6E\u81EA\u52A8\u751F\u6548",
283
+ hrInstallBin: "\u5B89\u88C5 headroom \u672C\u4F53",
284
+ hrBinInstalled: "headroom \u672C\u4F53\u5DF2\u5B89\u88C5\uFF1B\u91CD\u65B0\u6253\u5F00\u4F1A\u8BDD\u540E\u4EE3\u7406\u53EF\u7528\uFF08SessionStart \u94A9\u5B50\u81EA\u52A8\u62C9\u8D77\uFF09",
221
285
  headroomInstallTitle: "\u672A\u5B89\u88C5 headroom \u63D2\u4EF6",
222
286
  headroomInstallDesc: "\u4ECE zcode-plugins \u5E02\u573A\u5B89\u88C5\u540E\u5373\u53EF\u5728\u6B64\u7BA1\u7406\u538B\u7F29\u4EE3\u7406\uFF1B\u5EFA\u8BAE\u5B89\u88C5\u540E\u91CD\u542F\u4F1A\u8BDD\u751F\u6548\u3002",
223
287
  headroomInstall: "\u5B89\u88C5",
224
288
  headroomInstalling: "\u5B89\u88C5\u4E2D\u2026",
225
289
  headroomInstalled: "\u5DF2\u5B89\u88C5 headroom \u63D2\u4EF6",
226
290
  headroomInstallRetry: "\u5B89\u88C5\u672A\u751F\u6548\uFF0C\u8BF7\u91CD\u8BD5\u6216\u624B\u52A8\u5728\u63D2\u4EF6\u5E02\u573A\u5B89\u88C5",
291
+ pluginInstallCardTitle: "\u672A\u5B89\u88C5 {name} \u63D2\u4EF6",
292
+ pluginInstallCardDesc: "\u4ECE zcode-plugins \u5E02\u573A\u5B89\u88C5\u540E\u5373\u53EF\u5728\u6B64\u7BA1\u7406\uFF1B\u5EFA\u8BAE\u5B89\u88C5\u540E\u91CD\u542F\u4F1A\u8BDD\u751F\u6548\u3002",
293
+ pluginInstallDone: "\u5DF2\u5B89\u88C5 {name} \u63D2\u4EF6",
294
+ pluginEnable: "\u542F\u7528",
295
+ hrStarting: "\u6B63\u5728\u542F\u52A8\u538B\u7F29\u4EE3\u7406\u2026",
296
+ hrStarted: "\u538B\u7F29\u4EE3\u7406\u5DF2\u542F\u52A8",
297
+ hrStartTimeout: "\u4EE3\u7406\u542F\u52A8\u8D85\u65F6\uFF0890 \u79D2\uFF09\uFF0C\u8BF7\u68C0\u67E5 ~/.zcode-headroom-proxy.log",
298
+ hrStopping: "\u6B63\u5728\u505C\u6B62\u538B\u7F29\u4EE3\u7406\u2026",
299
+ hrRestarting: "\u6B63\u5728\u91CD\u542F\u538B\u7F29\u4EE3\u7406\u2026",
300
+ hrStopped: "\u538B\u7F29\u4EE3\u7406\u5DF2\u505C\u6B62",
301
+ hrUsageNone: "\u5F53\u524D\u6CA1\u6709\u8D70 headroom \u7684\u4F9B\u5E94\u5546\uFF0C\u672A\u5207\u6362",
227
302
  headroomLoadFailed: "\u8BFB\u53D6 Headroom \u914D\u7F6E\u5931\u8D25",
228
303
  headroomStatusTitle: "\u8FD0\u884C\u72B6\u6001",
229
304
  headroomStatusLoadFailed: "\u8BFB\u53D6\u8FD0\u884C\u72B6\u6001\u5931\u8D25",
@@ -249,7 +324,7 @@
249
324
  headroomRestart: "\u91CD\u542F",
250
325
  headroomStopAction: "\u505C\u6B62",
251
326
  headroomStopTitle: "\u505C\u6B62 Headroom \u4EE3\u7406",
252
- headroomStopDesc: "\u505C\u6B62\u540E\uFF0CZCode \u4E2D\u6307\u5411\u8BE5\u4EE3\u7406\uFF08127.0.0.1\uFF09\u7684\u4F9B\u5E94\u5546\u5C06\u65E0\u6CD5\u8FDE\u63A5\uFF0C\u76F4\u81F3\u91CD\u65B0\u542F\u52A8\u4EE3\u7406\u3002\u786E\u8BA4\u505C\u6B62\uFF1F",
327
+ headroomStopDesc: "\u505C\u6B62\u540E\uFF0CZCode \u53CA\u5176\u4ED6\u7A0B\u5E8F\u4E2D\u6307\u5411\u8BE5\u4EE3\u7406\uFF08127.0.0.1\uFF09\u7684\u8BF7\u6C42\u90FD\u4F1A\u5931\u8D25\uFF0C\u76F4\u81F3\u91CD\u65B0\u542F\u52A8\u4EE3\u7406\u3002headroom \u662F\u5171\u4EAB\u7684\uFF0C\u5176\u4ED6\u5DE5\u5177\u53EF\u80FD\u6B63\u5728\u4F7F\u7528\u3002\u786E\u8BA4\u505C\u6B62\uFF1F",
253
328
  headroomStoppedWarn: "\u4EE3\u7406\u5DF2\u505C\u6B62\uFF1B\u6307\u5411 127.0.0.1 \u7684\u4F9B\u5E94\u5546\u5C06\u65E0\u6CD5\u8FDE\u63A5",
254
329
  headroomBackend: "\u538B\u7F29\u8BBE\u5907",
255
330
  headroomBackendAuto: "\u81EA\u52A8 \xB7 \u663E\u5361\u4F18\u5148",
@@ -267,8 +342,6 @@
267
342
  headroomInterval: "\u76D1\u89C6\u95F4\u9694",
268
343
  headroomIntervalDesc: "\u7701\u7535\u76D1\u89C6\u5668\u8F6E\u8BE2\u7535\u6E90\u72B6\u6001\u7684\u95F4\u9694\uFF0810\u20133600 \u79D2\uFF0C\u9ED8\u8BA4 60\uFF09\uFF1B\u4FDD\u5B58\u540E\u4ECE\u4E0B\u4E00\u8F6E\u5DE1\u68C0\u8D77\u751F\u6548\uFF0C\u65E0\u9700\u91CD\u542F\u3002",
269
344
  headroomSecUnit: "\u79D2",
270
- headroomVersionPlugin: "\u63D2\u4EF6",
271
- headroomVersionBin: "\u7A0B\u5E8F",
272
345
  upgradeCheck: "\u68C0\u67E5\u66F4\u65B0",
273
346
  upgradeChecking: "\u68C0\u67E5\u4E2D\u2026",
274
347
  upgradeUpToDate: "\u5DF2\u662F\u6700\u65B0\uFF08{v}\uFF09",
@@ -280,7 +353,6 @@
280
353
  upgradeDone: "\u5DF2\u66F4\u65B0\u5230 {v}",
281
354
  upgradeFinished: "\u5347\u7EA7\u5B8C\u6210",
282
355
  upgradeCheckFailed: "\u68C0\u67E5\u66F4\u65B0\u5931\u8D25",
283
- version: "\u7248\u672C",
284
356
  close: "\u5173\u95ED",
285
357
  cancel: "\u53D6\u6D88",
286
358
  pathLabel: "\u8DEF\u5F84",
@@ -324,22 +396,31 @@
324
396
  tabFeatures: "Features",
325
397
  tabStyles: "Styles",
326
398
  tabProxy: "Proxy",
399
+ tabBehavior: "Behavior",
327
400
  tabAgents: "Global Prompt",
328
401
  agentsDesc: "Written to ~/.zcode/AGENTS.md and used as default instructions in every session across all projects. Takes effect for new sessions; per-project AGENTS.md can extend or override it. Content over 100 KB is truncated by the app.",
329
402
  agentsPlaceholder: "Instructions followed by all projects by default; save empty to remove the global prompt",
330
403
  agentsSave: "Save",
331
404
  agentsSaved: "Global prompt saved.",
332
405
  agentsLoadFailed: "Failed to load the global prompt",
333
- proxyDesc: "Set an HTTP proxy for network access made by the local helper: plugin marketplace updates and installs, headroom update checks and upgrades. Does not affect the ZCode app itself or model requests. Applies immediately.",
406
+ proxyDesc: "Set an HTTP proxy for network access made by the local helper: plugin marketplace updates and installs, plugin dependency installs and upgrades, and WebDAV sync can all use it \u2014 pick the scopes with the toggles below. Does not affect the ZCode app itself or model requests. Applies immediately.",
334
407
  proxyPlaceholder: "http://127.0.0.1:7890",
335
408
  proxySave: "Save",
336
409
  proxySaved: "Proxy settings saved.",
337
410
  proxyHint: "Save empty to clear the proxy.",
338
411
  proxyTest: "Test",
339
412
  proxyTesting: "Testing\u2026",
413
+ proxyTestEmpty: "Enter a proxy address first",
340
414
  proxyTestFailed: "Test failed",
415
+ proxyScopePlugins: "Plugin marketplace",
416
+ proxyScopePluginsDesc: "Marketplace checks, installs and uninstalls (zcode CLI git operations) go through the proxy.",
417
+ proxyScopeDeps: "Plugin dependencies",
418
+ proxyScopeDepsDesc: "headroom and rtk installs, update checks and upgrades (pip and GitHub downloads) go through the proxy.",
419
+ proxyScopeSync: "Sync",
420
+ proxyScopeSyncDesc: "WebDAV upload and download go through the proxy. Off by default: domestic WebDAV providers work fine direct, and a stopped proxy breaks sync.",
341
421
  tabSync: "Sync",
342
- syncDesc: "Manually back up ZCode Pro settings and model settings to WebDAV, or restore them on a new computer. The two items are stored as separate files, so you can upload or overwrite either one alone; data moves only when you click, never automatically.",
422
+ tabDebug: "Debug",
423
+ syncDesc: "Manually back up ZCode Pro settings and model settings to WebDAV, or restore them on a new computer. The two items are stored as separate files, so you can upload or overwrite either one alone; every upload also keeps a timestamped history version and download lets you pick any of them. Data moves only when you click, never automatically.",
343
424
  syncUrlPlaceholder: "https://dav.example.com/dav/",
344
425
  syncDir: "\u4E0A\u4F20\u76EE\u5F55",
345
426
  syncDirPlaceholder: "zcode-pro",
@@ -347,7 +428,7 @@
347
428
  syncPassword: "Password",
348
429
  syncSave: "Save",
349
430
  syncSaved: "Sync settings saved.",
350
- syncFormHint: "Sync files go to zcodepro.json and model.json inside the folder (default zcode-pro, multi-level allowed, created automatically).",
431
+ syncFormHint: "Sync files go inside the folder (default zcode-pro, multi-level allowed, created automatically): zcodepro.json and model.json are always the latest version; timestamped history versions are kept alongside.",
351
432
  syncRecLink: "Jianguoyun WebDAV",
352
433
  syncRecSuffix: " \u2014 use the app password, not your account password.",
353
434
  syncIncludeZcp: "ZCode Pro settings",
@@ -357,16 +438,44 @@
357
438
  syncUpload: "Upload",
358
439
  syncDownload: "Download",
359
440
  syncUploading: "Uploading\u2026",
360
- syncDownloading: "Downloading\u2026",
361
441
  syncUploaded: "Uploaded to the server.",
362
442
  syncDownloaded: "Restored from the server.",
363
443
  syncLastPush: "Last upload",
364
444
  syncLastPull: "Last download",
365
445
  syncNever: "never",
366
446
  syncConfirmTitle: "Download from the server",
367
- syncConfirmDesc: "Cloud data will overwrite the selected sections locally (a .zcodepro-backup copy is kept next to each file); your local sync config and credentials stay untouched. Restarting ZCode is recommended after restoring model settings. Download now?",
368
- syncConfirmGo: "Download",
447
+ syncConfirmDesc: "The selected file will overwrite the matching local section (a .zcodepro-backup copy is kept next to each file); your local sync config and credentials stay untouched. Restarting ZCode is recommended after restoring model settings. Import now?",
448
+ syncConfirmGo: "Import",
449
+ syncPickDesc: "Fixed names are always the latest version; timestamped files are the history each upload leaves behind. Import overwrites the matching local section.",
450
+ syncVerDeleteLatestNote: 'Note: this is the "latest" file that older zcode-pro installs download from; deleting it breaks their download. Timestamped history versions are not affected.',
369
451
  syncHint: "Sync credentials are stored locally in zcodepro.json only; model settings contain the API keys of custom providers and go to your own WebDAV only.",
452
+ syncTest: "Test",
453
+ syncTesting: "Testing\u2026",
454
+ syncTestEmpty: "Enter the WebDAV URL first",
455
+ syncTestFailed: "Test failed",
456
+ syncTestStepConnect: "Connect",
457
+ syncTestStepDir: "Folder",
458
+ syncTestStepList: "Files",
459
+ syncTestFilesFound: "{n} backup file(s) on the server",
460
+ syncPickTitle: "Select a file",
461
+ syncPickLoading: "Loading files from the server\u2026",
462
+ syncPickEmpty: "No backups on the server yet. Upload from a computer first.",
463
+ syncVerImport: "Import",
464
+ syncVerImporting: "Importing\u2026",
465
+ syncVerDelete: "Delete",
466
+ syncVerSave: "Save to disk",
467
+ syncVerSaving: "Saving\u2026",
468
+ syncVerLatest: "Latest",
469
+ syncVerDeleteTitle: "Delete server file",
470
+ syncVerDeleteDesc: "This backup will be deleted from the server and cannot be recovered: ",
471
+ syncVerDeleted: "Backup deleted from the server.",
472
+ syncVerSaved: "Saved to",
473
+ devMode: "Developer mode",
474
+ devModeRowDesc: 'Shows the "Reload ZCode Pro" button; can also be enabled with the ZCODEPRO_DEV=1 environment variable. This tab is visible only while developer mode is active.',
475
+ devModeDesc: 'For development: after changing zcode-pro code, click "Reload ZCode Pro" instead of restarting ZCode. It builds automatically (when running from the source repo), restarts the ZCode Pro launcher process (fresh helper and injected script), and every ZCode window refreshes and re-injects. The ZCode app itself and all sessions stay untouched.',
476
+ devReload: "Reload ZCode Pro",
477
+ devReloading: "Reloading\u2026",
478
+ devReloadDesc: "Every ZCode window refreshes automatically within 2-3 seconds to load the new script.",
370
479
  rowGapName: "Paragraph spacing",
371
480
  rowGapDesc: "Vertical spacing between text blocks (turns, paragraphs inside answers).",
372
481
  listSpacingName: "List spacing",
@@ -383,6 +492,8 @@
383
492
  tableCellPaddingDesc: "Padding inside table cells; left input is vertical, right is horizontal.",
384
493
  lineHeightName: "Answer line height",
385
494
  lineHeightDesc: "Line height of answer text (multiplier).",
495
+ thinkingLineHeightName: "Work-log line height",
496
+ thinkingLineHeightDesc: "Line height of the \u201Cworked for\u2026\u201D history panel (tool entries and reasoning, multiplier).",
386
497
  userLineHeightName: "Question line height",
387
498
  userLineHeightDesc: "Line height of question text (multiplier).",
388
499
  contentWidthName: "Content width",
@@ -397,6 +508,30 @@
397
508
  featureSessionSwitchDesc: "alt+z toggles between the current and the last session; hold alt and press x/c to move the highlight across recently used sessions, release alt to switch (like alt+tab).",
398
509
  featureTitleLock: "Keep renamed session titles",
399
510
  featureTitleLockDesc: "Sessions you renamed keep their custom title: after new replies the app may show an auto-generated title instead, which reverts after a restart; this keeps the renamed title in the sidebar.",
511
+ tabTitleGen: "Task name prompt",
512
+ titleGenShortDesc: "When a new session starts answering, generates the task name with the chosen provider and prompt and saves it as a manual rename the built-in title never overrides.",
513
+ titleGenEnabled: "Enable",
514
+ titleGenProvider: "Provider",
515
+ titleGenProviderNone: "Custom endpoint (no provider)",
516
+ titleGenProviderSession: "Follow current session",
517
+ titleGenProviderDeleted: " (deleted)",
518
+ titleGenModel: "Model",
519
+ titleGenModelHint: "Pick from the list, or type a model id",
520
+ titleGenModelEmpty: "(not set)",
521
+ titleGenModelSessionHint: "Pick from the list; the first entry follows the session model",
522
+ titleGenModelFollowSession: "Follow the session model",
523
+ titleGenModelManualHint: "Type a model id",
524
+ titleGenBaseUrl: "Base URL",
525
+ titleGenApiKey: "API Key",
526
+ titleGenFormat: "Protocol",
527
+ titleGenPrompt: "Prompt",
528
+ titleGenPromptPlaceholder: "Leave empty for the built-in default prompt (button below shows it)",
529
+ titleGenPromptReset: "Use default prompt",
530
+ titleGenSample: "Test message",
531
+ titleGenTest: "Test",
532
+ titleGenTesting: "Generating\u2026",
533
+ titleGenTestOk: "Result: ",
534
+ titleGenTestFailed: "Generation failed",
400
535
  featureWsRunning: "Running indicator on collapsed projects (experimental)",
401
536
  featureWsRunningDesc: "Spins a collapsed project's icon while any of its sessions is still running; stops when they all finish or the project is expanded. While spinning, the project is briefly re-expanded in the background every 60 s to re-check and then collapsed \u2014 rows stay hidden, with only a subtle quarter-second height ripple at the project.",
402
537
  tabPerformance: "Performance",
@@ -446,7 +581,7 @@
446
581
  featurePinnedExpand: "Keep projects collapsed for pinned sessions (experimental)",
447
582
  featurePinnedExpandDesc: "Keeps the project collapsed after clicking a pinned session. Note: it briefly expands first, then collapses.",
448
583
  featureAutoUpdatePlugins: "Auto-update zcode-plugins plugins",
449
- featureAutoUpdatePluginsDesc: "Checks and updates installed zcode-plugins plugins on startup (including newly added ones). Updates apply to new sessions.",
584
+ featureAutoUpdatePluginsDesc: "Checks and updates installed zcode-plugins plugins on startup (updates only \u2014 never installs; uninstalled plugins stay uninstalled). Updates apply to new sessions.",
450
585
  pluginsUpdateTitle: "Plugin updates \xB7 zcode-plugins",
451
586
  pluginsCheckNow: "Check & update",
452
587
  pluginsUpdating: "Updating plugins\u2026",
@@ -458,7 +593,7 @@
458
593
  pluginsAutoUpdated: "zcode-plugins updated",
459
594
  tabVision: "Vision",
460
595
  visionDesc: "When the main model cannot see images (e.g. glm-5.3), images in messages are recognized by the vision models below and passed to the main model as text (zcode-vision plugin; /vision-* commands edit the same file). Changes save automatically.",
461
- visionEnabled: "Enable image recognition",
596
+ visionEnabled: "Enable",
462
597
  visionForceIntercept: "Intercept even if the main model sees images",
463
598
  visionMode: "Chain mode",
464
599
  visionModeFallback: "Fallback",
@@ -471,10 +606,6 @@
471
606
  visionMoveDown: "Down",
472
607
  visionName: "Name",
473
608
  visionUseProvider: "Provider",
474
- visionUseProviderHint: "Pick from the list or type: session = follow the current session provider, or a provider name/ID; baseUrl/API key/format follow that provider",
475
- visionUseProviderNone: "Not following (enter base URL)",
476
- visionUseProviderSession: "Follow session provider (session)",
477
- visionUseProviderUnknown: "Unknown provider; recognition will fail. Pick from the list or type session",
478
609
  visionSkipAfterFailures: "Skip after failures",
479
610
  visionSkipAfterFailuresHint: "After this many consecutive failures a proxy is skipped and the next one is tried; 0 = never skip",
480
611
  visionSkipMinutes: "Skip minutes",
@@ -483,21 +614,21 @@
483
614
  visionResetChainConfirm: "Reset chain mode and the proxy list to the default 2-level chain (glm-session \u2192 glm-flash)? Current proxy edits will be lost. Continue?",
484
615
  visionBaseUrl: "Base URL",
485
616
  visionModel: "Model",
486
- visionFormat: "Format",
487
617
  visionApiKey: "API Key",
488
618
  visionApiKeyHint: "Leave empty to reuse the GLM subscription key",
489
619
  visionPrompt: "Recognition prompt ({prev} = previous step in pipeline)",
490
620
  visionTest: "Test",
491
621
  visionTesting: "Testing\u2026",
492
622
  visionTestFailed: "Vision proxy test failed",
493
- visionTestOutput: "Test output",
494
623
  visionCompressKB: "Compress threshold KB",
495
624
  visionCompressKBHint: "Images above this size are compressed first (max edge 2000, JPEG); 0 = never",
496
625
  visionLoadFailed: "Failed to load the vision config",
497
626
  tabRtk: "rtk",
498
627
  rtkDesc: "rtk plugin: compresses common dev command output by 60-90% before it reaches the context (/rtk-* commands edit the same files). Changes save automatically and apply immediately.",
499
- rtkEnabled: "Enable rtk compression",
628
+ rtkEnabled: "Enable",
500
629
  rtkNotInstalled: "rtk is not installed. The settings below take effect after installation (/rtk-setup in a session).",
630
+ rtkInstallBin: "Install rtk binary",
631
+ rtkBinInstalled: "rtk installed; PATH takes effect for sessions after signing out and back in \u2014 the plugin hook works immediately",
501
632
  rtkWhitelistTitle: "Whitelist (pass through without compression)",
502
633
  rtkBuiltinLabel: "Built-in exemptions (read-only, follow plugin updates):",
503
634
  rtkWhitelistDesc: "Each entry is name (e.g. docker) or git:name (e.g. git:clone). Commands with tiny or no output need no compression; adding them skips compression. Built-ins (git add/commit/push, mkdir/cp, \u2026) cannot be removed. Delete the file to reset.",
@@ -510,12 +641,25 @@
510
641
  tabHeadroom: "Headroom",
511
642
  headroomDesc: "Headroom plugin: a local compression proxy on 127.0.0.1 (port 8787 by default) that compresses context sent to the model to save tokens (/hr-* commands edit the same file). Device and power-save changes apply immediately; switching the device restarts the proxy, so requests in progress may be interrupted once.",
512
643
  headroomBinMissing: "The headroom program was not found, so the proxy cannot start. The settings below take effect once it is installed.",
644
+ hrInstallBin: "Install headroom binary",
645
+ hrBinInstalled: "headroom installed; reopen a session and the hook will start the proxy automatically",
513
646
  headroomInstallTitle: "headroom plugin not installed",
514
647
  headroomInstallDesc: "Install it from the zcode-plugins marketplace to manage the compression proxy here; restarting the session afterwards is recommended.",
515
648
  headroomInstall: "Install",
516
649
  headroomInstalling: "Installing\u2026",
517
650
  headroomInstalled: "headroom plugin installed",
518
651
  headroomInstallRetry: "Installation did not take effect; retry or install it from the marketplace manually",
652
+ pluginInstallCardTitle: "{name} plugin not installed",
653
+ pluginInstallCardDesc: "Install it from the zcode-plugins marketplace to manage it here; restarting the session afterwards is recommended.",
654
+ pluginInstallDone: "{name} plugin installed",
655
+ pluginEnable: "Enable",
656
+ hrStarting: "Starting compression proxy\u2026",
657
+ hrStarted: "Compression proxy started",
658
+ hrStartTimeout: "Proxy start timed out (90s); check ~/.zcode-headroom-proxy.log",
659
+ hrStopping: "Stopping compression proxy\u2026",
660
+ hrRestarting: "Restarting compression proxy\u2026",
661
+ hrStopped: "Compression proxy stopped",
662
+ hrUsageNone: "No providers route through headroom; nothing changed",
519
663
  headroomLoadFailed: "Failed to load the Headroom config",
520
664
  headroomStatusTitle: "Status",
521
665
  headroomStatusLoadFailed: "Failed to load the status",
@@ -541,7 +685,7 @@
541
685
  headroomRestart: "Restart",
542
686
  headroomStopAction: "Stop",
543
687
  headroomStopTitle: "Stop the Headroom proxy",
544
- headroomStopDesc: "Providers in ZCode that point at this proxy (127.0.0.1) will fail to connect until it is started again. Stop it?",
688
+ headroomStopDesc: "Requests from ZCode and other apps pointing at this proxy (127.0.0.1) will fail until it is started again. headroom is shared \u2014 other tools may be using it. Stop it?",
545
689
  headroomStoppedWarn: "Proxy stopped; providers pointing at 127.0.0.1 cannot connect.",
546
690
  headroomBackend: "Compression device",
547
691
  headroomBackendAuto: "Auto \xB7 GPU first",
@@ -559,8 +703,6 @@
559
703
  headroomInterval: "Watch interval",
560
704
  headroomIntervalDesc: "Seconds between power checks (10\u20133600, default 60); applies from the next round, no restart needed.",
561
705
  headroomSecUnit: "s",
562
- headroomVersionPlugin: "plugin",
563
- headroomVersionBin: "program",
564
706
  upgradeCheck: "Check update",
565
707
  upgradeChecking: "Checking\u2026",
566
708
  upgradeUpToDate: "Up to date ({v})",
@@ -572,7 +714,6 @@
572
714
  upgradeDone: "Updated to {v}",
573
715
  upgradeFinished: "Update finished",
574
716
  upgradeCheckFailed: "Update check failed",
575
- version: "Version",
576
717
  close: "Close",
577
718
  cancel: "Cancel",
578
719
  pathLabel: "Path",
@@ -678,7 +819,9 @@
678
819
  "sync-remote-missing": "No synced data on the server yet; upload from another computer first",
679
820
  "sync-remote-invalid": "The URL already holds a different file; use another file path",
680
821
  "sync-request": "The WebDAV request failed",
681
- "sync-run": "Sync failed"
822
+ "sync-run": "Sync failed",
823
+ "sync-file-invalid": "Invalid backup file name",
824
+ "sync-export": "Failed to save the backup to disk"
682
825
  };
683
826
  function errText(res) {
684
827
  if (!res) return "";
@@ -753,8 +896,9 @@
753
896
  "div",
754
897
  {
755
898
  id: "__zcodepro_toast__",
756
- class: "fixed bottom-6 right-6 z-[100] flex max-w-md items-center gap-2 rounded-lg border px-4 py-3 text-ui-sm shadow-lg " + (kind === "error" ? "border-destructive/40 bg-menu text-foreground" : "border-border bg-menu text-foreground"),
757
- style: "animation:zcodepro-fade-in .18s ease"
899
+ class: "fixed bottom-6 z-[100] flex max-w-md items-center gap-2 rounded-lg border px-4 py-3 text-ui-sm shadow-lg " + (kind === "error" ? "border-destructive/40 bg-menu text-foreground" : "border-border bg-menu text-foreground"),
900
+ // left 用内联样式:ZCode 的 Tailwind 未编译 left-6/right-6 工具类(bottom-6 有)
901
+ style: "animation:zcodepro-fade-in .18s ease; left: 1.5rem;"
758
902
  },
759
903
  h("span", { class: "truncate" }, text)
760
904
  );
@@ -766,7 +910,7 @@
766
910
  var overlayClassBare = "fixed inset-0 isolate z-50 flex items-center justify-center duration-100 p-4 platform-linux-desktop:top-12";
767
911
  var contentClass = "w-full sm:max-w-md rounded-2xl border-none bg-popover/98 p-5 text-ui-base/relaxed text-foreground ring-border shadow-2xl";
768
912
  var dialogStack = [];
769
- function openDialog({ title, description, onMount, onClose, width = "sm:max-w-md", overlay = "dim", draggable = false, posKey = "", dismissOnOutside = true }) {
913
+ function openDialog({ title, description, onMount, onClose, width = "sm:max-w-md", overlay = "dim", draggable = false, posKey = "", dismissOnOutside = true, bodyClass = "" }) {
770
914
  const L = t();
771
915
  const prevActive = document.activeElement;
772
916
  let cleanupDrag = null;
@@ -810,7 +954,7 @@
810
954
  titleEl,
811
955
  ...description ? [h("p", { class: "mt-2 text-ui-sm/relaxed text-foreground-subtle" }, description)] : []
812
956
  );
813
- const body = h("div", { class: "mt-4 zcodepro-dialog-body" });
957
+ const body = h("div", { class: "zcodepro-dialog-body " + (bodyClass || "mt-4") });
814
958
  content.append(body);
815
959
  let restorePos = null;
816
960
  if (draggable) {
@@ -1072,7 +1216,7 @@
1072
1216
  "data-state": checked ? "on" : "off"
1073
1217
  }, knob);
1074
1218
  const extraEl = extra ? h("div", {
1075
- class: "flex shrink-0 items-center pt-0.5",
1219
+ class: "flex shrink-0 items-center",
1076
1220
  onClick: (e) => {
1077
1221
  e.stopPropagation();
1078
1222
  },
@@ -1086,7 +1230,7 @@
1086
1230
  const row = h(
1087
1231
  "div",
1088
1232
  {
1089
- class: `flex cursor-pointer items-start gap-2 rounded-lg ${desc ? "p-2" : "p-1.5"} transition-colors hover:bg-surface-hover`,
1233
+ class: `flex cursor-pointer items-center gap-2 rounded-lg ${desc ? "p-2" : "p-1.5"} transition-colors hover:bg-surface-hover`,
1090
1234
  onClick: () => {
1091
1235
  onToggle();
1092
1236
  }
@@ -1174,13 +1318,14 @@
1174
1318
  align-items: center;
1175
1319
  justify-content: center;
1176
1320
  height: 24px;
1177
- padding: 0 14px;
1321
+ padding: 0 8px;
1178
1322
  border: none;
1179
1323
  border-radius: 9999px;
1180
1324
  background: transparent;
1181
1325
  cursor: pointer;
1182
1326
  font-size: 12px;
1183
1327
  line-height: 1;
1328
+ white-space: nowrap;
1184
1329
  color: var(--color-muted-foreground, #888);
1185
1330
  transition: color 0.15s ease, background-color 0.15s ease;
1186
1331
  }
@@ -1269,6 +1414,8 @@
1269
1414
  .zcodepro-textarea:disabled { opacity: 0.5; }
1270
1415
  /* \u5C0F\u578B\u6587\u672C\u57DF\uFF08\u89C6\u89C9\u4EE3\u7406\u63D0\u793A\u8BCD\u7B49\uFF09\uFF1A11rem \u6700\u5C0F\u9AD8\u5EA6\u53EA\u5C5E\u4E8E\u5168\u5C40\u63D0\u793A\u8BCD\u5927\u7F16\u8F91\u6846\uFF0C\u8FD9\u91CC\u6309 rows \u5B9A\u9AD8 */
1271
1416
  .zcodepro-textarea.zcodepro-textarea-sm { min-height: 0; }
1417
+ /* \u5168\u5C40\u63D0\u793A\u8BCD\u5927\u7F16\u8F91\u6846\uFF1A\u9ED8\u8BA4/\u6700\u5927\u9AD8\u5EA6\u90FD\u6BD4\u9ED8\u8BA4\u6587\u672C\u57DF\u66F4\u9AD8 */
1418
+ .zcodepro-textarea.zcodepro-textarea-lg { min-height: 16rem; max-height: 40rem; }
1272
1419
  /* \u884C\u9AD8\u6ED1\u6746\uFF08\u6837\u5F0F\u8C03\u6574\u6807\u7B7E\u9875\uFF09 */
1273
1420
  .zcodepro-range {
1274
1421
  -webkit-appearance: none;
@@ -1454,6 +1601,14 @@
1454
1601
  }
1455
1602
  /* \u539F\u4FA7\u680F\u83DC\u5355\u6574\u4F53\u9690\u85CF\uFF08toolbar-icons \u5F00\u542F\u65F6\uFF09 */
1456
1603
  .zcodepro-tb-hidden { display: none !important; }
1604
+ /* \u539F\u751F\u9876\u680F\u65B0\u5EFA\u4EFB\u52A1\u6309\u94AE\uFF08\u6587\u4EF6\u67E5\u770B\u7B49\u5B50\u9875\u5C55\u5F00\u7684\u90A3\u4E2A\uFF09\uFF1A\u5E38\u9A7B DOM\u3001\u9760\u52A8\u753B\u5BB9\u5668
1605
+ \u5728 w-7/w-0 \u95F4\u5207\u6362\u663E\u9690\u3002\u529F\u80FD\u5F00\u542F\u671F\u95F4\u6574\u4F53\u9489\u6B7B\u6536\u8D77\u6001\u2014\u2014\u65B0\u5EFA\u5165\u53E3\u7EDF\u4E00\u7531\u6211\u4EEC
1606
+ \u7684\u6309\u94AE\u884C\u63D0\u4F9B\uFF0C\u4E5F\u5C31\u4E0D\u518D\u9700\u8981\u968F\u5B50\u9875\u8FDB\u51FA\u52A8\u6001\u663E\u9690\u81EA\u5DF1\u7684\u65B0\u5EFA\u6309\u94AE */
1607
+ .zcodepro-tb-hidden-native div[class*="transition-[opacity,width]"] {
1608
+ width: 0 !important;
1609
+ opacity: 0 !important;
1610
+ pointer-events: none !important;
1611
+ }
1457
1612
  /* \u5DF2\u7F6E\u9876\u5206\u533A\u53EF\u6298\u53E0\uFF08pinned-collapse.js\uFF09\uFF1A\u6807\u9898\u6574\u884C\u53EF\u70B9\uFF0C\u6298\u53E0\u65F6\u85CF\u8D77\u4EFB\u52A1\u5217\u8868\uFF0C
1458
1613
  chevron \u65CB\u5411\u4E0E\u201C\u9879\u76EE\u201D\u5206\u533A\u4E00\u81F4\uFF08\u6536\u8D77\u6307\u5411\u53F3\u4FA7\uFF09 */
1459
1614
  .zcodepro-pin-head {
@@ -2108,6 +2263,12 @@
2108
2263
  const path = resolveTargetPath(content);
2109
2264
  if (!path) return;
2110
2265
  const L = t();
2266
+ const fire = (endpoint, label) => {
2267
+ rpc(endpoint, { method: "POST", body: { path } }).then((r) => {
2268
+ if (r && r.ok === false) showToast(`${label}\u5931\u8D25\uFF1A${r.error || "\u672A\u77E5\u9519\u8BEF"}`, "error");
2269
+ }).catch(() => {
2270
+ });
2271
+ };
2111
2272
  const noApps = items.find((el) => NO_APPS_TEXTS.some((x) => itemText(el) === x)) || content.querySelector('[data-disabled][role="menuitem"]');
2112
2273
  const openAnchor = noApps && content.contains(noApps) ? noApps : copyAbs;
2113
2274
  appendMenuItem(content, copyAbs, openAnchor, {
@@ -2115,10 +2276,7 @@
2115
2276
  label: L.fileOpenDefault,
2116
2277
  icon: ICON_OPEN_DEFAULT,
2117
2278
  onPick: () => {
2118
- try {
2119
- void rpc("/open-path", { method: "POST", body: { path } });
2120
- } catch {
2121
- }
2279
+ void fire("/open-path", L.fileOpenDefault);
2122
2280
  }
2123
2281
  });
2124
2282
  appendMenuItem(content, copyAbs, copyAbs, {
@@ -2126,10 +2284,7 @@
2126
2284
  label: L.fileReveal,
2127
2285
  icon: ICON_REVEAL,
2128
2286
  onPick: () => {
2129
- try {
2130
- void rpc("/reveal-path", { method: "POST", body: { path } });
2131
- } catch {
2132
- }
2287
+ void fire("/reveal-path", L.fileReveal);
2133
2288
  }
2134
2289
  });
2135
2290
  })();
@@ -2341,6 +2496,56 @@
2341
2496
  }, true);
2342
2497
  }
2343
2498
 
2499
+ // src/inject/provider-fields.js
2500
+ var PROVIDER_FIELD_CLS = "h-8 w-full rounded-lg border border-border bg-input px-2.5 text-ui-sm text-foreground outline-none transition-shadow placeholder:text-foreground-subtle focus-visible:border-ring focus-visible:ring-2 focus-visible:ring-ring/40";
2501
+ function comboField(current2, options, onChange, { disabled = false, placeholder = "", staleLabel = "\uFF08\u624B\u52A8\u8F93\u5165\uFF09", emptyLabel = "" } = {}) {
2502
+ const sel = h("select", { class: PROVIDER_FIELD_CLS, disabled: disabled || void 0 });
2503
+ const input = h("input", { type: "text", value: current2, placeholder, class: PROVIDER_FIELD_CLS, disabled: disabled || void 0 });
2504
+ const sync3 = () => {
2505
+ const cur = input.value;
2506
+ const opts = [...options];
2507
+ if (cur && !opts.some((o) => o.value === cur)) opts.push({ value: cur, label: `${cur}${staleLabel}` });
2508
+ if (!cur && !opts.some((o) => o.value === "")) opts.unshift({ value: "", label: emptyLabel });
2509
+ sel.replaceChildren(...opts.map((o) => h("option", { value: o.value, title: o.value }, o.label)));
2510
+ sel.value = cur;
2511
+ };
2512
+ sel.addEventListener("change", () => {
2513
+ input.value = sel.value;
2514
+ onChange(sel.value, "select");
2515
+ });
2516
+ input.addEventListener("input", () => onChange(input.value, "type"));
2517
+ input.addEventListener("blur", () => sync3());
2518
+ sync3();
2519
+ return h("div", { class: "flex flex-col gap-1" }, sel, input);
2520
+ }
2521
+ function providerOptionsOf(providers, L) {
2522
+ return [
2523
+ { value: "", label: L.titleGenProviderNone },
2524
+ { value: "session", label: L.titleGenProviderSession },
2525
+ ...providers.map((p) => ({ value: p.id, label: p.name }))
2526
+ ];
2527
+ }
2528
+ function modelOptionsOf(providerValue, providers, { followSession = false, followSessionLabel = "" } = {}) {
2529
+ const v = (providerValue || "").trim();
2530
+ const cur = providers.find((p) => p.id === v);
2531
+ if (cur) return cur.models.map((m) => ({ value: m, label: m }));
2532
+ if (v === "session") {
2533
+ const head = followSession && followSessionLabel ? [{ value: "", label: followSessionLabel }] : [];
2534
+ return [...head, ...[...new Set(providers.flatMap((p) => p.models))].map((m) => ({ value: m, label: m }))];
2535
+ }
2536
+ return [];
2537
+ }
2538
+ function modelPlaceholderOf(providerValue, L) {
2539
+ const v = (providerValue || "").trim();
2540
+ if (v === "session") return L.titleGenModelSessionHint;
2541
+ if (v === "") return L.titleGenModelManualHint;
2542
+ return L.titleGenModelHint;
2543
+ }
2544
+ function isManualConn(providerValue, providers) {
2545
+ const v = (providerValue || "").trim();
2546
+ return v === "" || v !== "session" && !providers.some((p) => p.id === v);
2547
+ }
2548
+
2344
2549
  // src/inject/features/styles.js
2345
2550
  var STYLE_DEFAULTS = {
2346
2551
  rowGap: 20,
@@ -2361,6 +2566,8 @@
2361
2566
  // 单元格左右边距(应用默认 3px)
2362
2567
  lineHeight: 1.75,
2363
2568
  // 回答行高(leading-[1.75],挂在答案内容容器上)
2569
+ thinkingLineHeight: 1.5,
2570
+ // 思考行高:“已工作/工作中”过程视图的行距(实测应用默认 1.5;范围见 buildCss 注释)
2364
2571
  userLineHeight: 1.5,
2365
2572
  // 提问行高(用户消息文本容器,默认 normal=1.5)
2366
2573
  contentWidth: null,
@@ -2457,6 +2664,12 @@
2457
2664
  if (typeof lh === "number" && Number.isFinite(lh) && lh >= 0.8) {
2458
2665
  parts.push(`${CONV} .space-y-4{line-height:${lh} !important;}`);
2459
2666
  }
2667
+ const tlh = styles.thinkingLineHeight;
2668
+ if (typeof tlh === "number" && Number.isFinite(tlh) && tlh >= 0.8) {
2669
+ parts.push(
2670
+ `[data-testid^="chat-assistant-history-content"]{line-height:${tlh} !important;}`
2671
+ );
2672
+ }
2460
2673
  const ulh = styles.userLineHeight;
2461
2674
  if (typeof ulh === "number" && Number.isFinite(ulh) && ulh >= 0.8) {
2462
2675
  parts.push(`${CONV} [class*="user-row"] .whitespace-pre-wrap{line-height:${ulh} !important;}`);
@@ -2553,7 +2766,10 @@
2553
2766
  tableCellPaddingV: `${CONV} td, ${CONV} th`,
2554
2767
  tableCellPaddingH: `${CONV} td, ${CONV} th`,
2555
2768
  codeLineHeight: `${CONV} diffs-container, ${CONV} pre`,
2556
- lineHeight: `${CONV} .space-y-4`,
2769
+ lineHeight: `${CONV} .space-y-4:not([data-reasoning-content] *)`,
2770
+ // 思考行高作用在“已工作/工作中”过程视图容器上;视图折叠未挂载时不标
2771
+ // (触发行不属此设置范围,不额外描边)
2772
+ thinkingLineHeight: `[data-testid^="chat-assistant-history-content"]`,
2557
2773
  userLineHeight: `${CONV} [class*="user-row"] .whitespace-pre-wrap`,
2558
2774
  contentWidth: "[data-v4-timeline-content-column]",
2559
2775
  sidebarProjectSpacing: '[data-testid^="workspace-item-"]',
@@ -2600,11 +2816,13 @@
2600
2816
  } catch {
2601
2817
  }
2602
2818
  }
2603
- function makeUpgradeControls({ endpoint, metaRefresh }) {
2819
+ function makeUpgradeControls({ endpoint, metaRefresh, idleText }) {
2604
2820
  const L = t();
2605
2821
  const state = h("span", { class: "min-w-0 flex-1 truncate text-left text-ui-xs/relaxed text-foreground-subtle" });
2822
+ const hasIdle = () => typeof idleText === "function";
2606
2823
  const setState = (text, kind = "") => {
2607
- state.textContent = text;
2824
+ const shown = text || (hasIdle() ? idleText() || "" : "");
2825
+ state.textContent = shown ? kind === "error" ? shown : ` \xB7 ${shown}` : "";
2608
2826
  state.className = "min-w-0 flex-1 truncate text-left text-ui-xs/relaxed " + (kind === "error" ? "text-destructive" : "text-foreground-subtle");
2609
2827
  };
2610
2828
  let latest = null;
@@ -2722,6 +2940,7 @@
2722
2940
  const res = await rpc(endpoint + "/upgrade");
2723
2941
  if (res.ok && res.upgrade && res.upgrade.running) pollStart();
2724
2942
  })();
2943
+ setState("");
2725
2944
  return { btn, state, stop: stopPoll };
2726
2945
  }
2727
2946
  function openSettingsDialog() {
@@ -2732,8 +2951,10 @@
2732
2951
  openDialog({
2733
2952
  title: L.settingsTitle,
2734
2953
  // 宽度类必须用宿主样式表已有的工具类(注入的类名不会生成 CSS):
2735
- // sm:max-w-3xl 不存在会失去约束拉满全屏;max-w-2xl=42rem 存在且尺寸合适
2736
- width: "max-w-2xl",
2954
+ // sm:max-w-3xl 不存在会失去约束拉满全屏;max-w-3xl=48rem 存在——英文界面
2955
+ // 标签栏较长,42rem 会挤出一块空白并触发横向滚动
2956
+ width: "max-w-3xl",
2957
+ bodyClass: "mt-1",
2737
2958
  overlay: "none",
2738
2959
  draggable: true,
2739
2960
  posKey: "settings",
@@ -2794,7 +3015,9 @@
2794
3015
  const config = await getConfig(true);
2795
3016
  const agentsRes = await rpc("/agents");
2796
3017
  const visionRes = await rpc("/vision");
2797
- const visionProvidersRes = await rpc("/vision/providers");
3018
+ const titleGenRes = await rpc("/title-gen");
3019
+ const titleGenProvidersRes = await rpc("/title-gen/providers");
3020
+ const titleGenProviders = titleGenProvidersRes.ok && Array.isArray(titleGenProvidersRes.providers) ? titleGenProvidersRes.providers : [];
2798
3021
  const rtkRes = await rpc("/rtk");
2799
3022
  const headroomRes = await rpc("/headroom");
2800
3023
  const setFeature = async (key, value) => {
@@ -2816,7 +3039,7 @@
2816
3039
  "data-zcodepro-status": "1"
2817
3040
  },
2818
3041
  h("span", { class: "inline-block size-2 rounded-full bg-emerald-500" }),
2819
- h("span", { class: "text-foreground-subtle/70" }, `${L.version} ${health.version} \xB7 ${HELPER_URL}`)
3042
+ h("span", { class: "text-foreground-subtle/70" }, `${health.version} \xB7 ${HELPER_URL}`)
2820
3043
  );
2821
3044
  const rows = h("div", { class: "divide-y divide-border rounded-xl border border-border" });
2822
3045
  const refreshRows = () => {
@@ -2930,7 +3153,7 @@
2930
3153
  const runPluginsUpdate = async () => {
2931
3154
  pluginsBtn.disabled = true;
2932
3155
  pluginsStatusLine.textContent = L.pluginsUpdating;
2933
- const res = await rpc("/plugins/update", { method: "POST", body: { installMissing: true } });
3156
+ const res = await rpc("/plugins/update", { method: "POST", body: {} });
2934
3157
  pluginsBtn.disabled = false;
2935
3158
  if (!res.ok) {
2936
3159
  pluginsStatusLine.textContent = "";
@@ -2985,25 +3208,30 @@
2985
3208
  );
2986
3209
  const paneStyles = h("div", { role: "tabpanel", class: "mt-4", style: "display:none" });
2987
3210
  const panePerf = h("div", { role: "tabpanel", class: "mt-4", style: "display:none" });
2988
- const paneAgents = h("div", { role: "tabpanel", class: "mt-4", style: "display:none" });
3211
+ const paneAgents = h("div", { role: "tabpanel", class: "mt-2", style: "display:none" });
2989
3212
  const paneProxy = h("div", { role: "tabpanel", class: "mt-4", style: "display:none" });
2990
3213
  const paneSync = h("div", { role: "tabpanel", class: "mt-4", style: "display:none" });
2991
3214
  const paneVision = h("div", { role: "tabpanel", class: "mt-4", style: "display:none" });
2992
3215
  const paneRtk = h("div", { role: "tabpanel", class: "mt-4", style: "display:none" });
2993
3216
  const paneHeadroom = h("div", { role: "tabpanel", class: "mt-4", style: "display:none" });
2994
- const panes = { features: paneFeatures, styles: paneStyles, perf: panePerf, agents: paneAgents, proxy: paneProxy, sync: paneSync, vision: paneVision, headroom: paneHeadroom, rtk: paneRtk };
3217
+ const paneTitleGen = h("div", { role: "tabpanel", class: "mt-4", style: "display:none" });
3218
+ const paneDebug = h("div", { role: "tabpanel", class: "mt-4", style: "display:none" });
3219
+ const panes = { features: paneFeatures, styles: paneStyles, perf: panePerf, agents: paneAgents, proxy: paneProxy, sync: paneSync, vision: paneVision, headroom: paneHeadroom, rtk: paneRtk, debug: paneDebug };
2995
3220
  const tabDefs = [
2996
3221
  ["features", L.tabFeatures],
3222
+ ["agents", L.tabBehavior],
2997
3223
  ["styles", L.tabStyles],
2998
3224
  ["perf", L.tabPerformance],
2999
- ["agents", L.tabAgents],
3000
3225
  ["proxy", L.tabProxy],
3001
3226
  ["sync", L.tabSync],
3002
3227
  ["vision", L.tabVision],
3003
3228
  ["headroom", L.tabHeadroom],
3004
- ["rtk", L.tabRtk]
3229
+ ["rtk", L.tabRtk],
3230
+ // “调试”标签页只对开发模式生效的用户显示(配置开关或 ZCODEPRO_DEV=1),
3231
+ // 普通用户不可见
3232
+ ...config.devMode === true || config.devActive === true ? [["debug", L.tabDebug]] : []
3005
3233
  ];
3006
- const tablist = h("div", { role: "tablist", "aria-orientation": "horizontal", class: "zcodepro-tablist mt-4" });
3234
+ const tablist = h("div", { role: "tablist", "aria-orientation": "horizontal", class: "zcodepro-tablist mt-1" });
3007
3235
  const renderTabs = () => tablist.replaceChildren(...tabDefs.map(([id, label]) => h("button", {
3008
3236
  type: "button",
3009
3237
  role: "tab",
@@ -3119,6 +3347,7 @@
3119
3347
  styleCell(L.rowGapName, L.rowGapDesc, "rowGap"),
3120
3348
  styleCell(L.userLineHeightName, L.userLineHeightDesc, "userLineHeight", { min: 1, max: 3, step: 0.05, unit: "x" }),
3121
3349
  styleCell(L.lineHeightName, L.lineHeightDesc, "lineHeight", { min: 1, max: 3, step: 0.05, unit: "x" }),
3350
+ styleCell(L.thinkingLineHeightName, L.thinkingLineHeightDesc, "thinkingLineHeight", { min: 1, max: 3, step: 0.05, unit: "x" }),
3122
3351
  styleCell(L.codeLineHeightName, L.codeLineHeightDesc, "codeLineHeight", { min: 1, max: 3, step: 0.05, unit: "x" }),
3123
3352
  styleCell(L.listSpacingName, L.listSpacingDesc, "listSpacing"),
3124
3353
  styleCell(L.listItemSpacingName, L.listItemSpacingDesc, "listItemSpacing"),
@@ -3211,7 +3440,7 @@
3211
3440
  )
3212
3441
  );
3213
3442
  const agentsArea = h("textarea", {
3214
- class: "zcodepro-textarea",
3443
+ class: "zcodepro-textarea zcodepro-textarea-lg",
3215
3444
  placeholder: L.agentsPlaceholder,
3216
3445
  spellcheck: "false"
3217
3446
  });
@@ -3242,12 +3471,36 @@
3242
3471
  agentsSaveBtn.disabled = false;
3243
3472
  }
3244
3473
  };
3245
- paneAgents.append(
3474
+ const agentsPromptPane = h("div", { class: "mt-4" });
3475
+ agentsPromptPane.append(
3246
3476
  h("p", { class: "text-ui-sm/relaxed text-foreground-subtle" }, L.agentsDesc),
3247
3477
  ...agentsFailed ? [h("p", { class: "mt-2 text-ui-sm text-destructive" }, L.agentsLoadFailed + ": " + errText(agentsRes))] : [],
3248
3478
  h("div", { class: "mt-3" }, agentsArea),
3249
3479
  h("div", { class: "mt-3 flex justify-end" }, agentsSaveBtn)
3250
3480
  );
3481
+ let agentsSubTab = "prompt";
3482
+ const agentsSubPanes = { prompt: agentsPromptPane, titleGen: paneTitleGen };
3483
+ const agentsSubList = h("div", { role: "tablist", "aria-orientation": "horizontal", class: "zcodepro-tablist" });
3484
+ const renderAgentsSubTabs = () => {
3485
+ agentsSubList.replaceChildren(...[
3486
+ ["prompt", L.tabAgents],
3487
+ ["titleGen", L.tabTitleGen]
3488
+ ].map(([id, label]) => h("button", {
3489
+ type: "button",
3490
+ role: "tab",
3491
+ class: "zcodepro-tab",
3492
+ "aria-selected": String(agentsSubTab === id),
3493
+ "data-state": agentsSubTab === id ? "active" : "inactive",
3494
+ onClick: () => {
3495
+ agentsSubTab = id;
3496
+ for (const [pid, pane] of Object.entries(agentsSubPanes)) pane.style.display = pid === id ? "" : "none";
3497
+ renderAgentsSubTabs();
3498
+ }
3499
+ }, label)));
3500
+ };
3501
+ for (const [pid, pane] of Object.entries(agentsSubPanes)) pane.style.display = pid === agentsSubTab ? "" : "none";
3502
+ renderAgentsSubTabs();
3503
+ paneAgents.append(agentsSubList, agentsPromptPane, paneTitleGen);
3251
3504
  const savedProxy = typeof config.proxy === "string" ? config.proxy : "";
3252
3505
  const proxyInputCls = "h-8 w-full rounded-lg border border-border bg-input px-2.5 text-ui-sm text-foreground outline-none transition-shadow placeholder:text-foreground-subtle focus-visible:border-ring focus-visible:ring-2 focus-visible:ring-ring/40";
3253
3506
  const proxyInput = h("input", { type: "text", value: savedProxy, placeholder: L.proxyPlaceholder, spellcheck: "false", class: proxyInputCls + " min-w-0 flex-1" });
@@ -3278,6 +3531,11 @@
3278
3531
  }, "shrink-0");
3279
3532
  const runProxyTest = async () => {
3280
3533
  if (proxyTesting) return;
3534
+ if (!proxyInput.value.trim()) {
3535
+ proxyTestState.className = "mt-2 text-ui-xs/relaxed text-amber-500";
3536
+ proxyTestState.textContent = L.proxyTestEmpty;
3537
+ return;
3538
+ }
3281
3539
  proxyTesting = true;
3282
3540
  proxyTestBtn.disabled = true;
3283
3541
  proxyTestBtn.textContent = L.proxyTesting;
@@ -3297,6 +3555,37 @@
3297
3555
  proxyTestState.className = "mt-2 text-ui-xs/relaxed " + (allOk ? "text-foreground-subtle" : "text-amber-500");
3298
3556
  proxyTestState.textContent = `${res.proxy}\uFF1A${parts.join(" \xB7 ")}`;
3299
3557
  };
3558
+ const persistProxyScope = async (partial) => {
3559
+ const res = await rpc("/config", { method: "POST", body: { proxyScopes: partial } });
3560
+ if (!res.ok) {
3561
+ showToast(L.failed + ": " + errText(res), "error");
3562
+ return false;
3563
+ }
3564
+ if (res.config && res.config.proxyScopes) config.proxyScopes = res.config.proxyScopes;
3565
+ return true;
3566
+ };
3567
+ const proxyScopeWrap = h("div");
3568
+ const renderProxyScopes = () => {
3569
+ const sc = config.proxyScopes || {};
3570
+ proxyScopeWrap.replaceChildren(
3571
+ settingRow(L.proxyScopePlugins, L.proxyScopePluginsDesc, sc.plugins !== false, async () => {
3572
+ const next = !(sc.plugins !== false);
3573
+ if (await persistProxyScope({ plugins: next })) sc.plugins = next;
3574
+ renderProxyScopes();
3575
+ }),
3576
+ settingRow(L.proxyScopeDeps, L.proxyScopeDepsDesc, sc.deps !== false, async () => {
3577
+ const next = !(sc.deps !== false);
3578
+ if (await persistProxyScope({ deps: next })) sc.deps = next;
3579
+ renderProxyScopes();
3580
+ }),
3581
+ settingRow(L.proxyScopeSync, L.proxyScopeSyncDesc, sc.sync === true, async () => {
3582
+ const next = !(sc.sync === true);
3583
+ if (await persistProxyScope({ sync: next })) sc.sync = next;
3584
+ renderProxyScopes();
3585
+ })
3586
+ );
3587
+ };
3588
+ renderProxyScopes();
3300
3589
  paneProxy.append(
3301
3590
  h("p", { class: "text-ui-sm/relaxed text-foreground-subtle" }, L.proxyDesc),
3302
3591
  h(
@@ -3307,7 +3596,8 @@
3307
3596
  proxyTestBtn
3308
3597
  ),
3309
3598
  h("p", { class: "mt-2 text-ui-xs/relaxed text-foreground-subtle" }, L.proxyHint),
3310
- proxyTestState
3599
+ proxyTestState,
3600
+ h("div", { class: "mt-3 divide-y divide-border rounded-xl border border-border" }, proxyScopeWrap)
3311
3601
  );
3312
3602
  const perfToggleWrap = h("div");
3313
3603
  const renderPerfToggle = () => {
@@ -3414,6 +3704,43 @@
3414
3704
  syncCheckDirty();
3415
3705
  }
3416
3706
  };
3707
+ const syncTestState = h("p", { class: "mt-2 text-ui-xs/relaxed text-foreground-subtle" });
3708
+ let syncTesting = false;
3709
+ const syncTestBtn = btnSmall(L.syncTest, () => {
3710
+ void runSyncTest();
3711
+ }, "shrink-0");
3712
+ const runSyncTest = async () => {
3713
+ if (syncTesting) return;
3714
+ if (!syncUrlInput.value.trim()) {
3715
+ syncTestState.className = "mt-2 text-ui-xs/relaxed text-amber-500";
3716
+ syncTestState.textContent = L.syncTestEmpty;
3717
+ return;
3718
+ }
3719
+ syncTesting = true;
3720
+ syncTestBtn.disabled = true;
3721
+ syncTestBtn.textContent = L.syncTesting;
3722
+ syncTestState.textContent = "";
3723
+ syncTestState.className = "mt-2 text-ui-xs/relaxed text-foreground-subtle";
3724
+ const res = await rpc("/sync/test", { method: "POST", body: { sync: syncFormValues() } });
3725
+ syncTesting = false;
3726
+ syncTestBtn.disabled = false;
3727
+ syncTestBtn.textContent = L.syncTest;
3728
+ if (!res.ok) {
3729
+ syncTestState.className = "mt-2 text-ui-xs/relaxed text-destructive";
3730
+ syncTestState.textContent = L.syncTestFailed + ": " + errText(res);
3731
+ return;
3732
+ }
3733
+ const stepName = { connect: L.syncTestStepConnect, dir: L.syncTestStepDir, list: L.syncTestStepList };
3734
+ const parts = (res.targets || []).map((t2) => `${stepName[t2.step] || t2.step} ${t2.ok ? "\u2713" + (t2.ms != null ? " " + t2.ms + "ms" : "") : "\u2717 " + (t2.error || t2.httpCode || "")}`.trim());
3735
+ const allOk = (res.targets || []).length > 0 && (res.targets || []).every((t2) => t2.ok);
3736
+ let host = syncUrlInput.value.trim();
3737
+ try {
3738
+ host = new URL(host).host;
3739
+ } catch {
3740
+ }
3741
+ syncTestState.className = "mt-2 text-ui-xs/relaxed " + (allOk ? "text-foreground-subtle" : "text-amber-500");
3742
+ syncTestState.textContent = `${host}\uFF1A${parts.join(" \xB7 ")}` + (allOk && res.fileCount != null ? `\uFF08${L.syncTestFilesFound.replace("{n}", res.fileCount)}\uFF09` : "");
3743
+ };
3417
3744
  const JIANGUO_HELP = "https://help.jianguoyun.com/?p=2064";
3418
3745
  const syncRecLine = h("p", { class: "mt-1 text-ui-xs/relaxed text-foreground-subtle" });
3419
3746
  if (typeof window.zcode?.openExternal === "function") {
@@ -3457,56 +3784,216 @@
3457
3784
  };
3458
3785
  renderSyncStatus(savedSync);
3459
3786
  const syncUploadBtn = btnSmall(L.syncUpload, () => {
3460
- void runSyncDirection("push");
3787
+ void runSyncPush();
3461
3788
  }, "shrink-0", "primary");
3462
3789
  const syncDownloadBtn = btnSmall(L.syncDownload, () => {
3463
- confirmSyncDownload();
3790
+ openSyncVersionPicker();
3464
3791
  }, "shrink-0");
3465
- const confirmSyncDownload = () => {
3792
+ const openSyncVersionPicker = () => {
3466
3793
  openDialog({
3467
- title: L.syncConfirmTitle,
3468
- description: L.syncConfirmDesc,
3469
- width: "sm:max-w-md",
3470
- onMount: ({ body: confirmBody, close: closeConfirm }) => {
3471
- confirmBody.append(
3472
- dialogFooter(
3473
- btnSecondary(L.cancel, () => closeConfirm()),
3474
- btnPrimary(L.syncConfirmGo, () => {
3475
- closeConfirm();
3476
- void runSyncDirection("pull");
3477
- }, "min-w-24")
3478
- )
3479
- );
3794
+ title: L.syncPickTitle,
3795
+ description: L.syncPickDesc,
3796
+ width: "max-w-2xl",
3797
+ dismissOnOutside: false,
3798
+ onMount: async ({ body: pickBody, content: content2, close: closePicker }) => {
3799
+ const titleEl2 = content2 && content2.firstElementChild;
3800
+ if (titleEl2 && titleEl2.tagName === "H2") {
3801
+ titleEl2.classList.add("flex", "w-full", "items-center", "justify-between");
3802
+ titleEl2.replaceChildren(
3803
+ h("span", { class: "flex min-w-0 items-center" }, L.syncPickTitle),
3804
+ h("button", {
3805
+ type: "button",
3806
+ class: "zcodepro-close",
3807
+ title: L.close,
3808
+ "aria-label": L.close,
3809
+ onMousedown: (e) => {
3810
+ e.stopPropagation();
3811
+ },
3812
+ onClick: () => {
3813
+ closePicker();
3814
+ }
3815
+ }, "\u2715")
3816
+ );
3817
+ }
3818
+ const listWrap = h("div", { class: "space-y-1" });
3819
+ pickBody.append(listWrap);
3820
+ const fmtSize = (n) => {
3821
+ if (!Number.isFinite(n) || n <= 0) return "";
3822
+ return n >= 1048576 ? (n / 1048576).toFixed(2) + " MB" : Math.max(1, Math.round(n / 1024)) + " KB";
3823
+ };
3824
+ const setStatus = (el) => {
3825
+ listWrap.replaceChildren(el);
3826
+ };
3827
+ const setRowBusy = (btns, busy) => {
3828
+ for (const b of btns) b.disabled = busy;
3829
+ };
3830
+ const versionRow = (f) => {
3831
+ const isLatest = !/-\d{4}-\d{2}-\d{2}T/.test(f.name);
3832
+ const importBtn = btnSmall(L.syncVerImport, () => {
3833
+ confirmImport(f, rowBtns, importBtn);
3834
+ }, "shrink-0");
3835
+ const deleteBtn = btnSmall(L.syncVerDelete, () => {
3836
+ confirmDelete(f, rowBtns, isLatest);
3837
+ }, "shrink-0");
3838
+ const saveBtn = btnSmall(L.syncVerSave, () => {
3839
+ void exportVersion(f, rowBtns, saveBtn);
3840
+ }, "shrink-0");
3841
+ const rowBtns = [importBtn, deleteBtn, saveBtn];
3842
+ return h(
3843
+ "div",
3844
+ { class: "flex items-center gap-3 rounded-xl border border-border px-3 py-2" },
3845
+ h(
3846
+ "div",
3847
+ { class: "min-w-0 flex-1" },
3848
+ h(
3849
+ "div",
3850
+ { class: "flex min-w-0 items-center gap-1.5" },
3851
+ h("span", { class: "truncate font-mono text-ui-xs text-foreground", title: f.name }, f.name),
3852
+ ...isLatest ? [h("span", { class: "shrink-0 rounded border border-border px-1.5 text-ui-xs text-foreground-subtle" }, L.syncVerLatest)] : []
3853
+ ),
3854
+ h(
3855
+ "div",
3856
+ { class: "mt-0.5 text-ui-xs/relaxed text-foreground-subtle" },
3857
+ [fmtSize(f.size), f.lastModified ? new Date(f.lastModified).toLocaleString() : ""].filter(Boolean).join(" \xB7 ")
3858
+ )
3859
+ ),
3860
+ h("div", { class: "flex shrink-0 items-center gap-1.5" }, importBtn, deleteBtn, saveBtn)
3861
+ );
3862
+ };
3863
+ const loadList = async () => {
3864
+ setStatus(h("p", { class: "text-ui-sm/relaxed text-foreground-subtle" }, L.syncPickLoading));
3865
+ const res = await rpc("/sync/versions", { method: "POST", body: {} });
3866
+ if (!res.ok) {
3867
+ setStatus(h("p", { class: "text-ui-sm text-destructive" }, L.failed + ": " + errText(res)));
3868
+ return;
3869
+ }
3870
+ const files = res.files || [];
3871
+ if (!files.length) {
3872
+ setStatus(h("p", { class: "text-ui-sm/relaxed text-foreground-subtle" }, L.syncPickEmpty));
3873
+ return;
3874
+ }
3875
+ const parts = [];
3876
+ for (const [prefix, label] of [["zcodepro", L.syncIncludeZcp], ["model", L.syncIncludeModel]]) {
3877
+ const group = files.filter((f) => f.name === `${prefix}.json` || f.name.startsWith(`${prefix}-`));
3878
+ if (!group.length) continue;
3879
+ const latest = group.find((f) => f.name === `${prefix}.json`);
3880
+ const history = group.filter((f) => f !== latest).sort((a, b) => (b.lastModified || 0) - (a.lastModified || 0) || b.name.localeCompare(a.name));
3881
+ parts.push(h(
3882
+ "div",
3883
+ { class: "px-1 pt-2 text-ui-xs font-medium text-foreground-subtle" },
3884
+ `${label}${isZhLocale() ? "\uFF08" : " ("}${group.length}${isZhLocale() ? "\uFF09" : ")"}`
3885
+ ));
3886
+ for (const f of [latest, ...history]) {
3887
+ if (f) parts.push(versionRow(f));
3888
+ }
3889
+ }
3890
+ listWrap.replaceChildren(...parts);
3891
+ };
3892
+ const confirmImport = (f, rowBtns, importBtn) => {
3893
+ openDialog({
3894
+ title: L.syncConfirmTitle,
3895
+ width: "sm:max-w-md",
3896
+ onMount: ({ body: confirmBody, close: closeConfirm }) => {
3897
+ confirmBody.append(
3898
+ h("p", { class: "mb-2 truncate font-mono text-ui-xs text-foreground-subtle", title: f.name }, f.name),
3899
+ h("p", { class: "text-ui-sm/relaxed text-foreground-subtle" }, L.syncConfirmDesc),
3900
+ dialogFooter(
3901
+ btnSecondary(L.cancel, () => closeConfirm()),
3902
+ btnPrimary(L.syncConfirmGo, () => {
3903
+ closeConfirm();
3904
+ void importVersion(f, rowBtns, importBtn);
3905
+ }, "min-w-24")
3906
+ )
3907
+ );
3908
+ }
3909
+ });
3910
+ };
3911
+ const importVersion = async (f, rowBtns, importBtn) => {
3912
+ setRowBusy(rowBtns, true);
3913
+ importBtn.textContent = L.syncVerImporting;
3914
+ const res = await rpc("/sync/version/apply", { method: "POST", body: { file: f.name } });
3915
+ setRowBusy(rowBtns, false);
3916
+ importBtn.textContent = L.syncVerImport;
3917
+ if (!res.ok) {
3918
+ showToast(L.failed + ": " + errText(res), "error");
3919
+ return;
3920
+ }
3921
+ closePicker();
3922
+ renderSyncStatus(res.sync || {});
3923
+ clearConfigCache();
3924
+ window.dispatchEvent(new CustomEvent("zcodepro:config-changed"));
3925
+ Object.assign(config, await getConfig(true));
3926
+ refreshRows();
3927
+ renderToolbarSwitch();
3928
+ const names = (res.applied || []).map((k) => k === "model" ? L.syncIncludeModel : L.syncIncludeZcp).join(isZhLocale() ? "\u3001" : ", ");
3929
+ showToast(names ? `${L.syncDownloaded}\uFF08${names}\uFF09` : L.syncDownloaded, "success");
3930
+ };
3931
+ const confirmDelete = (f, rowBtns, isLatest) => {
3932
+ openDialog({
3933
+ title: L.syncVerDeleteTitle,
3934
+ width: "sm:max-w-md",
3935
+ onMount: ({ body: confirmBody, close: closeConfirm }) => {
3936
+ confirmBody.append(
3937
+ h(
3938
+ "p",
3939
+ { class: "text-ui-sm/relaxed text-foreground-subtle" },
3940
+ L.syncVerDeleteDesc,
3941
+ h("span", { class: "font-mono" }, f.name)
3942
+ ),
3943
+ ...isLatest ? [h("p", { class: "mt-2 text-ui-xs/relaxed text-amber-500" }, L.syncVerDeleteLatestNote)] : [],
3944
+ dialogFooter(
3945
+ btnSecondary(L.cancel, () => closeConfirm()),
3946
+ btnPrimary(L.syncVerDelete, () => {
3947
+ closeConfirm();
3948
+ void deleteVersion(f, rowBtns);
3949
+ }, "min-w-24")
3950
+ )
3951
+ );
3952
+ }
3953
+ });
3954
+ };
3955
+ const deleteVersion = async (f, rowBtns) => {
3956
+ setRowBusy(rowBtns, true);
3957
+ const res = await rpc("/sync/version/delete", { method: "POST", body: { file: f.name } });
3958
+ setRowBusy(rowBtns, false);
3959
+ if (!res.ok) {
3960
+ showToast(L.failed + ": " + errText(res), "error");
3961
+ return;
3962
+ }
3963
+ showToast(L.syncVerDeleted, "success");
3964
+ void loadList();
3965
+ };
3966
+ const exportVersion = async (f, rowBtns, saveBtn) => {
3967
+ setRowBusy(rowBtns, true);
3968
+ saveBtn.textContent = L.syncVerSaving;
3969
+ const res = await rpc("/sync/version/export", { method: "POST", body: { file: f.name } });
3970
+ setRowBusy(rowBtns, false);
3971
+ saveBtn.textContent = L.syncVerSave;
3972
+ if (!res.ok) {
3973
+ if (res.code !== "export-cancelled") showToast(L.failed + ": " + errText(res), "error");
3974
+ return;
3975
+ }
3976
+ showToast(`${L.syncVerSaved} ${res.path}`, "success");
3977
+ };
3978
+ void loadList();
3480
3979
  }
3481
3980
  });
3482
3981
  };
3483
- const runSyncDirection = async (direction) => {
3982
+ const runSyncPush = async () => {
3484
3983
  syncUploadBtn.disabled = true;
3485
3984
  syncDownloadBtn.disabled = true;
3486
- const btn = direction === "push" ? syncUploadBtn : syncDownloadBtn;
3487
- const idleText = btn.textContent;
3488
- btn.textContent = direction === "push" ? L.syncUploading : L.syncDownloading;
3489
- const res = await rpc("/sync/run", { method: "POST", body: { direction } });
3985
+ const idleText = syncUploadBtn.textContent;
3986
+ syncUploadBtn.textContent = L.syncUploading;
3987
+ const res = await rpc("/sync/run", { method: "POST", body: { direction: "push" } });
3490
3988
  syncUploadBtn.disabled = false;
3491
3989
  syncDownloadBtn.disabled = false;
3492
- btn.textContent = idleText;
3990
+ syncUploadBtn.textContent = idleText;
3493
3991
  if (!res.ok) {
3494
3992
  showToast(L.failed + ": " + errText(res), "error");
3495
3993
  return;
3496
3994
  }
3497
3995
  renderSyncStatus(res.sync || {});
3498
- if (direction === "pull") {
3499
- clearConfigCache();
3500
- window.dispatchEvent(new CustomEvent("zcodepro:config-changed"));
3501
- Object.assign(config, await getConfig(true));
3502
- refreshRows();
3503
- renderToolbarSwitch();
3504
- }
3505
- if (direction === "push") showToast(L.syncUploaded, "success");
3506
- else {
3507
- const names = (res.applied || []).map((k) => k === "model" ? L.syncIncludeModel : L.syncIncludeZcp).join(isZhLocale() ? "\u3001" : ", ");
3508
- showToast(names ? `${L.syncDownloaded}\uFF08${names}\uFF09` : L.syncDownloaded, "success");
3509
- }
3996
+ showToast(L.syncUploaded, "success");
3510
3997
  };
3511
3998
  paneSync.append(
3512
3999
  h("p", { class: "text-ui-sm/relaxed text-foreground-subtle" }, L.syncDesc),
@@ -3526,9 +4013,10 @@
3526
4013
  h("p", { class: "text-ui-xs/relaxed text-foreground-subtle" }, L.syncFormHint),
3527
4014
  syncRecLine
3528
4015
  ),
3529
- syncSaveBtn
4016
+ h("div", { class: "flex shrink-0 items-center gap-1.5" }, syncSaveBtn, syncTestBtn)
3530
4017
  )
3531
4018
  ),
4019
+ syncTestState,
3532
4020
  h("div", { class: "mt-2 divide-y divide-border rounded-xl border border-border" }, syncIncludeWrap),
3533
4021
  h(
3534
4022
  "div",
@@ -3538,6 +4026,59 @@
3538
4026
  ),
3539
4027
  h("p", { class: "mt-2 text-ui-xs/relaxed text-foreground-subtle" }, L.syncHint)
3540
4028
  );
4029
+ const persistDevMode = async (v) => {
4030
+ const res = await rpc("/config", { method: "POST", body: { devMode: v } });
4031
+ if (!res.ok) {
4032
+ showToast(L.failed + ": " + errText(res), "error");
4033
+ return false;
4034
+ }
4035
+ if (res.config) config.devMode = res.config.devMode;
4036
+ return true;
4037
+ };
4038
+ const devReloadBtn = btnSmall(L.devReload, () => {
4039
+ void runDevReload();
4040
+ }, "shrink-0", "primary");
4041
+ const runDevReload = async () => {
4042
+ devReloadBtn.disabled = true;
4043
+ devReloadBtn.textContent = L.devReloading;
4044
+ try {
4045
+ await rpc("/dev/reload", { method: "POST", body: {} });
4046
+ } catch {
4047
+ }
4048
+ setTimeout(() => {
4049
+ try {
4050
+ location.reload();
4051
+ } catch {
4052
+ }
4053
+ }, 2e3);
4054
+ };
4055
+ const devModeWrap = h("div");
4056
+ const renderDevPane = () => {
4057
+ const dev = config.devMode === true;
4058
+ devModeWrap.replaceChildren(
4059
+ h("p", { class: "text-ui-sm/relaxed text-foreground-subtle" }, L.devModeDesc),
4060
+ h(
4061
+ "div",
4062
+ { class: "mt-3 divide-y divide-border rounded-xl border border-border" },
4063
+ settingRow(L.devMode, L.devModeRowDesc, dev, async () => {
4064
+ if (await persistDevMode(!dev)) renderDevPane();
4065
+ }),
4066
+ ...dev ? [h(
4067
+ "div",
4068
+ { class: "flex items-center justify-between gap-2 px-2 py-1.5" },
4069
+ h(
4070
+ "div",
4071
+ { class: "min-w-0" },
4072
+ h("div", { class: "text-ui-sm font-medium text-foreground" }, L.devReload),
4073
+ h("div", { class: "mt-0.5 text-ui-xs/relaxed text-foreground-subtle" }, L.devReloadDesc)
4074
+ ),
4075
+ devReloadBtn
4076
+ )] : []
4077
+ )
4078
+ );
4079
+ };
4080
+ renderDevPane();
4081
+ paneDebug.append(devModeWrap);
3541
4082
  const VISION_DEFAULT_PROMPT = "\u8BF7\u8BE6\u7EC6\u63CF\u8FF0\u8FD9\u5F20\u56FE\u7247\u7684\u5168\u90E8\u5185\u5BB9\u3002\u82E5\u662F\u754C\u9762\u6216\u56FE\u8868\u622A\u56FE\uFF0C\u8BF7\u5148\u628A\u6240\u6709\u9519\u8BEF\u3001\u8B66\u544A\u3001\u5F02\u5E38\u72B6\u6001\u9010\u5B57\u5F15\u7528\u51FA\u6765\uFF08\u542B\u5B8C\u6574\u539F\u6587\uFF09\uFF0C\u518D\u63CF\u8FF0\u6574\u4F53\u5E03\u5C40\u3001\u6587\u5B57\u4E0E\u5173\u952E\u6570\u636E\u3002";
3542
4083
  const VISION_DEFAULT_CFG = {
3543
4084
  enabled: true,
@@ -3555,7 +4096,6 @@
3555
4096
  skipMinutes: 30,
3556
4097
  forceIntercept: true
3557
4098
  };
3558
- const visionProviders = visionProvidersRes.ok && Array.isArray(visionProvidersRes.providers) ? visionProvidersRes.providers : [];
3559
4099
  let visionCfg = visionRes.ok && visionRes.config && typeof visionRes.config === "object" ? structuredClone(visionRes.config) : null;
3560
4100
  let visionSaveTimer = null;
3561
4101
  const persistVision = async () => {
@@ -3575,7 +4115,57 @@
3575
4115
  style: "display:none"
3576
4116
  });
3577
4117
  const visionInputCls = "h-8 w-full rounded-lg border border-border bg-input px-2.5 text-ui-sm text-foreground outline-none transition-shadow placeholder:text-foreground-subtle focus-visible:border-ring focus-visible:ring-2 focus-visible:ring-ring/40";
4118
+ const pluginInstallCard = (pluginName, onInstalled) => {
4119
+ const installBtn = btnSmall(L.headroomInstall, () => {
4120
+ void runInstall();
4121
+ }, "shrink-0");
4122
+ const installState = h("div", { class: "mt-0.5 truncate text-ui-xs/relaxed text-foreground-subtle" });
4123
+ const runInstall = async () => {
4124
+ installBtn.disabled = true;
4125
+ installBtn.textContent = L.headroomInstalling;
4126
+ installState.textContent = "";
4127
+ const res = await rpc("/plugins/update", { method: "POST", body: { name: pluginName } });
4128
+ if (!res.ok) {
4129
+ installBtn.disabled = false;
4130
+ installBtn.textContent = L.headroomInstall;
4131
+ showToast(L.pluginsUpdateFailed + ": " + errText(res), "error");
4132
+ return;
4133
+ }
4134
+ if (onInstalled) await onInstalled();
4135
+ showToast(L.pluginInstallDone.replaceAll("{name}", pluginName), "success");
4136
+ };
4137
+ return h(
4138
+ "div",
4139
+ { class: "mt-3 flex items-center justify-between gap-2 rounded-xl border border-border p-2" },
4140
+ h(
4141
+ "div",
4142
+ { class: "min-w-0" },
4143
+ h("div", { class: "text-ui-sm font-medium text-foreground" }, L.pluginInstallCardTitle.replaceAll("{name}", pluginName)),
4144
+ h("div", { class: "mt-0.5 text-ui-xs/relaxed text-foreground-subtle" }, L.pluginInstallCardDesc),
4145
+ installState
4146
+ ),
4147
+ installBtn
4148
+ );
4149
+ };
3578
4150
  const renderVision = () => {
4151
+ if (!visionRes.ok) {
4152
+ paneVision.replaceChildren(
4153
+ h("p", { class: "text-ui-sm/relaxed text-foreground-subtle" }, L.visionDesc),
4154
+ h("p", { class: "mt-2 text-ui-sm text-destructive" }, L.visionLoadFailed + ": " + errText(visionRes))
4155
+ );
4156
+ return;
4157
+ }
4158
+ if (!visionRes.hook) {
4159
+ paneVision.replaceChildren(
4160
+ h("p", { class: "text-ui-sm/relaxed text-foreground-subtle" }, L.visionDesc),
4161
+ pluginInstallCard("zcode-vision", async () => {
4162
+ const meta = await rpc("/vision");
4163
+ if (meta.ok) Object.assign(visionRes, meta);
4164
+ renderVision();
4165
+ })
4166
+ );
4167
+ return;
4168
+ }
3579
4169
  if (!visionCfg) {
3580
4170
  const initBtn = btnSmall(L.visionAddProxy, async () => {
3581
4171
  const res = await rpc("/vision", { method: "POST", body: { config: VISION_DEFAULT_CFG } });
@@ -3587,6 +4177,16 @@
3587
4177
  paneVision.replaceChildren(
3588
4178
  h("p", { class: "text-ui-sm/relaxed text-foreground-subtle" }, L.visionDesc),
3589
4179
  ...visionRes.ok ? [] : [h("p", { class: "mt-2 text-ui-sm text-destructive" }, L.visionLoadFailed + ": " + errText(visionRes))],
4180
+ // 信息卡(仅版本行;配置未建,启用开关随配置出现)
4181
+ h(
4182
+ "div",
4183
+ { class: "mt-3 rounded-xl border border-border p-2" },
4184
+ h(
4185
+ "div",
4186
+ { class: "flex flex-wrap items-center gap-2" },
4187
+ h("span", { class: "shrink-0 whitespace-nowrap text-ui-xs/relaxed text-foreground-subtle" }, `\u63D2\u4EF6 ${visionRes.pluginVersion || "\u2014"}`)
4188
+ )
4189
+ ),
3590
4190
  h("div", { class: "mt-3 flex justify-end" }, initBtn)
3591
4191
  );
3592
4192
  return;
@@ -3687,53 +4287,45 @@
3687
4287
  h(
3688
4288
  "div",
3689
4289
  { class: "mt-2 grid grid-cols-1 gap-2 sm:grid-cols-2" },
3690
- field(L.visionUseProvider, (() => {
3691
- const wrap = h("div", { class: "flex flex-col gap-1" });
3692
- const input = h("input", { type: "text", value: p.useProvider || "", placeholder: L.visionUseProviderHint, class: visionInputCls });
3693
- const warn = h("p", { class: "text-ui-xs text-destructive", style: "display:none" }, L.visionUseProviderUnknown);
3694
- const isKnown = (v) => !v || v === "session" || visionProviders.some((pr) => pr.id === v || pr.name === v || (pr.aliases || []).includes(v));
3695
- const sel = h("select", { class: visionInputCls });
3696
- const syncSelect = () => {
3697
- const cur = (p.useProvider || "").trim();
3698
- const opts = [h("option", { value: "" }, L.visionUseProviderNone), h("option", { value: "session" }, L.visionUseProviderSession)];
3699
- for (const pr of visionProviders) {
3700
- opts.push(h("option", { value: pr.id }, pr.name && pr.name !== pr.id ? `${pr.name}\uFF08${pr.id}\uFF09` : pr.id));
3701
- }
3702
- if (cur && !opts.some((o) => o.value === cur)) opts.push(h("option", { value: cur }, `${cur}\uFF08${L.visionUseProvider}\uFF09`));
3703
- sel.replaceChildren(...opts);
3704
- sel.value = cur;
3705
- };
3706
- sel.addEventListener("change", () => {
3707
- input.value = sel.value;
3708
- p.useProvider = sel.value.trim();
3709
- warn.style.display = "none";
4290
+ // 供应商与模型走 provider-fields 共用逻辑:跟随会话时模型下拉为
4291
+ // 全部供应商模型汇总,具体供应商为其模型列表;已删 id 标注「已删除」
4292
+ field(L.visionUseProvider, comboField(
4293
+ p.useProvider || "",
4294
+ providerOptionsOf(titleGenProviders, L),
4295
+ (v, src) => {
4296
+ p.useProvider = v;
3710
4297
  saveVisionSoon();
3711
- });
3712
- input.addEventListener("input", () => {
3713
- p.useProvider = input.value.trim();
4298
+ if (src === "select") renderVision();
4299
+ },
4300
+ { placeholder: L.titleGenProviderSession, staleLabel: L.titleGenProviderDeleted }
4301
+ )),
4302
+ // 跟随会话时模型下拉头部有「跟随当前会话当前模型」(值为空,
4303
+ // 插件 ≥1.6.1 在调用时解析为会话当前模型);具体供应商为其模型列表
4304
+ field(L.visionModel, comboField(
4305
+ p.model || "",
4306
+ modelOptionsOf(p.useProvider, titleGenProviders, {
4307
+ followSession: (p.useProvider || "").trim() === "session",
4308
+ followSessionLabel: L.titleGenModelFollowSession
4309
+ }),
4310
+ (v) => {
4311
+ p.model = v;
3714
4312
  saveVisionSoon();
3715
- });
3716
- input.addEventListener("blur", () => {
3717
- p.useProvider = input.value.trim();
3718
- warn.style.display = isKnown(p.useProvider) ? "none" : "";
3719
- syncSelect();
3720
- });
3721
- syncSelect();
3722
- wrap.append(sel, input, warn);
3723
- return wrap;
3724
- })()),
3725
- field(L.visionModel, textIn(p.model, (v) => {
3726
- p.model = v;
3727
- saveVisionSoon();
3728
- })),
3729
- field(L.visionBaseUrl, textIn(p.baseUrl, (v) => {
3730
- p.baseUrl = v;
3731
- saveVisionSoon();
3732
- })),
3733
- field(L.visionApiKey + " \xB7 " + L.visionApiKeyHint, textIn(p.apiKey, (v) => {
3734
- p.apiKey = v;
3735
- saveVisionSoon();
3736
- }, { type: "password" }))
4313
+ },
4314
+ {
4315
+ placeholder: modelPlaceholderOf(p.useProvider, L),
4316
+ emptyLabel: (p.useProvider || "").trim() === "session" ? L.titleGenModelFollowSession : L.titleGenModelEmpty
4317
+ }
4318
+ )),
4319
+ ...isManualConn(p.useProvider, titleGenProviders) ? [
4320
+ field(L.visionBaseUrl, textIn(p.baseUrl, (v) => {
4321
+ p.baseUrl = v;
4322
+ saveVisionSoon();
4323
+ })),
4324
+ field(L.visionApiKey + " \xB7 " + L.visionApiKeyHint, textIn(p.apiKey, (v) => {
4325
+ p.apiKey = v;
4326
+ saveVisionSoon();
4327
+ }, { type: "password" }))
4328
+ ] : []
3737
4329
  ),
3738
4330
  (() => {
3739
4331
  const n = h("textarea", { class: "zcodepro-textarea zcodepro-textarea-sm", rows: "3", placeholder: L.visionPrompt });
@@ -3759,7 +4351,26 @@
3759
4351
  });
3760
4352
  paneVision.replaceChildren(
3761
4353
  h("p", { class: "text-ui-sm/relaxed text-foreground-subtle" }, L.visionDesc),
3762
- // 链模式 + 两个开关合到一行(顶部已有简介,开关不再单占一块、不带描述)
4354
+ // 信息卡:插件版本 + 启用图片识别开关(与 rtk/headroom 面板同款式)
4355
+ h(
4356
+ "div",
4357
+ { class: "mt-3 rounded-xl border border-border p-2" },
4358
+ h(
4359
+ "div",
4360
+ { class: "flex flex-wrap items-center gap-2" },
4361
+ h("span", { class: "shrink-0 whitespace-nowrap text-ui-xs/relaxed text-foreground-subtle" }, `\u63D2\u4EF6 ${visionRes.pluginVersion || "\u2014"}`),
4362
+ h(
4363
+ "div",
4364
+ { class: "ml-auto" },
4365
+ settingRow(L.visionEnabled, "", visionCfg.enabled !== false, async () => {
4366
+ visionCfg.enabled = !(visionCfg.enabled !== false);
4367
+ await persistVision();
4368
+ renderVision();
4369
+ })
4370
+ )
4371
+ )
4372
+ ),
4373
+ // 链模式 + 强制拦截开关合到一行(启用开关已上移信息卡)
3763
4374
  h(
3764
4375
  "div",
3765
4376
  { class: "mt-3 flex flex-wrap items-center gap-x-4 gap-y-1.5 rounded-xl border border-border p-1" },
@@ -3769,11 +4380,6 @@
3769
4380
  h(
3770
4381
  "div",
3771
4382
  { class: "ml-auto flex items-center gap-4" },
3772
- settingRow(L.visionEnabled, "", visionCfg.enabled !== false, async () => {
3773
- visionCfg.enabled = !(visionCfg.enabled !== false);
3774
- await persistVision();
3775
- renderVision();
3776
- }),
3777
4383
  settingRow(L.visionForceIntercept, "", visionCfg.forceIntercept !== false, async () => {
3778
4384
  visionCfg.forceIntercept = !(visionCfg.forceIntercept !== false);
3779
4385
  await persistVision();
@@ -3855,13 +4461,16 @@
3855
4461
  };
3856
4462
  const rtkInputCls = "h-8 w-full rounded-lg border border-border bg-input px-2.5 text-ui-sm text-foreground outline-none transition-shadow placeholder:text-foreground-subtle focus-visible:border-ring focus-visible:ring-2 focus-visible:ring-ring/40";
3857
4463
  const rtkVersionLine = h("span", { class: "shrink-0 whitespace-nowrap text-ui-xs/relaxed text-foreground-subtle" });
4464
+ let rtkLocText = "";
3858
4465
  const renderRtkVersion = (meta) => {
3859
- const v = meta.version || "rtk";
3860
- rtkVersionLine.textContent = meta.binPath ? `${v} \xB7 ${meta.binPath}` : v;
4466
+ const v = String(meta.version || "").replace(/^rtk\s+/i, "").trim();
4467
+ rtkVersionLine.textContent = `\u63D2\u4EF6 ${meta.pluginVersion || "\u2014"}${v ? ` \xB7 rtk ${v}` : ""}`;
4468
+ rtkLocText = meta.binPath || "";
3861
4469
  };
3862
4470
  renderRtkVersion(rtkRes);
3863
4471
  const rtkUp = makeUpgradeControls({
3864
4472
  endpoint: "/rtk",
4473
+ idleText: () => rtkLocText,
3865
4474
  metaRefresh: async () => {
3866
4475
  const meta = await rpc("/rtk");
3867
4476
  if (meta.ok) {
@@ -3874,19 +4483,49 @@
3874
4483
  const rtkSwitchRow = h("div");
3875
4484
  const rtkVersionCard = h(
3876
4485
  "div",
3877
- { class: "mt-3 flex items-center gap-2 rounded-xl border border-border p-2" },
4486
+ { class: "mt-3 flex flex-wrap items-center gap-2 rounded-xl border border-border p-2" },
3878
4487
  rtkVersionLine,
3879
4488
  rtkUp.state,
3880
4489
  rtkUp.btn,
3881
- rtkSwitchRow
4490
+ h("div", { class: "ml-auto shrink-0" }, rtkSwitchRow)
3882
4491
  );
3883
4492
  const rtkBody = h("div");
4493
+ const rtkPluginRow = h("div");
4494
+ const renderRtkPluginRow = () => {
4495
+ rtkPluginRow.replaceChildren(...rtkRes.hook ? [] : [
4496
+ pluginInstallCard("rtk", async () => {
4497
+ const meta = await rpc("/rtk");
4498
+ if (meta.ok) {
4499
+ Object.assign(rtkRes, meta);
4500
+ renderRtkVersion(meta);
4501
+ rtkCfg = {
4502
+ installed: meta.installed !== false,
4503
+ version: meta.version || "",
4504
+ binPath: meta.binPath || "",
4505
+ mode: meta.mode === "off" ? "off" : "hint",
4506
+ whitelist: Array.isArray(meta.whitelist) ? [...meta.whitelist] : [],
4507
+ builtinGit: Array.isArray(meta.builtinGit) ? [...meta.builtinGit] : [],
4508
+ builtinPlain: Array.isArray(meta.builtinPlain) ? [...meta.builtinPlain] : []
4509
+ };
4510
+ }
4511
+ renderRtkPluginRow();
4512
+ renderRtk();
4513
+ })
4514
+ ]);
4515
+ };
4516
+ renderRtkPluginRow();
3884
4517
  paneRtk.append(
3885
4518
  h("p", { class: "text-ui-sm/relaxed text-foreground-subtle" }, L.rtkDesc),
4519
+ rtkPluginRow,
3886
4520
  rtkVersionCard,
3887
4521
  rtkBody
3888
4522
  );
3889
4523
  const renderRtk = () => {
4524
+ if (rtkRes.ok && !rtkRes.hook) {
4525
+ rtkVersionCard.style.display = "none";
4526
+ rtkBody.replaceChildren();
4527
+ return;
4528
+ }
3890
4529
  rtkVersionCard.style.display = rtkCfg && rtkCfg.installed ? "" : "none";
3891
4530
  if (!rtkCfg) {
3892
4531
  rtkBody.replaceChildren(
@@ -3932,7 +4571,55 @@
3932
4571
  })
3933
4572
  );
3934
4573
  rtkBody.replaceChildren(
3935
- ...rtkCfg.installed ? [] : [h("p", { class: "text-ui-sm text-amber-500" }, L.rtkNotInstalled)],
4574
+ ...rtkCfg.installed ? [] : [h(
4575
+ "div",
4576
+ { class: "flex items-center gap-2" },
4577
+ h("p", { class: "min-w-0 flex-1 text-ui-sm text-amber-500" }, L.rtkNotInstalled),
4578
+ // 一键安装本体:与“检查更新”同一条 helper 升级管线(未装则装到 ~/.local/bin)
4579
+ (() => {
4580
+ const b = btnSmall(L.rtkInstallBin, () => {
4581
+ void run();
4582
+ }, "shrink-0");
4583
+ const run = async () => {
4584
+ b.disabled = true;
4585
+ b.textContent = L.headroomInstalling;
4586
+ const res = await rpc("/rtk", { method: "POST", body: { upgrade: true } });
4587
+ if (!res.ok) {
4588
+ b.disabled = false;
4589
+ b.textContent = L.rtkInstallBin;
4590
+ showToast(L.failed + ": " + errText(res), "error");
4591
+ return;
4592
+ }
4593
+ const done = await new Promise((resolve) => {
4594
+ const iv = setInterval(async () => {
4595
+ const s = await rpc("/rtk/upgrade");
4596
+ if (s.ok && s.upgrade && !s.upgrade.running) {
4597
+ clearInterval(iv);
4598
+ resolve(s.upgrade);
4599
+ }
4600
+ }, 1200);
4601
+ setTimeout(() => {
4602
+ clearInterval(iv);
4603
+ resolve(null);
4604
+ }, 10 * 60 * 1e3);
4605
+ });
4606
+ if (done && !done.error && done.version) {
4607
+ const meta = await rpc("/rtk");
4608
+ if (meta.ok) {
4609
+ Object.assign(rtkRes, meta);
4610
+ rtkCfg = { ...rtkCfg, installed: meta.installed !== false, version: meta.version || "", binPath: meta.binPath || "" };
4611
+ }
4612
+ showToast(L.rtkBinInstalled, "success");
4613
+ renderRtk();
4614
+ } else {
4615
+ b.disabled = false;
4616
+ b.textContent = L.rtkInstallBin;
4617
+ showToast(done && done.error || L.headroomInstallRetry, "error");
4618
+ }
4619
+ };
4620
+ return b;
4621
+ })()
4622
+ )],
3936
4623
  h(
3937
4624
  "div",
3938
4625
  { class: "mt-2 rounded-xl border border-border p-2" },
@@ -3989,15 +4676,16 @@
3989
4676
  };
3990
4677
  renderRtk();
3991
4678
  const hrVersionLine = h("span", { class: "shrink-0 whitespace-nowrap text-ui-xs/relaxed text-foreground-subtle" });
4679
+ let hrLocText = "";
4680
+ const hrSwitchRow = h("div");
3992
4681
  const renderHrVersion = (meta) => {
3993
- const parts = [];
3994
- if (meta.pluginVersion) parts.push(`${L.headroomVersionPlugin} ${meta.pluginVersion}`);
3995
- if (meta.version) parts.push(`${L.headroomVersionBin} ${meta.version}`);
3996
- hrVersionLine.textContent = parts.length ? parts.join(" \xB7 ") : `${L.headroomVersionPlugin} \u2014`;
4682
+ hrVersionLine.textContent = `\u63D2\u4EF6 ${meta.pluginVersion || "\u2014"}${meta.version ? ` \xB7 headroom ${meta.version}` : ""}`;
4683
+ hrLocText = meta.binPath || "";
3997
4684
  };
3998
4685
  renderHrVersion(headroomRes);
3999
4686
  const hrUp = makeUpgradeControls({
4000
4687
  endpoint: "/headroom",
4688
+ idleText: () => hrLocText,
4001
4689
  metaRefresh: async () => {
4002
4690
  const meta = await rpc("/headroom");
4003
4691
  if (meta.ok) {
@@ -4025,7 +4713,7 @@
4025
4713
  installBtn.disabled = true;
4026
4714
  installBtn.textContent = L.headroomInstalling;
4027
4715
  installState.textContent = "";
4028
- const res = await rpc("/plugins/update", { method: "POST", body: { name: "headroom", installMissing: true } });
4716
+ const res = await rpc("/plugins/update", { method: "POST", body: { name: "headroom" } });
4029
4717
  if (!res.ok) {
4030
4718
  installBtn.disabled = false;
4031
4719
  installBtn.textContent = L.headroomInstall;
@@ -4036,6 +4724,7 @@
4036
4724
  if (meta.ok) Object.assign(headroomRes, meta);
4037
4725
  if (headroomRes.hook) {
4038
4726
  showToast(L.headroomInstalled, "success");
4727
+ renderHrVersion(headroomRes);
4039
4728
  renderHeadroomPane();
4040
4729
  } else {
4041
4730
  installBtn.disabled = false;
@@ -4065,17 +4754,60 @@
4065
4754
  const hrRun = async (reqBody, warn) => {
4066
4755
  if (hrBusy) return false;
4067
4756
  hrBusy = true;
4068
- const res = await rpc("/headroom", { method: "POST", body: reqBody });
4069
- hrBusy = false;
4757
+ const action = reqBody && reqBody.action;
4758
+ const isStart = action === "start" || action === "restart";
4759
+ const isStop = action === "stop";
4760
+ const rpcDone = rpc("/headroom", { method: "POST", body: reqBody });
4761
+ let timerIv = null;
4762
+ if (isStart || isStop) {
4763
+ const wantUp = isStart;
4764
+ const msg = action === "restart" ? L.hrRestarting : isStart ? L.hrStarting : L.hrStopping;
4765
+ const doneMsg = isStart ? L.hrStarted : L.hrStopped;
4766
+ const timeoutMs = isStart ? 90 * 1e3 : 30 * 1e3;
4767
+ const t0 = Date.now();
4768
+ const timerSpan = h("span", { class: "text-ui-sm text-foreground-subtle/70" }, msg + " (0s)");
4769
+ hrStatusLine.replaceChildren(timerSpan);
4770
+ timerIv = setInterval(() => {
4771
+ timerSpan.textContent = msg + ` (${Math.round((Date.now() - t0) / 1e3)}s)`;
4772
+ }, 1e3);
4773
+ void (async () => {
4774
+ const kick = await rpcDone;
4775
+ if (!kick.ok) return;
4776
+ while (Date.now() - t0 < timeoutMs) {
4777
+ await new Promise((r) => setTimeout(r, 2e3));
4778
+ const st = await rpc("/headroom/status");
4779
+ if (!st.ok) continue;
4780
+ const up = !!(st.status && st.status.up);
4781
+ if (up === wantUp) {
4782
+ clearInterval(timerIv);
4783
+ hrBusy = false;
4784
+ await hrRefresh();
4785
+ showToast(doneMsg, isStart ? "success" : void 0);
4786
+ return;
4787
+ }
4788
+ }
4789
+ clearInterval(timerIv);
4790
+ hrBusy = false;
4791
+ if (isStart) showToast(L.hrStartTimeout, "error");
4792
+ void hrRefresh();
4793
+ })();
4794
+ }
4795
+ const res = await rpcDone;
4070
4796
  if (!res.ok) {
4797
+ if (timerIv) clearInterval(timerIv);
4798
+ hrBusy = false;
4071
4799
  showToast(L.failed + ": " + errText(res), "error");
4800
+ void hrRefresh();
4072
4801
  return false;
4073
4802
  }
4074
4803
  if (res.config) Object.assign(hrCfg, res.config);
4075
4804
  if (warn) showToast(warn);
4076
- setTimeout(() => {
4077
- void hrRefresh();
4078
- }, 1200);
4805
+ if (!isStart && !isStop) {
4806
+ hrBusy = false;
4807
+ setTimeout(() => {
4808
+ void hrRefresh();
4809
+ }, 1200);
4810
+ }
4079
4811
  return true;
4080
4812
  };
4081
4813
  const hrDevLabel = (v) => v == null ? "\u2014" : v === "auto" ? L.hrValAuto : v === "cpu" ? "CPU" : v;
@@ -4120,6 +4852,23 @@
4120
4852
  hrHintLine.textContent = s.saverHint || "";
4121
4853
  hrHintLine.style.display = s.saverHint ? "" : "none";
4122
4854
  };
4855
+ const renderHrUsageSwitch = () => {
4856
+ const usageOn = !!(headroomRes.usage && headroomRes.usage.enabled);
4857
+ hrSwitchRow.replaceChildren(
4858
+ settingRow(L.pluginEnable, "", usageOn, async () => {
4859
+ const next = !usageOn;
4860
+ const res = await rpc("/headroom", { method: "POST", body: { usage: next } });
4861
+ if (!res.ok) {
4862
+ showToast(L.failed + ": " + errText(res), "error");
4863
+ return;
4864
+ }
4865
+ if (res.usage) headroomRes.usage = res.usage;
4866
+ renderHrUsageSwitch();
4867
+ if (next && !res.changed && !(res.usage && res.usage.enabled)) showToast(L.hrUsageNone);
4868
+ })
4869
+ );
4870
+ };
4871
+ renderHrUsageSwitch();
4123
4872
  const hrRefreshBtn = btnSmall(L.headroomRefresh, () => {
4124
4873
  void hrRefresh();
4125
4874
  });
@@ -4212,14 +4961,69 @@
4212
4961
  hrPwrHint.style.display = hrCfg.kompressBackend === "cpu" ? "" : "none";
4213
4962
  };
4214
4963
  updateHrPwrHint();
4964
+ const hrInstallBinBtn = (() => {
4965
+ const b = btnSmall(L.hrInstallBin, () => {
4966
+ void run();
4967
+ }, "shrink-0");
4968
+ let busy = false;
4969
+ const run = async () => {
4970
+ if (busy) return;
4971
+ busy = true;
4972
+ b.disabled = true;
4973
+ b.textContent = L.headroomInstalling;
4974
+ const res = await rpc("/headroom", { method: "POST", body: { upgrade: true } });
4975
+ if (!res.ok) {
4976
+ b.disabled = false;
4977
+ b.textContent = L.hrInstallBin;
4978
+ showToast(L.failed + ": " + errText(res), "error");
4979
+ busy = false;
4980
+ return;
4981
+ }
4982
+ const done = await new Promise((resolve) => {
4983
+ const iv = setInterval(async () => {
4984
+ const s = await rpc("/headroom/upgrade");
4985
+ if (s.ok && s.upgrade && !s.upgrade.running) {
4986
+ clearInterval(iv);
4987
+ resolve(s.upgrade);
4988
+ }
4989
+ }, 2e3);
4990
+ setTimeout(() => {
4991
+ clearInterval(iv);
4992
+ resolve(null);
4993
+ }, 12 * 60 * 1e3);
4994
+ });
4995
+ busy = false;
4996
+ if (done && !done.error && done.version) {
4997
+ const meta = await rpc("/headroom");
4998
+ if (meta.ok) {
4999
+ renderHrVersion(meta);
5000
+ Object.assign(headroomRes, meta);
5001
+ }
5002
+ showToast(L.hrBinInstalled, "success");
5003
+ renderHeadroomPane();
5004
+ } else {
5005
+ b.disabled = false;
5006
+ b.textContent = L.hrInstallBin;
5007
+ showToast(done && done.error || L.headroomInstallRetry, "error");
5008
+ }
5009
+ };
5010
+ return b;
5011
+ })();
4215
5012
  paneHeadroom.append(
4216
5013
  h("p", { class: "text-ui-sm/relaxed text-foreground-subtle" }, L.headroomDesc),
4217
- ...headroomRes.installed === false ? [h("p", { class: "mt-2 text-ui-sm text-amber-500" }, L.headroomBinMissing)] : [],
5014
+ ...headroomRes.installed === false ? [h(
5015
+ "div",
5016
+ { class: "mt-2 flex items-center gap-2" },
5017
+ h("p", { class: "min-w-0 flex-1 text-ui-sm text-amber-500" }, L.headroomBinMissing),
5018
+ hrInstallBinBtn
5019
+ )] : [],
4218
5020
  h(
4219
5021
  "div",
4220
- { class: "mt-3 flex items-center justify-between gap-2 rounded-xl border border-border p-2" },
4221
- h("div", { class: "flex min-w-0 items-center gap-2" }, hrVersionLine, hrUp.state),
4222
- hrUp.btn
5022
+ { class: "mt-3 flex flex-wrap items-center gap-2 rounded-xl border border-border p-2" },
5023
+ hrVersionLine,
5024
+ hrUp.state,
5025
+ hrUp.btn,
5026
+ h("div", { class: "ml-auto shrink-0" }, hrSwitchRow)
4223
5027
  ),
4224
5028
  h(
4225
5029
  "div",
@@ -4262,18 +5066,196 @@
4262
5066
  );
4263
5067
  };
4264
5068
  renderHeadroomPane();
5069
+ let titleGenCfg = titleGenRes.ok && titleGenRes.titleGen && typeof titleGenRes.titleGen === "object" ? structuredClone(titleGenRes.titleGen) : null;
5070
+ const titleGenDefaultPrompt = titleGenRes.ok && typeof titleGenRes.defaultPrompt === "string" ? titleGenRes.defaultPrompt : "";
5071
+ let titleGenSaveTimer = null;
5072
+ const persistTitleGen = async () => {
5073
+ const res = await rpc("/title-gen", { method: "POST", body: { titleGen: titleGenCfg } });
5074
+ clearConfigCache();
5075
+ if (!res.ok) showToast(L.failed + ": " + errText(res), "error");
5076
+ return res.ok;
5077
+ };
5078
+ const saveTitleGenSoon = () => {
5079
+ clearTimeout(titleGenSaveTimer);
5080
+ titleGenSaveTimer = setTimeout(() => {
5081
+ void persistTitleGen();
5082
+ }, 500);
5083
+ };
5084
+ const renderTitleGenPane = () => {
5085
+ if (!titleGenCfg) {
5086
+ paneTitleGen.replaceChildren(
5087
+ h("p", { class: "text-ui-sm/relaxed text-foreground-subtle" }, L.titleGenShortDesc),
5088
+ h("p", { class: "mt-2 text-ui-sm text-destructive" }, L.visionLoadFailed + ": " + errText(titleGenRes))
5089
+ );
5090
+ return;
5091
+ }
5092
+ const inputCls = "h-8 w-full rounded-lg border border-border bg-input px-2.5 text-ui-sm text-foreground outline-none transition-shadow placeholder:text-foreground-subtle focus-visible:border-ring focus-visible:ring-2 focus-visible:ring-ring/40";
5093
+ const field = (labelText, node) => h(
5094
+ "label",
5095
+ { class: "block min-w-0" },
5096
+ h("span", { class: "mb-1 block text-ui-xs font-medium text-foreground-subtle" }, labelText),
5097
+ node
5098
+ );
5099
+ const showInlineConn = isManualConn(titleGenCfg.provider, titleGenProviders);
5100
+ const providerCombo = comboField(
5101
+ titleGenCfg.provider || "",
5102
+ providerOptionsOf(titleGenProviders, L),
5103
+ (v, src) => {
5104
+ titleGenCfg.provider = v;
5105
+ if (src === "select") {
5106
+ void persistTitleGen();
5107
+ renderTitleGenPane();
5108
+ } else saveTitleGenSoon();
5109
+ },
5110
+ { placeholder: L.titleGenProviderSession, staleLabel: L.titleGenProviderDeleted }
5111
+ );
5112
+ const modelCombo = comboField(
5113
+ titleGenCfg.model || "",
5114
+ // 跟随会话时选项表头部有“跟随当前会话当前模型”(值为空,生成时用
5115
+ // 会话当前模型),选了具体模型后也能再切回它
5116
+ modelOptionsOf(titleGenCfg.provider, titleGenProviders, {
5117
+ followSession: true,
5118
+ followSessionLabel: L.titleGenModelFollowSession
5119
+ }),
5120
+ (v) => {
5121
+ titleGenCfg.model = v;
5122
+ saveTitleGenSoon();
5123
+ },
5124
+ {
5125
+ placeholder: modelPlaceholderOf(titleGenCfg.provider, L),
5126
+ emptyLabel: (titleGenCfg.provider || "").trim() === "session" ? L.titleGenModelFollowSession : L.titleGenModelEmpty
5127
+ }
5128
+ );
5129
+ const textIn = (value, onInput, { type = "text", placeholder = "" } = {}) => {
5130
+ const n = h("input", { type, value: value || "", placeholder, class: inputCls });
5131
+ n.addEventListener("input", () => onInput(n.value));
5132
+ return n;
5133
+ };
5134
+ const formatSel = h(
5135
+ "select",
5136
+ { class: inputCls },
5137
+ h("option", { value: "anthropic" }, "anthropic"),
5138
+ h("option", { value: "openai" }, "openai")
5139
+ );
5140
+ formatSel.value = titleGenCfg.format === "openai" ? "openai" : "anthropic";
5141
+ formatSel.addEventListener("change", () => {
5142
+ titleGenCfg.format = formatSel.value;
5143
+ void persistTitleGen();
5144
+ });
5145
+ const inlineConnCard = h(
5146
+ "div",
5147
+ {
5148
+ class: "mt-2 grid grid-cols-1 gap-2 rounded-xl border border-border p-2 sm:grid-cols-3",
5149
+ style: showInlineConn ? "" : "display:none"
5150
+ },
5151
+ field(L.titleGenBaseUrl, textIn(titleGenCfg.baseUrl, (v) => {
5152
+ titleGenCfg.baseUrl = v;
5153
+ saveTitleGenSoon();
5154
+ }, { placeholder: "https://\u2026" })),
5155
+ field(L.titleGenApiKey, textIn(titleGenCfg.apiKey, (v) => {
5156
+ titleGenCfg.apiKey = v;
5157
+ saveTitleGenSoon();
5158
+ }, { type: "password" })),
5159
+ field(L.titleGenFormat, formatSel)
5160
+ );
5161
+ const promptArea = h("textarea", {
5162
+ class: "zcodepro-textarea zcodepro-textarea-sm",
5163
+ rows: "8",
5164
+ placeholder: L.titleGenPromptPlaceholder
5165
+ });
5166
+ promptArea.value = titleGenCfg.prompt || "";
5167
+ promptArea.addEventListener("input", () => {
5168
+ titleGenCfg.prompt = promptArea.value;
5169
+ saveTitleGenSoon();
5170
+ });
5171
+ const sampleText = isZhLocale() ? "\u5E2E\u6211\u770B\u4E0B\u8FD9\u4E2A\u62A5\u9519\u662F\u4EC0\u4E48\u539F\u56E0\uFF1ANullPointerException at com.example.OrderService.calculate(OrderService.java:87)" : "Why does my Spring Boot app throw NullPointerException intermittently in OrderService.calculate?";
5172
+ const sampleInput = h("input", {
5173
+ type: "text",
5174
+ class: inputCls + " min-w-0 flex-1",
5175
+ value: sampleText
5176
+ });
5177
+ let testBtn;
5178
+ const testState = h("span", { class: "min-w-0 flex-1 truncate text-ui-xs/relaxed text-foreground-subtle" });
5179
+ testBtn = btnSmall(L.titleGenTest, async () => {
5180
+ testBtn.disabled = true;
5181
+ testBtn.textContent = L.titleGenTesting;
5182
+ testState.textContent = L.titleGenTesting;
5183
+ const res = await rpc("/title-gen/run", {
5184
+ method: "POST",
5185
+ body: { text: sampleInput.value, test: true }
5186
+ });
5187
+ testBtn.disabled = false;
5188
+ testBtn.textContent = L.titleGenTest;
5189
+ if (res && res.ok) {
5190
+ testState.textContent = L.titleGenTestOk + res.title;
5191
+ showToast(res.title, "success");
5192
+ } else {
5193
+ testState.textContent = res && errText(res) || L.titleGenTestFailed;
5194
+ testState.className = "min-w-0 flex-1 truncate text-ui-xs/relaxed text-destructive";
5195
+ showToast(L.titleGenTestFailed + ": " + (res ? errText(res) : ""), "error");
5196
+ }
5197
+ }, "shrink-0");
5198
+ paneTitleGen.replaceChildren(
5199
+ // 信息卡:一句话简介在左,启用开关右对齐
5200
+ h(
5201
+ "div",
5202
+ { class: "mt-3 flex items-center justify-between gap-2 rounded-xl border border-border p-2" },
5203
+ h("span", { class: "min-w-0 text-ui-sm/relaxed text-foreground-subtle" }, L.titleGenShortDesc),
5204
+ h("div", { class: "shrink-0" }, settingRow(L.titleGenEnabled, "", titleGenCfg.enabled === true, async () => {
5205
+ titleGenCfg.enabled = !(titleGenCfg.enabled === true);
5206
+ await persistTitleGen();
5207
+ renderTitleGenPane();
5208
+ }))
5209
+ ),
5210
+ // 供应商 + 模型
5211
+ h(
5212
+ "div",
5213
+ { class: "mt-2 grid grid-cols-1 gap-2 rounded-xl border border-border p-2 sm:grid-cols-2" },
5214
+ field(L.titleGenProvider, providerCombo),
5215
+ field(L.titleGenModel, modelCombo)
5216
+ ),
5217
+ inlineConnCard,
5218
+ // 提示词 + 载入默认
5219
+ h(
5220
+ "div",
5221
+ { class: "mt-2 rounded-xl border border-border p-2" },
5222
+ h(
5223
+ "div",
5224
+ { class: "mb-1 flex items-center justify-between gap-2" },
5225
+ h("span", { class: "text-ui-xs font-medium text-foreground-subtle" }, L.titleGenPrompt),
5226
+ btnSmall(L.titleGenPromptReset, () => {
5227
+ promptArea.value = titleGenDefaultPrompt;
5228
+ titleGenCfg.prompt = titleGenDefaultPrompt;
5229
+ void persistTitleGen();
5230
+ promptArea.focus();
5231
+ })
5232
+ ),
5233
+ promptArea
5234
+ ),
5235
+ // 测试
5236
+ h(
5237
+ "div",
5238
+ { class: "mt-2 flex items-center gap-2 rounded-xl border border-border p-2" },
5239
+ sampleInput,
5240
+ testBtn
5241
+ ),
5242
+ testState
5243
+ );
5244
+ };
5245
+ renderTitleGenPane();
4265
5246
  body.append(
4266
5247
  statusLine,
4267
5248
  tablist,
4268
5249
  paneFeatures,
5250
+ paneAgents,
4269
5251
  paneStyles,
4270
5252
  panePerf,
4271
- paneAgents,
4272
5253
  paneProxy,
4273
5254
  paneSync,
4274
5255
  paneVision,
4275
5256
  paneHeadroom,
4276
- paneRtk
5257
+ paneRtk,
5258
+ paneDebug
4277
5259
  );
4278
5260
  }
4279
5261
  });
@@ -4740,6 +5722,16 @@
4740
5722
  const map = await fetchPinned();
4741
5723
  if (map) scan2();
4742
5724
  }
5725
+ function setRowTitle(key, text) {
5726
+ for (const item of findRows(key)) {
5727
+ for (const el of titleElsOf(item)) writeTitle(el, text);
5728
+ }
5729
+ }
5730
+ function refreshPinnedTitles() {
5731
+ return fetchPinned().then((m) => {
5732
+ if (m) scan2();
5733
+ });
5734
+ }
4743
5735
  function startTitleLock() {
4744
5736
  if (installed2 || typeof document === "undefined") return;
4745
5737
  installed2 = true;
@@ -4775,8 +5767,146 @@
4775
5767
  });
4776
5768
  }
4777
5769
 
4778
- // src/inject/features/pinned-expand.js
5770
+ // src/inject/features/title-gen.js
5771
+ var CONV2 = '[class*="@md/conversation"]';
5772
+ var USER_ROW = '[class*="user-row"]';
5773
+ var SCAN_DEBOUNCE_MS3 = 300;
5774
+ var SCAN_FALLBACK_MS3 = 5e3;
5775
+ var SELECT_WAIT_MS = 2e3;
5776
+ var MSG_WAIT_MS = 1e4;
5777
+ var QUIET_FALLBACK_MS = 12e4;
5778
+ var COLLECT_MAX_MS = 6e5;
4779
5779
  var installed3 = false;
5780
+ var knownKeys = /* @__PURE__ */ new Set();
5781
+ var attempted = /* @__PURE__ */ new Set();
5782
+ var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
5783
+ function sessIdOf2(key) {
5784
+ const i = key.indexOf(":sess_");
5785
+ return i > 0 ? key.slice(i + 1) : "";
5786
+ }
5787
+ function currentKey() {
5788
+ for (const el of document.querySelectorAll("[data-task-item-key]")) {
5789
+ if ((el.className + "").includes("bg-selected")) return el.getAttribute("data-task-item-key") || "";
5790
+ }
5791
+ return "";
5792
+ }
5793
+ function userTexts() {
5794
+ return [...document.querySelectorAll(`${CONV2} ${USER_ROW}`)].map((r) => {
5795
+ const el = r.querySelector(".whitespace-pre-wrap");
5796
+ return ((el ? el.textContent : r.innerText) || "").trim();
5797
+ }).filter(Boolean);
5798
+ }
5799
+ async function onNewTaskRow(key) {
5800
+ const selDeadline = Date.now() + SELECT_WAIT_MS;
5801
+ while (currentKey() !== key && Date.now() < selDeadline) await sleep(200);
5802
+ if (currentKey() !== key) return;
5803
+ const cfg = await getConfig().catch(() => null);
5804
+ if (!cfg || !cfg.titleGen || cfg.titleGen.enabled !== true) return;
5805
+ const sessId = sessIdOf2(key);
5806
+ if (!sessId || attempted.has(sessId)) return;
5807
+ attempted.add(sessId);
5808
+ let texts = [];
5809
+ const msgDeadline = Date.now() + MSG_WAIT_MS;
5810
+ while (Date.now() < msgDeadline) {
5811
+ texts = userTexts();
5812
+ if (texts.length) break;
5813
+ await sleep(400);
5814
+ }
5815
+ if (!texts.length) return;
5816
+ let assistantSeen = false;
5817
+ const convObserver = new MutationObserver((muts) => {
5818
+ if (currentKey() !== key) return;
5819
+ for (const m of muts) {
5820
+ const t2 = m.type === "characterData" ? m.target.parentElement : m.target;
5821
+ if (t2 instanceof Element && t2.closest(USER_ROW)) continue;
5822
+ for (const n of [t2, ...m.addedNodes]) {
5823
+ if (n instanceof Element && n.closest && n.closest(CONV2)) {
5824
+ assistantSeen = true;
5825
+ return;
5826
+ }
5827
+ }
5828
+ }
5829
+ });
5830
+ convObserver.observe(document.body, { childList: true, subtree: true, characterData: true });
5831
+ const startAt = Date.now();
5832
+ let lastCount = texts.length;
5833
+ let lastChange = Date.now();
5834
+ try {
5835
+ while (Date.now() - startAt < COLLECT_MAX_MS) {
5836
+ await sleep(500);
5837
+ if (currentKey() !== key) {
5838
+ lastChange = Date.now();
5839
+ continue;
5840
+ }
5841
+ texts = userTexts();
5842
+ if (texts.length !== lastCount) {
5843
+ lastCount = texts.length;
5844
+ lastChange = Date.now();
5845
+ }
5846
+ if (assistantSeen) break;
5847
+ if (Date.now() - lastChange >= QUIET_FALLBACK_MS) break;
5848
+ }
5849
+ } finally {
5850
+ convObserver.disconnect();
5851
+ }
5852
+ if (!texts.length) return;
5853
+ const res = await rpc("/title-gen/run", { method: "POST", body: { sessId, text: texts.join("\n") } }).catch(() => null);
5854
+ if (res && res.ok && res.written && res.title) {
5855
+ setRowTitle(key, res.title);
5856
+ void refreshPinnedTitles();
5857
+ }
5858
+ }
5859
+ function scanForNewRows() {
5860
+ for (const el of document.querySelectorAll("[data-task-item-key]")) {
5861
+ const key = el.getAttribute("data-task-item-key") || "";
5862
+ if (!key || knownKeys.has(key)) continue;
5863
+ knownKeys.add(key);
5864
+ void onNewTaskRow(key);
5865
+ }
5866
+ }
5867
+ var TASK_SEL2 = "[data-task-item-key]";
5868
+ var inTaskRow3 = (node) => !!(node instanceof Element && node.closest && node.closest(TASK_SEL2));
5869
+ function taskRowMutations3(muts) {
5870
+ for (const m of muts) {
5871
+ if (m.type === "attributes") {
5872
+ if (inTaskRow3(m.target)) return true;
5873
+ } else if (m.type === "characterData") {
5874
+ if (m.target.parentElement && inTaskRow3(m.target.parentElement)) return true;
5875
+ } else if (inTaskRow3(m.target)) {
5876
+ return true;
5877
+ } else {
5878
+ for (const n of m.addedNodes) {
5879
+ if (n instanceof Element && (n.matches(TASK_SEL2) || !!n.querySelector(TASK_SEL2))) return true;
5880
+ }
5881
+ }
5882
+ }
5883
+ return false;
5884
+ }
5885
+ var scanDebounce3 = 0;
5886
+ function scheduleScan3() {
5887
+ if (scanDebounce3) return;
5888
+ scanDebounce3 = setTimeout(() => {
5889
+ scanDebounce3 = 0;
5890
+ scanForNewRows();
5891
+ }, SCAN_DEBOUNCE_MS3);
5892
+ }
5893
+ function startTitleGen() {
5894
+ if (installed3 || typeof document === "undefined") return;
5895
+ installed3 = true;
5896
+ scanForNewRows();
5897
+ new MutationObserver((muts) => {
5898
+ if (taskRowMutations3(muts)) scheduleScan3();
5899
+ }).observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ["class"], characterData: true });
5900
+ setInterval(() => {
5901
+ if (!document.hidden) scanForNewRows();
5902
+ }, SCAN_FALLBACK_MS3);
5903
+ document.addEventListener("visibilitychange", () => {
5904
+ if (!document.hidden) scanForNewRows();
5905
+ });
5906
+ }
5907
+
5908
+ // src/inject/features/pinned-expand.js
5909
+ var installed4 = false;
4780
5910
  var keepCollapsed = false;
4781
5911
  var CONFIG_POLL_MIN_MS = 5e3;
4782
5912
  var CONFIG_POLL_MAX_MS = 6e4;
@@ -4803,8 +5933,8 @@
4803
5933
  }, pollMs);
4804
5934
  }
4805
5935
  function startPinnedExpandSuppression() {
4806
- if (installed3 || typeof document === "undefined") return;
4807
- installed3 = true;
5936
+ if (installed4 || typeof document === "undefined") return;
5937
+ installed4 = true;
4808
5938
  void refreshConfig();
4809
5939
  pollConfig();
4810
5940
  document.addEventListener("visibilitychange", () => {
@@ -4883,10 +6013,10 @@
4883
6013
  }
4884
6014
 
4885
6015
  // src/inject/features/ws-running.js
4886
- var installed4 = false;
6016
+ var installed5 = false;
4887
6017
  var enabled3 = true;
4888
- var SCAN_DEBOUNCE_MS3 = 150;
4889
- var SCAN_FALLBACK_MS3 = 5e3;
6018
+ var SCAN_DEBOUNCE_MS4 = 150;
6019
+ var SCAN_FALLBACK_MS4 = 5e3;
4890
6020
  var PEEK_FIRST_MS = 2e4;
4891
6021
  var PEEK_REPEAT_MS = 6e4;
4892
6022
  var PEEK_SETTLE_MS = 160;
@@ -4894,7 +6024,7 @@
4894
6024
  var PEEK_UNMOUNT_WAIT_MS = 800;
4895
6025
  var CONFIG_POLL_MIN_MS2 = 5e3;
4896
6026
  var CONFIG_POLL_MAX_MS2 = 6e4;
4897
- var TASK_SEL2 = "[data-task-item-key]";
6027
+ var TASK_SEL3 = "[data-task-item-key]";
4898
6028
  var WS_SEL = '[data-testid^="workspace-item-"]';
4899
6029
  var runningByWs = /* @__PURE__ */ new Map();
4900
6030
  var peekTimers = /* @__PURE__ */ new Map();
@@ -4917,10 +6047,10 @@
4917
6047
  return [...holder.children].some((c) => c.tagName === "svg" && (c.getAttribute("class") || "").includes("lucide-loader"));
4918
6048
  }
4919
6049
  function domRowsOf(ws) {
4920
- return [...document.querySelectorAll(TASK_SEL2)].filter((e) => workspaceOf2(e.getAttribute("data-task-item-key") || "") === ws);
6050
+ return [...document.querySelectorAll(TASK_SEL3)].filter((e) => workspaceOf2(e.getAttribute("data-task-item-key") || "") === ws);
4921
6051
  }
4922
6052
  function currentTaskRow2() {
4923
- for (const el of document.querySelectorAll(TASK_SEL2)) {
6053
+ for (const el of document.querySelectorAll(TASK_SEL3)) {
4924
6054
  if ((el.className + "").includes("bg-selected")) return el;
4925
6055
  }
4926
6056
  return null;
@@ -4936,12 +6066,12 @@
4936
6066
  const backKey = cur ? cur.getAttribute("data-task-item-key") : "";
4937
6067
  synthClick(head);
4938
6068
  if (!backKey) return;
4939
- const back = cur && cur.isConnected ? cur : document.querySelector(`${TASK_SEL2}[data-task-item-key="${CSS.escape(backKey)}"]`);
6069
+ const back = cur && cur.isConnected ? cur : document.querySelector(`${TASK_SEL3}[data-task-item-key="${CSS.escape(backKey)}"]`);
4940
6070
  if (back) synthClick(back);
4941
6071
  }
4942
6072
  function scan3() {
4943
6073
  wsWithRows = /* @__PURE__ */ new Set();
4944
- for (const el of document.querySelectorAll(TASK_SEL2)) {
6074
+ for (const el of document.querySelectorAll(TASK_SEL3)) {
4945
6075
  const key = el.getAttribute("data-task-item-key") || "";
4946
6076
  const ws = workspaceOf2(key);
4947
6077
  if (!ws) continue;
@@ -4967,8 +6097,8 @@
4967
6097
  if (m.type !== "childList") continue;
4968
6098
  for (const n of m.removedNodes) {
4969
6099
  if (!(n instanceof Element)) continue;
4970
- if (n.matches(TASK_SEL2)) removed.push(n);
4971
- else for (const r of n.querySelectorAll(TASK_SEL2)) removed.push(r);
6100
+ if (n.matches(TASK_SEL3)) removed.push(n);
6101
+ else for (const r of n.querySelectorAll(TASK_SEL3)) removed.push(r);
4972
6102
  }
4973
6103
  }
4974
6104
  if (!removed.length) return;
@@ -5151,7 +6281,7 @@
5151
6281
  var PEEK_EXEMPT_CLASS = "zcodepro-peek-exempt";
5152
6282
  var cssAttr = (v) => String(v).replace(/\\/g, "\\\\").replace(/"/g, '\\"');
5153
6283
  function installPeekHider(ws) {
5154
- const rowSel3 = `${TASK_SEL2}[data-task-item-key^="${cssAttr(ws + ":")}"]`;
6284
+ const rowSel3 = `${TASK_SEL3}[data-task-item-key^="${cssAttr(ws + ":")}"]`;
5155
6285
  for (const r of document.querySelectorAll(rowSel3)) r.classList.add(PEEK_EXEMPT_CLASS);
5156
6286
  const style = document.createElement("style");
5157
6287
  style.textContent = `${rowSel3}:not(.${PEEK_EXEMPT_CLASS}){display:none!important}`;
@@ -5163,7 +6293,7 @@
5163
6293
  for (const r of document.querySelectorAll("." + PEEK_EXEMPT_CLASS)) r.classList.remove(PEEK_EXEMPT_CLASS);
5164
6294
  }
5165
6295
  function waitForRowsGone(ws) {
5166
- const sel = `${TASK_SEL2}[data-task-item-key^="${cssAttr(ws + ":")}"]:not(.${PEEK_EXEMPT_CLASS})`;
6296
+ const sel = `${TASK_SEL3}[data-task-item-key^="${cssAttr(ws + ":")}"]:not(.${PEEK_EXEMPT_CLASS})`;
5167
6297
  return new Promise((resolve) => {
5168
6298
  const t0 = Date.now();
5169
6299
  const timer = setInterval(() => {
@@ -5185,12 +6315,12 @@
5185
6315
  }
5186
6316
  function relevant(muts) {
5187
6317
  for (const m of muts) {
5188
- if (inSel(m.target, TASK_SEL2) || inSel(m.target, WS_SEL)) return true;
6318
+ if (inSel(m.target, TASK_SEL3) || inSel(m.target, WS_SEL)) return true;
5189
6319
  for (const n of m.addedNodes) {
5190
- if (n instanceof Element && (n.matches(TASK_SEL2) || n.matches(WS_SEL) || n.querySelector(TASK_SEL2) || n.querySelector(WS_SEL))) return true;
6320
+ if (n instanceof Element && (n.matches(TASK_SEL3) || n.matches(WS_SEL) || n.querySelector(TASK_SEL3) || n.querySelector(WS_SEL))) return true;
5191
6321
  }
5192
6322
  for (const n of m.removedNodes) {
5193
- if (n instanceof Element && (n.matches(TASK_SEL2) || n.matches(WS_SEL) || n.querySelector(TASK_SEL2))) return true;
6323
+ if (n instanceof Element && (n.matches(TASK_SEL3) || n.matches(WS_SEL) || n.querySelector(TASK_SEL3))) return true;
5194
6324
  }
5195
6325
  }
5196
6326
  return false;
@@ -5221,27 +6351,27 @@
5221
6351
  pollConfig2();
5222
6352
  }, pollMs2);
5223
6353
  }
5224
- var scanDebounce3 = 0;
6354
+ var scanDebounce4 = 0;
5225
6355
  function startWsRunningSpin() {
5226
- if (installed4 || typeof document === "undefined") return;
5227
- installed4 = true;
6356
+ if (installed5 || typeof document === "undefined") return;
6357
+ installed5 = true;
5228
6358
  void refreshConfig2();
5229
6359
  pollConfig2();
5230
6360
  const observer2 = new MutationObserver((muts) => {
5231
6361
  if (!relevant(muts)) return;
5232
6362
  handleRemovals(muts);
5233
- if (!scanDebounce3) {
5234
- scanDebounce3 = setTimeout(() => {
5235
- scanDebounce3 = 0;
6363
+ if (!scanDebounce4) {
6364
+ scanDebounce4 = setTimeout(() => {
6365
+ scanDebounce4 = 0;
5236
6366
  scan3();
5237
- }, SCAN_DEBOUNCE_MS3);
6367
+ }, SCAN_DEBOUNCE_MS4);
5238
6368
  }
5239
6369
  });
5240
6370
  observer2.observe(document.body, { childList: true, subtree: true });
5241
6371
  scan3();
5242
6372
  setInterval(() => {
5243
6373
  if (!document.hidden && enabled3) scan3();
5244
- }, SCAN_FALLBACK_MS3);
6374
+ }, SCAN_FALLBACK_MS4);
5245
6375
  document.addEventListener("visibilitychange", () => {
5246
6376
  if (!document.hidden) scan3();
5247
6377
  });
@@ -5405,7 +6535,7 @@
5405
6535
  // src/inject/features/toolbar-icons.js
5406
6536
  var HIDE_CLASS = "zcodepro-tb-hidden";
5407
6537
  var SYNC_DEBOUNCE_MS = 150;
5408
- var SCAN_FALLBACK_MS4 = 5e3;
6538
+ var SCAN_FALLBACK_MS5 = 5e3;
5409
6539
  var ROW_SEL = "[data-task-item-key]";
5410
6540
  var MENU_SEL = '[data-testid="task-new-button"], [data-testid="automations-open"], [data-testid="plugin-store-sidebar-open"], [data-slot="command"], [data-testid="desktop-top-nav-back"]';
5411
6541
  var enabled4 = true;
@@ -5477,6 +6607,7 @@
5477
6607
  menu.classList.add(HIDE_CLASS);
5478
6608
  origs = next;
5479
6609
  rowEl?.remove();
6610
+ navRow.classList.add("zcodepro-tb-hidden-native");
5480
6611
  const L = t();
5481
6612
  const mk = (kind, orig, nameSel, fallback) => h("button", {
5482
6613
  type: "button",
@@ -5502,7 +6633,9 @@
5502
6633
  const pIcon = iconOf(next.plugin);
5503
6634
  if (pIcon) pluginBtn.append(pIcon);
5504
6635
  rowEl = h("div", { class: "zcodepro-tb-row" }, newBtn, searchBtn, autoBtn, pluginBtn);
5505
- navRow.append(rowEl);
6636
+ const updBtn = [...navRow.querySelectorAll("button")].find((b) => /bg-(success|emerald)/.test(b.className || ""));
6637
+ if (updBtn) updBtn.before(rowEl);
6638
+ else navRow.append(rowEl);
5506
6639
  return true;
5507
6640
  }
5508
6641
  function sidebarCollapsed() {
@@ -5532,10 +6665,18 @@
5532
6665
  if (sidebarCollapsed() || sidebarTooNarrow()) {
5533
6666
  if (rowEl) rowEl.style.display = "none";
5534
6667
  menu.classList.remove(HIDE_CLASS);
6668
+ document.querySelectorAll(".zcodepro-tb-hidden-native").forEach((el) => el.classList.remove("zcodepro-tb-hidden-native"));
5535
6669
  return;
5536
6670
  }
5537
6671
  const alive = origs && origs.menu === menu && origs.newTask.isConnected && origs.search.isConnected && origs.auto.isConnected && origs.plugin.isConnected && rowEl && rowEl.isConnected;
5538
- if (!alive && !rebuild()) return;
6672
+ if (!alive && !rebuild()) {
6673
+ if (rowEl) rowEl.style.display = "none";
6674
+ setTimeout(scheduleSync, 400);
6675
+ setTimeout(scheduleSync, 1200);
6676
+ return;
6677
+ }
6678
+ const navRow = document.querySelector('[data-testid="desktop-top-nav-back"]')?.parentElement;
6679
+ if (navRow) navRow.classList.add("zcodepro-tb-hidden-native");
5539
6680
  menu.classList.add(HIDE_CLASS);
5540
6681
  if (rowEl) rowEl.style.display = "";
5541
6682
  syncActive();
@@ -5571,6 +6712,7 @@
5571
6712
  rowEl = null;
5572
6713
  if (origs) {
5573
6714
  origs.menu.classList.remove(HIDE_CLASS);
6715
+ document.querySelectorAll(".zcodepro-tb-hidden-native").forEach((el) => el.classList.remove("zcodepro-tb-hidden-native"));
5574
6716
  origs = null;
5575
6717
  }
5576
6718
  }
@@ -5591,7 +6733,7 @@
5591
6733
  }).observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ["class", "aria-pressed"] });
5592
6734
  setInterval(() => {
5593
6735
  if (!document.hidden) sync();
5594
- }, SCAN_FALLBACK_MS4);
6736
+ }, SCAN_FALLBACK_MS5);
5595
6737
  document.addEventListener("visibilitychange", () => {
5596
6738
  if (!document.hidden) sync();
5597
6739
  });
@@ -5602,7 +6744,7 @@
5602
6744
  var HEAD_CLASS = "zcodepro-pin-head";
5603
6745
  var STORE_KEY = "zcodepro:pinned-collapsed";
5604
6746
  var SYNC_DEBOUNCE_MS2 = 300;
5605
- var SCAN_FALLBACK_MS5 = 5e3;
6747
+ var SCAN_FALLBACK_MS6 = 5e3;
5606
6748
  var enabled5 = true;
5607
6749
  var syncTimer2 = 0;
5608
6750
  function findPinnedHeader() {
@@ -5721,7 +6863,7 @@
5721
6863
  }).observe(document.body, { childList: true, subtree: true });
5722
6864
  setInterval(() => {
5723
6865
  if (!document.hidden) sync2();
5724
- }, SCAN_FALLBACK_MS5);
6866
+ }, SCAN_FALLBACK_MS6);
5725
6867
  document.addEventListener("visibilitychange", () => {
5726
6868
  if (!document.hidden) sync2();
5727
6869
  });
@@ -5736,7 +6878,7 @@
5736
6878
  if (status.updates.length === 0) return;
5737
6879
  const config = await getConfig();
5738
6880
  if ((config.features || {}).autoUpdatePlugins === true) {
5739
- const res = await rpc("/plugins/update", { method: "POST", body: { installMissing: true } });
6881
+ const res = await rpc("/plugins/update", { method: "POST", body: {} });
5740
6882
  if (res.ok) showToast(L.pluginsAutoUpdated, "success");
5741
6883
  else showToast(L.pluginsUpdateFailed + ": " + (res.error || ""), "error");
5742
6884
  return;
@@ -5758,8 +6900,14 @@
5758
6900
  // src/inject/index.js
5759
6901
  (function zcodeproInject() {
5760
6902
  if (typeof window === "undefined") return;
5761
- if (window.__zcodeproInjected) return;
6903
+ if (window.__zcodeproInjected) {
6904
+ const boot = window.__ZCODEPRO__ || {};
6905
+ const sameGeneration = window.__zcodeproBootedToken && boot.token && window.__zcodeproBootedToken === boot.token;
6906
+ if (!sameGeneration) location.reload();
6907
+ return;
6908
+ }
5762
6909
  window.__zcodeproInjected = true;
6910
+ window.__zcodeproBootedToken = (window.__ZCODEPRO__ || {}).token || "";
5763
6911
  try {
5764
6912
  startLocaleResolver();
5765
6913
  } catch {
@@ -5796,6 +6944,10 @@
5796
6944
  startTitleLock();
5797
6945
  } catch {
5798
6946
  }
6947
+ try {
6948
+ startTitleGen();
6949
+ } catch {
6950
+ }
5799
6951
  try {
5800
6952
  startImageMenu();
5801
6953
  } catch {