codebee 0.1.12 → 0.1.14
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/CHANGELOG.md +13 -0
- package/README.md +21 -0
- package/app/core/covergen.py +200 -0
- package/app/core/flows.py +3 -0
- package/app/core/goal_service.py +11 -2
- package/app/core/jobs.py +5 -0
- package/app/core/market_remote.py +6 -3
- package/app/core/modelhub.py +42 -15
- package/app/core/notify.py +96 -0
- package/app/core/paihang.py +65 -0
- package/app/core/pipeline.py +26 -12
- package/app/core/publish/browser.py +72 -10
- package/app/core/publish/flow.py +81 -2
- package/app/core/publish/flows-qimao-calibrated.json +233 -0
- package/app/core/publish/manager.py +83 -13
- package/app/core/publish/qimao.py +29 -0
- package/app/core/settings.py +3 -1
- package/app/core/settings_schema.py +15 -2
- package/app/core/step_runner.py +23 -1
- package/app/core/store.py +16 -0
- package/app/core/tlsctx.py +48 -0
- package/app/main.py +27 -0
- package/app/ui/app.js +101 -0
- package/app/ui/i18n.js +10 -0
- package/app/ui/index.html +1 -0
- package/app/ui/style.css +10 -0
- package/package.json +1 -1
|
@@ -95,3 +95,32 @@ def tag_groups(meta):
|
|
|
95
95
|
if isinstance(v, list) and v:
|
|
96
96
|
out.append((key, [str(x) for x in v]))
|
|
97
97
|
return out
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def chapter_manage_url(book):
|
|
101
|
+
"""章节管理页(上线验证用):有 book_id 直达,否则回作品管理页。"""
|
|
102
|
+
bid = str((book or {}).get("book_id") or "")
|
|
103
|
+
if bid: # 实测 .../front/book-manage/manage?id=
|
|
104
|
+
return CONFIG["book_manage"] + "/manage?id=" + bid
|
|
105
|
+
return CONFIG["book_manage"]
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def draft_url(book):
|
|
109
|
+
"""草稿箱页(发章真发布链中转站)。"""
|
|
110
|
+
bid = str((book or {}).get("book_id") or "")
|
|
111
|
+
if bid:
|
|
112
|
+
return CONFIG["book_manage"] + "/draft?id=" + bid
|
|
113
|
+
return CONFIG["book_manage"]
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def editor_url(book):
|
|
117
|
+
"""章节编辑器直达(绕开会开新 tab 的「上传章节」点击)。
|
|
118
|
+
|
|
119
|
+
缺 title 参数会被平台重定向回首页(真机实测),必须带上。"""
|
|
120
|
+
bid = str((book or {}).get("book_id") or "")
|
|
121
|
+
title = str((book or {}).get("title") or "")
|
|
122
|
+
if bid:
|
|
123
|
+
from urllib.parse import quote
|
|
124
|
+
return (CONFIG["book_manage"].rsplit("/", 1)[0]
|
|
125
|
+
+ "/book-upload?id=" + bid + "&title=" + quote(title))
|
|
126
|
+
return CONFIG["book_manage"]
|
package/app/core/settings.py
CHANGED
|
@@ -18,7 +18,7 @@ _FILE = paths.DATA_DIR / "settings.json"
|
|
|
18
18
|
# 成功发章上限、平台连续失败几次后暂停自动发布(publish/auto.py 读取)
|
|
19
19
|
DEFAULTS = {"max_concurrent_jobs": 6, "default_workdir": "", "hooks_token": "",
|
|
20
20
|
"telemetry_errors": True, "publish_daily_cap": 10,
|
|
21
|
-
"publish_fail_streak": 3}
|
|
21
|
+
"publish_fail_streak": 3, "notify_webhook": ""}
|
|
22
22
|
# 并发上限 12:worker 只是拉起 CLI 子进程的调度位,跨任务无共享资源;
|
|
23
23
|
# 同任务单飞守卫在 jobs 层。默认 6 对齐「多任务并行不排队」的使用预期。
|
|
24
24
|
MIN_WORKERS, MAX_WORKERS = 1, 12
|
|
@@ -99,6 +99,8 @@ def save(patch):
|
|
|
99
99
|
cur["publish_fail_streak"] = max(1, min(10, int(patch.get("publish_fail_streak"))))
|
|
100
100
|
except (TypeError, ValueError):
|
|
101
101
|
return cur, "publish_fail_streak 必须是 1-10 的整数"
|
|
102
|
+
if "notify_webhook" in patch:
|
|
103
|
+
cur["notify_webhook"] = str(patch.get("notify_webhook") or "").strip()[:300]
|
|
102
104
|
_FILE.parent.mkdir(parents=True, exist_ok=True)
|
|
103
105
|
tmp = _FILE.with_suffix(".tmp")
|
|
104
106
|
tmp.write_text(json.dumps(cur, ensure_ascii=False, indent=2), encoding="utf-8")
|
|
@@ -149,6 +149,12 @@ def revision(ns):
|
|
|
149
149
|
return _REVISIONS.get(ns, 0)
|
|
150
150
|
|
|
151
151
|
|
|
152
|
+
def names():
|
|
153
|
+
"""已注册 namespace 清单(GET /api/settings-v2 用)。"""
|
|
154
|
+
with _LOCK:
|
|
155
|
+
return sorted(_NAMESPACES)
|
|
156
|
+
|
|
157
|
+
|
|
152
158
|
def mutate(ns, ops, expected_revision=None):
|
|
153
159
|
"""写一组操作。ops = [{"op":"set","path":..., "value":...}, ...]。
|
|
154
160
|
|
|
@@ -189,15 +195,22 @@ def mutate(ns, ops, expected_revision=None):
|
|
|
189
195
|
|
|
190
196
|
|
|
191
197
|
def describe(ns, redact_secrets=True):
|
|
192
|
-
"""给 UI 的视图:默认把 redact 字段替换为 __REDACTED__(不泄露真值)。
|
|
198
|
+
"""给 UI 的视图:默认把 redact 字段替换为 __REDACTED__(不泄露真值)。
|
|
199
|
+
|
|
200
|
+
fields 带回字段元数据(类型/说明/choices/clamp),前端调参卡按 schema
|
|
201
|
+
渲染控件,schema 变更零前端改动。"""
|
|
193
202
|
with _LOCK:
|
|
194
203
|
vals = json.loads(json.dumps(_VALUES.get(ns, {}))) # deep copy
|
|
195
204
|
ndef = _NAMESPACES.get(ns) or {"fields": {}}
|
|
205
|
+
fields = [{"path": f.path, "type": f.ftype, "description": f.description,
|
|
206
|
+
"choices": f.choices, "clamp": f.clamp}
|
|
207
|
+
for f in ndef["fields"].values()]
|
|
196
208
|
for f in ndef["fields"].values():
|
|
197
209
|
if f.redact and redact_secrets:
|
|
198
210
|
if _get_path(vals, f.path) not in (None, ""):
|
|
199
211
|
_set_path(vals, f.path, "__REDACTED__")
|
|
200
|
-
return {"ns": ns, "revision": _REVISIONS.get(ns, 0), "values": vals
|
|
212
|
+
return {"ns": ns, "revision": _REVISIONS.get(ns, 0), "values": vals,
|
|
213
|
+
"fields": fields}
|
|
201
214
|
|
|
202
215
|
|
|
203
216
|
# ---- 默认 namespace(编排阈值的 schema 化存放处;pipeline 可渐进接入) ----
|
package/app/core/step_runner.py
CHANGED
|
@@ -14,7 +14,7 @@ from __future__ import annotations
|
|
|
14
14
|
|
|
15
15
|
import logging
|
|
16
16
|
|
|
17
|
-
from .compaction import maybe_compact
|
|
17
|
+
from .compaction import maybe_compact, DEFAULT_PRESSURE_THRESHOLD
|
|
18
18
|
from .error_codes import ErrorCode
|
|
19
19
|
|
|
20
20
|
log = logging.getLogger(__name__)
|
|
@@ -28,6 +28,23 @@ _OVERFLOW_CODES = {ErrorCode.CONTEXT_OVERFLOW, ErrorCode.MAX_TOKENS}
|
|
|
28
28
|
_PRECHECK_RATIO = 0.9
|
|
29
29
|
|
|
30
30
|
|
|
31
|
+
def _v2_compaction_tuning():
|
|
32
|
+
"""settings_v2 orchestrator.compaction 微调 → (threshold|None, retain|None)。
|
|
33
|
+
|
|
34
|
+
None = 未配置(走 maybe_compact 的模块默认);读取/注册失败静默回落。
|
|
35
|
+
retain 允许 0(尾部不保留),与 None(未配置)语义不同,勿合并。"""
|
|
36
|
+
try:
|
|
37
|
+
from .settings_schema import get as ss_get, register_default_namespaces
|
|
38
|
+
register_default_namespaces()
|
|
39
|
+
c = (ss_get("orchestrator") or {}).get("compaction") or {}
|
|
40
|
+
th = c.get("pressure_threshold")
|
|
41
|
+
rt = c.get("retain_tail_tokens")
|
|
42
|
+
return (float(th) if th else None,
|
|
43
|
+
int(rt) if rt is not None else None)
|
|
44
|
+
except Exception:
|
|
45
|
+
return None, None
|
|
46
|
+
|
|
47
|
+
|
|
31
48
|
def execute_step(session, run_agent_fn, prompt, *, model: str = "",
|
|
32
49
|
llm_caller=None, retain_tail_tokens=None, **kwargs):
|
|
33
50
|
"""执行一次 step;撑爆时压缩并守门重试一次。
|
|
@@ -47,9 +64,14 @@ def execute_step(session, run_agent_fn, prompt, *, model: str = "",
|
|
|
47
64
|
Returns:
|
|
48
65
|
(result, retried: bool)
|
|
49
66
|
"""
|
|
67
|
+
v2_th, v2_rt = _v2_compaction_tuning()
|
|
50
68
|
compact_kwargs = {}
|
|
51
69
|
if retain_tail_tokens is not None:
|
|
52
70
|
compact_kwargs["retain_tail_tokens"] = retain_tail_tokens
|
|
71
|
+
elif v2_rt is not None:
|
|
72
|
+
compact_kwargs["retain_tail_tokens"] = v2_rt
|
|
73
|
+
if v2_th is not None:
|
|
74
|
+
compact_kwargs["threshold"] = v2_th
|
|
53
75
|
if llm_caller is not None and model:
|
|
54
76
|
try:
|
|
55
77
|
from .token_meter import token_meter
|
package/app/core/store.py
CHANGED
|
@@ -299,6 +299,22 @@ def set_book_meta(task_id, platform, entry):
|
|
|
299
299
|
return True
|
|
300
300
|
|
|
301
301
|
|
|
302
|
+
def set_cover_gen(task_id, entry):
|
|
303
|
+
"""写任务的封面生成状态(cover_gen = {status, file?, run_id?, model?, error?, at})。
|
|
304
|
+
|
|
305
|
+
与 set_book_meta 同理必须 bump_state(SSE 推送翻卡片)。任务不存在返回 False。"""
|
|
306
|
+
if not _valid_id(task_id):
|
|
307
|
+
return False
|
|
308
|
+
with LOCK:
|
|
309
|
+
task = _TASKS.get(task_id)
|
|
310
|
+
if not task:
|
|
311
|
+
return False
|
|
312
|
+
task["cover_gen"] = entry
|
|
313
|
+
_save_json(paths.TASKS_DIR / (task_id + ".json"), task)
|
|
314
|
+
bump_state()
|
|
315
|
+
return True
|
|
316
|
+
|
|
317
|
+
|
|
302
318
|
def set_auto_publish(task_id, ap):
|
|
303
319
|
"""写任务的定时发布配置(auto.py 每日到点读它触发批量发布)。
|
|
304
320
|
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""HTTPS 证书校验上下文:补齐 macOS 上 Python 缺失的 CA 源(不关校验)。
|
|
3
|
+
|
|
4
|
+
现象:用官方 pkg 装 Node 的 Mac 上往往再装 python.org 的 Python,它不读
|
|
5
|
+
系统钥匙串,默认验证路径下没有根证书,所有 HTTPS 请求报
|
|
6
|
+
CERTIFICATE_VERIFY_FAILED(unable to get local issuer certificate)。
|
|
7
|
+
|
|
8
|
+
修法是给默认上下文**追加**可用 CA 源,校验语义只增不减:
|
|
9
|
+
1. certifi(装了就用,跨平台最全);
|
|
10
|
+
2. /etc/ssl/cert.pem(macOS 系统自带 CA 束,Catalina 起就有)。
|
|
11
|
+
两个都拿不到时返回默认上下文——报错与旧行为一致,绝不静默关校验。
|
|
12
|
+
"""
|
|
13
|
+
import ssl
|
|
14
|
+
import sys
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def cafiles():
|
|
18
|
+
"""候选 CA 束路径;坏路径/不存在由 load_verify_locations 抛错后被吞。"""
|
|
19
|
+
out = []
|
|
20
|
+
try:
|
|
21
|
+
import certifi
|
|
22
|
+
out.append(certifi.where())
|
|
23
|
+
except Exception:
|
|
24
|
+
pass
|
|
25
|
+
if sys.platform == "darwin":
|
|
26
|
+
out.append("/etc/ssl/cert.pem")
|
|
27
|
+
return out
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _build():
|
|
31
|
+
ctx = ssl.create_default_context()
|
|
32
|
+
for f in cafiles():
|
|
33
|
+
try:
|
|
34
|
+
ctx.load_verify_locations(cafile=f)
|
|
35
|
+
except Exception:
|
|
36
|
+
pass
|
|
37
|
+
return ctx
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
_CTX = None
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def context():
|
|
44
|
+
"""带兜底 CA 源的 ssl.SSLContext(进程内缓存;校验语义与默认一致)。"""
|
|
45
|
+
global _CTX
|
|
46
|
+
if _CTX is None:
|
|
47
|
+
_CTX = _build()
|
|
48
|
+
return _CTX
|
package/app/main.py
CHANGED
|
@@ -235,6 +235,12 @@ class Handler(BaseHTTPRequestHandler):
|
|
|
235
235
|
return self._json(200, skills.view())
|
|
236
236
|
if path == "/api/settings":
|
|
237
237
|
return self._json(200, dict(settings.load(), **jobs.workers_info()))
|
|
238
|
+
if path == "/api/settings-v2":
|
|
239
|
+
# schema 化设置全貌(secret 已脱敏;前端调参卡直读)
|
|
240
|
+
from core import settings_schema as ss2
|
|
241
|
+
ss2.register_default_namespaces()
|
|
242
|
+
return self._json(200, {"namespaces": {
|
|
243
|
+
ns: ss2.describe(ns) for ns in ss2.names()}})
|
|
238
244
|
if path == "/api/selfupdate":
|
|
239
245
|
from core import selfupdate
|
|
240
246
|
return self._json(200, selfupdate.check(
|
|
@@ -580,6 +586,11 @@ class Handler(BaseHTTPRequestHandler):
|
|
|
580
586
|
m = re.match(r"^/api/tasks/([^/]+)/book-meta$", path)
|
|
581
587
|
if m:
|
|
582
588
|
return self._api_book_meta_generate(m.group(1))
|
|
589
|
+
m = re.match(r"^/api/tasks/([^/]+)/cover$", path)
|
|
590
|
+
if m:
|
|
591
|
+
from core import covergen
|
|
592
|
+
ok, err = covergen.start(m.group(1))
|
|
593
|
+
return self._json(400, {"error": err}) if not ok else self._json(200, {"ok": True})
|
|
583
594
|
m = re.match(r"^/api/publish/(fanqie|qimao)/(connect|disconnect|probe)$", path)
|
|
584
595
|
if m:
|
|
585
596
|
return self._api_publish_platform_op(m.group(1), m.group(2))
|
|
@@ -809,6 +820,22 @@ class Handler(BaseHTTPRequestHandler):
|
|
|
809
820
|
return self._json(400, {"error": err, "settings": view})
|
|
810
821
|
n = jobs.configure(view["max_concurrent_jobs"])
|
|
811
822
|
return self._json(200, {"ok": True, "settings": view, "workers": n})
|
|
823
|
+
m = re.match(r"^/api/settings-v2/([a-z_-]+)$", path)
|
|
824
|
+
if m:
|
|
825
|
+
# schema 化设置写入口:{ops:[{op:"set",path,value}], expected_revision?}
|
|
826
|
+
# 带 expected_revision 做 CAS,冲突 409(前端据此重拉重试)
|
|
827
|
+
from core import settings_schema as ss2
|
|
828
|
+
ns = m.group(1)
|
|
829
|
+
body = self._body() or {}
|
|
830
|
+
try:
|
|
831
|
+
rev = ss2.mutate(ns, body.get("ops") or [],
|
|
832
|
+
expected_revision=body.get("expected_revision"))
|
|
833
|
+
except ss2.SettingsConflictError as e:
|
|
834
|
+
return self._json(409, {"error": str(e), "revision": ss2.revision(ns)})
|
|
835
|
+
except ValueError as e:
|
|
836
|
+
return self._json(400, {"error": str(e)})
|
|
837
|
+
return self._json(200, {"ok": True, "revision": rev,
|
|
838
|
+
"values": ss2.describe(ns)["values"]})
|
|
812
839
|
if path == "/api/settings/default-workdir":
|
|
813
840
|
body = self._body()
|
|
814
841
|
old = settings.default_workdir()
|
package/app/ui/app.js
CHANGED
|
@@ -26,6 +26,7 @@ function flowIconHtml(f) {
|
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
function flowDesc(f) {
|
|
29
|
+
if (f.id === "rank_scan") return t("抓七猫榜 → AI 选题洞察");
|
|
29
30
|
if (f.engine === "direct") return t("单智能体直达(快)");
|
|
30
31
|
if (f.engine === "code") return t("实现 → 验证 → 评审");
|
|
31
32
|
// 连载与单稿件同引擎,描述必须区分:连载强调逐章与断点续跑
|
|
@@ -3852,6 +3853,27 @@ function renderBookMetaPanel(task) {
|
|
|
3852
3853
|
pbBlock(task, p.id) +
|
|
3853
3854
|
"</div>";
|
|
3854
3855
|
}).join("") + "</div>";
|
|
3856
|
+
// 封面卡(covergen,借鉴 oh-story 封面图环节):curl 落盘运行目录,done 后可点开预览
|
|
3857
|
+
const cg = task.cover_gen || {};
|
|
3858
|
+
const cgSt = cg.status || "";
|
|
3859
|
+
let cgBody = "";
|
|
3860
|
+
if (cgSt === "running") {
|
|
3861
|
+
cgBody = '<div class="bm-empty"><svg class="ico spin" aria-hidden="true"><use href="#i-refresh"/></svg> ' + esc(t("生成中…")) + "</div>";
|
|
3862
|
+
} else if (cgSt === "done") {
|
|
3863
|
+
cgBody = '<div class="bm-empty">' + esc(t("封面已生成(cover.png),在「成果」页签查看")) + "</div>";
|
|
3864
|
+
} else if (cgSt === "failed") {
|
|
3865
|
+
cgBody = '<div class="bm-errhint">' + esc(cg.error || t("生成失败")) + "</div>";
|
|
3866
|
+
} else {
|
|
3867
|
+
cgBody = '<div class="bm-empty">' + esc(t("生成竖版封面插画,产出 cover.png")) + "</div>";
|
|
3868
|
+
}
|
|
3869
|
+
const cgBusy = taskBusy || cgSt === "running";
|
|
3870
|
+
const cgAction = cgSt === "running"
|
|
3871
|
+
? '<button class="ghost" disabled><svg class="ico spin" aria-hidden="true"><use href="#i-refresh"/></svg>' + t("生成中…") + "</button>"
|
|
3872
|
+
: '<button class="primary" onclick="coverGen(\'' + esc(task.id) + '\')">' +
|
|
3873
|
+
(cgSt === "failed" ? t("重试") : t("生成封面")) + "</button>";
|
|
3874
|
+
html += '<div class="bm-cards"><div class="bm-card st-' + (cgSt || "new") + '">' +
|
|
3875
|
+
'<div class="bm-card-head"><span class="bm-plat-name"><svg class="ico" aria-hidden="true"><use href="#i-book-open"/></svg>' + esc(t("封面图")) + "</span>" +
|
|
3876
|
+
bmStatusChip(cgSt) + '<span class="flex1"></span>' + cgAction + "</div>" + cgBody + "</div></div>";
|
|
3855
3877
|
box.classList.remove("hidden");
|
|
3856
3878
|
box.innerHTML = html;
|
|
3857
3879
|
// TAB 徽章:已生成平台数(生成中显示 ●,随下次轮询刷新)
|
|
@@ -3874,6 +3896,16 @@ window.bmGen = async function (taskId, platform) {
|
|
|
3874
3896
|
} catch (e) { toast(t("生成失败:") + e.message, true); }
|
|
3875
3897
|
};
|
|
3876
3898
|
|
|
3899
|
+
/* 封面图生成(covergen):curl 子进程直接落盘运行目录,Python 不经手图像字节 */
|
|
3900
|
+
window.coverGen = async function (taskId) {
|
|
3901
|
+
try {
|
|
3902
|
+
await api("/api/tasks/" + encodeURIComponent(taskId) + "/cover",
|
|
3903
|
+
{ method: "POST", body: "{}" });
|
|
3904
|
+
toast(t("已开始生成封面,完成后这里会自动更新"));
|
|
3905
|
+
await refreshState(); render();
|
|
3906
|
+
} catch (e) { toast(t("封面生成失败:") + e.message, true); }
|
|
3907
|
+
};
|
|
3908
|
+
|
|
3877
3909
|
window.bmCopyBtn = function (btn) {
|
|
3878
3910
|
copyText(btn.dataset.text || "");
|
|
3879
3911
|
toast(t("已复制:") + (btn.dataset.label || ""));
|
|
@@ -7976,8 +8008,77 @@ async function loadSettings() {
|
|
|
7976
8008
|
queueGitProbe(); // 目录在场即探测代码版本,点亮分支胶囊
|
|
7977
8009
|
}
|
|
7978
8010
|
} catch (e) { /* 忽略 */ }
|
|
8011
|
+
loadSettingsV2(); // 引擎调参卡独立拉取(挂了不影响基础设置)
|
|
7979
8012
|
}
|
|
7980
8013
|
|
|
8014
|
+
/* ---------------- settings_v2 引擎调参卡(schema 驱动) ----------------
|
|
8015
|
+
* /api/settings-v2 拉 describe(含字段元数据),按 namespace 渲染控件;
|
|
8016
|
+
* 保存走 /api/settings-v2/<ns> mutate(expected_revision CAS,409=别处已改,
|
|
8017
|
+
* 自动重拉最新值)。schema 变更零前端改动。 */
|
|
8018
|
+
async function loadSettingsV2() {
|
|
8019
|
+
const box = $("set-v2-card");
|
|
8020
|
+
if (!box) return;
|
|
8021
|
+
let v2 = null;
|
|
8022
|
+
try { v2 = await api("/api/settings-v2"); } catch (e) { box.innerHTML = ""; return; }
|
|
8023
|
+
S.settingsV2 = v2;
|
|
8024
|
+
const NS_LABEL = { orchestrator: t("编排引擎"), budget: t("预算"), cascade: t("级联路由") };
|
|
8025
|
+
let html = "<label>" + esc(t("引擎调参(schema 化设置,立即生效)")) + "</label>";
|
|
8026
|
+
for (const ns of Object.keys(v2.namespaces || {})) {
|
|
8027
|
+
const d = v2.namespaces[ns] || {};
|
|
8028
|
+
html += '<div class="set-v2-ns" data-ns="' + esc(ns) + '" data-rev="' + (d.revision || 0) + '">' +
|
|
8029
|
+
'<div class="set-v2-ns-h">' + esc(NS_LABEL[ns] || ns) +
|
|
8030
|
+
'<span class="flex1"></span><span class="set-v2-rev">rev ' + (d.revision || 0) + "</span></div>";
|
|
8031
|
+
for (const f of (d.fields || [])) {
|
|
8032
|
+
const iid = "setv2-" + esc(ns) + "-" + f.path.replace(/\./g, "-");
|
|
8033
|
+
const val = f.path.split(".").reduce((o, k) => (o && o[k] !== undefined) ? o[k] : undefined, d.values || {});
|
|
8034
|
+
const cur = val === undefined ? "" : val;
|
|
8035
|
+
let inp;
|
|
8036
|
+
if (f.type === "bool") {
|
|
8037
|
+
inp = '<input id="' + iid + '" type="checkbox"' + (cur ? " checked" : "") + ">";
|
|
8038
|
+
} else if (f.type === "int" || f.type === "float") {
|
|
8039
|
+
const step = f.type === "float" ? "0.01" : "1";
|
|
8040
|
+
inp = '<input id="' + iid + '" type="number" step="' + step + '" value="' + esc(String(cur)) + '"' +
|
|
8041
|
+
(f.clamp ? ' min="' + f.clamp[0] + '" max="' + f.clamp[1] + '"' : "") + ' style="max-width:140px">';
|
|
8042
|
+
} else {
|
|
8043
|
+
inp = '<input id="' + iid + '" type="text" value="' + esc(String(cur)) + '">';
|
|
8044
|
+
}
|
|
8045
|
+
html += '<div class="set-v2-row">' + inp +
|
|
8046
|
+
'<span class="set-v2-lb" title="' + esc(f.description || f.path) + '">' + esc(f.description || f.path) + "</span></div>";
|
|
8047
|
+
}
|
|
8048
|
+
html += '<div class="input-row" style="margin-top:6px"><button class="ghost small" onclick="saveSettingsV2(\'' + esc(ns) + '\')">' + t("保存") + "</button>" +
|
|
8049
|
+
'<span class="set-v2-msg msg"></span></div></div>';
|
|
8050
|
+
}
|
|
8051
|
+
box.innerHTML = html;
|
|
8052
|
+
}
|
|
8053
|
+
|
|
8054
|
+
window.saveSettingsV2 = async function (ns) {
|
|
8055
|
+
const v2 = S.settingsV2 || {};
|
|
8056
|
+
const d = (v2.namespaces || {})[ns];
|
|
8057
|
+
const box = document.querySelector('.set-v2-ns[data-ns="' + ns + '"]');
|
|
8058
|
+
if (!d || !box) return;
|
|
8059
|
+
const ops = [];
|
|
8060
|
+
for (const f of (d.fields || [])) {
|
|
8061
|
+
const iid = "setv2-" + ns + "-" + f.path.replace(/\./g, "-");
|
|
8062
|
+
const el = document.getElementById(iid);
|
|
8063
|
+
if (!el) continue;
|
|
8064
|
+
if (f.type === "bool") ops.push({ op: "set", path: f.path, value: el.checked });
|
|
8065
|
+
else if (f.type === "int" || f.type === "float") ops.push({ op: "set", path: f.path, value: Number(el.value) });
|
|
8066
|
+
else ops.push({ op: "set", path: f.path, value: el.value });
|
|
8067
|
+
}
|
|
8068
|
+
const msg = box.querySelector(".set-v2-msg");
|
|
8069
|
+
try {
|
|
8070
|
+
const r = await api("/api/settings-v2/" + ns, {
|
|
8071
|
+
method: "POST", body: JSON.stringify({ ops, expected_revision: d.revision || 0 }) });
|
|
8072
|
+
if (msg) { msg.className = "set-v2-msg msg ok"; msg.textContent = t("已保存 rev ") + r.revision; }
|
|
8073
|
+
loadSettingsV2();
|
|
8074
|
+
} catch (e) {
|
|
8075
|
+
if (/期望 rev/.test(String(e.message))) {
|
|
8076
|
+
if (msg) { msg.className = "set-v2-msg msg err"; msg.textContent = t("配置已被别处修改,已刷新,请重试"); }
|
|
8077
|
+
loadSettingsV2();
|
|
8078
|
+
} else if (msg) { msg.className = "set-v2-msg msg err"; msg.textContent = e.message; }
|
|
8079
|
+
}
|
|
8080
|
+
};
|
|
8081
|
+
|
|
7981
8082
|
/* 保存默认保存路径;可选把旧默认路径下的现有任务目录迁移到新路径 */
|
|
7982
8083
|
async function saveDefaultWorkdir() {
|
|
7983
8084
|
const msg = $("settings-msg");
|
package/app/ui/i18n.js
CHANGED
|
@@ -900,6 +900,7 @@
|
|
|
900
900
|
"启用中": "Enabled",
|
|
901
901
|
"设为主模型": "Set as primary model",
|
|
902
902
|
"单智能体直达(快)": "Single-agent direct (fast)",
|
|
903
|
+
"抓七猫榜 → AI 选题洞察": "Scan Qimao rankings → AI topic insights",
|
|
903
904
|
"新增函数补用例": "Add tests for new functions",
|
|
904
905
|
"同类加函数任务,只要verify_pass=false即不得因review_pass=true或高分判定通过;必须将verify失败原因作为修复输入。": "For similar add-a-function tasks, verify_pass=false blocks acceptance regardless of review_pass or high scores; the verify failure details are the input to the next fix.",
|
|
905
906
|
"凡主角获取越权信息或关键证据(系统记录、录音、账目),必须当章或前文落实来源链(人脉、留底、委托调查),并让角色当场追问一句'东西哪来的';无来源的特权查询与来历不明的证据一律禁止上稿。": "Whenever the protagonist obtains privileged information or key evidence (system records, recordings, ledgers), establish the source chain in the same or an earlier chapter and have a character ask \"where did this come from\" on the spot; unsourced privileged queries and evidence of unknown origin are banned from the manuscript.",
|
|
@@ -1571,6 +1572,15 @@
|
|
|
1571
1572
|
"重新生成": "Regenerate",
|
|
1572
1573
|
"重新生成会覆盖现有内容": "Regenerating overwrites the existing content",
|
|
1573
1574
|
"生成失败": "Generation failed",
|
|
1575
|
+
"封面图": "Cover image",
|
|
1576
|
+
"生成封面": "Generate cover",
|
|
1577
|
+
"重试": "Retry",
|
|
1578
|
+
"生成中…": "Generating…",
|
|
1579
|
+
"封面生成中…": "Cover generation started…",
|
|
1580
|
+
"封面已生成(cover.png),在「成果」页签查看": "Cover generated (cover.png) — view it in the Artifacts tab",
|
|
1581
|
+
"生成竖版封面插画,产出 cover.png": "Generate a portrait cover illustration → cover.png",
|
|
1582
|
+
"封面生成失败:": "Cover generation failed: ",
|
|
1583
|
+
"已开始生成封面,完成后这里会自动更新": "Cover generation started — this panel updates automatically when done",
|
|
1574
1584
|
"已开始生成,完成后这里会自动更新": "Generation started — this panel updates automatically when done",
|
|
1575
1585
|
"来源:": "Source: ",
|
|
1576
1586
|
"(待补充)": "(to fill)",
|
package/app/ui/index.html
CHANGED
|
@@ -691,6 +691,7 @@
|
|
|
691
691
|
<label class="toggle" style="margin-top:6px"><input id="set-workdir-migrate" type="checkbox"> <span data-i18n="保存时把「旧默认路径下」的现有任务目录迁移到新路径(运行中的跳过;手动指定的目录不受影响)">保存时把「旧默认路径下」的现有任务目录迁移到新路径(运行中的跳过;手动指定的目录不受影响)</span></label>
|
|
692
692
|
<p class="hint" id="set-workdir-hint"></p>
|
|
693
693
|
</div>
|
|
694
|
+
<div id="set-v2-card" class="field"></div>
|
|
694
695
|
<div id="settings-msg" class="msg"></div>
|
|
695
696
|
</div>
|
|
696
697
|
</div>
|
package/app/ui/style.css
CHANGED
|
@@ -5113,3 +5113,13 @@ body.welcome-open { overflow: hidden; }
|
|
|
5113
5113
|
}
|
|
5114
5114
|
.cl-opt:hover { border-color: var(--accent); color: var(--accent); opacity: 1; }
|
|
5115
5115
|
.cl-opt.picked { border-color: var(--accent); color: var(--accent); opacity: .75; cursor: default; }
|
|
5116
|
+
|
|
5117
|
+
/* ---------------- settings_v2 引擎调参卡(schema 驱动,追加于文件尾防并行重排) ---------------- */
|
|
5118
|
+
#set-v2-card { margin-top: 14px; }
|
|
5119
|
+
.set-v2-ns { border: 1px solid var(--border); border-radius: 10px; padding: 10px 12px; margin: 8px 0; }
|
|
5120
|
+
.set-v2-ns-h { display: flex; align-items: center; gap: 8px; font-weight: 600; margin-bottom: 6px; }
|
|
5121
|
+
.set-v2-rev { font-size: 11px; color: var(--muted); font-weight: 400; }
|
|
5122
|
+
.set-v2-row { display: flex; align-items: center; gap: 10px; margin: 5px 0; }
|
|
5123
|
+
.set-v2-row input[type="number"], .set-v2-row input[type="text"] { width: 140px; }
|
|
5124
|
+
.set-v2-lb { font-size: 12px; color: var(--muted); }
|
|
5125
|
+
.set-v2-msg { font-size: 12px; }
|
package/package.json
CHANGED