@dnalec/dsh-auto-approve 0.1.3 → 0.2.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/CHANGELOG.md +85 -0
- package/README.md +44 -94
- package/README.zh.md +34 -85
- package/client.js +400 -265
- package/locales.mjs +204 -132
- package/package.json +6 -9
- package/src/index.mjs +276 -472
- package/src/preset-patch.mjs +333 -78
- package/src/rules.mjs +365 -120
- package/src/util.mjs +48 -71
- package/src/provisioning.mjs +0 -131
- package/src/qqbot.mjs +0 -313
- package/src/tickets.mjs +0 -277
package/client.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* dsh-auto-approve — 浏览器半:提示条、审批时间线、设置页。
|
|
3
|
-
*
|
|
3
|
+
* 访问模式芯片上的盾+A 由本插件画在「自动审批」按钮/菜单上,不改 DSH。
|
|
4
4
|
* 纯 JS:无 TS / JSX / import。React 用 createElement。
|
|
5
|
+
|
|
5
6
|
* 网页文案:locales.mjs 的 zh/en,apply 时 ctx.locale.register。
|
|
6
7
|
* Host API 走 connection.rpc.call('/api', 'dsh-auto-approve', …)(已鉴权),不用裸 HTTP。
|
|
7
|
-
* 设置页折叠状态用 React state 控 details,避免 snapshot
|
|
8
|
+
* 设置页折叠状态用 React state 控 details,避免 snapshot 刷新后合上。人工只走原网页审批框。
|
|
8
9
|
* 恢复默认:第一次点武装,5 秒内再点才执行。
|
|
9
10
|
*/
|
|
10
11
|
window.__ModuleLoader__.load({
|
|
@@ -74,7 +75,7 @@ window.__ModuleLoader__.load({
|
|
|
74
75
|
.ab-set-fold-body{padding:12px 14px 14px;display:flex;flex-direction:column;gap:12px}
|
|
75
76
|
.ab-set-fold-sum{flex:none;color:var(--dsw-alias-label-secondary);font-size:12px;line-height:18px;text-align:right;max-width:46%}
|
|
76
77
|
.ab-set-fold-chev{flex:none;color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:20px}
|
|
77
|
-
.ab-set-fold[open] .ab-set-fold-chev{transform:rotate(180deg)}
|
|
78
|
+
.ab-set-fold[open]>summary .ab-set-fold-chev{transform:rotate(180deg)}
|
|
78
79
|
.ab-set-card-head{flex-direction:column;gap:4px;display:flex}
|
|
79
80
|
.ab-set-card-title{color:var(--dsw-alias-label-primary);align-items:center;gap:8px;font-size:14px;font-weight:500;line-height:20px;display:flex}
|
|
80
81
|
.ab-set-card-sub{color:var(--dsw-alias-label-tertiary);margin:0;font-size:13px;line-height:20px}
|
|
@@ -98,6 +99,7 @@ window.__ModuleLoader__.load({
|
|
|
98
99
|
.ab-set-kw-text{flex:1 1 auto;min-width:0;cursor:text;color:var(--dsw-alias-label-primary);font:12px/18px var(--ds-font-family-code);word-break:break-all}
|
|
99
100
|
.ab-set-textarea{box-sizing:border-box;width:100%;min-height:72px;resize:none;overflow:hidden;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);font:12px/18px inherit;border-radius:8px;outline:none;padding:6px 10px}
|
|
100
101
|
.ab-set-textarea:focus{border-color:var(--dsw-alias-state-business-primary)}
|
|
102
|
+
.ab-set-textarea-prompt{min-height:180px;max-height:360px;resize:vertical;overflow:auto;font-family:var(--ds-font-family-code);white-space:pre-wrap}
|
|
101
103
|
.ab-set-input-ui{font-family:inherit;width:100%}
|
|
102
104
|
.ab-set-foot{justify-content:flex-end}
|
|
103
105
|
.ab-set-list-crit{gap:8px}
|
|
@@ -105,16 +107,13 @@ window.__ModuleLoader__.load({
|
|
|
105
107
|
.ab-set-crit-top{display:flex;align-items:center;gap:8px;min-width:0}
|
|
106
108
|
.ab-set-item-fields{min-width:0;flex-direction:column;gap:6px;display:flex}
|
|
107
109
|
.ab-set-item-id{flex:1 1 auto;min-width:0;color:var(--dsw-alias-label-primary);font-size:13px;font-weight:500;line-height:20px;font-family:var(--ds-font-family-code)}
|
|
108
|
-
.ab-set-item-label{flex:1 1 auto;min-width:0;color:var(--dsw-alias-label-primary);font-size:12px;line-height:18px;font-family:var(--ds-font-family-code);word-break:break-all}
|
|
109
110
|
.ab-set-item-meta{flex:none;color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:16px;white-space:nowrap}
|
|
110
111
|
.ab-set-item-del{flex:none;width:24px;height:24px;color:var(--dsw-alias-label-tertiary);cursor:pointer;background:transparent;border:none;border-radius:6px;display:inline-flex;align-items:center;justify-content:center;font-size:12px;padding:0}
|
|
111
112
|
.ab-set-item-del:hover{background:var(--dsw-alias-interactive-bg-hover-danger);color:var(--dsw-alias-state-error-primary)}
|
|
112
113
|
.ab-set-empty{color:var(--dsw-alias-label-caption);margin:0;font-size:13px;line-height:20px;padding:4px 2px}
|
|
113
114
|
.ab-set-tag{box-sizing:border-box;flex:none;height:18px;border-radius:9px;align-items:center;padding:0 8px;font-size:11px;line-height:18px;display:inline-flex}
|
|
114
115
|
.ab-set-tag-blue{color:var(--dsw-alias-state-business-primary);background:var(--dsw-alias-state-business-tertiary)}
|
|
115
|
-
.ab-set-tag-
|
|
116
|
-
.ab-set-tag-gray{color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-bg-module-platform)}
|
|
117
|
-
.ab-set-tag-red{color:var(--dsw-alias-state-error-primary);background:var(--dsw-alias-interactive-bg-hover-danger)}
|
|
116
|
+
.ab-set-tag-warn{color:var(--dsw-alias-state-warn-label);background:var(--dsw-alias-state-warn-tertiary)}
|
|
118
117
|
.ab-set-steps{display:flex;flex-wrap:wrap;gap:6px}
|
|
119
118
|
.ab-set-step{box-sizing:border-box;margin:0;border:1px solid var(--dsw-alias-border-l2);background:transparent;border-radius:10px;padding:8px 10px;min-width:108px;flex:1 1 108px;cursor:pointer;display:flex;flex-direction:column;gap:2px;align-items:flex-start;text-align:left;font:inherit;color:inherit}
|
|
120
119
|
.ab-set-step:hover{background:var(--dsw-alias-interactive-bg-hover)}
|
|
@@ -131,18 +130,20 @@ window.__ModuleLoader__.load({
|
|
|
131
130
|
.ab-set-choice-on{border-color:var(--dsw-alias-state-business-primary);background:var(--dsw-alias-state-business-tertiary)}
|
|
132
131
|
.ab-set-choice-t{color:var(--dsw-alias-label-primary);font-size:13px;font-weight:500;line-height:20px}
|
|
133
132
|
.ab-set-choice-d{color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px}
|
|
134
|
-
.ab-set-
|
|
135
|
-
.ab-set-
|
|
136
|
-
.ab-
|
|
137
|
-
.ab-
|
|
138
|
-
.
|
|
139
|
-
|
|
140
|
-
.
|
|
133
|
+
.ab-set-choice:disabled{opacity:.4;cursor:default}
|
|
134
|
+
.ab-set-flash{position:sticky;top:0;z-index:2;background:var(--dsw-alias-bg-layer-1);padding:4px 0}
|
|
135
|
+
.ab-access-glyph{display:inline-flex;flex:none;align-items:center;color:inherit}
|
|
136
|
+
.ab-access-glyph svg{width:14px;height:14px}
|
|
137
|
+
/* 菜单行里的图标色必须跟 DSH 自己的 .itemIcon 一致(label-tertiary):
|
|
138
|
+
菜单按钮是 label-primary,注入的徽标会继承它,看起来比旁边三个图标更黑。
|
|
139
|
+
触发器正好相反 —— .trigger 是 label-secondary、.triggerIcon 不设色,inherit 就对了。 */
|
|
140
|
+
[role="menuitem"] .ab-access-glyph{color:var(--dsw-alias-label-tertiary,currentColor)}
|
|
141
|
+
[role="menuitem"] .ab-access-glyph svg{width:16px;height:16px}
|
|
141
142
|
`
|
|
142
143
|
|
|
143
144
|
const NS = "dsh-auto-approve"
|
|
144
|
-
const ZH = JSON.parse(String.raw`{"slot.notice":"自动放行提示","slot.history":"审批","slot.settings":"自动审批","action.reject":"拒绝","action.allow":"允许","action.human":"人工","verdict.keyword-allow":"关键词允许","verdict.keyword-reject":"关键词拒绝","path.keyword-reject":"关键词拒绝","path.keyword-allow":"关键词允许","path.keyword-human":"关键词转人工","path.criteria-reject":"审核表拒绝","path.criteria-allow":"审核表允许","path.criteria-human":"审核表转人工","path.judge-failed":"判定失败转人工","path.missing-payload":"缺少工具参数转人工","path.truncated-payload":"参数过长转人工","path.plugin-error":"插件异常转人工","notice.feedError":"审批提示暂时不可用","notice.feedErrorTag":"连接失败","notice.pendingTitle":"
|
|
145
|
-
const EN = JSON.parse(String.raw`{"slot.notice":"Auto-approve notice","slot.history":"Approvals","slot.settings":"Auto-approve","action.reject":"Reject","action.allow":"Allow","action.human":"Human","verdict.keyword-allow":"Keyword allow","verdict.keyword-reject":"Keyword reject","path.keyword-reject":"Keyword reject","path.keyword-allow":"Keyword allow","path.keyword-human":"Keyword → human","path.criteria-reject":"Criteria reject","path.criteria-allow":"Criteria allow","path.criteria-human":"Criteria → human","path.judge-failed":"Judge failed → human","path.missing-payload":"Missing tool args → human","path.truncated-payload":"Truncated args → human","path.plugin-error":"Plugin error → human","notice.feedError":"Approval notices unavailable","notice.feedErrorTag":"Connection failed","notice.pendingTitle":"Waiting for human{ticket}: {preview}","notice.pendingTag":"Human review{ticket}","notice.qqApproved":"Approved in QQ{ticket}: {preview}","notice.webApproved":"Approved{ticket}: {preview}","notice.qqApprovedTag":"Approved in QQ{ticket}","notice.webApprovedTag":"Approved","notice.rejectedTitle":"Rejected{ticket}: {preview}","notice.rejectedTag":"Rejected{ticket}","notice.autoTag":"Auto-allowed · {verdict}","notice.autoDefault":"Auto-allowed","notice.rejectedDefault":"Rejected","notice.hint":"Notice","notice.close":"Dismiss","notice.feedLoadFailed":"Approval notices failed to load; retrying","history.noSession":"No session selected","history.loadFailed":"Failed to load: {error}","history.loading":"Loading…","history.title":"Approvals","history.emptySub":"This session has no approval records yet","history.emptyHint":"Auto-allows and human escalations appear here","history.sub":"Newest first · expand for command and details","history.tagAuto":"Auto-allowed","history.tagPending":"Pending {ticket}","history.tagQqAllow":"QQ allow {ticket}","history.tagAllow":"Allow {ticket}","history.tagReject":"Reject {ticket}","detail.request":"Request","detail.audit":"Review","detail.command":"Command","detail.path":"Path","detail.description":"Description","detail.old":"Original","detail.new":"Replacement","detail.content":"Write contents","detail.code":"Code","detail.url":"URL","detail.query":"Query","detail.script":"Script","detail.sql":"SQL","detail.prompt":"Prompt","detail.cwd":"Working directory","detail.sandbox":"Sandbox","detail.justification":"Model justification","detail.pipe":"Pipeline","detail.keyword":"Keyword","detail.judgeModel":"Judge model","detail.judgeCategory":"Category","detail.judgeAction":"Action","detail.judgeReason":"Judge reason","detail.judgeError":"Judge error","detail.judgeRaw":"Judge raw output","detail.ticket":"Ticket","detail.source":"Source","detail.judgeFailed":"Incomplete (parse failure/timeout → human; does not run Other)","set.title":"Auto-approve","set.intro":"Actions that need approval are auto-approved and can be pushed to a configured chat. Designed for a private bot: if no chat is bound, a C2C reply of “是” binds that chat. Do not let strangers DM this bot.","set.allowlistCorrupt":"The rules file is corrupt. This process is using in-memory defaults and will not overwrite the disk. Repair allowlist.json, or Restore defaults to write shipped rules.","set.pluginCorrupt":"Plugin config is corrupt; saves are refused so chatId is not wiped. Repair ~/.dsh/approval-bridge/config.json, or ","set.pluginCorruptOverwrite":"Overwrite corrupt config","set.qqCorrupt":"QQ credentials file is corrupt and was not overwritten. Repair ~/.dsh/approval-bridge/qqbot.json, or save credentials / scan again.","set.loading":"Loading…","set.loadFailed":"Failed to load: {error}","set.resetConfirm":"Click again to confirm restore","set.presetMissingTitle":"Permission preset","set.presetMissingSub":"Startup writes auto-approve. It was not detected; you can write it now.","set.presetWrite":"Write permission preset","set.presetWriteNote":"Restart dsh web after writing","set.presetWrote":"Written. Restart dsh web","set.modeTitle":"Auto-approve mode","set.modeWs":"workspace-write (recommended)","set.modeWsHint":"In-workspace writes skip approval; outside the workspace still goes through the judge.","set.modeRo":"read-only","set.modeRoHint":"In-workspace writes also go through the judge.","set.save":"Save","set.hotOk":"Applied (live; no restart)","set.modeSaved":"Written. Restart dsh web and re-select Auto-approve or start a new session","set.overview":"Approval overview","set.overviewSub":"Keywords (reject > human > allow) → the judge classifies; the table decides. Parse failure → human.","set.stepKeywords":"Keywords","set.stepCriteria":"Criteria","set.stepJudge":"Judge model","set.counts":"rej {reject} · hum {human} · all {allow}","set.criteriaCount":"{n} rows","set.followDefault":"Follow default","set.unconfigured":"Not configured","set.kwSub":"Match command, path, and workdir. Reject/human also match the tool name; allow keywords do not, so bash/write is not allowed as a class.","set.empty":"None","set.presetTag":"Shipped","set.kwEditTitle":"Click to edit","set.kwPlaceholder":"New keyword","set.add":"Add","set.resetKeywords":"Restore default keywords","set.resetKeywordsOk":"Default keywords restored","set.confirm":"Confirm","set.delete":"Delete","set.criteriaSub":"The judge uses this table. Other cannot be deleted.","set.labelPlaceholder":"Label","set.descPlaceholder":"Description (goes into the judge prompt)","set.resetCriteria":"Restore default criteria ({lang})","set.resetCriteriaOk":"Restored {lang} default criteria","set.addCriterion":"Add criterion","set.criterionId":"id","set.judgeTitle":"Judge model","set.judgeSubLead":"Independent of the session model. Empty follows the deployment default","set.judgeSubFallback":" ({provider} / {model})","set.judgeSubNoFallback":" (no default to follow)","set.judgeSubTail":". With no usable route, that request goes to a human; the plugin will not guess a model. Prefer effort off.","set.followProvider":"Follow default provider","set.followModel":"Follow default model","set.modelDefaultEffort":"Model default","set.effortOff":"off (recommended)","set.judgeTimeoutMs":"Judge timeout (ms):","set.judgeSaved":"Judge model saved","set.judgeLang":"Prompt language","set.judgeLangZh":"Chinese","set.judgeLangEn":"English","set.judgeLangHint":"Changes only the judge prompt framework, not the current criteria table. Restore default criteria uses this language.","set.qqTitle":"QQ approval channel","set.qqSub":"Scan or paste credentials. Designed for a private bot: if no chatId is bound, a C2C reply of “是” binds that chat. Do not let strangers DM this bot. Groups must @ the bot and set userId.","set.qqScan":"Scan to create bot","set.qqRescan":"Scan again","set.qqCancelScan":"Cancel scan","set.qqCancelOk":"Scan cancelled","set.qqScanStart":"Scan started; use mobile QQ","set.qqScanOk":"Scan succeeded; credentials saved","set.qqScanFail":"Scan failed","set.qqScanStatus":"Scan: {status}","set.qqQrAlt":"QQ bot QR code","set.qqQrTip":"Scan with mobile QQ and confirm creating the bot (about 5 minutes)","set.qqAppId":"AppID","set.qqAppSecret":"AppSecret","set.qqSecretKeep":"AppSecret (leave blank to keep)","set.qqSaveConnect":"Save and connect","set.qqCredsReconnect":"Credentials saved; reconnecting","set.qqReconnect":"Reconnect","set.qqReconnected":"Reconnecting","set.qqUnknown":"Unknown","set.qqChatOn":"Chat selected","set.qqChatOff":"No chat selected","set.qqPush":"Push: {state}","set.qqPushOn":"on","set.qqPushOff":"off","set.qqPushToggled":"Push toggled","set.qqEnablePush":"Enable push","set.qqPausePush":"Pause push","set.qqCurrentChat":"Current chatId: {chatId}","set.qqUnset":"(none)","set.qqChatPlaceholder":"chatId (C2C openid / group g:openid)","set.qqGroupUser":"Group userId","set.qqTimeoutS":"Timeout s","set.prov.loggingIn":"Signing in","set.prov.waitScan":"Waiting for scan","set.prov.saving":"Saving credentials","set.prov.connected":"Connected","set.chatId":"chatId","set.userId":"userId (group)","set.notifyTimeout":"Reminder interval (seconds)","set.saveTarget":"Save target / timeout","set.targetSaved":"Push target saved","set.recent":"Recent incoming (click to set as the approval chat; groups must @ the bot and match userId)","set.noMail":"No messages yet. Friend the bot and send it anything.","set.group":"Group","set.c2c":"C2C","set.chatPicked":"Approval chat selected","set.overwriteOk":"Corrupt plugin config overwritten","rpc.unavailable":"connection.rpc is unavailable","rpc.failed":"RPC failed"}`)
|
|
145
|
+
const ZH = JSON.parse(String.raw`{"slot.notice":"自动放行提示","slot.history":"审批","slot.settings":"自动审批","action.reject":"拒绝","action.allow":"允许","action.human":"人工","criterion.deletion":"删除/覆盖不可再生数据","criterion.credential":"凭据/密钥/授权修改","criterion.remote":"远程系统/生产环境/数据库","criterion.system":"系统级路径/配置","criterion.bulk":"批量不可回补操作","criterion.approval-config":"自动审批配置","criterion.safe":"安全/常规可回补","criterion.other":"其他(拿不准)","sandbox.read-only":"只读","sandbox.workspace-write":"工作区可写","sandbox.danger-full-access":"全权限","source.web":"网页","verdict.keyword-allow":"关键词允许","verdict.keyword-reject":"关键词拒绝","verdict.keyword-human":"关键词转人工","verdict.criteria-reject":"审核表拒绝","verdict.criteria-allow":"审核表允许","verdict.criteria-human":"审核表转人工","verdict.judge-failed":"判定失败转人工","verdict.missing-payload":"缺少工具参数转人工","verdict.truncated-payload":"参数过长转人工","verdict.plugin-error":"插件异常转人工","verdict.human":"转人工","verdict.cancelled":"人工取消","verdict.unavailable":"审批不可用","path.keyword-reject":"关键词拒绝","path.keyword-allow":"关键词允许","path.keyword-human":"关键词转人工","path.criteria-reject":"审核表拒绝","path.criteria-allow":"审核表允许","path.criteria-human":"审核表转人工","path.judge-failed":"判定失败转人工","path.missing-payload":"缺少工具参数转人工","path.truncated-payload":"参数过长转人工","path.plugin-error":"插件异常转人工","notice.feedError":"审批提示暂时不可用","notice.feedErrorTag":"连接失败","notice.pendingTitle":"等待人工审批:{preview}","notice.pendingTag":"人工审批中","notice.webApproved":"人工审批通过:{preview}","notice.webApprovedTag":"人工审批通过","notice.rejectedTitle":"已拒绝:{preview}","notice.rejectedTag":"已拒绝","notice.cancelledTitle":"已取消人工审批:{preview}","notice.unavailableTitle":"审批不可用:{preview}","notice.autoTag":"自动放行 · {verdict}","notice.autoDefault":"自动放行","notice.rejectedDefault":"已拒绝","notice.hint":"提示","notice.close":"关闭","notice.feedLoadFailed":"审批提示加载失败,将自动重试","history.noSession":"未选择会话","history.loadFailed":"加载失败:{error}","history.loading":"加载中…","history.title":"审批","history.emptySub":"当前会话还没有审批记录","history.emptyHint":"自动放行与转人工都会出现在这里","history.sub":"最新在上 · 点开看命令与详情","history.tagAuto":"自动放行","history.tagPending":"转人工","history.tagAllow":"人工批准","history.tagReject":"人工拒绝","history.tagCancel":"人工取消","history.tagUnavailable":"审批不可用","history.unknownTool":"工具","detail.request":"请求","detail.audit":"审核","detail.command":"命令","detail.path":"路径","detail.description":"描述","detail.old":"原文","detail.new":"改成","detail.content":"写入内容","detail.code":"代码","detail.url":"URL","detail.query":"查询","detail.script":"脚本","detail.sql":"SQL","detail.prompt":"提示词","detail.input":"输入","detail.text":"文本","detail.body":"正文","detail.message":"消息","detail.pattern":"模式","detail.selector":"选择器","detail.cwd":"工作目录","detail.workdir":"命令工作目录","detail.empty":"(空)","detail.sandbox":"沙箱","detail.justification":"模型理由","detail.pipe":"管道","detail.keyword":"关键词","detail.judgeModel":"审核模型","detail.judgeCategory":"审核类别","detail.judgeAction":"执行动作","detail.judgeReason":"审核理由","detail.judgeError":"审核失败","detail.judgeRaw":"审核结果原文","detail.source":"来源","detail.judgeFailed":"未完成(解析失败/超时转人工,不执行「其他」的动作)","set.title":"自动审批","set.intro":"需要审批的行为由审核模型自动判定。允许或拒绝与在网页点按钮相同;拿不准则交给原来的人工审批框。","set.allowlistCorrupt":"规则文件损坏,当前是内存默认,普通保存不会覆盖磁盘。请修好 allowlist.json,或点「恢复默认」写回出厂规则。","set.pluginCorrupt":"插件配置损坏,拒绝保存以免清空审核模型。请修好 ~/.dsh/auto-approve/config.json,或","set.pluginCorruptOverwrite":"覆盖损坏配置","set.loading":"加载中…","set.loadFailed":"加载失败:{error}","set.resetConfirm":"再点一次确认恢复","set.presetMissingTitle":"权限预设","set.driftTitle":"权限预设表需要更新","set.driftBody":"DSH 出厂权限预设表多了 {keys}。本 profile 的 permission 行由本插件写入,patch 会整块替换 config,所以不会自动出现这些预设;请更新插件或手工合并该行。","set.presetMissingSub":"插件启动时会写入 auto-approve。当前未检测到,可手动补写。","set.presetWrite":"写入权限预设","set.presetWriteNote":"写入后需重启 dsh web","set.presetWrote":"已写入,请重启 dsh web","set.modeTitle":"自动审批模式","set.modeWs":"工作区可写(推荐)","set.modeWsHint":"工作区内直接放行,越出工作区需审核。","set.modeRo":"只读","set.modeRoHint":"工作区内写操作也需审核。","set.modeHint":"点选即写入。改完需重启 dsh web,并重新选择「自动审批」或开新会话。","set.save":"保存","set.hotOk":"已生效(热更新,无需重启)","set.modeSaved":"已写入。请重启 dsh web,并重新选择「自动审批」或开新会话","set.overview":"审批总览","set.overviewSub":"关键词(拒绝优先于人工优先于允许)→ 审核表由模型归类,程序按表执行。解析失败转人工。","set.stepKeywords":"关键词","set.stepCriteria":"审核表","set.stepJudge":"审核模型","set.counts":"拒 {reject} · 人 {human} · 允 {allow}","set.criteriaCount":"{n} 项","set.followDefault":"跟随默认","set.unconfigured":"未配置","set.kwSub":"匹配命令、路径和工作目录。拒绝/人工词也匹配工具名;允许词不匹配工具名,避免把 bash/write 整类放行。","set.empty":"暂无","set.presetTag":"预置","set.kwEditTitle":"点击修改","set.kwPlaceholder":"新关键词","set.add":"添加","set.resetKeywords":"恢复默认关键词","set.resetKeywordsOk":"已恢复默认关键词","set.confirm":"确认","set.delete":"删除","set.criteriaSub":"审核模型以此表作为审核标准。「其他」作为兜底选项不能删除。","set.labelPlaceholder":"标签","set.descPlaceholder":"说明(写入审核提示词)","set.resetCriteria":"恢复默认审核表({lang})","set.resetCriteriaOk":"已恢复为{lang}默认审核表","set.addCriterion":"添加审核项","set.criterionId":"id","set.judgeTitle":"审核模型","set.judgeSubLead":"独立于当前会话模型。空则跟随部署默认","set.judgeSubFallback":"({provider} / {model})","set.judgeSubNoFallback":"(当前没有默认可跟随)","set.judgeSubTail":"。没有可用路由时当次转人工,不会再猜一个模型。建议思考强度 off。","set.followProvider":"跟随默认提供方","set.followModel":"跟随默认模型","set.modelDefaultEffort":"模型默认","set.effortOff":"off(建议)","set.judgeTimeoutMs":"审核超时(ms):","set.judgeSaved":"审核设置已保存","set.judgeLang":"提示词语言","set.judgeLangZh":"中文","set.judgeLangEn":"English","set.judgeLangHint":"切换发给审核模型的框架语言与卡片标签,不会改当前审核表。下面的提示词按此语言编辑;恢复默认审核表也用这里选的语言。","set.judgePrompt":"审核提示词","set.judgePromptHint":"按所选语言内置默认提示词。用 {{criteria}} 插入当前审核表;删掉占位符时会把审核表附在末尾。与审核模型、超时一起保存。","set.judgePromptCustom":"已自定义","set.judgePromptDefault":"默认","set.resetJudgePrompt":"恢复默认提示词","set.resetJudgePromptOk":"已恢复默认提示词","set.overwriteOk":"已覆盖损坏的插件配置","err.missingPayload":"工具参数不完整,转人工","err.missingPayloadUncaptured":"未捕获工具参数,转人工","err.truncatedPayload":"工具参数过长已截断,转人工(禁止按前缀自动放行)","err.pluginError":"插件判定异常,转人工","err.allowlistCorrupt":"规则文件损坏,拒绝覆盖。请先「恢复默认」写回出厂规则,或修好磁盘上的 allowlist.json","err.allowlistWrite":"写入 allowlist 失败","err.criterionNotFound":"未找到该审核项","err.criterionNeedId":"需要 id 或 label","err.criterionIdExists":"id 已存在","err.criterionOtherLocked":"「其他」不可删除","err.criteriaOp":"审核表请用添加 / 修改 / 删除 / 恢复默认","err.opMustSet":"{kind} 只能用修改操作","err.invalidNumber":"无效数值","err.keywordEmpty":"关键词不能为空","err.keywordNotFound":"未找到该关键词","err.keywordsOp":"关键词请用添加 / 修改 / 删除 / 恢复默认","err.valueEmpty":"值不能为空","err.unknownKind":"未知规则类型:{kind}","err.unknownOp":"未知操作:{op}","err.ruleNotFound":"未找到匹配的规则","err.badBody":"请求体不是 JSON","err.needSessionId":"需要 sessionId","err.pluginCorrupt":"插件配置损坏,拒绝覆盖。请修好磁盘文件,或点「覆盖损坏配置」","err.pluginWrite":"写入配置失败","err.unknownEndpoint":"未知接口:{endpoint}","err.internal":"内部错误:{error}","err.catalog":"无法列出模型:{error}","err.info":"无法读取模型信息:{error}","err.judgeUnconfigured":"未配置审核模型","err.judgeEffort":"思考强度 {effort} 不受支持","err.judgeTimeout":"审核超时({ms}ms)","err.judgeRetryTimeout":"审核重试超时({ms}ms)","err.judgeFailed":"审核失败","err.judgeCall":"审核模型调用失败:{error}","err.judgeEmpty":"审核模型输出为空","err.judgeParse":"审核模型输出无法解析","err.judgeUpstream":"审核模型路由失败:{error}","err.noPresetsKey":"permission 条目缺少 presets 键,请手动添加","err.preset":"写入预设失败:{error}","err.presetSandboxMissing":"预设里没有 sandbox 行,无法写入沙箱模式;请检查 profile 的 cordis.patch.yml","rpc.unavailable":"connection.rpc 不可用","rpc.failed":"RPC 失败"}`)
|
|
146
|
+
const EN = JSON.parse(String.raw`{"slot.notice":"Auto-approve notice","slot.history":"Approvals","slot.settings":"Auto-approve","action.reject":"Reject","action.allow":"Allow","action.human":"Human","criterion.deletion":"Delete/overwrite irreplaceable data","criterion.credential":"Credentials/keys/auth changes","criterion.remote":"Remote/production/database","criterion.system":"System paths/config","criterion.bulk":"Bulk irreversible operations","criterion.approval-config":"Auto-approve configuration","criterion.safe":"Safe/routine reversible","criterion.other":"Other (unsure)","sandbox.read-only":"Read-only","sandbox.workspace-write":"Workspace write","sandbox.danger-full-access":"Full access","source.web":"Web","verdict.keyword-allow":"Keyword allow","verdict.keyword-reject":"Keyword reject","verdict.keyword-human":"Keyword → human","verdict.criteria-reject":"Criteria reject","verdict.criteria-allow":"Criteria allow","verdict.criteria-human":"Criteria → human","verdict.judge-failed":"Judge failed → human","verdict.missing-payload":"Missing tool args → human","verdict.truncated-payload":"Truncated args → human","verdict.plugin-error":"Plugin error → human","verdict.human":"To human","verdict.cancelled":"Human cancel","verdict.unavailable":"Unavailable","path.keyword-reject":"Keyword reject","path.keyword-allow":"Keyword allow","path.keyword-human":"Keyword → human","path.criteria-reject":"Criteria reject","path.criteria-allow":"Criteria allow","path.criteria-human":"Criteria → human","path.judge-failed":"Judge failed → human","path.missing-payload":"Missing tool args → human","path.truncated-payload":"Truncated args → human","path.plugin-error":"Plugin error → human","notice.feedError":"Approval notices unavailable","notice.feedErrorTag":"Connection failed","notice.pendingTitle":"Waiting for human: {preview}","notice.pendingTag":"Human review","notice.webApproved":"Approved: {preview}","notice.webApprovedTag":"Approved","notice.rejectedTitle":"Rejected: {preview}","notice.rejectedTag":"Rejected","notice.cancelledTitle":"Cancelled: {preview}","notice.unavailableTitle":"Unavailable: {preview}","notice.autoTag":"Auto-allowed · {verdict}","notice.autoDefault":"Auto-allowed","notice.rejectedDefault":"Rejected","notice.hint":"Notice","notice.close":"Dismiss","notice.feedLoadFailed":"Approval notices failed to load; retrying","history.noSession":"No session selected","history.loadFailed":"Failed to load: {error}","history.loading":"Loading…","history.title":"Approvals","history.emptySub":"This session has no approval records yet","history.emptyHint":"Auto-allows and human escalations appear here","history.sub":"Newest first · expand for command and details","history.tagAuto":"Auto-allowed","history.tagPending":"To human","history.tagAllow":"Human allow","history.tagReject":"Human reject","history.tagCancel":"Human cancel","history.tagUnavailable":"Unavailable","history.unknownTool":"Tool","detail.request":"Request","detail.audit":"Review","detail.command":"Command","detail.path":"Path","detail.description":"Description","detail.old":"Original","detail.new":"Replacement","detail.content":"Write contents","detail.code":"Code","detail.url":"URL","detail.query":"Query","detail.script":"Script","detail.sql":"SQL","detail.prompt":"Prompt","detail.input":"Input","detail.text":"Text","detail.body":"Body","detail.message":"Message","detail.pattern":"Pattern","detail.selector":"Selector","detail.cwd":"Working directory","detail.workdir":"Command working directory","detail.empty":"(empty)","detail.sandbox":"Sandbox","detail.justification":"Model justification","detail.pipe":"Pipeline","detail.keyword":"Keyword","detail.judgeModel":"Judge model","detail.judgeCategory":"Category","detail.judgeAction":"Action","detail.judgeReason":"Judge reason","detail.judgeError":"Judge error","detail.judgeRaw":"Judge raw output","detail.source":"Source","detail.judgeFailed":"Incomplete (parse failure/timeout → human; does not run Other)","set.title":"Auto-approve","set.intro":"Actions that need approval are judged automatically. Allow and reject match clicking the Web buttons; uncertainty goes to the original human dialog.","set.allowlistCorrupt":"The rules file is corrupt. This process is using in-memory defaults and will not overwrite the disk. Repair allowlist.json, or Restore defaults to write shipped rules.","set.pluginCorrupt":"Plugin config is corrupt; saves are refused so the judge model is not wiped. Repair ~/.dsh/auto-approve/config.json, or ","set.pluginCorruptOverwrite":"Overwrite corrupt config","set.loading":"Loading…","set.loadFailed":"Failed to load: {error}","set.resetConfirm":"Click again to confirm restore","set.presetMissingTitle":"Permission preset","set.driftTitle":"Permission preset table needs updating","set.driftBody":"The shipped permission table has new presets: {keys}. This profile's permission row is written by this plugin, and a patch replaces that config wholesale, so they will not appear automatically. Update the plugin or merge that row by hand.","set.presetMissingSub":"Startup writes auto-approve. It was not detected; you can write it now.","set.presetWrite":"Write permission preset","set.presetWriteNote":"Restart dsh web after writing","set.presetWrote":"Written. Restart dsh web","set.modeTitle":"Auto-approve mode","set.modeWs":"Workspace write (recommended)","set.modeWsHint":"In-workspace writes skip approval; outside the workspace still goes through the judge.","set.modeRo":"Read-only","set.modeRoHint":"In-workspace writes also go through the judge.","set.modeHint":"Clicking a mode writes it. Then restart dsh web and re-select Auto-approve or start a new session.","set.save":"Save","set.hotOk":"Applied (live; no restart)","set.modeSaved":"Written. Restart dsh web and re-select Auto-approve or start a new session","set.overview":"Approval overview","set.overviewSub":"Keywords (reject > human > allow) → the judge classifies; the table decides. Parse failure → human.","set.stepKeywords":"Keywords","set.stepCriteria":"Criteria","set.stepJudge":"Judge model","set.counts":"rej {reject} · hum {human} · all {allow}","set.criteriaCount":"{n} rows","set.followDefault":"Follow default","set.unconfigured":"Not configured","set.kwSub":"Match command, path, and workdir. Reject/human also match the tool name; allow keywords do not, so bash/write is not allowed as a class.","set.empty":"None","set.presetTag":"Shipped","set.kwEditTitle":"Click to edit","set.kwPlaceholder":"New keyword","set.add":"Add","set.resetKeywords":"Restore default keywords","set.resetKeywordsOk":"Default keywords restored","set.confirm":"Confirm","set.delete":"Delete","set.criteriaSub":"The judge uses this table. Other cannot be deleted.","set.labelPlaceholder":"Label","set.descPlaceholder":"Description (goes into the judge prompt)","set.resetCriteria":"Restore default criteria ({lang})","set.resetCriteriaOk":"Restored {lang} default criteria","set.addCriterion":"Add criterion","set.criterionId":"id","set.judgeTitle":"Judge model","set.judgeSubLead":"Independent of the session model. Empty follows the deployment default","set.judgeSubFallback":" ({provider} / {model})","set.judgeSubNoFallback":" (no default to follow)","set.judgeSubTail":". With no usable route, that request goes to a human; the plugin will not guess a model. Prefer effort off.","set.followProvider":"Follow default provider","set.followModel":"Follow default model","set.modelDefaultEffort":"Model default","set.effortOff":"off (recommended)","set.judgeTimeoutMs":"Judge timeout (ms):","set.judgeSaved":"Judge settings saved","set.judgeLang":"Prompt language","set.judgeLangZh":"Chinese","set.judgeLangEn":"English","set.judgeLangHint":"Switches the judge prompt framework and card labels, not the current criteria table. The prompt below is edited per language; restore default criteria also uses this language.","set.judgePrompt":"Judge prompt","set.judgePromptHint":"Built-in default for the selected language. Use {{criteria}} to insert the current criteria table; if the placeholder is removed, the table is appended. Saved with the judge model and timeout.","set.judgePromptCustom":"Custom","set.judgePromptDefault":"Default","set.resetJudgePrompt":"Restore default prompt","set.resetJudgePromptOk":"Default prompt restored","set.overwriteOk":"Corrupt plugin config overwritten","err.missingPayload":"Tool arguments are incomplete; sending to human","err.missingPayloadUncaptured":"Tool arguments were not captured; sending to human","err.truncatedPayload":"Tool arguments were truncated; sending to human (no prefix auto-allow)","err.pluginError":"Plugin error; sending to human","err.allowlistCorrupt":"Rules file is corrupt; refusing to overwrite. Restore defaults or fix allowlist.json on disk.","err.allowlistWrite":"Failed to write allowlist","err.criterionNotFound":"Criterion not found","err.criterionNeedId":"id or label is required","err.criterionIdExists":"id already exists","err.criterionOtherLocked":"“Other” cannot be deleted","err.criteriaOp":"Criteria accept add / set / remove / reset","err.opMustSet":"{kind} only accepts set","err.invalidNumber":"Invalid number","err.keywordEmpty":"Keyword cannot be empty","err.keywordNotFound":"Keyword not found","err.keywordsOp":"Keywords accept add / set / remove / reset","err.valueEmpty":"Value cannot be empty","err.unknownKind":"Unknown rule kind: {kind}","err.unknownOp":"Unknown operation: {op}","err.ruleNotFound":"No matching rule","err.badBody":"Request body is not JSON","err.needSessionId":"sessionId is required","err.pluginCorrupt":"Plugin config is corrupt; refusing to overwrite. Fix the file on disk, or overwrite the corrupt config.","err.pluginWrite":"Failed to write plugin config","err.unknownEndpoint":"Unknown endpoint: {endpoint}","err.internal":"Internal error: {error}","err.catalog":"Could not list models: {error}","err.info":"Could not load model info: {error}","err.judgeUnconfigured":"Judge model is not configured","err.judgeEffort":"reasoningEffort {effort} is not supported","err.judgeTimeout":"Judge timed out ({ms}ms)","err.judgeRetryTimeout":"Judge retry timed out ({ms}ms)","err.judgeFailed":"Judge failed","err.judgeCall":"Judge call failed: {error}","err.judgeEmpty":"Judge output was empty","err.judgeParse":"Judge output could not be parsed","err.judgeUpstream":"Judge route failed: {error}","err.noPresetsKey":"The permission entry has no presets key; add it manually","err.preset":"Failed to write preset: {error}","err.presetSandboxMissing":"The preset has no sandbox line; cannot set the sandbox mode. Check the profile cordis.patch.yml","rpc.unavailable":"connection.rpc is unavailable","rpc.failed":"RPC failed"}`)
|
|
146
147
|
|
|
147
148
|
function fillLocale(template, params) {
|
|
148
149
|
if (!params) return template
|
|
@@ -152,6 +153,7 @@ window.__ModuleLoader__.load({
|
|
|
152
153
|
}
|
|
153
154
|
|
|
154
155
|
function tFrom(props) {
|
|
156
|
+
if (props && typeof props.t === 'function') return props.t
|
|
155
157
|
const sp = (props && props.slotsProps) || {}
|
|
156
158
|
if (typeof sp.t === 'function') return sp.t
|
|
157
159
|
return function (key, params) {
|
|
@@ -159,42 +161,39 @@ window.__ModuleLoader__.load({
|
|
|
159
161
|
}
|
|
160
162
|
}
|
|
161
163
|
|
|
164
|
+
function lookupLabel(t, prefix, id) {
|
|
165
|
+
if (!id) return ''
|
|
166
|
+
const key = prefix + '.' + id
|
|
167
|
+
const v = t(key)
|
|
168
|
+
return v === key ? '' : v
|
|
169
|
+
}
|
|
170
|
+
|
|
162
171
|
function verdictLabel(t, verdict) {
|
|
163
172
|
if (!verdict) return t('notice.autoDefault')
|
|
164
|
-
|
|
165
|
-
const v = t(key)
|
|
166
|
-
return v === key ? String(verdict) : v
|
|
173
|
+
return lookupLabel(t, 'verdict', verdict) || lookupLabel(t, 'path', verdict) || String(verdict)
|
|
167
174
|
}
|
|
168
175
|
|
|
169
176
|
function pathLabel(t, path) {
|
|
170
|
-
|
|
171
|
-
const key = 'path.' + path
|
|
172
|
-
const v = t(key)
|
|
173
|
-
return v === key ? String(path) : v
|
|
177
|
+
return lookupLabel(t, 'path', path) || lookupLabel(t, 'verdict', path) || (path ? String(path) : '')
|
|
174
178
|
}
|
|
175
179
|
|
|
176
180
|
function actionLabel(t, action) {
|
|
177
|
-
|
|
178
|
-
const key = 'action.' + action
|
|
179
|
-
const v = t(key)
|
|
180
|
-
return v === key ? String(action) : v
|
|
181
|
+
return lookupLabel(t, 'action', action) || (action ? String(action) : '')
|
|
181
182
|
}
|
|
182
183
|
|
|
183
|
-
function
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
loggingIn: 'set.prov.loggingIn',
|
|
190
|
-
waitScan: 'set.prov.waitScan',
|
|
191
|
-
saving: 'set.prov.saving',
|
|
192
|
-
connected: 'set.prov.connected',
|
|
193
|
-
}
|
|
194
|
-
const key = map[status]
|
|
195
|
-
return key ? t(key) : String(status || '')
|
|
184
|
+
function criterionLabel(t, criterion, storedLabel) {
|
|
185
|
+
return lookupLabel(t, 'criterion', criterion) || storedLabel || (criterion ? String(criterion) : '')
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function sandboxLabel(t, mode) {
|
|
189
|
+
return lookupLabel(t, 'sandbox', mode) || (mode ? String(mode) : '')
|
|
196
190
|
}
|
|
197
191
|
|
|
192
|
+
function sourceLabel(t, source) {
|
|
193
|
+
return lookupLabel(t, 'source', source) || (source ? String(source) : '')
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
|
|
198
197
|
function countsLabel(t, reject, human, allow) {
|
|
199
198
|
return t('set.counts', { reject: String(reject), human: String(human), allow: String(allow) })
|
|
200
199
|
}
|
|
@@ -210,7 +209,7 @@ window.__ModuleLoader__.load({
|
|
|
210
209
|
}
|
|
211
210
|
|
|
212
211
|
function hasVal(value) {
|
|
213
|
-
return value !== undefined && value !== null
|
|
212
|
+
return value !== undefined && value !== null
|
|
214
213
|
}
|
|
215
214
|
|
|
216
215
|
function isLongVal(value) {
|
|
@@ -218,24 +217,26 @@ window.__ModuleLoader__.load({
|
|
|
218
217
|
return s.length > 72 || s.indexOf('\n') !== -1
|
|
219
218
|
}
|
|
220
219
|
|
|
221
|
-
function Detail(label, value) {
|
|
220
|
+
function Detail(label, value, emptyText) {
|
|
222
221
|
if (!hasVal(value)) return null
|
|
223
|
-
|
|
222
|
+
if (value === '' && !emptyText) return null
|
|
223
|
+
const shown = value === '' ? emptyText : String(value)
|
|
224
|
+
const long = isLongVal(shown)
|
|
224
225
|
return [
|
|
225
226
|
React.createElement('div', { className: 'ab-detail-k', key: label + '-k' }, label),
|
|
226
227
|
React.createElement(long ? 'pre' : 'div', {
|
|
227
228
|
className: 'ab-detail-v' + (long ? ' ab-detail-v-box' : ''),
|
|
228
229
|
key: label + '-v',
|
|
229
|
-
},
|
|
230
|
+
}, shown),
|
|
230
231
|
]
|
|
231
232
|
}
|
|
232
233
|
|
|
233
|
-
function DetailSection(title, items) {
|
|
234
|
+
function DetailSection(title, items, emptyText) {
|
|
234
235
|
const kids = []
|
|
235
236
|
for (let i = 0; i < items.length; i++) {
|
|
236
237
|
const row = items[i]
|
|
237
238
|
if (!row) continue
|
|
238
|
-
const node = Detail(row[0], row[1])
|
|
239
|
+
const node = Detail(row[0], row[1], emptyText)
|
|
239
240
|
if (node) kids.push(node[0], node[1])
|
|
240
241
|
}
|
|
241
242
|
if (!kids.length) return null
|
|
@@ -252,11 +253,13 @@ window.__ModuleLoader__.load({
|
|
|
252
253
|
}
|
|
253
254
|
|
|
254
255
|
function fmtTime(iso) {
|
|
256
|
+
if (!iso) return ''
|
|
255
257
|
try {
|
|
256
258
|
const d = new Date(iso)
|
|
259
|
+
if (Number.isNaN(d.getTime())) return ''
|
|
257
260
|
const p = (n) => String(n).padStart(2, '0')
|
|
258
261
|
return p(d.getMonth() + 1) + '-' + p(d.getDate()) + ' ' + p(d.getHours()) + ':' + p(d.getMinutes()) + ':' + p(d.getSeconds())
|
|
259
|
-
} catch (e) { return
|
|
262
|
+
} catch (e) { return '' }
|
|
260
263
|
}
|
|
261
264
|
|
|
262
265
|
function GlyphCheck() {
|
|
@@ -276,18 +279,37 @@ window.__ModuleLoader__.load({
|
|
|
276
279
|
}
|
|
277
280
|
|
|
278
281
|
function latestUnsettledPending(evs) {
|
|
279
|
-
const settled = Object.create(null)
|
|
280
282
|
for (let i = evs.length - 1; i >= 0; i--) {
|
|
281
|
-
const
|
|
282
|
-
|
|
283
|
-
if (kind === 'manual-
|
|
284
|
-
if (ev.ticket != null) settled[ev.ticket] = true
|
|
285
|
-
}
|
|
286
|
-
if (kind === 'manual-pending' && (ev.ticket == null || !settled[ev.ticket])) return ev
|
|
283
|
+
const kind = evs[i].kind || ''
|
|
284
|
+
if (kind === 'manual-approved' || kind === 'manual-rejected' || kind === 'manual-cancelled' || kind === 'manual-unavailable') return null
|
|
285
|
+
if (kind === 'manual-pending') return evs[i]
|
|
287
286
|
}
|
|
288
287
|
return null
|
|
289
288
|
}
|
|
290
289
|
|
|
290
|
+
|
|
291
|
+
function formatErr(t, code, details) {
|
|
292
|
+
if (!code) return ''
|
|
293
|
+
const d = details && typeof details === 'object' ? details : {}
|
|
294
|
+
const raw = String(code)
|
|
295
|
+
const key = raw.indexOf('err.') === 0 || raw.indexOf('rpc.') === 0 ? raw : 'err.' + raw
|
|
296
|
+
const v = t(key, d)
|
|
297
|
+
if (v && v !== key) return v
|
|
298
|
+
if (raw.indexOf('err.') !== 0 && raw.indexOf('rpc.') !== 0) {
|
|
299
|
+
const direct = t(raw, d)
|
|
300
|
+
if (direct && direct !== raw) return direct
|
|
301
|
+
}
|
|
302
|
+
if (d.error) return String(d.error)
|
|
303
|
+
return raw
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
function codedText(t, text, details) {
|
|
307
|
+
if (!text) return ''
|
|
308
|
+
const s = String(text)
|
|
309
|
+
if (s.indexOf('err.') === 0 || s.indexOf('rpc.') === 0) return formatErr(t, s, details || {}) || s
|
|
310
|
+
return s
|
|
311
|
+
}
|
|
312
|
+
|
|
291
313
|
function makeRpc(connection, t) {
|
|
292
314
|
const tr = typeof t === 'function' ? t : function (key) { return ZH[key] || key }
|
|
293
315
|
return function rpc(endpoint, payload) {
|
|
@@ -299,8 +321,8 @@ window.__ModuleLoader__.load({
|
|
|
299
321
|
payload: payload || {},
|
|
300
322
|
}).then(function (r) {
|
|
301
323
|
if (!r || r.ok === false) {
|
|
302
|
-
const
|
|
303
|
-
throw new Error(
|
|
324
|
+
const err = r && r.error
|
|
325
|
+
throw new Error(formatErr(tr, err && err.code, err && (err.details || {})) || (err && err.message) || tr('rpc.failed'))
|
|
304
326
|
}
|
|
305
327
|
return r.value
|
|
306
328
|
})
|
|
@@ -381,8 +403,7 @@ window.__ModuleLoader__.load({
|
|
|
381
403
|
if (!notice) return null
|
|
382
404
|
const kind = notice.kind || 'auto'
|
|
383
405
|
const isPending = kind === 'manual-pending'
|
|
384
|
-
const isManual = kind === 'manual-approved' || kind === 'manual-rejected'
|
|
385
|
-
const ticket = notice.ticket ? (' #' + notice.ticket) : ''
|
|
406
|
+
const isManual = kind === 'manual-approved' || kind === 'manual-rejected' || kind === 'manual-cancelled' || kind === 'manual-unavailable'
|
|
386
407
|
let title = ''
|
|
387
408
|
let tagText = ''
|
|
388
409
|
let glyph = null
|
|
@@ -391,17 +412,25 @@ window.__ModuleLoader__.load({
|
|
|
391
412
|
tagText = t('notice.feedErrorTag')
|
|
392
413
|
glyph = React.createElement('span', { className: 'ab-notice-glyph-warn' }, '!')
|
|
393
414
|
} else if (isPending) {
|
|
394
|
-
title = t('notice.pendingTitle', {
|
|
395
|
-
tagText = t('notice.pendingTag'
|
|
415
|
+
title = t('notice.pendingTitle', { preview: eventPreview(notice) })
|
|
416
|
+
tagText = t('notice.pendingTag')
|
|
396
417
|
glyph = React.createElement('span', { className: 'ab-notice-glyph-warn' }, '◔')
|
|
397
418
|
} else if (kind === 'manual-approved') {
|
|
398
|
-
title = t(
|
|
399
|
-
tagText = t(
|
|
419
|
+
title = t('notice.webApproved', { preview: eventPreview(notice) })
|
|
420
|
+
tagText = t('notice.webApprovedTag')
|
|
400
421
|
glyph = React.createElement('span', { className: 'ab-notice-glyph-warn' }, '✓')
|
|
422
|
+
} else if (kind === 'manual-cancelled') {
|
|
423
|
+
title = t('notice.cancelledTitle', { preview: eventPreview(notice) })
|
|
424
|
+
tagText = t('history.tagCancel')
|
|
425
|
+
glyph = React.createElement('span', { className: 'ab-notice-glyph-warn' }, '✕')
|
|
426
|
+
} else if (kind === 'manual-unavailable') {
|
|
427
|
+
title = t('notice.unavailableTitle', { preview: eventPreview(notice) })
|
|
428
|
+
tagText = t('history.tagUnavailable')
|
|
429
|
+
glyph = React.createElement('span', { className: 'ab-notice-glyph-warn' }, '!')
|
|
401
430
|
} else if (kind === 'manual-rejected' || isAutoReject(notice.verdict)) {
|
|
402
|
-
title = t('notice.rejectedTitle', {
|
|
431
|
+
title = t('notice.rejectedTitle', { preview: eventPreview(notice) })
|
|
403
432
|
tagText = kind === 'manual-rejected'
|
|
404
|
-
? t('notice.rejectedTag'
|
|
433
|
+
? t('notice.rejectedTag')
|
|
405
434
|
: (verdictLabel(t, notice.verdict) || t('notice.rejectedDefault'))
|
|
406
435
|
glyph = React.createElement('span', { className: 'ab-notice-glyph-err' }, '✕')
|
|
407
436
|
} else {
|
|
@@ -417,11 +446,11 @@ window.__ModuleLoader__.load({
|
|
|
417
446
|
React.createElement('span', { className: 'ab-notice-glyph' }, glyph),
|
|
418
447
|
React.createElement('div', { className: 'ab-notice-body' },
|
|
419
448
|
React.createElement('div', { className: 'ab-notice-head' },
|
|
420
|
-
React.createElement('span', { className: 'ab-notice-tool' }, notice.tool || (kind === 'feed-error' ? t('notice.hint') : '
|
|
449
|
+
React.createElement('span', { className: 'ab-notice-tool' }, notice.tool || (kind === 'feed-error' ? t('notice.hint') : t('history.unknownTool'))),
|
|
421
450
|
React.createElement('span', { className: 'ab-notice-text' }, title),
|
|
422
451
|
),
|
|
423
452
|
React.createElement('div', { className: 'ab-notice-meta' },
|
|
424
|
-
React.createElement('span', { className: isPending ? 'ab-tag-warn' : (kind === 'manual-rejected' || isAutoReject(notice.verdict) || kind === 'feed-error') ? 'ab-tag-err' :
|
|
453
|
+
React.createElement('span', { className: isPending || kind === 'manual-approved' || kind === 'manual-cancelled' || kind === 'manual-unavailable' ? 'ab-tag-warn' : (kind === 'manual-rejected' || isAutoReject(notice.verdict) || kind === 'feed-error') ? 'ab-tag-err' : 'ab-tag' }, tagText),
|
|
425
454
|
React.createElement('span', { className: 'ab-time' }, fmtTime(notice.ts)),
|
|
426
455
|
),
|
|
427
456
|
),
|
|
@@ -437,7 +466,7 @@ window.__ModuleLoader__.load({
|
|
|
437
466
|
function HistoryView(props) {
|
|
438
467
|
const t = tFrom(props)
|
|
439
468
|
const rpc = props.rpc
|
|
440
|
-
const sessionId = (props &&
|
|
469
|
+
const sessionId = (props && props.sessionId) || resolveFrame(props.slotsProps || props).sessionId || null
|
|
441
470
|
const [events, setEvents] = React.useState(null)
|
|
442
471
|
const [error, setError] = React.useState(null)
|
|
443
472
|
const [openId, setOpenId] = React.useState(null)
|
|
@@ -445,6 +474,9 @@ window.__ModuleLoader__.load({
|
|
|
445
474
|
React.useEffect(function () {
|
|
446
475
|
let alive = true
|
|
447
476
|
let timer = null
|
|
477
|
+
setEvents(null)
|
|
478
|
+
setError(null)
|
|
479
|
+
setOpenId(null)
|
|
448
480
|
if (!sessionId || !rpc) return
|
|
449
481
|
const load = function () {
|
|
450
482
|
rpc('events', { sessionId: sessionId, since: 0 }).then(function (data) {
|
|
@@ -481,22 +513,29 @@ window.__ModuleLoader__.load({
|
|
|
481
513
|
React.createElement('div', { className: 'ab-list' },
|
|
482
514
|
events.map(function (ev) {
|
|
483
515
|
const kind = ev.kind || 'auto'
|
|
484
|
-
const ticket = ev.ticket ? '#' + ev.ticket : ''
|
|
485
516
|
let glyph = React.createElement(GlyphCheck, null)
|
|
486
517
|
let tag = t('history.tagAuto')
|
|
487
518
|
let tagCls = 'ab-tag'
|
|
488
519
|
if (kind === 'manual-pending') {
|
|
489
520
|
glyph = React.createElement('span', { className: 'ab-notice-glyph-warn' }, '◔')
|
|
490
|
-
tag = t('history.tagPending'
|
|
521
|
+
tag = t('history.tagPending')
|
|
491
522
|
tagCls = 'ab-tag-warn'
|
|
492
523
|
} else if (kind === 'manual-approved') {
|
|
493
524
|
glyph = React.createElement('span', { className: 'ab-notice-glyph-warn' }, '✓')
|
|
494
|
-
tag = t(
|
|
525
|
+
tag = t('history.tagAllow')
|
|
495
526
|
tagCls = 'ab-tag-warn'
|
|
496
527
|
} else if (kind === 'manual-rejected') {
|
|
497
528
|
glyph = React.createElement('span', { className: 'ab-notice-glyph-err' }, '✕')
|
|
498
|
-
tag = t('history.tagReject'
|
|
529
|
+
tag = t('history.tagReject')
|
|
499
530
|
tagCls = 'ab-tag-err'
|
|
531
|
+
} else if (kind === 'manual-cancelled') {
|
|
532
|
+
glyph = React.createElement('span', { className: 'ab-notice-glyph-warn' }, '✕')
|
|
533
|
+
tag = t('history.tagCancel')
|
|
534
|
+
tagCls = 'ab-tag-warn'
|
|
535
|
+
} else if (kind === 'manual-unavailable') {
|
|
536
|
+
glyph = React.createElement('span', { className: 'ab-notice-glyph-warn' }, '!')
|
|
537
|
+
tag = t('history.tagUnavailable')
|
|
538
|
+
tagCls = 'ab-tag-warn'
|
|
500
539
|
} else if (isAutoReject(ev.verdict)) {
|
|
501
540
|
glyph = React.createElement('span', { className: 'ab-notice-glyph-err' }, '✕')
|
|
502
541
|
tag = verdictLabel(t, ev.verdict) || t('notice.rejectedDefault')
|
|
@@ -522,7 +561,7 @@ window.__ModuleLoader__.load({
|
|
|
522
561
|
onClick: function () { setOpenId(open ? null : ev.id) },
|
|
523
562
|
},
|
|
524
563
|
React.createElement('div', { className: 'ab-row-top' },
|
|
525
|
-
React.createElement('span', { className: 'ab-row-tool' }, ev.tool || '
|
|
564
|
+
React.createElement('span', { className: 'ab-row-tool' }, ev.tool || t('history.unknownTool')),
|
|
526
565
|
React.createElement('span', { className: tagCls }, tag),
|
|
527
566
|
React.createElement('span', { className: 'ab-time' }, fmtTime(ev.ts)),
|
|
528
567
|
React.createElement('span', { className: 'ab-row-chev' }, '▾'),
|
|
@@ -533,7 +572,7 @@ window.__ModuleLoader__.load({
|
|
|
533
572
|
? React.createElement('div', { className: 'ab-details' },
|
|
534
573
|
DetailSection(t('detail.request'), [
|
|
535
574
|
[t('detail.command'), args.command],
|
|
536
|
-
[t('detail.path'), args.file_path
|
|
575
|
+
[t('detail.path'), args.file_path != null ? args.file_path : args.path],
|
|
537
576
|
[t('detail.description'), args.description],
|
|
538
577
|
[t('detail.old'), args.old_string],
|
|
539
578
|
[t('detail.new'), args.new_string],
|
|
@@ -544,23 +583,29 @@ window.__ModuleLoader__.load({
|
|
|
544
583
|
[t('detail.script'), args.script],
|
|
545
584
|
[t('detail.sql'), args.sql],
|
|
546
585
|
[t('detail.prompt'), args.prompt],
|
|
586
|
+
[t('detail.input'), args.input],
|
|
587
|
+
[t('detail.text'), args.text],
|
|
588
|
+
[t('detail.body'), args.body],
|
|
589
|
+
[t('detail.message'), args.message],
|
|
590
|
+
[t('detail.pattern'), args.pattern],
|
|
591
|
+
[t('detail.selector'), args.selector],
|
|
547
592
|
[t('detail.cwd'), ev.cwd],
|
|
548
|
-
[t('detail.
|
|
593
|
+
[t('detail.workdir'), args.workdir && args.workdir !== ev.cwd ? args.workdir : undefined],
|
|
594
|
+
[t('detail.sandbox'), sandboxLabel(t, ev.mode)],
|
|
549
595
|
[t('detail.justification'), ev.justification || ev.reason],
|
|
550
|
-
]),
|
|
596
|
+
], t('detail.empty')),
|
|
551
597
|
DetailSection(t('detail.audit'), [
|
|
552
598
|
[t('detail.pipe'), pathLabel(t, ev.path) || ev.path],
|
|
553
599
|
[t('detail.keyword'), ev.keyword],
|
|
554
600
|
[t('detail.judgeModel'), [j.provider, j.model, j.effort].filter(Boolean).join(' / ')],
|
|
555
601
|
[t('detail.judgeCategory'), j.failed
|
|
556
602
|
? t('detail.judgeFailed')
|
|
557
|
-
: (
|
|
603
|
+
: criterionLabel(t, j.criterion || ev.category, j.label)],
|
|
558
604
|
[t('detail.judgeAction'), actionLabel(t, j.action) || j.action],
|
|
559
|
-
[t('detail.judgeReason'), j.reason || ev.judgeReason],
|
|
560
|
-
[t('detail.judgeError'), j.error],
|
|
605
|
+
[t('detail.judgeReason'), codedText(t, j.reason || ev.judgeReason)],
|
|
606
|
+
[t('detail.judgeError'), formatErr(t, j.errorCode || j.error, { ms: j.errorMs || '', error: j.errorDetail || '', effort: j.errorEffort || '' })],
|
|
561
607
|
[t('detail.judgeRaw'), j.raw],
|
|
562
|
-
[t('detail.
|
|
563
|
-
[t('detail.source'), ev.source],
|
|
608
|
+
[t('detail.source'), sourceLabel(t, ev.source)],
|
|
564
609
|
]),
|
|
565
610
|
)
|
|
566
611
|
: null,
|
|
@@ -571,7 +616,7 @@ window.__ModuleLoader__.load({
|
|
|
571
616
|
)
|
|
572
617
|
}
|
|
573
618
|
|
|
574
|
-
/** 设置 →
|
|
619
|
+
/** 设置 → 自动审批。恢复默认需连点两次。 */
|
|
575
620
|
function SettingsPage(props) {
|
|
576
621
|
const t = tFrom(props)
|
|
577
622
|
const rpc = props.rpc
|
|
@@ -584,22 +629,24 @@ window.__ModuleLoader__.load({
|
|
|
584
629
|
const [newKeywordAction, setNewKeywordAction] = React.useState('human')
|
|
585
630
|
const [newCriterion, setNewCriterion] = React.useState({ id: '', label: '', description: '', action: 'human' })
|
|
586
631
|
const [timeoutMs, setTimeoutMs] = React.useState('20000')
|
|
587
|
-
const [appId, setAppId] = React.useState('')
|
|
588
|
-
const [appSecret, setAppSecret] = React.useState('')
|
|
589
|
-
const [chatIdInput, setChatIdInput] = React.useState('')
|
|
590
|
-
const [userIdInput, setUserIdInput] = React.useState('')
|
|
591
|
-
const [timeoutSecs, setTimeoutSecs] = React.useState('120')
|
|
592
632
|
const [models, setModels] = React.useState([])
|
|
593
633
|
const [efforts, setEfforts] = React.useState([])
|
|
594
634
|
const [judgeProvider, setJudgeProvider] = React.useState('')
|
|
595
635
|
const [judgeModel, setJudgeModel] = React.useState('')
|
|
596
636
|
const [judgeEffort, setJudgeEffort] = React.useState('')
|
|
597
637
|
const [judgePromptLang, setJudgePromptLang] = React.useState('zh')
|
|
638
|
+
const [judgePromptDrafts, setJudgePromptDrafts] = React.useState({ zh: '', en: '' })
|
|
598
639
|
const [presetSandbox, setPresetSandbox] = React.useState('workspace-write')
|
|
599
|
-
const [foldOpen, setFoldOpen] = React.useState({ keywords: false, criteria: false
|
|
640
|
+
const [foldOpen, setFoldOpen] = React.useState({ keywords: false, criteria: false })
|
|
600
641
|
const [kwEdit, setKwEdit] = React.useState(null)
|
|
601
642
|
const [resetArmed, setResetArmed] = React.useState(null)
|
|
602
|
-
|
|
643
|
+
const feedbackTimerRef = React.useRef(null)
|
|
644
|
+
const kwSkipBlurRef = React.useRef(false)
|
|
645
|
+
React.useEffect(function () {
|
|
646
|
+
return function () {
|
|
647
|
+
if (feedbackTimerRef.current) clearTimeout(feedbackTimerRef.current)
|
|
648
|
+
}
|
|
649
|
+
}, [])
|
|
603
650
|
const load = function (opts) {
|
|
604
651
|
const keepEdits = opts && opts.keepEdits
|
|
605
652
|
rpc('snapshot').then(function (data) {
|
|
@@ -610,10 +657,17 @@ window.__ModuleLoader__.load({
|
|
|
610
657
|
setJudgeModel((data.plugin.judge && data.plugin.judge.model) || '')
|
|
611
658
|
setJudgeEffort((data.plugin.judge && data.plugin.judge.reasoningEffort) || '')
|
|
612
659
|
setJudgePromptLang((data.plugin && data.plugin.judgePromptLang) === 'en' ? 'en' : 'zh')
|
|
660
|
+
setJudgePromptDrafts((function () {
|
|
661
|
+
const shipped = (data.predefined && data.predefined.judgePrompts) || {}
|
|
662
|
+
const custom = (data.plugin && data.plugin.judgePrompts) || {}
|
|
663
|
+
const pick = function (lang) {
|
|
664
|
+
const c = custom[lang]
|
|
665
|
+
if (c && String(c).trim()) return String(c)
|
|
666
|
+
return String(shipped[lang] || '')
|
|
667
|
+
}
|
|
668
|
+
return { zh: pick('zh'), en: pick('en') }
|
|
669
|
+
})())
|
|
613
670
|
setPresetSandbox((data.plugin && data.plugin.presetSandbox) === 'read-only' ? 'read-only' : 'workspace-write')
|
|
614
|
-
setChatIdInput((data.plugin.notify && data.plugin.notify.chatId) || '')
|
|
615
|
-
setUserIdInput((data.plugin.notify && data.plugin.notify.userId) || '')
|
|
616
|
-
setTimeoutSecs(String((data.plugin.notify && data.plugin.notify.timeoutSecs) || 120))
|
|
617
671
|
}
|
|
618
672
|
setError(null)
|
|
619
673
|
}).catch(function (e) {
|
|
@@ -627,49 +681,39 @@ window.__ModuleLoader__.load({
|
|
|
627
681
|
return function () { clearTimeout(disarm) }
|
|
628
682
|
}, [resetArmed])
|
|
629
683
|
|
|
630
|
-
const provisioning = snapshot && snapshot.qq && snapshot.qq.provisioning ? snapshot.qq.provisioning : null
|
|
631
|
-
const provisioningStatus = provisioning && provisioning.status ? provisioning.status : ''
|
|
632
|
-
const provisioningActive = provisioningStatus === '登录中' || provisioningStatus === '等待扫码' || provisioningStatus === '保存凭据'
|
|
633
|
-
const qqOpenedForProvision = React.useRef(false)
|
|
634
|
-
React.useEffect(function () {
|
|
635
|
-
if (provisioningActive && !qqOpenedForProvision.current) {
|
|
636
|
-
qqOpenedForProvision.current = true
|
|
637
|
-
setFoldOpen(function (s) { return Object.assign({}, s, { qq: true }) })
|
|
638
|
-
}
|
|
639
|
-
if (!provisioningActive) qqOpenedForProvision.current = false
|
|
640
|
-
}, [provisioningActive])
|
|
641
|
-
React.useEffect(function () {
|
|
642
|
-
if (!provisioningActive) return
|
|
643
|
-
const timer = setInterval(function () { load({ keepEdits: true }) }, 2000)
|
|
644
|
-
return function () { clearInterval(timer) }
|
|
645
|
-
}, [provisioningActive])
|
|
646
|
-
|
|
647
684
|
React.useEffect(function () {
|
|
648
685
|
const provider = judgeProvider || (snapshot && snapshot.fallback && snapshot.fallback.provider)
|
|
649
686
|
if (!provider || !rpc) return
|
|
687
|
+
let alive = true
|
|
650
688
|
rpc('judge-catalog', { provider: provider }).then(function (data) {
|
|
689
|
+
if (!alive) return
|
|
651
690
|
setModels(data.models || [])
|
|
652
|
-
}).catch(function () { setModels([]) })
|
|
691
|
+
}).catch(function () { if (alive) setModels([]) })
|
|
692
|
+
return function () { alive = false }
|
|
653
693
|
}, [judgeProvider, snapshot, rpc])
|
|
654
694
|
|
|
655
695
|
React.useEffect(function () {
|
|
656
696
|
const provider = judgeProvider || (snapshot && snapshot.fallback && snapshot.fallback.provider)
|
|
657
697
|
const model = judgeModel || (snapshot && snapshot.fallback && snapshot.fallback.model)
|
|
658
698
|
if (!provider || !model || !rpc) { setEfforts([]); return }
|
|
699
|
+
let alive = true
|
|
659
700
|
rpc('judge-info', { provider: provider, model: model }).then(function (data) {
|
|
701
|
+
if (!alive) return
|
|
660
702
|
setEfforts(data.efforts || [])
|
|
661
|
-
}).catch(function () { setEfforts([]) })
|
|
703
|
+
}).catch(function () { if (alive) setEfforts([]) })
|
|
704
|
+
return function () { alive = false }
|
|
662
705
|
}, [judgeProvider, judgeModel, snapshot, rpc])
|
|
663
706
|
|
|
664
707
|
const showFeedback = function (msg, ok) {
|
|
665
708
|
setFeedback({ msg: String(msg), ok: ok !== false })
|
|
666
|
-
|
|
709
|
+
if (feedbackTimerRef.current) clearTimeout(feedbackTimerRef.current)
|
|
710
|
+
feedbackTimerRef.current = setTimeout(function () { setFeedback(null) }, 4000)
|
|
667
711
|
}
|
|
668
712
|
|
|
669
|
-
const run = function (endpoint, payload, okMsg) {
|
|
713
|
+
const run = function (endpoint, payload, okMsg, opts) {
|
|
670
714
|
setBusy(true)
|
|
671
715
|
return rpc(endpoint, payload).then(function (res) {
|
|
672
|
-
load()
|
|
716
|
+
load({ keepEdits: !(opts && opts.reload) })
|
|
673
717
|
showFeedback(okMsg || t('set.hotOk'))
|
|
674
718
|
return res
|
|
675
719
|
}).catch(function (e) {
|
|
@@ -686,9 +730,7 @@ window.__ModuleLoader__.load({
|
|
|
686
730
|
const setup = snapshot.setup || { configured: false }
|
|
687
731
|
const predefined = snapshot.predefined || {}
|
|
688
732
|
const preDeny = new Set(predefined.rejectKeywords || predefined.denyKeywords || [])
|
|
689
|
-
const qq = snapshot.qq || {}
|
|
690
733
|
const plugin = snapshot.plugin || {}
|
|
691
|
-
const notify = plugin.notify || {}
|
|
692
734
|
const providers = snapshot.providers || []
|
|
693
735
|
const fallback = snapshot.fallback || {}
|
|
694
736
|
const judgeTimeoutLabel = Math.round((Number(cfg.judgeTimeoutMs) || 20000) / 1000) + 's'
|
|
@@ -723,14 +765,47 @@ window.__ModuleLoader__.load({
|
|
|
723
765
|
return
|
|
724
766
|
}
|
|
725
767
|
setResetArmed(null)
|
|
768
|
+
if (kind === 'judgePrompt') {
|
|
769
|
+
const lang = judgePromptLang === 'en' ? 'en' : 'zh'
|
|
770
|
+
const prompts = {}
|
|
771
|
+
prompts[lang] = ''
|
|
772
|
+
run('save-plugin', { judgePrompts: prompts }, okMsg, { reload: true })
|
|
773
|
+
return
|
|
774
|
+
}
|
|
726
775
|
run('rule-op', { op: 'reset', kind: kind, value: value }, okMsg)
|
|
727
776
|
},
|
|
728
777
|
}, armed ? t('set.resetConfirm') : idleLabel)
|
|
729
778
|
}
|
|
730
779
|
|
|
780
|
+
const saveJudge = function () {
|
|
781
|
+
const n = Number(timeoutMs)
|
|
782
|
+
if (!Number.isFinite(n) || n <= 0) {
|
|
783
|
+
showFeedback(t('err.invalidNumber'), false)
|
|
784
|
+
return
|
|
785
|
+
}
|
|
786
|
+
run('save-plugin', {
|
|
787
|
+
judgePromptLang: judgePromptLang,
|
|
788
|
+
judge: { provider: judgeProvider, model: judgeModel, reasoningEffort: judgeEffort },
|
|
789
|
+
judgeTimeoutMs: n,
|
|
790
|
+
judgePrompts: (function () {
|
|
791
|
+
const shipped = (snapshot.predefined && snapshot.predefined.judgePrompts) || {}
|
|
792
|
+
const store = function (lang) {
|
|
793
|
+
const draft = String((judgePromptDrafts && judgePromptDrafts[lang]) || '')
|
|
794
|
+
const def = String(shipped[lang] || '')
|
|
795
|
+
if (!draft.trim() || draft.trim() === def.trim()) return ''
|
|
796
|
+
return draft
|
|
797
|
+
}
|
|
798
|
+
return { zh: store('zh'), en: store('en') }
|
|
799
|
+
})(),
|
|
800
|
+
}, t('set.judgeSaved'), { reload: true })
|
|
801
|
+
}
|
|
802
|
+
|
|
731
803
|
return React.createElement('div', { className: 'ab-set', ref: rootRef },
|
|
732
804
|
React.createElement('h3', { className: 'ab-set-title' }, t('set.title')),
|
|
733
805
|
React.createElement('p', { className: 'ab-set-intro' }, t('set.intro')),
|
|
806
|
+
feedback
|
|
807
|
+
? React.createElement('div', { className: 'ab-set-flash ' + (feedback.ok ? 'ab-set-ok' : 'ab-set-err') }, feedback.msg)
|
|
808
|
+
: null,
|
|
734
809
|
(cfg && cfg.corrupt)
|
|
735
810
|
? React.createElement('div', { className: 'ab-set-err' }, t('set.allowlistCorrupt'))
|
|
736
811
|
: null,
|
|
@@ -739,14 +814,10 @@ window.__ModuleLoader__.load({
|
|
|
739
814
|
t('set.pluginCorrupt'),
|
|
740
815
|
React.createElement('button', {
|
|
741
816
|
type: 'button', className: 'ab-set-btn ab-set-btn-danger', disabled: busy,
|
|
742
|
-
onClick: function () { run('save-plugin', { overwriteCorrupt: true }, t('set.overwriteOk')) },
|
|
817
|
+
onClick: function () { run('save-plugin', { overwriteCorrupt: true }, t('set.overwriteOk'), { reload: true }) },
|
|
743
818
|
}, t('set.pluginCorruptOverwrite')),
|
|
744
819
|
)
|
|
745
820
|
: null,
|
|
746
|
-
(snapshot.qq && snapshot.qq.credsCorrupt)
|
|
747
|
-
? React.createElement('div', { className: 'ab-set-err' }, t('set.qqCorrupt'))
|
|
748
|
-
: null,
|
|
749
|
-
|
|
750
821
|
!setup.configured ? React.createElement('div', { className: 'ab-set-card' },
|
|
751
822
|
React.createElement('div', { className: 'ab-set-card-head' },
|
|
752
823
|
React.createElement('div', { className: 'ab-set-card-title' }, t('set.presetMissingTitle')),
|
|
@@ -759,6 +830,14 @@ window.__ModuleLoader__.load({
|
|
|
759
830
|
React.createElement('span', { className: 'ab-set-note' }, t('set.presetWriteNote')),
|
|
760
831
|
),
|
|
761
832
|
) : null,
|
|
833
|
+
(setup.configured && setup.drift && setup.drift.baseKnown && (setup.drift.missing || []).length)
|
|
834
|
+
? React.createElement('div', { className: 'ab-set-card' },
|
|
835
|
+
React.createElement('div', { className: 'ab-set-card-head' },
|
|
836
|
+
React.createElement('div', { className: 'ab-set-card-title' }, t('set.driftTitle')),
|
|
837
|
+
React.createElement('p', { className: 'ab-set-card-sub' },
|
|
838
|
+
t('set.driftBody', { keys: (setup.drift.missing || []).join(' / ') }))),
|
|
839
|
+
)
|
|
840
|
+
: null,
|
|
762
841
|
|
|
763
842
|
React.createElement('div', { className: 'ab-set-card' },
|
|
764
843
|
React.createElement('div', { className: 'ab-set-card-head' },
|
|
@@ -767,7 +846,13 @@ window.__ModuleLoader__.load({
|
|
|
767
846
|
React.createElement('button', {
|
|
768
847
|
type: 'button',
|
|
769
848
|
className: 'ab-set-choice' + (presetSandbox === 'workspace-write' ? ' ab-set-choice-on' : ''),
|
|
770
|
-
|
|
849
|
+
disabled: busy,
|
|
850
|
+
onClick: function () {
|
|
851
|
+
if (presetSandbox === 'workspace-write') return
|
|
852
|
+
run('save-plugin', { presetSandbox: 'workspace-write' }, t('set.modeSaved')).then(function (res) {
|
|
853
|
+
if (res) setPresetSandbox('workspace-write')
|
|
854
|
+
})
|
|
855
|
+
},
|
|
771
856
|
},
|
|
772
857
|
React.createElement('span', { className: 'ab-set-choice-t' }, t('set.modeWs')),
|
|
773
858
|
React.createElement('span', { className: 'ab-set-choice-d' }, t('set.modeWsHint')),
|
|
@@ -775,24 +860,19 @@ window.__ModuleLoader__.load({
|
|
|
775
860
|
React.createElement('button', {
|
|
776
861
|
type: 'button',
|
|
777
862
|
className: 'ab-set-choice' + (presetSandbox === 'read-only' ? ' ab-set-choice-on' : ''),
|
|
778
|
-
|
|
863
|
+
disabled: busy,
|
|
864
|
+
onClick: function () {
|
|
865
|
+
if (presetSandbox === 'read-only') return
|
|
866
|
+
run('save-plugin', { presetSandbox: 'read-only' }, t('set.modeSaved')).then(function (res) {
|
|
867
|
+
if (res) setPresetSandbox('read-only')
|
|
868
|
+
})
|
|
869
|
+
},
|
|
779
870
|
},
|
|
780
871
|
React.createElement('span', { className: 'ab-set-choice-t' }, t('set.modeRo')),
|
|
781
872
|
React.createElement('span', { className: 'ab-set-choice-d' }, t('set.modeRoHint')),
|
|
782
873
|
),
|
|
783
874
|
),
|
|
784
|
-
React.createElement('
|
|
785
|
-
React.createElement('button', {
|
|
786
|
-
type: 'button', className: 'ab-set-btn ab-set-btn-primary', disabled: busy,
|
|
787
|
-
onClick: function () {
|
|
788
|
-
run(
|
|
789
|
-
'save-plugin',
|
|
790
|
-
{ presetSandbox: presetSandbox },
|
|
791
|
-
t('set.modeSaved'),
|
|
792
|
-
)
|
|
793
|
-
},
|
|
794
|
-
}, t('set.save')),
|
|
795
|
-
),
|
|
875
|
+
React.createElement('p', { className: 'ab-set-note' }, t('set.modeHint')),
|
|
796
876
|
),
|
|
797
877
|
|
|
798
878
|
React.createElement('div', { className: 'ab-set-card' },
|
|
@@ -822,7 +902,7 @@ window.__ModuleLoader__.load({
|
|
|
822
902
|
return React.createElement('span', {
|
|
823
903
|
className: 'ab-set-inv ' + (c.action === 'reject' ? 'ab-set-inv-hard' : c.action === 'allow' ? 'ab-set-inv-ok' : 'ab-set-inv-mode'),
|
|
824
904
|
key: c.id, title: c.id,
|
|
825
|
-
}, (c.label || c.id) + ' · ' + (actionLabel(t, c.action) || c.action))
|
|
905
|
+
}, (criterionLabel(t, c.id, c.label) || c.id) + ' · ' + (actionLabel(t, c.action) || c.action))
|
|
826
906
|
}),
|
|
827
907
|
),
|
|
828
908
|
),
|
|
@@ -866,12 +946,19 @@ window.__ModuleLoader__.load({
|
|
|
866
946
|
defaultValue: row.text,
|
|
867
947
|
autoFocus: true,
|
|
868
948
|
onBlur: function (e) {
|
|
949
|
+
if (kwSkipBlurRef.current) {
|
|
950
|
+
kwSkipBlurRef.current = false
|
|
951
|
+
return
|
|
952
|
+
}
|
|
869
953
|
commitKw(e.target.value, row.action)
|
|
870
954
|
setKwEdit(null)
|
|
871
955
|
},
|
|
872
956
|
onKeyDown: function (e) {
|
|
873
957
|
if (e.key === 'Enter') e.currentTarget.blur()
|
|
874
|
-
if (e.key === 'Escape')
|
|
958
|
+
if (e.key === 'Escape') {
|
|
959
|
+
kwSkipBlurRef.current = true
|
|
960
|
+
setKwEdit(null)
|
|
961
|
+
}
|
|
875
962
|
},
|
|
876
963
|
})
|
|
877
964
|
: React.createElement('span', {
|
|
@@ -922,8 +1009,10 @@ window.__ModuleLoader__.load({
|
|
|
922
1009
|
React.createElement('button', {
|
|
923
1010
|
type: 'button', className: 'ab-set-btn', disabled: busy || !newKeyword.trim(),
|
|
924
1011
|
onClick: function () {
|
|
925
|
-
|
|
926
|
-
|
|
1012
|
+
const text = newKeyword.trim()
|
|
1013
|
+
run('rule-op', { op: 'add', kind: 'keywords', value: { text: text, action: newKeywordAction } }).then(function (res) {
|
|
1014
|
+
if (res) setNewKeyword('')
|
|
1015
|
+
})
|
|
927
1016
|
},
|
|
928
1017
|
}, t('set.add')),
|
|
929
1018
|
),
|
|
@@ -961,7 +1050,7 @@ window.__ModuleLoader__.load({
|
|
|
961
1050
|
}
|
|
962
1051
|
return React.createElement('div', { className: 'ab-set-item ab-set-item-crit', key: c.id },
|
|
963
1052
|
React.createElement('div', { className: 'ab-set-crit-top' },
|
|
964
|
-
React.createElement('span', { className: 'ab-set-item-id' }, c.id),
|
|
1053
|
+
React.createElement('span', { className: 'ab-set-item-id', title: c.id }, criterionLabel(t, c.id) || c.id),
|
|
965
1054
|
React.createElement('select', {
|
|
966
1055
|
className: 'ab-set-select', value: c.action || 'human',
|
|
967
1056
|
onChange: function (e) { commitCrit({ action: e.target.value }) },
|
|
@@ -986,6 +1075,7 @@ window.__ModuleLoader__.load({
|
|
|
986
1075
|
),
|
|
987
1076
|
React.createElement('div', { className: 'ab-set-item-fields' },
|
|
988
1077
|
React.createElement('input', {
|
|
1078
|
+
key: c.id + ':label:' + (c.label || ''),
|
|
989
1079
|
className: 'ab-set-input ab-set-input-ui',
|
|
990
1080
|
defaultValue: c.label || '',
|
|
991
1081
|
placeholder: t('set.labelPlaceholder'),
|
|
@@ -997,6 +1087,7 @@ window.__ModuleLoader__.load({
|
|
|
997
1087
|
onKeyDown: function (e) { if (e.key === 'Enter') e.currentTarget.blur() },
|
|
998
1088
|
}),
|
|
999
1089
|
React.createElement('textarea', {
|
|
1090
|
+
key: c.id + ':desc:' + (c.description || ''),
|
|
1000
1091
|
className: 'ab-set-textarea',
|
|
1001
1092
|
defaultValue: c.description || '',
|
|
1002
1093
|
placeholder: t('set.descPlaceholder'),
|
|
@@ -1050,8 +1141,10 @@ window.__ModuleLoader__.load({
|
|
|
1050
1141
|
type: 'button', className: 'ab-set-btn ab-set-btn-primary',
|
|
1051
1142
|
disabled: busy || !(newCriterion.id || newCriterion.label),
|
|
1052
1143
|
onClick: function () {
|
|
1053
|
-
|
|
1054
|
-
|
|
1144
|
+
const row = newCriterion
|
|
1145
|
+
run('rule-op', { op: 'add', kind: 'criteria', value: row }).then(function (res) {
|
|
1146
|
+
if (res) setNewCriterion({ id: '', label: '', description: '', action: 'human' })
|
|
1147
|
+
})
|
|
1055
1148
|
},
|
|
1056
1149
|
}, t('set.addCriterion')),
|
|
1057
1150
|
),
|
|
@@ -1105,15 +1198,6 @@ window.__ModuleLoader__.load({
|
|
|
1105
1198
|
return React.createElement('option', { value: e.id, key: e.id }, e.name || e.id)
|
|
1106
1199
|
}),
|
|
1107
1200
|
),
|
|
1108
|
-
React.createElement('button', {
|
|
1109
|
-
type: 'button', className: 'ab-set-btn ab-set-btn-primary', disabled: busy,
|
|
1110
|
-
onClick: function () {
|
|
1111
|
-
run('save-plugin', {
|
|
1112
|
-
judgePromptLang: judgePromptLang,
|
|
1113
|
-
judge: { provider: judgeProvider, model: judgeModel, reasoningEffort: judgeEffort },
|
|
1114
|
-
}, t('set.judgeSaved'))
|
|
1115
|
-
},
|
|
1116
|
-
}, t('set.save')),
|
|
1117
1201
|
),
|
|
1118
1202
|
React.createElement('div', { className: 'ab-set-row' },
|
|
1119
1203
|
React.createElement('span', { className: 'ab-set-item-meta' }, t('set.judgeLang')),
|
|
@@ -1124,130 +1208,173 @@ window.__ModuleLoader__.load({
|
|
|
1124
1208
|
React.createElement('option', { value: 'zh' }, t('set.judgeLangZh')),
|
|
1125
1209
|
React.createElement('option', { value: 'en' }, t('set.judgeLangEn')),
|
|
1126
1210
|
),
|
|
1127
|
-
React.createElement('span', { className: 'ab-set-
|
|
1211
|
+
React.createElement('span', { className: 'ab-set-card-sub' }, t('set.judgeLangHint')),
|
|
1212
|
+
),
|
|
1213
|
+
React.createElement('div', { className: 'ab-set-item-fields' },
|
|
1214
|
+
React.createElement('div', { className: 'ab-set-row' },
|
|
1215
|
+
React.createElement('span', { className: 'ab-set-item-meta' }, t('set.judgePrompt')),
|
|
1216
|
+
(function () {
|
|
1217
|
+
const shipped = String(((snapshot.predefined && snapshot.predefined.judgePrompts) || {})[judgePromptLang] || '')
|
|
1218
|
+
const draft = String((judgePromptDrafts && judgePromptDrafts[judgePromptLang]) || '')
|
|
1219
|
+
const custom = draft.trim() && draft.trim() !== shipped.trim()
|
|
1220
|
+
return React.createElement('span', { className: 'ab-set-tag ' + (custom ? 'ab-set-tag-warn' : 'ab-set-tag-blue') }, custom ? t('set.judgePromptCustom') : t('set.judgePromptDefault'))
|
|
1221
|
+
})(),
|
|
1222
|
+
),
|
|
1223
|
+
React.createElement('p', { className: 'ab-set-card-sub' }, t('set.judgePromptHint')),
|
|
1224
|
+
React.createElement('textarea', {
|
|
1225
|
+
key: 'judge-prompt:' + judgePromptLang,
|
|
1226
|
+
className: 'ab-set-textarea ab-set-textarea-prompt',
|
|
1227
|
+
value: (judgePromptDrafts && judgePromptDrafts[judgePromptLang]) || '',
|
|
1228
|
+
spellCheck: false,
|
|
1229
|
+
rows: 12,
|
|
1230
|
+
onChange: function (e) {
|
|
1231
|
+
const v = e.target.value
|
|
1232
|
+
const lang = judgePromptLang
|
|
1233
|
+
setJudgePromptDrafts(function (prev) {
|
|
1234
|
+
const next = Object.assign({}, prev)
|
|
1235
|
+
next[lang] = v
|
|
1236
|
+
return next
|
|
1237
|
+
})
|
|
1238
|
+
},
|
|
1239
|
+
}),
|
|
1128
1240
|
),
|
|
1129
1241
|
React.createElement('div', { className: 'ab-set-row' },
|
|
1130
1242
|
React.createElement('span', { className: 'ab-set-item-meta' }, t('set.judgeTimeoutMs')),
|
|
1131
1243
|
React.createElement('input', { className: 'ab-set-input ab-set-input-num', type: 'number', min: 1000, value: timeoutMs, onChange: function (e) { setTimeoutMs(e.target.value) } }),
|
|
1244
|
+
),
|
|
1245
|
+
React.createElement('div', { className: 'ab-set-row ab-set-foot' },
|
|
1246
|
+
resetBtn('judgePrompt', t('set.resetJudgePrompt'), t('set.resetJudgePromptOk')),
|
|
1132
1247
|
React.createElement('button', {
|
|
1133
|
-
type: 'button', className: 'ab-set-btn', disabled: busy,
|
|
1134
|
-
onClick:
|
|
1248
|
+
type: 'button', className: 'ab-set-btn ab-set-btn-primary', disabled: busy,
|
|
1249
|
+
onClick: saveJudge,
|
|
1135
1250
|
}, t('set.save')),
|
|
1136
1251
|
),
|
|
1137
1252
|
),
|
|
1138
1253
|
|
|
1139
|
-
React.createElement('details', {
|
|
1140
|
-
className: 'ab-set-card ab-set-fold', 'data-ab-stage': 'qq',
|
|
1141
|
-
open: foldOpen.qq,
|
|
1142
|
-
onToggle: function (e) {
|
|
1143
|
-
const open = e.currentTarget.open
|
|
1144
|
-
setFoldOpen(function (prev) { return Object.assign({}, prev, { qq: open }) })
|
|
1145
|
-
},
|
|
1146
|
-
},
|
|
1147
|
-
React.createElement('summary', null,
|
|
1148
|
-
React.createElement('div', { className: 'ab-set-card-head' },
|
|
1149
|
-
React.createElement('div', { className: 'ab-set-card-title' },
|
|
1150
|
-
t('set.qqTitle'),
|
|
1151
|
-
React.createElement('span', { className: 'ab-set-fold-chev' }, '▾')),
|
|
1152
|
-
React.createElement('p', { className: 'ab-set-card-sub' }, t('set.qqSub'))),
|
|
1153
|
-
React.createElement('div', { className: 'ab-set-fold-sum' },
|
|
1154
|
-
(provisionLabel(t, qq.status) || t('set.qqUnknown')) + ' · ' + (notify.chatId ? t('set.qqChatOn') : t('set.qqChatOff')))),
|
|
1155
|
-
React.createElement('div', { className: 'ab-set-fold-body' },
|
|
1156
|
-
(provisioning && provisioning.error && !provisioningActive)
|
|
1157
|
-
? React.createElement('p', { className: 'ab-set-err' }, (provisionLabel(t, provisioningStatus) || t('set.qqScanFail')) + ':' + provisioning.error)
|
|
1158
|
-
: null,
|
|
1159
|
-
provisioningActive
|
|
1160
|
-
? React.createElement('div', null,
|
|
1161
|
-
React.createElement('p', { className: 'ab-set-card-sub' }, t('set.qqScanStatus', { status: provisionLabel(t, provisioningStatus) || provisioningStatus })),
|
|
1162
|
-
provisioning.qrDataUrl
|
|
1163
|
-
? React.createElement('div', { className: 'ab-set-qr' },
|
|
1164
|
-
React.createElement('img', { src: provisioning.qrDataUrl, alt: t('set.qqQrAlt') }),
|
|
1165
|
-
React.createElement('p', { className: 'ab-set-qr-tip' }, t('set.qqQrTip')),
|
|
1166
|
-
)
|
|
1167
|
-
: null,
|
|
1168
|
-
React.createElement('div', { className: 'ab-set-row' },
|
|
1169
|
-
React.createElement('button', {
|
|
1170
|
-
type: 'button', className: 'ab-set-btn', disabled: busy,
|
|
1171
|
-
onClick: function () { run('qq-cancel-provision', {}, t('set.qqCancelOk')) },
|
|
1172
|
-
}, t('set.qqCancelScan')),
|
|
1173
|
-
),
|
|
1174
|
-
)
|
|
1175
|
-
: React.createElement('div', { className: 'ab-set-row' },
|
|
1176
|
-
React.createElement('button', {
|
|
1177
|
-
type: 'button',
|
|
1178
|
-
className: 'ab-set-btn' + (qq.hasSecret ? '' : ' ab-set-btn-primary'),
|
|
1179
|
-
disabled: busy,
|
|
1180
|
-
onClick: function () { run('qq-provision', {}, t('set.qqScanStart')) },
|
|
1181
|
-
}, qq.hasSecret ? t('set.qqRescan') : t('set.qqScan')),
|
|
1182
|
-
provisioningStatus === '已连接'
|
|
1183
|
-
? React.createElement('span', { className: 'ab-set-ok' }, t('set.qqScanOk'))
|
|
1184
|
-
: null,
|
|
1185
|
-
),
|
|
1186
|
-
React.createElement('div', { className: 'ab-set-row' },
|
|
1187
|
-
React.createElement('input', { className: 'ab-set-input ab-set-input-grow', placeholder: t('set.qqAppId'), value: appId, onChange: function (e) { setAppId(e.target.value) } }),
|
|
1188
|
-
React.createElement('input', { className: 'ab-set-input ab-set-input-grow', type: 'password', placeholder: qq.hasSecret ? t('set.qqSecretKeep') : t('set.qqAppSecret'), value: appSecret, onChange: function (e) { setAppSecret(e.target.value) } }),
|
|
1189
|
-
React.createElement('button', { type: 'button', className: 'ab-set-btn' + (qq.hasSecret ? ' ab-set-btn-primary' : ''), disabled: busy, onClick: function () { run('save-qq-creds', { appId: appId, appSecret: appSecret }, t('set.qqCredsReconnect')) } }, t('set.qqSaveConnect')),
|
|
1190
|
-
React.createElement('button', { type: 'button', className: 'ab-set-btn', disabled: busy, onClick: function () { run('reconnect-qq', {}, t('set.qqReconnected')) } }, t('set.qqReconnect')),
|
|
1191
|
-
),
|
|
1192
|
-
React.createElement('div', { className: 'ab-set-row' },
|
|
1193
|
-
React.createElement('span', { className: 'ab-set-item-meta' }, t('set.qqPush', { state: notify.enabled !== false ? t('set.qqPushOn') : t('set.qqPushOff') })),
|
|
1194
|
-
React.createElement('button', {
|
|
1195
|
-
type: 'button', className: 'ab-set-btn', disabled: busy,
|
|
1196
|
-
onClick: function () { run('save-plugin', { notify: { enabled: notify.enabled === false } }, t('set.qqPushToggled')) },
|
|
1197
|
-
}, notify.enabled === false ? t('set.qqEnablePush') : t('set.qqPausePush')),
|
|
1198
|
-
React.createElement('span', { className: 'ab-set-item-meta' }, t('set.qqCurrentChat', { chatId: notify.chatId || t('set.qqUnset') })),
|
|
1199
|
-
),
|
|
1200
|
-
React.createElement('div', { className: 'ab-set-row' },
|
|
1201
|
-
React.createElement('input', {
|
|
1202
|
-
className: 'ab-set-input ab-set-input-grow', placeholder: t('set.qqChatPlaceholder'),
|
|
1203
|
-
value: chatIdInput, onChange: function (e) { setChatIdInput(e.target.value) },
|
|
1204
|
-
}),
|
|
1205
|
-
React.createElement('input', {
|
|
1206
|
-
className: 'ab-set-input', placeholder: t('set.qqGroupUser'),
|
|
1207
|
-
value: userIdInput, onChange: function (e) { setUserIdInput(e.target.value) },
|
|
1208
|
-
}),
|
|
1209
|
-
React.createElement('input', {
|
|
1210
|
-
className: 'ab-set-input ab-set-input-num', type: 'number', min: 30, placeholder: t('set.qqTimeoutS'),
|
|
1211
|
-
value: timeoutSecs, onChange: function (e) { setTimeoutSecs(e.target.value) },
|
|
1212
|
-
}),
|
|
1213
|
-
React.createElement('button', {
|
|
1214
|
-
type: 'button', className: 'ab-set-btn', disabled: busy,
|
|
1215
|
-
onClick: function () {
|
|
1216
|
-
run('save-plugin', {
|
|
1217
|
-
notify: {
|
|
1218
|
-
enabled: notify.enabled !== false,
|
|
1219
|
-
chatId: chatIdInput.trim(),
|
|
1220
|
-
userId: userIdInput.trim(),
|
|
1221
|
-
timeoutSecs: Number(timeoutSecs) || 120,
|
|
1222
|
-
},
|
|
1223
|
-
}, t('set.targetSaved'))
|
|
1224
|
-
},
|
|
1225
|
-
}, t('set.saveTarget')),
|
|
1226
|
-
),
|
|
1227
|
-
React.createElement('p', { className: 'ab-set-card-sub' }, t('set.recent')),
|
|
1228
|
-
(!qq.recentChats || qq.recentChats.length === 0)
|
|
1229
|
-
? React.createElement('p', { className: 'ab-set-empty' }, t('set.noMail'))
|
|
1230
|
-
: React.createElement('div', { className: 'ab-set-list' },
|
|
1231
|
-
qq.recentChats.map(function (c) {
|
|
1232
|
-
return React.createElement('div', {
|
|
1233
|
-
className: 'ab-set-item ab-chat', key: c.chatId + c.userId,
|
|
1234
|
-
onClick: function () { run('set-chat', { chatId: c.chatId, userId: c.isGroup ? c.userId : '' }, t('set.chatPicked')) },
|
|
1235
|
-
},
|
|
1236
|
-
React.createElement('span', { className: 'ab-set-item-label' },
|
|
1237
|
-
(c.isGroup ? t('set.group') + ' ' : t('set.c2c') + ' ') + (c.username || c.userId) + ' · ' + c.chatId),
|
|
1238
|
-
React.createElement('span', { className: 'ab-set-item-meta' }, c.preview || ''),
|
|
1239
|
-
)
|
|
1240
|
-
}),
|
|
1241
|
-
),
|
|
1242
|
-
),
|
|
1243
|
-
),
|
|
1244
|
-
|
|
1245
|
-
feedback
|
|
1246
|
-
? React.createElement('div', { className: feedback.ok ? 'ab-set-ok' : 'ab-set-err' }, feedback.msg)
|
|
1247
|
-
: null,
|
|
1248
1254
|
)
|
|
1249
1255
|
}
|
|
1250
1256
|
|
|
1257
|
+
const AUTO_PRESET_LABEL = '自动审批'
|
|
1258
|
+
const ACCESS_GLYPH_CLASS = 'ab-access-glyph'
|
|
1259
|
+
const SHIELD_OUTLINE = 'M8.20554 0.899994L14.7901 3.36857V7.01026C14.7901 12 11.0466 14.2103 8.20554 15.3C5.36446 14.2103 1.62012 12 1.62012 7.01026V3.36857L8.20554 0.899994Z'
|
|
1260
|
+
const LETTER_A = 'M8.205 4.95 11.62 11.45H10.1l-.55-1.42H6.655l-.55 1.42H4.59L8.205 4.95Zm-.72 3.68h1.44L8.205 6.35 7.485 8.63Z'
|
|
1261
|
+
|
|
1262
|
+
function accessGlyphSvg() {
|
|
1263
|
+
const ns = 'http://www.w3.org/2000/svg'
|
|
1264
|
+
const svg = document.createElementNS(ns, 'svg')
|
|
1265
|
+
svg.setAttribute('width', '16')
|
|
1266
|
+
svg.setAttribute('height', '16')
|
|
1267
|
+
svg.setAttribute('viewBox', '0 0 16 16')
|
|
1268
|
+
svg.setAttribute('fill', 'none')
|
|
1269
|
+
svg.setAttribute('aria-hidden', 'true')
|
|
1270
|
+
const shield = document.createElementNS(ns, 'path')
|
|
1271
|
+
shield.setAttribute('d', SHIELD_OUTLINE)
|
|
1272
|
+
shield.setAttribute('stroke', 'currentColor')
|
|
1273
|
+
shield.setAttribute('stroke-width', '1.31831')
|
|
1274
|
+
shield.setAttribute('stroke-linejoin', 'round')
|
|
1275
|
+
const letter = document.createElementNS(ns, 'path')
|
|
1276
|
+
letter.setAttribute('d', LETTER_A)
|
|
1277
|
+
letter.setAttribute('fill', 'currentColor')
|
|
1278
|
+
letter.setAttribute('fill-rule', 'evenodd')
|
|
1279
|
+
svg.appendChild(shield)
|
|
1280
|
+
svg.appendChild(letter)
|
|
1281
|
+
return svg
|
|
1282
|
+
}
|
|
1283
|
+
|
|
1284
|
+
function isAccessTrigger(el) {
|
|
1285
|
+
if (!el || el.tagName !== 'BUTTON') return false
|
|
1286
|
+
const label = el.getAttribute('aria-label') || ''
|
|
1287
|
+
if (label.indexOf(AUTO_PRESET_LABEL) === -1) return false
|
|
1288
|
+
return label.indexOf('访问模式') === 0 || label.indexOf('Access mode') === 0
|
|
1289
|
+
}
|
|
1290
|
+
|
|
1291
|
+
function isAccessMenuItem(el) {
|
|
1292
|
+
if (!el || typeof el.getAttribute !== 'function') return false
|
|
1293
|
+
if (el.getAttribute('role') !== 'menuitem') return false
|
|
1294
|
+
return String(el.textContent || '').replace(/\s+/g, '') === AUTO_PRESET_LABEL
|
|
1295
|
+
}
|
|
1296
|
+
|
|
1297
|
+
function decorateAccess(el) {
|
|
1298
|
+
if (!el || el.querySelector('.' + ACCESS_GLYPH_CLASS)) return
|
|
1299
|
+
const span = document.createElement('span')
|
|
1300
|
+
span.className = ACCESS_GLYPH_CLASS
|
|
1301
|
+
span.setAttribute('aria-hidden', 'true')
|
|
1302
|
+
span.appendChild(accessGlyphSvg())
|
|
1303
|
+
el.insertBefore(span, el.firstChild)
|
|
1304
|
+
}
|
|
1305
|
+
|
|
1306
|
+
/** 不再匹配的元素要摘掉徽标:切走「自动审批」后按钮是同一个 DOM 节点。 */
|
|
1307
|
+
function undecorateAccess(el) {
|
|
1308
|
+
if (!el || typeof el.querySelector !== 'function') return
|
|
1309
|
+
const glyph = el.querySelector('.' + ACCESS_GLYPH_CLASS)
|
|
1310
|
+
if (glyph && glyph.parentNode) glyph.parentNode.removeChild(glyph)
|
|
1311
|
+
}
|
|
1312
|
+
|
|
1313
|
+
function matchesAccess(el) {
|
|
1314
|
+
return isAccessTrigger(el) || isAccessMenuItem(el)
|
|
1315
|
+
}
|
|
1316
|
+
|
|
1317
|
+
function scanAccessGlyphs(root) {
|
|
1318
|
+
// 只处理元素/文档:MutationObserver 的 addedNodes 含文本节点,
|
|
1319
|
+
// 落到 document 兜底会让每次文本变化都做整篇 querySelectorAll。
|
|
1320
|
+
if (!root || (root.nodeType !== 1 && root.nodeType !== 9) || typeof root.querySelectorAll !== 'function') return
|
|
1321
|
+
const scope = root
|
|
1322
|
+
const buttons = scope.querySelectorAll('button[aria-label]')
|
|
1323
|
+
for (let i = 0; i < buttons.length; i++) {
|
|
1324
|
+
if (isAccessTrigger(buttons[i])) decorateAccess(buttons[i])
|
|
1325
|
+
}
|
|
1326
|
+
const items = scope.querySelectorAll('[role="menuitem"]')
|
|
1327
|
+
for (let i = 0; i < items.length; i++) {
|
|
1328
|
+
if (isAccessMenuItem(items[i])) decorateAccess(items[i])
|
|
1329
|
+
}
|
|
1330
|
+
// 反向清理:已经不匹配的元素(换预设 / 菜单关闭后 role 变化)必须摘掉徽标。
|
|
1331
|
+
const glyphs = scope.querySelectorAll('.' + ACCESS_GLYPH_CLASS)
|
|
1332
|
+
for (let i = 0; i < glyphs.length; i++) {
|
|
1333
|
+
const parent = glyphs[i].parentNode
|
|
1334
|
+
if (parent && !matchesAccess(parent)) undecorateAccess(parent)
|
|
1335
|
+
}
|
|
1336
|
+
if (root.nodeType === 1) {
|
|
1337
|
+
if (matchesAccess(root)) decorateAccess(root)
|
|
1338
|
+
else undecorateAccess(root)
|
|
1339
|
+
}
|
|
1340
|
+
}
|
|
1341
|
+
|
|
1342
|
+
function mountAccessGlyphs() {
|
|
1343
|
+
scanAccessGlyphs(document)
|
|
1344
|
+
const obs = new MutationObserver(function (records) {
|
|
1345
|
+
for (let i = 0; i < records.length; i++) {
|
|
1346
|
+
const rec = records[i]
|
|
1347
|
+
if (rec.type === 'attributes' && rec.target) {
|
|
1348
|
+
scanAccessGlyphs(rec.target)
|
|
1349
|
+
continue
|
|
1350
|
+
}
|
|
1351
|
+
// childList:React 先插入空按钮、再把标签塞进去时,那条记录的 target 是标签自己,
|
|
1352
|
+
// 只看 addedNodes 会让下拉里的「自动审批」永远拿不到徽标。这里只沿 target 向上找
|
|
1353
|
+
// 最近的触发器/menu 项重扫(不是扫整棵 target 子树,避免流式输出时每次变更都全量子树查询)。
|
|
1354
|
+
if (rec.target && rec.target.nodeType === 1 && typeof rec.target.closest === 'function') {
|
|
1355
|
+
const owner = rec.target.closest('button[aria-label], [role="menuitem"]')
|
|
1356
|
+
if (owner) scanAccessGlyphs(owner)
|
|
1357
|
+
}
|
|
1358
|
+
const nodes = rec.addedNodes
|
|
1359
|
+
for (let j = 0; j < nodes.length; j++) scanAccessGlyphs(nodes[j])
|
|
1360
|
+
}
|
|
1361
|
+
})
|
|
1362
|
+
obs.observe(document.body, {
|
|
1363
|
+
childList: true,
|
|
1364
|
+
subtree: true,
|
|
1365
|
+
attributes: true,
|
|
1366
|
+
attributeFilter: ['aria-label'],
|
|
1367
|
+
})
|
|
1368
|
+
return function () {
|
|
1369
|
+
obs.disconnect()
|
|
1370
|
+
const leftover = document.querySelectorAll('.' + ACCESS_GLYPH_CLASS)
|
|
1371
|
+
for (let i = 0; i < leftover.length; i++) {
|
|
1372
|
+
const n = leftover[i]
|
|
1373
|
+
if (n.parentNode) n.parentNode.removeChild(n)
|
|
1374
|
+
}
|
|
1375
|
+
}
|
|
1376
|
+
}
|
|
1377
|
+
|
|
1251
1378
|
const plugin = {
|
|
1252
1379
|
inject: ['connection', 'slots', 'locale'],
|
|
1253
1380
|
async apply(ctx) {
|
|
@@ -1273,11 +1400,19 @@ window.__ModuleLoader__.load({
|
|
|
1273
1400
|
}
|
|
1274
1401
|
}
|
|
1275
1402
|
})
|
|
1403
|
+
ctx.effect(function () {
|
|
1404
|
+
try {
|
|
1405
|
+
return mountAccessGlyphs()
|
|
1406
|
+
} catch (e) {
|
|
1407
|
+
console.error('[dsh-auto-approve] 访问模式图标失败:' + String((e && e.message) || e))
|
|
1408
|
+
return function () {}
|
|
1409
|
+
}
|
|
1410
|
+
})
|
|
1276
1411
|
|
|
1277
1412
|
slots.inject('conversation.input.dock', function () {
|
|
1278
1413
|
return slots.register(
|
|
1279
1414
|
{ name: 'conversation.input.dock', id: 'dsh-auto-approve.notice', order: 30, locale: NS, label: function () { return t('slot.notice') } },
|
|
1280
|
-
function (props) { return React.createElement(NoticeStrip, { slotsProps: props, rpc: rpc }) },
|
|
1415
|
+
function (props) { return React.createElement(NoticeStrip, { slotsProps: props, rpc: rpc, t: t }) },
|
|
1281
1416
|
)
|
|
1282
1417
|
})
|
|
1283
1418
|
|
|
@@ -1291,14 +1426,14 @@ window.__ModuleLoader__.load({
|
|
|
1291
1426
|
label: function () { return t('slot.history') },
|
|
1292
1427
|
inject: (sessionId) => ({ sessionId }),
|
|
1293
1428
|
},
|
|
1294
|
-
function (props) { return React.createElement(HistoryView, { slotsProps: props, rpc: rpc }) },
|
|
1429
|
+
function (props) { return React.createElement(HistoryView, { slotsProps: props, rpc: rpc, t: t }) },
|
|
1295
1430
|
)
|
|
1296
1431
|
})
|
|
1297
1432
|
|
|
1298
1433
|
slots.inject('settings.section', function () {
|
|
1299
1434
|
return slots.register(
|
|
1300
1435
|
{ name: 'settings.section', id: 'dsh-auto-approve.settings', order: 55, locale: NS, label: function () { return t('slot.settings') } },
|
|
1301
|
-
function (props) { return React.createElement(SettingsPage, { slotsProps: props, rpc: rpc }) },
|
|
1436
|
+
function (props) { return React.createElement(SettingsPage, { slotsProps: props, rpc: rpc, t: t }) },
|
|
1302
1437
|
)
|
|
1303
1438
|
})
|
|
1304
1439
|
},
|