zcode-pro 1.3.1 → 1.4.1
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/README.en.md +57 -30
- package/README.md +57 -30
- package/dist/inject.js +1195 -66
- package/dist/zcode_pro-1.4.0-py3-none-any.whl +0 -0
- package/dist/zcode_pro-1.4.0.tar.gz +0 -0
- package/package.json +1 -1
- package/src/host/helper.mjs +492 -57
- package/src/host/webdav.mjs +19 -11
- package/src/inject/core.js +132 -20
- package/src/inject/features/settings-dialog.js +400 -44
- package/src/inject/features/status-bar.js +17 -4
- package/src/inject/features/theme.js +500 -0
- package/src/inject/index.js +2 -0
- package/src/inject/ui.js +33 -2
package/src/host/webdav.mjs
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
// ZCODEPRO_SYNC_FORCE_FETCH=1 可强制走 fetch(排障/测试用)。
|
|
7
7
|
import { spawn, spawnSync } from 'node:child_process';
|
|
8
8
|
import { mkdtemp } from 'node:fs/promises'; // fs 的 mkdtemp 是回调版,await 它会把 undefined 当回调
|
|
9
|
-
import { readFileSync, rmSync } from 'node:fs';
|
|
9
|
+
import { readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
10
10
|
import { tmpdir } from 'node:os';
|
|
11
11
|
import { join } from 'node:path';
|
|
12
12
|
|
|
@@ -88,15 +88,17 @@ function fetchErrText(err) {
|
|
|
88
88
|
// curl 不可用时的兜底实现:返回值约定与 curl 版一致({ ok, httpCode, body, error,
|
|
89
89
|
// redirectUrl },HTTP 4xx/5xx 也算 ok)。凭据走 Authorization 头;proxyUrl 在这条
|
|
90
90
|
// 路径不生效(fetch 不读代理环境变量,调用方仍照传以免改签名);redirect 不跟随
|
|
91
|
-
|
|
92
|
-
|
|
91
|
+
//(对齐 curl 不带 -L),302 的目标地址放 redirectUrl。
|
|
92
|
+
// downloadFile:GET 二进制落到文件(背景图库同步下载用),此时 body 为空串。
|
|
93
|
+
// contentType:PUT 的 Content-Type,缺省 application/json(历史行为),null = 不带
|
|
94
|
+
async function fetchWebdavRequest({ method, url, login = '', password = '', uploadFile = null, timeoutMs = 45000, headers = [], contentType, downloadFile = null }) {
|
|
93
95
|
// curl 路径的 headers 是 ["Depth: 1"] 字符串数组,fetch 要对象:按第一个冒号拆开
|
|
94
96
|
const hdrs = {};
|
|
95
97
|
for (const h of headers) {
|
|
96
98
|
const i = h.indexOf(':');
|
|
97
99
|
if (i > 0) hdrs[h.slice(0, i).trim()] = h.slice(i + 1).trim();
|
|
98
100
|
}
|
|
99
|
-
if (method === 'PUT') hdrs['Content-Type'] = 'application/json';
|
|
101
|
+
if (method === 'PUT') hdrs['Content-Type'] = contentType !== undefined ? contentType : 'application/json';
|
|
100
102
|
if (login || password) hdrs['Authorization'] = 'Basic ' + Buffer.from(`${login}:${password}`).toString('base64');
|
|
101
103
|
try {
|
|
102
104
|
const res = await fetch(url, {
|
|
@@ -106,6 +108,10 @@ async function fetchWebdavRequest({ method, url, login = '', password = '', uplo
|
|
|
106
108
|
signal: AbortSignal.timeout(Math.max(10000, timeoutMs)),
|
|
107
109
|
body: method === 'PUT' && uploadFile ? readFileSync(uploadFile) : undefined,
|
|
108
110
|
});
|
|
111
|
+
if (downloadFile) {
|
|
112
|
+
writeFileSync(downloadFile, Buffer.from(await res.arrayBuffer()));
|
|
113
|
+
return { ok: true, httpCode: res.status, body: '', error: '', redirectUrl: res.headers.get('location') || '' };
|
|
114
|
+
}
|
|
109
115
|
const body = await res.text().catch(() => '');
|
|
110
116
|
return { ok: true, httpCode: res.status, body, error: '', redirectUrl: res.headers.get('location') || '' };
|
|
111
117
|
} catch (err) {
|
|
@@ -118,20 +124,22 @@ async function fetchWebdavRequest({ method, url, login = '', password = '', uplo
|
|
|
118
124
|
// 返回 { ok, httpCode, body, error, redirectUrl }:ok 表示 curl 本身执行成功
|
|
119
125
|
// (HTTP 4xx/5xx 也算 ok,由调用方看 httpCode 分支);error 为中文原因;
|
|
120
126
|
// redirectUrl 为重定向目标(请求不跟随重定向,302 时才有值)。
|
|
121
|
-
export async function webdavRequest({ method, url, login = '', password = '', uploadFile = null, proxyUrl = '', timeoutMs = 45000, headers = [] }) {
|
|
127
|
+
export async function webdavRequest({ method, url, login = '', password = '', uploadFile = null, proxyUrl = '', timeoutMs = 45000, headers = [], contentType, downloadFile = null }) {
|
|
122
128
|
const curl = findCurl();
|
|
123
|
-
if (!curl) return fetchWebdavRequest({ method, url, login, password, uploadFile, timeoutMs, headers });
|
|
129
|
+
if (!curl) return fetchWebdavRequest({ method, url, login, password, uploadFile, timeoutMs, headers, contentType, downloadFile });
|
|
124
130
|
const dir = await mkdtemp(join(tmpdir(), 'zcodepro-webdav-'));
|
|
125
131
|
const bodyFile = join(dir, 'body');
|
|
126
132
|
try {
|
|
127
133
|
const args = ['-sS', '--connect-timeout', '10', '--max-time', String(Math.max(10, Math.ceil(timeoutMs / 1000))),
|
|
128
|
-
'-w', '%{http_code} %{redirect_url}', '-o', bodyFile, '--config', '-'];
|
|
134
|
+
'-w', '%{http_code} %{redirect_url}', '-o', downloadFile || bodyFile, '--config', '-'];
|
|
129
135
|
for (const hd of headers) {
|
|
130
136
|
// 头只能是这里拼好的固定字符串(Depth: 1 等),带换行的一律丢弃
|
|
131
137
|
if (typeof hd === 'string' && hd && !/[\r\n]/.test(hd)) args.push('-H', hd);
|
|
132
138
|
}
|
|
133
139
|
if (method === 'PUT' && uploadFile) {
|
|
134
|
-
|
|
140
|
+
const ct = contentType !== undefined ? contentType : 'application/json';
|
|
141
|
+
if (ct) args.push('-H', 'Content-Type: ' + ct);
|
|
142
|
+
args.push('-T', uploadFile);
|
|
135
143
|
} else {
|
|
136
144
|
args.push('-X', method);
|
|
137
145
|
}
|
|
@@ -171,9 +179,9 @@ export async function webdavRequest({ method, url, login = '', password = '', up
|
|
|
171
179
|
|
|
172
180
|
// PUT 前确保云端目录存在:409(父目录缺失)时逐段 MKCOL 再重试一次。
|
|
173
181
|
// 已存在的目录 MKCOL 多半回 405,忽略即可——这里是尽力而为,最终成败仍看 PUT。
|
|
174
|
-
export async function webdavPutWithMkcol({ url, login, password, uploadFile, proxyUrl, timeoutMs }) {
|
|
182
|
+
export async function webdavPutWithMkcol({ url, login, password, uploadFile, proxyUrl, timeoutMs, contentType }) {
|
|
175
183
|
const auth = { login, password, proxyUrl };
|
|
176
|
-
const first = await webdavRequest({ method: 'PUT', url, uploadFile, ...auth, timeoutMs });
|
|
184
|
+
const first = await webdavRequest({ method: 'PUT', url, uploadFile, contentType, ...auth, timeoutMs });
|
|
177
185
|
if (!first.ok || (first.httpCode >= 200 && first.httpCode < 300) || first.httpCode !== 409) return first;
|
|
178
186
|
try {
|
|
179
187
|
const u = new URL(url);
|
|
@@ -185,7 +193,7 @@ export async function webdavPutWithMkcol({ url, login, password, uploadFile, pro
|
|
|
185
193
|
await webdavRequest({ method: 'MKCOL', url: cur, ...auth, timeoutMs: 15000 });
|
|
186
194
|
}
|
|
187
195
|
} catch { /* URL 解析失败:直接重试原 PUT */ }
|
|
188
|
-
return webdavRequest({ method: 'PUT', url, uploadFile, ...auth, timeoutMs });
|
|
196
|
+
return webdavRequest({ method: 'PUT', url, uploadFile, contentType, ...auth, timeoutMs });
|
|
189
197
|
}
|
|
190
198
|
|
|
191
199
|
// 解析 PROPFIND(Depth: 1)的 multistatus 响应,只取文件条目:各网盘的命名空间
|
package/src/inject/core.js
CHANGED
|
@@ -9,6 +9,7 @@ export const HELPER_URL = BOOT.helperUrl || 'http://127.0.0.1:47889';
|
|
|
9
9
|
function liveBoot() {
|
|
10
10
|
return (typeof window !== 'undefined' && window.__ZCODEPRO__) || BOOT;
|
|
11
11
|
}
|
|
12
|
+
export { liveBoot };
|
|
12
13
|
|
|
13
14
|
export function rpc(path, options = {}) {
|
|
14
15
|
const boot = liveBoot();
|
|
@@ -24,17 +25,28 @@ export function rpc(path, options = {}) {
|
|
|
24
25
|
.catch((err) => ({ ok: false, error: String(err && err.message || err) }));
|
|
25
26
|
}
|
|
26
27
|
|
|
27
|
-
//
|
|
28
|
+
// 配置缓存:菜单每次打开时拉取,保证设置弹窗里的开关即时生效。
|
|
29
|
+
// 并发合并:config-changed 事件的多个监听方会同时 getConfig(true)(绕过
|
|
30
|
+
// 缓存),不合并时一次保存触发 N 个并发全量拉取(每个都是 helper 读盘+序列化)
|
|
28
31
|
let configCache = { value: null, at: 0 };
|
|
32
|
+
let configInflight = null;
|
|
29
33
|
export async function getConfig(force = false) {
|
|
30
34
|
const now = Date.now();
|
|
31
35
|
if (!force && configCache.value && now - configCache.at < 3000) return configCache.value;
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
36
|
+
if (configInflight) return configInflight;
|
|
37
|
+
configInflight = (async () => {
|
|
38
|
+
try {
|
|
39
|
+
const res = await rpc('/config');
|
|
40
|
+
if (res.ok && res.config) {
|
|
41
|
+
configCache = { value: res.config, at: Date.now() };
|
|
42
|
+
return res.config;
|
|
43
|
+
}
|
|
44
|
+
return configCache.value || { features: {} };
|
|
45
|
+
} finally {
|
|
46
|
+
configInflight = null;
|
|
47
|
+
}
|
|
48
|
+
})();
|
|
49
|
+
return configInflight;
|
|
38
50
|
}
|
|
39
51
|
export function clearConfigCache() { configCache = { value: null, at: 0 }; }
|
|
40
52
|
|
|
@@ -53,7 +65,7 @@ export const DEFAULT_SHORTCUTS = {
|
|
|
53
65
|
export const STATUS_SESSION_IDS = new Set(['ctx', 'sessionCost', 'questions']);
|
|
54
66
|
export const STATUS_GLOBAL_IDS = new Set(['todayTokens', 'todayCost', 'totalTokens', 'totalCost', 'tasks']);
|
|
55
67
|
|
|
56
|
-
// 状态栏统计项的默认清单(顺序即默认显示顺序);设置 →
|
|
68
|
+
// 状态栏统计项的默认清单(顺序即默认显示顺序);设置 → 外观 → 状态栏里
|
|
57
69
|
// 可调顺序与显隐,配置存在 config.statusBar.items(整表覆盖)
|
|
58
70
|
// 每轮回答统计项(turn-usage.js):回答末尾标注的三段,各自可显隐(顺序固定)
|
|
59
71
|
export const DEFAULT_TURN_ITEMS = [
|
|
@@ -106,7 +118,7 @@ export function isNewProjectSidebar() {
|
|
|
106
118
|
const zh = {
|
|
107
119
|
settingsTitle: 'ZCode Pro',
|
|
108
120
|
tabFeatures: '功能',
|
|
109
|
-
tabStyles: '
|
|
121
|
+
tabStyles: '外观',
|
|
110
122
|
tabProxy: '代理',
|
|
111
123
|
tabBehavior: '行为',
|
|
112
124
|
tabAgents: '全局提示词',
|
|
@@ -187,8 +199,6 @@ const zh = {
|
|
|
187
199
|
syncVerDeleteDesc: '将删除云端的这份备份,不可恢复:',
|
|
188
200
|
syncVerDeleted: '已删除云端备份',
|
|
189
201
|
syncVerSaved: '已保存到',
|
|
190
|
-
devMode: '开发模式',
|
|
191
|
-
devModeRowDesc: '开启后出现“重载 ZCode Pro”按钮;也可给启动器设环境变量 ZCODEPRO_DEV=1 启用。此标签页仅在开发模式生效时显示。',
|
|
192
202
|
devModeDesc: '开发调试用:改完 zcode-pro 代码后不必重启 ZCode,点“重载 ZCode Pro”即可加载新代码。流程为自动构建(需在源码目录运行)→ 以新进程重启 ZCode Pro(本地助手与注入脚本都换新)→ 所有 ZCode 窗口自动刷新并重新注入;ZCode 本体与已打开的会话不受影响。',
|
|
193
203
|
devReload: '重载 ZCode Pro',
|
|
194
204
|
devReloading: '重载中…',
|
|
@@ -296,13 +306,65 @@ const zh = {
|
|
|
296
306
|
featureNewtabPanelsDesc: '官方“新标签页”只列官方面板与推荐的官方插件,此功能把第三方市场插件(headroom、rtk 等)的面板卡片加进去,点击在侧边面板打开。',
|
|
297
307
|
ntpDescSuffix: '插件面板',
|
|
298
308
|
ntpNeedSession: '先打开一个任务会话,再从新标签页打开插件面板',
|
|
299
|
-
featureStatusBarDesc: '
|
|
300
|
-
tabStylesPane: '
|
|
309
|
+
featureStatusBarDesc: '提问输入框右下角和每轮回答最后显示统计项。',
|
|
310
|
+
tabStylesPane: '样式',
|
|
301
311
|
tabStatusPane: '状态栏',
|
|
312
|
+
themeColorsPane: '主题',
|
|
313
|
+
themeBgRegions: '分区背景',
|
|
314
|
+
themeBgRegionFollow: '无背景',
|
|
315
|
+
themeBgRegionLeft: '左侧边栏',
|
|
316
|
+
themeBgRegionCenter: '中间区域',
|
|
317
|
+
themeBgRegionRight: '右侧边栏',
|
|
318
|
+
themeBgPane: '背景图',
|
|
319
|
+
themeSkinTitle: '皮肤',
|
|
320
|
+
themeSkinDesc: '知名编辑器主题配色,自动匹配 ZCode 的浅色 / 深色模式;默认保持 ZCode 原样,自定义在下方自行调色。',
|
|
321
|
+
themeSkinCustom: '自定义',
|
|
322
|
+
themeCustomTitle: '自定义主题',
|
|
323
|
+
themeCustomHint: '改颜色即实时生效(自动切到自定义皮肤);浅色、深色各配一次,选好 5 个颜色即可生成整套界面配色。',
|
|
324
|
+
themeCustomLight: '浅色',
|
|
325
|
+
themeCustomDark: '深色',
|
|
326
|
+
themeCustomBg: '背景',
|
|
327
|
+
themeCustomCard: '卡片',
|
|
328
|
+
themeCustomFg: '正文',
|
|
329
|
+
themeCustomMuted: '次级文字',
|
|
330
|
+
themeCustomAccent: '强调色',
|
|
331
|
+
themeBgDesc: '背景来源可选本地图库、文件夹(定时轮换)或网址(定时刷新),左栏 / 中栏 / 右栏可分区独立设置;侧栏、会话流、输入框透出背景,弹窗菜单保持不透明保可读。本地图库随 WebDAV 同步上传(覆盖式,不留历史版本)。',
|
|
332
|
+
themeBgModeNone: '无背景',
|
|
333
|
+
themeBgModeLibrary: '本地图库',
|
|
334
|
+
themeBgModeFolder: '文件夹',
|
|
335
|
+
themeBgModeUrl: '网址',
|
|
336
|
+
themeBgLibrary: '我的图库',
|
|
337
|
+
themeBgUpload: '上传图片',
|
|
338
|
+
themeBgEmpty: '还没有上传图片,支持 png / jpg / webp / gif / bmp / avif,单张 10MB 以内',
|
|
339
|
+
themeBgDelete: '删除',
|
|
340
|
+
themeBgNoneHint: '选择一种背景来源;文件夹可定时轮换,网址可配合随机图 API 定时刷新。',
|
|
341
|
+
themeBgFolderPick: '浏览',
|
|
342
|
+
themeBgFolderPickTitle: '选择背景图文件夹',
|
|
343
|
+
themeBgFolderHint: '选择一个装壁纸的文件夹,图片原地读取不复制。',
|
|
344
|
+
themeBgFolderRandom: '随机顺序',
|
|
345
|
+
themeBgIntervalHint: '切换间隔(分钟)',
|
|
346
|
+
themeBgIntervalUnit: '分钟,0 = 不切换',
|
|
347
|
+
themeBgFolderCount: '文件夹里有 {n} 张图片,定时轮换中。',
|
|
348
|
+
themeBgFolderMissing: '文件夹不存在或里面没有图片。',
|
|
349
|
+
themeBgUrlHint: '定时刷新适合随机图 API(每次请求返回不同图);刷新时带缓存参数避免命中缓存。',
|
|
350
|
+
themeBgUrlIntervalHint: '刷新间隔(分钟)',
|
|
351
|
+
themeBgUrlInvalid: '网址需以 http(s):// 开头',
|
|
352
|
+
themeBgFit: '缩放方式',
|
|
353
|
+
themeBgFitCover: '填满',
|
|
354
|
+
themeBgFitContain: '适应',
|
|
355
|
+
themeBgFitFill: '拉伸',
|
|
356
|
+
themeBgFitTile: '平铺',
|
|
357
|
+
themeBgMask: '遮罩不透明度',
|
|
358
|
+
themeBgMaskHint: '数值越高,图片越向主题背景色淡化,文字对比越清晰',
|
|
359
|
+
themeBgMaskHint2: '图片向主题背景色淡化的程度',
|
|
360
|
+
themeBgBlur: '图片模糊',
|
|
361
|
+
themeBgPanel: '面板不透明度',
|
|
362
|
+
themeBgPanelHint: '侧栏、面板与标签栏的不透明程度。越低,透出的图片越多。',
|
|
363
|
+
themeBgDeletedActive: '当前背景已删除,已自动停用背景',
|
|
302
364
|
sbItemCtx: '上下文占用',
|
|
303
365
|
sbItemQuestions: '提问次数',
|
|
304
366
|
sbMaster: '启用统计',
|
|
305
|
-
sbMasterDesc: '
|
|
367
|
+
sbMasterDesc: '总开关:关闭后输入框右下角的统计行与每轮回答的用量标注一起停用。',
|
|
306
368
|
sbGroupSession: '会话统计(跟随当前会话)',
|
|
307
369
|
sbGroupGlobal: '总统计',
|
|
308
370
|
sbGroupTurn: '每轮统计(回答末尾)',
|
|
@@ -515,7 +577,7 @@ const zh = {
|
|
|
515
577
|
aliasMigrateDesc: '当前 ZCode 已支持修改项目名称(项目“更多”菜单 → 编辑项目),自定义别名功能已停用。可把已保存的别名写入官方项目名称,勾选要迁移的项目;迁移只改项目名称,磁盘目录不受影响。',
|
|
516
578
|
aliasMigrateItemHint: '迁移后将作为项目名称保存(可随时在“编辑项目”中再改)。仅无标记的条目可勾选;已迁移、已并入、项目已删除、本机不存在(旧机器路径)以及项目已重命名的都不可勾选——要改这些项目的名字,直接用项目“更多”菜单里的“编辑项目”。',
|
|
517
579
|
aliasMigrateStateDone: '已迁移',
|
|
518
|
-
aliasMigrateStateMerged: '
|
|
580
|
+
aliasMigrateStateMerged: '已并入“{name}”',
|
|
519
581
|
aliasMigrateStateDeleted: '项目已删除',
|
|
520
582
|
aliasMigrateStateMissing: '本机不存在',
|
|
521
583
|
aliasMigrateStateRenamed: '项目已重命名',
|
|
@@ -563,7 +625,7 @@ const zh = {
|
|
|
563
625
|
const en = {
|
|
564
626
|
settingsTitle: 'ZCode Pro',
|
|
565
627
|
tabFeatures: 'Features',
|
|
566
|
-
tabStyles: '
|
|
628
|
+
tabStyles: 'Appearance',
|
|
567
629
|
tabProxy: 'Proxy',
|
|
568
630
|
tabBehavior: 'Behavior',
|
|
569
631
|
tabAgents: 'Global Prompt',
|
|
@@ -644,8 +706,6 @@ const en = {
|
|
|
644
706
|
syncVerDeleteDesc: 'This backup will be deleted from the server and cannot be recovered: ',
|
|
645
707
|
syncVerDeleted: 'Backup deleted from the server.',
|
|
646
708
|
syncVerSaved: 'Saved to',
|
|
647
|
-
devMode: 'Developer mode',
|
|
648
|
-
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.',
|
|
649
709
|
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.',
|
|
650
710
|
devReload: 'Reload ZCode Pro',
|
|
651
711
|
devReloading: 'Reloading…',
|
|
@@ -753,13 +813,65 @@ const en = {
|
|
|
753
813
|
featureNewtabPanelsDesc: "The built-in \"new tab page\" only lists built-in panels and recommended official plugins; this adds cards for third-party marketplace plugin panels (headroom, rtk, …) that open in the side pane.",
|
|
754
814
|
ntpDescSuffix: 'plugin panel',
|
|
755
815
|
ntpNeedSession: 'Open a task session first, then open plugin panels from the new tab page',
|
|
756
|
-
featureStatusBarDesc: 'Shows statistics at the bottom-right of the
|
|
816
|
+
featureStatusBarDesc: 'Shows statistics at the bottom-right of the input box and at the end of each answer.',
|
|
757
817
|
tabStylesPane: 'Styles',
|
|
758
818
|
tabStatusPane: 'Status bar',
|
|
819
|
+
themeColorsPane: 'Theme',
|
|
820
|
+
themeBgRegions: 'Regional backgrounds',
|
|
821
|
+
themeBgRegionFollow: 'None',
|
|
822
|
+
themeBgRegionLeft: 'Left sidebar',
|
|
823
|
+
themeBgRegionCenter: 'Center area',
|
|
824
|
+
themeBgRegionRight: 'Right sidebar',
|
|
825
|
+
themeBgPane: 'Background',
|
|
826
|
+
themeSkinTitle: 'Skins',
|
|
827
|
+
themeSkinDesc: 'Famous editor theme palettes, auto-matching ZCode\u2019s light / dark mode; "Default" keeps ZCode as-is, "Custom" is tuned below.',
|
|
828
|
+
themeSkinCustom: 'Custom',
|
|
829
|
+
themeCustomTitle: 'Custom theme',
|
|
830
|
+
themeCustomHint: 'Color changes apply live (auto-switches to the custom skin); set light and dark separately — pick 5 colors and the whole palette is derived.',
|
|
831
|
+
themeCustomLight: 'Light',
|
|
832
|
+
themeCustomDark: 'Dark',
|
|
833
|
+
themeCustomBg: 'Background',
|
|
834
|
+
themeCustomCard: 'Card',
|
|
835
|
+
themeCustomFg: 'Text',
|
|
836
|
+
themeCustomMuted: 'Muted text',
|
|
837
|
+
themeCustomAccent: 'Accent',
|
|
838
|
+
themeBgDesc: 'Sources: local library, folder (rotate on a timer) or URL (refresh on a timer); left / center / right regions can each have their own. Sidebar, chat and composer reveal the background while menus and dialogs stay opaque. The library uploads with the zcode-pro sync group (overwrite, no history versions).',
|
|
839
|
+
themeBgModeNone: 'None',
|
|
840
|
+
themeBgModeLibrary: 'Library',
|
|
841
|
+
themeBgModeFolder: 'Folder',
|
|
842
|
+
themeBgModeUrl: 'URL',
|
|
843
|
+
themeBgLibrary: 'My library',
|
|
844
|
+
themeBgUpload: 'Upload',
|
|
845
|
+
themeBgEmpty: 'No images yet. png / jpg / webp / gif / bmp / avif, up to 10MB each',
|
|
846
|
+
themeBgDelete: 'Delete',
|
|
847
|
+
themeBgNoneHint: 'Pick a background source; folders rotate on a timer, URLs refresh on a timer for random-image APIs.',
|
|
848
|
+
themeBgFolderPick: 'Browse',
|
|
849
|
+
themeBgFolderPickTitle: 'Choose a wallpaper folder',
|
|
850
|
+
themeBgFolderHint: 'Pick a folder of wallpapers; images are read in place, not copied.',
|
|
851
|
+
themeBgFolderRandom: 'Random order',
|
|
852
|
+
themeBgIntervalHint: 'Switch interval (minutes)',
|
|
853
|
+
themeBgIntervalUnit: 'min, 0 = off',
|
|
854
|
+
themeBgFolderCount: '{n} images in the folder, rotating on the timer.',
|
|
855
|
+
themeBgFolderMissing: 'Folder missing or contains no images.',
|
|
856
|
+
themeBgUrlHint: 'Timed refresh suits random-image APIs (each request returns a new image); a cache-buster is appended on refresh.',
|
|
857
|
+
themeBgUrlIntervalHint: 'Refresh interval (minutes)',
|
|
858
|
+
themeBgUrlInvalid: 'URL must start with http(s)://',
|
|
859
|
+
themeBgFit: 'Fit',
|
|
860
|
+
themeBgFitCover: 'Fill',
|
|
861
|
+
themeBgFitContain: 'Fit',
|
|
862
|
+
themeBgFitFill: 'Stretch',
|
|
863
|
+
themeBgFitTile: 'Tile',
|
|
864
|
+
themeBgMask: 'Mask opacity',
|
|
865
|
+
themeBgMaskHint: 'Higher values fade the image toward the theme background color for clearer text',
|
|
866
|
+
themeBgMaskHint2: 'How much the image fades toward the theme background color',
|
|
867
|
+
themeBgBlur: 'Image blur',
|
|
868
|
+
themeBgPanel: 'Panel opacity',
|
|
869
|
+
themeBgPanelHint: 'Opacity of sidebar, panels and tab bar. Lower reveals more of the image.',
|
|
870
|
+
themeBgDeletedActive: 'Active background was deleted; background disabled',
|
|
759
871
|
sbItemCtx: 'Context usage',
|
|
760
872
|
sbItemQuestions: 'Questions asked',
|
|
761
873
|
sbMaster: 'Enable statistics',
|
|
762
|
-
sbMasterDesc: 'Master switch: turning this off disables both the
|
|
874
|
+
sbMasterDesc: 'Master switch: turning this off disables both the input-box statistics and per-turn labels.',
|
|
763
875
|
sbGroupSession: 'Session statistics (follows current session)',
|
|
764
876
|
sbGroupGlobal: 'Global statistics',
|
|
765
877
|
sbGroupTurn: 'Per-turn statistics (answer rows)',
|