@dnalec/dsh-auto-approve 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +152 -0
- package/README.zh.md +152 -0
- package/client.js +1312 -0
- package/cordis.patch.yml +5 -0
- package/locales.mjs +398 -0
- package/package.json +65 -0
- package/src/index.mjs +1143 -0
- package/src/preset-patch.mjs +164 -0
- package/src/provisioning.mjs +131 -0
- package/src/qqbot.mjs +313 -0
- package/src/rules.mjs +900 -0
- package/src/tickets.mjs +277 -0
- package/src/util.mjs +242 -0
package/client.js
ADDED
|
@@ -0,0 +1,1312 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dsh-auto-approve — 浏览器半:提示条、审批时间线、设置页。
|
|
3
|
+
*
|
|
4
|
+
* 纯 JS:无 TS / JSX / import。React 用 createElement。
|
|
5
|
+
* 网页文案:locales.mjs 的 zh/en,apply 时 ctx.locale.register。
|
|
6
|
+
* Host API 走 connection.rpc.call('/api', 'dsh-auto-approve', …)(已鉴权),不用裸 HTTP。
|
|
7
|
+
* 设置页折叠状态用 React state 控 details,避免 snapshot 刷新后合上。
|
|
8
|
+
* 恢复默认:第一次点武装,5 秒内再点才执行。
|
|
9
|
+
*/
|
|
10
|
+
window.__ModuleLoader__.load({
|
|
11
|
+
id: 'dsh-auto-approve',
|
|
12
|
+
factory: (require) => {
|
|
13
|
+
var module = { exports: {} }
|
|
14
|
+
var exports = module.exports
|
|
15
|
+
const React = require('react')
|
|
16
|
+
|
|
17
|
+
const CSS = `
|
|
18
|
+
.ab-notice{box-sizing:border-box;width:calc(100% - var(--dsh-composer-side-clearance) - var(--dsh-composer-side-clearance) - var(--dsh-composer-dock-inset) - var(--dsh-composer-dock-inset));max-width:calc(var(--dsh-composer-card-max-width) - var(--dsh-composer-dock-inset) - var(--dsh-composer-dock-inset));margin:0 auto;flex:none}
|
|
19
|
+
.ab-notice-card{box-sizing:border-box;display:flex;align-items:center;gap:10px;border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-layer-1);border-radius:12px;padding:6px 10px 6px 12px;box-shadow:var(--dsw-shadow-lv1)}
|
|
20
|
+
.ab-notice-card-pending{border-color:var(--dsw-alias-state-warn-primary);background:var(--dsw-alias-state-warn-tertiary)}
|
|
21
|
+
.ab-notice-card-manual{border-color:var(--dsw-alias-state-warn-primary)}
|
|
22
|
+
.ab-notice-card-err{border-color:var(--dsw-alias-state-error-primary)}
|
|
23
|
+
.ab-notice-glyph{color:var(--dsw-alias-state-success-primary);flex:none;display:inline-flex;align-items:center;justify-content:center}
|
|
24
|
+
.ab-notice-glyph-warn{color:var(--dsw-alias-state-warn-label);flex:none;display:inline-flex;align-items:center;justify-content:center;font-size:13px;line-height:16px;width:16px;height:16px}
|
|
25
|
+
.ab-notice-glyph-err{color:var(--dsw-alias-state-error-primary);flex:none;display:inline-flex;align-items:center;justify-content:center;font-size:13px;line-height:16px;width:16px;height:16px}
|
|
26
|
+
.ab-notice-body{flex:1 1 auto;min-width:0;display:flex;flex-direction:column;gap:1px}
|
|
27
|
+
.ab-notice-head{display:flex;align-items:center;gap:8px;min-width:0}
|
|
28
|
+
.ab-notice-tool{flex:none;color:var(--dsw-alias-label-primary);font:500 12px/18px var(--ds-font-family-code)}
|
|
29
|
+
.ab-notice-text{flex:1 1 auto;min-width:0;color:var(--dsw-alias-label-secondary);font-size:12px;line-height:18px;text-overflow:ellipsis;white-space:nowrap;overflow:hidden}
|
|
30
|
+
.ab-notice-meta{display:flex;align-items:center;gap:8px}
|
|
31
|
+
.ab-tag{box-sizing:border-box;flex:none;height:18px;color:var(--dsw-alias-state-success-primary);background:var(--dsw-alias-state-success-tertiary);border-radius:9px;align-items:center;padding:0 8px;font-size:11px;line-height:18px;display:inline-flex;letter-spacing:.02em}
|
|
32
|
+
.ab-tag-neutral{color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-bg-module-platform)}
|
|
33
|
+
.ab-tag-warn{box-sizing:border-box;flex:none;height:18px;color:var(--dsw-alias-state-warn-label);background:var(--dsw-alias-state-warn-tertiary);border-radius:9px;align-items:center;padding:0 8px;font-size:11px;line-height:18px;display:inline-flex}
|
|
34
|
+
.ab-tag-err{box-sizing:border-box;flex:none;height:18px;color:var(--dsw-alias-state-error-primary);background:var(--dsw-alias-interactive-bg-hover-danger);border-radius:9px;align-items:center;padding:0 8px;font-size:11px;line-height:18px;display:inline-flex}
|
|
35
|
+
.ab-time{flex:none;color:var(--dsw-alias-label-caption);font-size:11px;line-height:16px;font-variant-numeric:tabular-nums}
|
|
36
|
+
.ab-notice-close{width:24px;height:24px;flex:none;color:var(--dsw-alias-label-tertiary);cursor:pointer;background:transparent;border:none;border-radius:999px;display:inline-flex;align-items:center;justify-content:center;padding:0}
|
|
37
|
+
.ab-notice-close:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-secondary)}
|
|
38
|
+
.ab-view{box-sizing:border-box;height:100%;min-height:0;display:flex;flex-direction:column;background:var(--dsw-alias-bg-layer-1);position:relative}
|
|
39
|
+
.ab-view-head{box-sizing:border-box;flex:none;border-bottom:1px solid var(--dsw-alias-border-l2);padding:10px 14px 8px;display:flex;flex-direction:column;gap:2px}
|
|
40
|
+
.ab-view-title{color:var(--dsw-alias-label-primary);font-size:14px;font-weight:500;line-height:22px}
|
|
41
|
+
.ab-view-sub{color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px}
|
|
42
|
+
.ab-list{flex:1 1 auto;min-height:0;overflow-y:auto;padding:8px 14px 48px;display:flex;flex-direction:column;gap:2px}
|
|
43
|
+
.ab-row{box-sizing:border-box;display:flex;gap:10px;padding:9px 10px;border-radius:10px;width:100%;text-align:left;background:transparent;font:inherit;color:inherit}
|
|
44
|
+
.ab-row-open{background:var(--dsw-alias-interactive-bg-hover)}
|
|
45
|
+
.ab-row-rail{flex:none;display:flex;flex-direction:column;align-items:center;gap:4px;padding-top:2px}
|
|
46
|
+
.ab-row-line{flex:none;width:1px;background:var(--dsw-alias-border-l1);min-height:10px}
|
|
47
|
+
.ab-row:last-child .ab-row-line{display:none}
|
|
48
|
+
.ab-row-body{flex:1 1 auto;min-width:0;display:flex;flex-direction:column;gap:4px}
|
|
49
|
+
.ab-row-head{box-sizing:border-box;margin:0;padding:0;border:none;background:transparent;cursor:pointer;display:flex;flex-direction:column;gap:4px;width:100%;text-align:left;font:inherit;color:inherit}
|
|
50
|
+
.ab-row-head:hover .ab-row-tool{color:var(--dsw-alias-state-business-primary)}
|
|
51
|
+
.ab-row-top{display:flex;align-items:center;gap:8px;min-width:0}
|
|
52
|
+
.ab-row-tool{flex:none;color:var(--dsw-alias-label-primary);font:500 12px/18px var(--ds-font-family-code)}
|
|
53
|
+
.ab-row-chev{flex:none;margin-left:auto;color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:18px}
|
|
54
|
+
.ab-row-open .ab-row-chev{transform:rotate(180deg)}
|
|
55
|
+
.ab-row-reason{flex:1 1 auto;min-width:0;color:var(--dsw-alias-label-secondary);font-size:12px;line-height:18px;word-break:break-all;white-space:nowrap;text-overflow:ellipsis;overflow:hidden}
|
|
56
|
+
.ab-row-open .ab-row-reason{display:none}
|
|
57
|
+
.ab-details{flex-direction:column;gap:10px;padding:8px 0 2px;display:flex}
|
|
58
|
+
.ab-detail-sec{display:flex;flex-direction:column;gap:6px}
|
|
59
|
+
.ab-detail-h{color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:16px;letter-spacing:.04em}
|
|
60
|
+
.ab-detail-grid{display:grid;grid-template-columns:72px minmax(0,1fr);gap:4px 10px;align-items:start}
|
|
61
|
+
.ab-detail-k{color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:18px;padding-top:1px}
|
|
62
|
+
.ab-detail-v{margin:0;color:var(--dsw-alias-label-primary);font:12px/18px var(--ds-font-family-code);white-space:pre-wrap;word-break:break-all}
|
|
63
|
+
.ab-detail-v-box{background:var(--dsw-alias-bg-layer-2,var(--dsw-alias-bg-module-platform));border-radius:8px;padding:6px 8px;max-height:180px;overflow:auto}
|
|
64
|
+
.ab-empty,.ab-loading{flex:1 1 auto;color:var(--dsw-alias-label-tertiary);display:flex;align-items:center;justify-content:center;font-size:13px;line-height:20px;padding:24px}
|
|
65
|
+
.ab-set{box-sizing:border-box;width:100%;max-width:720px;padding:0 0 28px;color:var(--dsw-alias-label-primary);flex-direction:column;gap:12px;display:flex}
|
|
66
|
+
.ab-set-title{color:var(--dsw-alias-label-primary);margin:0;font-size:16px;font-weight:500;line-height:24px}
|
|
67
|
+
.ab-set-intro{color:var(--dsw-alias-label-tertiary);margin:0;font-size:14px;line-height:22px}
|
|
68
|
+
.ab-set-card{border:1px solid var(--dsw-alias-border-l2);border-radius:12px;flex-direction:column;gap:12px;padding:12px 14px;display:flex}
|
|
69
|
+
.ab-set-fold{padding:0}
|
|
70
|
+
.ab-set-fold>summary{list-style:none;cursor:pointer;display:flex;align-items:flex-start;justify-content:space-between;gap:12px;padding:12px 14px}
|
|
71
|
+
.ab-set-fold>summary::-webkit-details-marker{display:none}
|
|
72
|
+
.ab-set-fold>summary:hover{background:var(--dsw-alias-interactive-bg-hover);border-radius:12px}
|
|
73
|
+
.ab-set-fold[open]>summary{border-bottom:1px solid var(--dsw-alias-border-l2);border-radius:12px 12px 0 0}
|
|
74
|
+
.ab-set-fold-body{padding:12px 14px 14px;display:flex;flex-direction:column;gap:12px}
|
|
75
|
+
.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
|
+
.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-card-head{flex-direction:column;gap:4px;display:flex}
|
|
79
|
+
.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
|
+
.ab-set-card-sub{color:var(--dsw-alias-label-tertiary);margin:0;font-size:13px;line-height:20px}
|
|
81
|
+
.ab-set-note{color:var(--dsw-alias-state-warn-label);margin:0;font-size:13px;line-height:20px}
|
|
82
|
+
.ab-set-ok{color:var(--dsw-alias-state-success-primary);margin:0;font-size:13px;line-height:20px}
|
|
83
|
+
.ab-set-err{color:var(--dsw-alias-state-error-primary);margin:0;font-size:13px;line-height:20px}
|
|
84
|
+
.ab-set-row{flex-wrap:wrap;gap:8px;display:flex;align-items:center}
|
|
85
|
+
.ab-set-row-choices{align-items:stretch;flex-wrap:nowrap}
|
|
86
|
+
.ab-set-input,.ab-set-select{box-sizing:border-box;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);height:32px;min-width:0;color:var(--dsw-alias-label-primary);font:inherit;border-radius:8px;outline:none;padding:0 10px;font-size:13px;line-height:20px;font-family:var(--ds-font-family-code)}
|
|
87
|
+
.ab-set-input:focus,.ab-set-select:focus{border-color:var(--dsw-alias-state-business-primary)}
|
|
88
|
+
.ab-set-input-num{width:96px}
|
|
89
|
+
.ab-set-input-grow{flex:1 1 180px}
|
|
90
|
+
.ab-set-btn{box-sizing:border-box;height:32px;color:var(--dsw-alias-label-primary);cursor:pointer;font:inherit;border:1px solid var(--dsw-alias-border-l2);background:transparent;border-radius:16px;justify-content:center;align-items:center;padding:0 14px;font-size:13px;line-height:20px;display:inline-flex;white-space:nowrap}
|
|
91
|
+
.ab-set-btn:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}
|
|
92
|
+
.ab-set-btn:disabled{opacity:.4;cursor:default}
|
|
93
|
+
.ab-set-btn-primary{border:none;background:var(--dsw-alias-button-primary-fill);color:var(--dsw-alias-label-primary-foreground)}
|
|
94
|
+
.ab-set-btn-danger{color:var(--dsw-alias-label-primary-foreground);border:none;background:var(--dsw-alias-state-error-primary)}
|
|
95
|
+
.ab-set-list{flex-direction:column;gap:4px;display:flex}
|
|
96
|
+
.ab-set-item{border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-layer-1);border-radius:8px;align-items:center;gap:8px;padding:6px 10px;min-width:0;display:flex}
|
|
97
|
+
.ab-set-item-kw{padding:4px 8px}
|
|
98
|
+
.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
|
+
.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
|
+
.ab-set-textarea:focus{border-color:var(--dsw-alias-state-business-primary)}
|
|
101
|
+
.ab-set-input-ui{font-family:inherit;width:100%}
|
|
102
|
+
.ab-set-foot{justify-content:flex-end}
|
|
103
|
+
.ab-set-list-crit{gap:8px}
|
|
104
|
+
.ab-set-item-crit{flex-direction:column;align-items:stretch;gap:8px;padding:10px 12px}
|
|
105
|
+
.ab-set-crit-top{display:flex;align-items:center;gap:8px;min-width:0}
|
|
106
|
+
.ab-set-item-fields{min-width:0;flex-direction:column;gap:6px;display:flex}
|
|
107
|
+
.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
|
+
.ab-set-item-meta{flex:none;color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:16px;white-space:nowrap}
|
|
110
|
+
.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
|
+
.ab-set-item-del:hover{background:var(--dsw-alias-interactive-bg-hover-danger);color:var(--dsw-alias-state-error-primary)}
|
|
112
|
+
.ab-set-empty{color:var(--dsw-alias-label-caption);margin:0;font-size:13px;line-height:20px;padding:4px 2px}
|
|
113
|
+
.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
|
+
.ab-set-tag-blue{color:var(--dsw-alias-state-business-primary);background:var(--dsw-alias-state-business-tertiary)}
|
|
115
|
+
.ab-set-tag-green{color:var(--dsw-alias-state-success-primary);background:var(--dsw-alias-state-success-tertiary)}
|
|
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)}
|
|
118
|
+
.ab-set-steps{display:flex;flex-wrap:wrap;gap:6px}
|
|
119
|
+
.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
|
+
.ab-set-step:hover{background:var(--dsw-alias-interactive-bg-hover)}
|
|
121
|
+
.ab-set-step:focus{outline:none;border-color:var(--dsw-alias-state-business-primary)}
|
|
122
|
+
.ab-set-step-k{color:var(--dsw-alias-label-tertiary);align-items:center;gap:6px;font-size:11px;line-height:16px;display:flex}
|
|
123
|
+
.ab-set-step-v{color:var(--dsw-alias-label-primary);font-size:13px;font-weight:500;line-height:18px;word-break:break-all}
|
|
124
|
+
.ab-set-invs{flex-wrap:wrap;gap:6px;display:flex}
|
|
125
|
+
.ab-set-inv{box-sizing:border-box;border-radius:8px;padding:6px 10px;font-size:12px;line-height:18px;display:inline-flex;align-items:center;max-width:100%}
|
|
126
|
+
.ab-set-inv-hard{color:var(--dsw-alias-state-error-primary);background:var(--dsw-alias-interactive-bg-hover-danger)}
|
|
127
|
+
.ab-set-inv-ok{color:var(--dsw-alias-state-success-primary);background:var(--dsw-alias-state-success-tertiary)}
|
|
128
|
+
.ab-set-inv-mode{color:var(--dsw-alias-state-warn-label);background:var(--dsw-alias-state-warn-tertiary)}
|
|
129
|
+
.ab-set-choice{box-sizing:border-box;margin:0;flex:1 1 0;min-width:0;border:1px solid var(--dsw-alias-border-l2);background:transparent;border-radius:10px;padding:10px 12px;cursor:pointer;text-align:left;font:inherit;color:inherit;display:flex;flex-direction:column;gap:4px}
|
|
130
|
+
.ab-set-choice:hover{background:var(--dsw-alias-interactive-bg-hover)}
|
|
131
|
+
.ab-set-choice-on{border-color:var(--dsw-alias-state-business-primary);background:var(--dsw-alias-state-business-tertiary)}
|
|
132
|
+
.ab-set-choice-t{color:var(--dsw-alias-label-primary);font-size:13px;font-weight:500;line-height:20px}
|
|
133
|
+
.ab-set-choice-d{color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px}
|
|
134
|
+
.ab-set-learn-info{flex:1 1 auto;min-width:0;flex-direction:column;gap:2px;display:flex}
|
|
135
|
+
.ab-set-learn-sub{color:var(--dsw-alias-label-tertiary);font-size:11px}
|
|
136
|
+
.ab-chat{cursor:pointer}
|
|
137
|
+
.ab-chat:hover{background:var(--dsw-alias-interactive-bg-hover)}
|
|
138
|
+
.ab-set-qr{text-align:center;padding:4px 0 8px}
|
|
139
|
+
.ab-set-qr img{box-sizing:border-box;border-radius:8px;background:#fff;padding:8px;max-width:240px;width:100%;height:auto}
|
|
140
|
+
.ab-set-qr-tip{color:var(--dsw-alias-label-tertiary);margin:6px 0 0;font-size:12px;line-height:18px}
|
|
141
|
+
`
|
|
142
|
+
|
|
143
|
+
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":"等待人工审批{ticket}:{preview}","notice.pendingTag":"人工审批中{ticket}","notice.qqApproved":"已在 QQ 批准{ticket}:{preview}","notice.webApproved":"人工审批通过{ticket}:{preview}","notice.qqApprovedTag":"已在 QQ 批准{ticket}","notice.webApprovedTag":"人工审批通过","notice.rejectedTitle":"已拒绝{ticket}:{preview}","notice.rejectedTag":"已拒绝{ticket}","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":"待处理 {ticket}","history.tagQqAllow":"QQ 批准 {ticket}","history.tagAllow":"批准 {ticket}","history.tagReject":"拒绝 {ticket}","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.cwd":"工作目录","detail.sandbox":"沙箱","detail.justification":"模型理由","detail.pipe":"管道","detail.keyword":"关键词","detail.judgeModel":"审核模型","detail.judgeCategory":"审核类别","detail.judgeAction":"执行动作","detail.judgeReason":"审核理由","detail.judgeError":"审核失败","detail.judgeRaw":"审核结果原文","detail.ticket":"短号","detail.source":"来源","detail.judgeFailed":"未完成(解析失败/超时转人工,不执行「其他」的动作)","set.title":"自动审批","set.intro":"需要审批的行为会自动审批,并可推送到已配置的消息平台。按私人 bot 设计:未绑定聊天时,私聊回「是」会绑成审批目标,不要让陌生人能私聊这个 bot。","set.allowlistCorrupt":"规则文件损坏,当前是内存默认,普通保存不会覆盖磁盘。请修好 allowlist.json,或点「恢复默认」写回出厂规则。","set.pluginCorrupt":"插件配置损坏,拒绝保存以免清空 chatId。请修好 ~/.dsh/approval-bridge/config.json,或","set.pluginCorruptOverwrite":"覆盖损坏配置","set.qqCorrupt":"QQ 凭据文件损坏,未覆盖磁盘。请修好 ~/.dsh/approval-bridge/qqbot.json,或重新保存凭据 / 扫码。","set.loading":"加载中…","set.loadFailed":"加载失败:{error}","set.resetConfirm":"再点一次确认恢复","set.presetMissingTitle":"权限预设","set.presetMissingSub":"插件启动时会写入 auto-approve。当前未检测到,可手动补写。","set.presetWrite":"写入权限预设","set.presetWriteNote":"写入后需重启 dsh web","set.presetWrote":"已写入,请重启 dsh web","set.modeTitle":"自动审批模式","set.modeWs":"workspace-write(推荐)","set.modeWsHint":"工作区内直接放行,越出工作区需审核。","set.modeRo":"read-only","set.modeRoHint":"工作区内写操作也需审核。","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.qqTitle":"QQ 审批通道","set.qqSub":"扫码或填凭据。按私人 bot 设计:未绑定 chatId 时,私聊回「是」会绑成审批聊天。不要让陌生人能私聊这个 bot。群必须 @机器人并另配 userId。","set.qqScan":"扫码接入机器人","set.qqRescan":"重新扫码接入","set.qqCancelScan":"取消扫码","set.qqCancelOk":"已取消扫码","set.qqScanStart":"已开始扫码,请用手机 QQ 扫描","set.qqScanOk":"扫码成功,凭据已保存","set.qqScanFail":"扫码失败","set.qqScanStatus":"扫码接入:{status}","set.qqQrAlt":"QQ 机器人扫码二维码","set.qqQrTip":"请用手机 QQ 扫码并确认创建机器人(约 5 分钟有效)","set.qqAppId":"AppID","set.qqAppSecret":"AppSecret","set.qqSecretKeep":"AppSecret(留空则保留)","set.qqSaveConnect":"保存并连接","set.qqCredsReconnect":"凭据已保存并重连","set.qqReconnect":"重连","set.qqReconnected":"正在重连","set.qqUnknown":"未知","set.qqChatOn":"已选定聊天","set.qqChatOff":"未选定","set.qqPush":"推送:{state}","set.qqPushOn":"开","set.qqPushOff":"关","set.qqPushToggled":"已切换推送","set.qqEnablePush":"启用推送","set.qqPausePush":"暂停推送","set.qqCurrentChat":"当前 chatId:{chatId}","set.qqUnset":"(未选定)","set.qqChatPlaceholder":"chatId(私聊 openid / 群 g:openid)","set.qqGroupUser":"群 userId","set.qqTimeoutS":"超时s","set.prov.loggingIn":"登录中","set.prov.waitScan":"等待扫码","set.prov.saving":"保存凭据","set.prov.connected":"已连接","set.chatId":"chatId","set.userId":"userId(群)","set.notifyTimeout":"提醒间隔(秒)","set.saveTarget":"保存目标/超时","set.targetSaved":"推送目标已保存","set.recent":"最近来信(点选设为审批聊天;群必须 @机器人,还需匹配 userId)","set.noMail":"还没有来信。加好友后给机器人发任意一句。","set.group":"群","set.c2c":"私聊","set.chatPicked":"已选定审批聊天","set.overwriteOk":"已覆盖损坏的插件配置","rpc.unavailable":"connection.rpc 不可用","rpc.failed":"RPC 失败"}`)
|
|
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"}`)
|
|
146
|
+
|
|
147
|
+
function fillLocale(template, params) {
|
|
148
|
+
if (!params) return template
|
|
149
|
+
return String(template).replace(/\{(\w+)\}/g, function (m, name) {
|
|
150
|
+
return Object.prototype.hasOwnProperty.call(params, name) ? String(params[name]) : m
|
|
151
|
+
})
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function tFrom(props) {
|
|
155
|
+
const sp = (props && props.slotsProps) || {}
|
|
156
|
+
if (typeof sp.t === 'function') return sp.t
|
|
157
|
+
return function (key, params) {
|
|
158
|
+
return fillLocale(ZH[key] || key, params)
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function verdictLabel(t, verdict) {
|
|
163
|
+
if (!verdict) return t('notice.autoDefault')
|
|
164
|
+
const key = 'verdict.' + verdict
|
|
165
|
+
const v = t(key)
|
|
166
|
+
return v === key ? String(verdict) : v
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function pathLabel(t, path) {
|
|
170
|
+
if (!path) return ''
|
|
171
|
+
const key = 'path.' + path
|
|
172
|
+
const v = t(key)
|
|
173
|
+
return v === key ? String(path) : v
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function actionLabel(t, action) {
|
|
177
|
+
if (!action) return ''
|
|
178
|
+
const key = 'action.' + action
|
|
179
|
+
const v = t(key)
|
|
180
|
+
return v === key ? String(action) : v
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function provisionLabel(t, status) {
|
|
184
|
+
const map = {
|
|
185
|
+
'登录中': 'set.prov.loggingIn',
|
|
186
|
+
'等待扫码': 'set.prov.waitScan',
|
|
187
|
+
'保存凭据': 'set.prov.saving',
|
|
188
|
+
'已连接': 'set.prov.connected',
|
|
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 || '')
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function countsLabel(t, reject, human, allow) {
|
|
199
|
+
return t('set.counts', { reject: String(reject), human: String(human), allow: String(allow) })
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function isAutoReject(verdict) {
|
|
203
|
+
const v = String(verdict || '')
|
|
204
|
+
return v === 'keyword-reject' || v === 'criteria-reject' || /-reject$/.test(v)
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function eventPreview(ev) {
|
|
208
|
+
const a = (ev && ev.args) || {}
|
|
209
|
+
return a.command || a.file_path || a.path || a.code || a.url || a.script || a.sql || a.prompt || a.description || ev.justification || ev.reason || ''
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function hasVal(value) {
|
|
213
|
+
return value !== undefined && value !== null && value !== ''
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function isLongVal(value) {
|
|
217
|
+
const s = String(value)
|
|
218
|
+
return s.length > 72 || s.indexOf('\n') !== -1
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function Detail(label, value) {
|
|
222
|
+
if (!hasVal(value)) return null
|
|
223
|
+
const long = isLongVal(value)
|
|
224
|
+
return [
|
|
225
|
+
React.createElement('div', { className: 'ab-detail-k', key: label + '-k' }, label),
|
|
226
|
+
React.createElement(long ? 'pre' : 'div', {
|
|
227
|
+
className: 'ab-detail-v' + (long ? ' ab-detail-v-box' : ''),
|
|
228
|
+
key: label + '-v',
|
|
229
|
+
}, String(value)),
|
|
230
|
+
]
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function DetailSection(title, items) {
|
|
234
|
+
const kids = []
|
|
235
|
+
for (let i = 0; i < items.length; i++) {
|
|
236
|
+
const row = items[i]
|
|
237
|
+
if (!row) continue
|
|
238
|
+
const node = Detail(row[0], row[1])
|
|
239
|
+
if (node) kids.push(node[0], node[1])
|
|
240
|
+
}
|
|
241
|
+
if (!kids.length) return null
|
|
242
|
+
return React.createElement('div', { className: 'ab-detail-sec' },
|
|
243
|
+
React.createElement('div', { className: 'ab-detail-h' }, title),
|
|
244
|
+
React.createElement('div', { className: 'ab-detail-grid' }, kids),
|
|
245
|
+
)
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function sizeTextarea(el) {
|
|
249
|
+
if (!el) return
|
|
250
|
+
el.style.height = 'auto'
|
|
251
|
+
el.style.height = Math.max(72, el.scrollHeight) + 'px'
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function fmtTime(iso) {
|
|
255
|
+
try {
|
|
256
|
+
const d = new Date(iso)
|
|
257
|
+
const p = (n) => String(n).padStart(2, '0')
|
|
258
|
+
return p(d.getMonth() + 1) + '-' + p(d.getDate()) + ' ' + p(d.getHours()) + ':' + p(d.getMinutes()) + ':' + p(d.getSeconds())
|
|
259
|
+
} catch (e) { return String(iso || '') }
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function GlyphCheck() {
|
|
263
|
+
return React.createElement('svg', {
|
|
264
|
+
width: 14, height: 14, viewBox: '0 0 16 16', fill: 'none', 'aria-hidden': true,
|
|
265
|
+
},
|
|
266
|
+
React.createElement('circle', { cx: 8, cy: 8, r: 7, fill: 'var(--dsw-alias-state-success-tertiary)' }),
|
|
267
|
+
React.createElement('path', { d: 'M5 8.3L7.1 10.4L11 6', stroke: 'currentColor', strokeWidth: 1.6, strokeLinecap: 'round', strokeLinejoin: 'round' }),
|
|
268
|
+
)
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function resolveFrame(sp) {
|
|
272
|
+
if (!sp) return { sessionId: null }
|
|
273
|
+
if (sp.sessionId) return { sessionId: sp.sessionId }
|
|
274
|
+
if (sp.session && sp.session.id) return { sessionId: sp.session.id }
|
|
275
|
+
return { sessionId: null }
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function latestUnsettledPending(evs) {
|
|
279
|
+
const settled = Object.create(null)
|
|
280
|
+
for (let i = evs.length - 1; i >= 0; i--) {
|
|
281
|
+
const ev = evs[i]
|
|
282
|
+
const kind = ev.kind || ''
|
|
283
|
+
if (kind === 'manual-approved' || kind === 'manual-rejected' || kind === 'manual-cancelled') {
|
|
284
|
+
if (ev.ticket != null) settled[ev.ticket] = true
|
|
285
|
+
}
|
|
286
|
+
if (kind === 'manual-pending' && (ev.ticket == null || !settled[ev.ticket])) return ev
|
|
287
|
+
}
|
|
288
|
+
return null
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function makeRpc(connection, t) {
|
|
292
|
+
const tr = typeof t === 'function' ? t : function (key) { return ZH[key] || key }
|
|
293
|
+
return function rpc(endpoint, payload) {
|
|
294
|
+
if (!connection || !connection.rpc || typeof connection.rpc.call !== 'function') {
|
|
295
|
+
return Promise.reject(new Error(tr('rpc.unavailable')))
|
|
296
|
+
}
|
|
297
|
+
return connection.rpc.call('/api', 'dsh-auto-approve', {
|
|
298
|
+
endpoint: endpoint,
|
|
299
|
+
payload: payload || {},
|
|
300
|
+
}).then(function (r) {
|
|
301
|
+
if (!r || r.ok === false) {
|
|
302
|
+
const msg = r && r.error && r.error.message ? r.error.message : tr('rpc.failed')
|
|
303
|
+
throw new Error(msg)
|
|
304
|
+
}
|
|
305
|
+
return r.value
|
|
306
|
+
})
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/** 输入框上方的自动放行 / 拒绝 / 等待人工条。 */
|
|
311
|
+
function NoticeStrip(props) {
|
|
312
|
+
const t = tFrom(props)
|
|
313
|
+
const rpc = props.rpc
|
|
314
|
+
const frame = resolveFrame(props.slotsProps || {})
|
|
315
|
+
const sessionId = frame.sessionId
|
|
316
|
+
const [notice, setNotice] = React.useState(null)
|
|
317
|
+
const sinceRef = React.useRef(0)
|
|
318
|
+
const lastShownIdRef = React.useRef(0)
|
|
319
|
+
const hideTimerRef = React.useRef(null)
|
|
320
|
+
|
|
321
|
+
React.useEffect(function () {
|
|
322
|
+
let alive = true
|
|
323
|
+
let timer = null
|
|
324
|
+
sinceRef.current = 0
|
|
325
|
+
lastShownIdRef.current = 0
|
|
326
|
+
setNotice(null)
|
|
327
|
+
if (!sessionId || !rpc) return
|
|
328
|
+
|
|
329
|
+
const startPolling = function () {
|
|
330
|
+
if (!alive) return
|
|
331
|
+
timer = setInterval(function () {
|
|
332
|
+
rpc('events', { sessionId: sessionId, since: sinceRef.current }).then(function (data) {
|
|
333
|
+
if (!alive) return
|
|
334
|
+
const evs = (data && data.events) || []
|
|
335
|
+
if (evs.length === 0) return
|
|
336
|
+
const last = evs[evs.length - 1]
|
|
337
|
+
if (!last || last.id <= lastShownIdRef.current) return
|
|
338
|
+
lastShownIdRef.current = last.id
|
|
339
|
+
sinceRef.current = last.id
|
|
340
|
+
const kind = last.kind || 'auto'
|
|
341
|
+
setNotice(last)
|
|
342
|
+
if (hideTimerRef.current) clearTimeout(hideTimerRef.current)
|
|
343
|
+
if (kind !== 'manual-pending') {
|
|
344
|
+
const hold = kind === 'auto' ? 8000 : 4000
|
|
345
|
+
hideTimerRef.current = setTimeout(function () { setNotice(null) }, hold)
|
|
346
|
+
}
|
|
347
|
+
}).catch(function () {
|
|
348
|
+
if (!alive) return
|
|
349
|
+
setNotice(function (cur) {
|
|
350
|
+
if (cur && cur.kind && cur.kind !== 'feed-error') return cur
|
|
351
|
+
return { kind: 'feed-error', code: 'notice.feedError' }
|
|
352
|
+
})
|
|
353
|
+
})
|
|
354
|
+
}, 2000)
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
rpc('events', { sessionId: sessionId, since: 0 }).then(function (data) {
|
|
358
|
+
if (!alive) return
|
|
359
|
+
const evs = (data && data.events) || []
|
|
360
|
+
if (evs.length > 0) {
|
|
361
|
+
const last = evs[evs.length - 1]
|
|
362
|
+
sinceRef.current = last.id
|
|
363
|
+
lastShownIdRef.current = last.id
|
|
364
|
+
const pending = latestUnsettledPending(evs)
|
|
365
|
+
if (pending) setNotice(pending)
|
|
366
|
+
}
|
|
367
|
+
startPolling()
|
|
368
|
+
}).catch(function () {
|
|
369
|
+
if (!alive) return
|
|
370
|
+
setNotice({ kind: 'feed-error', code: 'notice.feedLoadFailed' })
|
|
371
|
+
startPolling()
|
|
372
|
+
})
|
|
373
|
+
|
|
374
|
+
return function () {
|
|
375
|
+
alive = false
|
|
376
|
+
if (timer) clearInterval(timer)
|
|
377
|
+
if (hideTimerRef.current) clearTimeout(hideTimerRef.current)
|
|
378
|
+
}
|
|
379
|
+
}, [sessionId, rpc])
|
|
380
|
+
|
|
381
|
+
if (!notice) return null
|
|
382
|
+
const kind = notice.kind || 'auto'
|
|
383
|
+
const isPending = kind === 'manual-pending'
|
|
384
|
+
const isManual = kind === 'manual-approved' || kind === 'manual-rejected'
|
|
385
|
+
const ticket = notice.ticket ? (' #' + notice.ticket) : ''
|
|
386
|
+
let title = ''
|
|
387
|
+
let tagText = ''
|
|
388
|
+
let glyph = null
|
|
389
|
+
if (kind === 'feed-error') {
|
|
390
|
+
title = t(notice.code || 'notice.feedError')
|
|
391
|
+
tagText = t('notice.feedErrorTag')
|
|
392
|
+
glyph = React.createElement('span', { className: 'ab-notice-glyph-warn' }, '!')
|
|
393
|
+
} else if (isPending) {
|
|
394
|
+
title = t('notice.pendingTitle', { ticket: ticket, preview: eventPreview(notice) })
|
|
395
|
+
tagText = t('notice.pendingTag', { ticket: ticket })
|
|
396
|
+
glyph = React.createElement('span', { className: 'ab-notice-glyph-warn' }, '◔')
|
|
397
|
+
} else if (kind === 'manual-approved') {
|
|
398
|
+
title = t(notice.source === 'qq' ? 'notice.qqApproved' : 'notice.webApproved', { ticket: ticket, preview: eventPreview(notice) })
|
|
399
|
+
tagText = t(notice.source === 'qq' ? 'notice.qqApprovedTag' : 'notice.webApprovedTag', { ticket: ticket })
|
|
400
|
+
glyph = React.createElement('span', { className: 'ab-notice-glyph-warn' }, '✓')
|
|
401
|
+
} else if (kind === 'manual-rejected' || isAutoReject(notice.verdict)) {
|
|
402
|
+
title = t('notice.rejectedTitle', { ticket: ticket, preview: eventPreview(notice) })
|
|
403
|
+
tagText = kind === 'manual-rejected'
|
|
404
|
+
? t('notice.rejectedTag', { ticket: ticket })
|
|
405
|
+
: (verdictLabel(t, notice.verdict) || t('notice.rejectedDefault'))
|
|
406
|
+
glyph = React.createElement('span', { className: 'ab-notice-glyph-err' }, '✕')
|
|
407
|
+
} else {
|
|
408
|
+
title = eventPreview(notice)
|
|
409
|
+
tagText = t('notice.autoTag', { verdict: verdictLabel(t, notice.verdict) || notice.verdict || t('notice.autoDefault') })
|
|
410
|
+
glyph = React.createElement(GlyphCheck, null)
|
|
411
|
+
}
|
|
412
|
+
const autoReject = isAutoReject(notice.verdict)
|
|
413
|
+
const isFeedErr = kind === 'feed-error'
|
|
414
|
+
const cardCls = 'ab-notice-card' + (isPending ? ' ab-notice-card-pending' : isManual ? ' ab-notice-card-manual' : (autoReject || isFeedErr) ? ' ab-notice-card-err' : '')
|
|
415
|
+
return React.createElement('div', { className: 'ab-notice' },
|
|
416
|
+
React.createElement('div', { className: cardCls },
|
|
417
|
+
React.createElement('span', { className: 'ab-notice-glyph' }, glyph),
|
|
418
|
+
React.createElement('div', { className: 'ab-notice-body' },
|
|
419
|
+
React.createElement('div', { className: 'ab-notice-head' },
|
|
420
|
+
React.createElement('span', { className: 'ab-notice-tool' }, notice.tool || (kind === 'feed-error' ? t('notice.hint') : 'tool')),
|
|
421
|
+
React.createElement('span', { className: 'ab-notice-text' }, title),
|
|
422
|
+
),
|
|
423
|
+
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' : kind === 'manual-approved' ? 'ab-tag-warn' : 'ab-tag' }, tagText),
|
|
425
|
+
React.createElement('span', { className: 'ab-time' }, fmtTime(notice.ts)),
|
|
426
|
+
),
|
|
427
|
+
),
|
|
428
|
+
React.createElement('button', {
|
|
429
|
+
type: 'button', className: 'ab-notice-close', title: t('notice.close'), 'aria-label': t('notice.close'),
|
|
430
|
+
onClick: function () { setNotice(null) },
|
|
431
|
+
}, '✕'),
|
|
432
|
+
),
|
|
433
|
+
)
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
/** 会话「审批」tab。行头可点,详情分组,方便选中文字。 */
|
|
437
|
+
function HistoryView(props) {
|
|
438
|
+
const t = tFrom(props)
|
|
439
|
+
const rpc = props.rpc
|
|
440
|
+
const sessionId = (props && (props.sessionId || (props.slotsProps && props.slotsProps.sessionId))) || null
|
|
441
|
+
const [events, setEvents] = React.useState(null)
|
|
442
|
+
const [error, setError] = React.useState(null)
|
|
443
|
+
const [openId, setOpenId] = React.useState(null)
|
|
444
|
+
|
|
445
|
+
React.useEffect(function () {
|
|
446
|
+
let alive = true
|
|
447
|
+
let timer = null
|
|
448
|
+
if (!sessionId || !rpc) return
|
|
449
|
+
const load = function () {
|
|
450
|
+
rpc('events', { sessionId: sessionId, since: 0 }).then(function (data) {
|
|
451
|
+
if (!alive) return
|
|
452
|
+
const list = ((data && data.events) || []).slice().reverse()
|
|
453
|
+
setEvents(list)
|
|
454
|
+
setError(null)
|
|
455
|
+
}).catch(function (e) {
|
|
456
|
+
if (!alive) return
|
|
457
|
+
setError(String((e && e.message) || e))
|
|
458
|
+
})
|
|
459
|
+
}
|
|
460
|
+
load()
|
|
461
|
+
timer = setInterval(load, 3000)
|
|
462
|
+
return function () { alive = false; if (timer) clearInterval(timer) }
|
|
463
|
+
}, [sessionId, rpc])
|
|
464
|
+
|
|
465
|
+
if (!sessionId) return React.createElement('div', { className: 'ab-empty' }, t('history.noSession'))
|
|
466
|
+
if (error) return React.createElement('div', { className: 'ab-empty' }, t('history.loadFailed', { error: error }))
|
|
467
|
+
if (!events) return React.createElement('div', { className: 'ab-loading' }, t('history.loading'))
|
|
468
|
+
if (events.length === 0) return React.createElement('div', { className: 'ab-view' },
|
|
469
|
+
React.createElement('div', { className: 'ab-view-head' },
|
|
470
|
+
React.createElement('div', { className: 'ab-view-title' }, t('history.title')),
|
|
471
|
+
React.createElement('div', { className: 'ab-view-sub' }, t('history.emptySub')),
|
|
472
|
+
),
|
|
473
|
+
React.createElement('div', { className: 'ab-empty' }, t('history.emptyHint')),
|
|
474
|
+
)
|
|
475
|
+
|
|
476
|
+
return React.createElement('div', { className: 'ab-view' },
|
|
477
|
+
React.createElement('div', { className: 'ab-view-head' },
|
|
478
|
+
React.createElement('div', { className: 'ab-view-title' }, t('history.title')),
|
|
479
|
+
React.createElement('div', { className: 'ab-view-sub' }, t('history.sub')),
|
|
480
|
+
),
|
|
481
|
+
React.createElement('div', { className: 'ab-list' },
|
|
482
|
+
events.map(function (ev) {
|
|
483
|
+
const kind = ev.kind || 'auto'
|
|
484
|
+
const ticket = ev.ticket ? '#' + ev.ticket : ''
|
|
485
|
+
let glyph = React.createElement(GlyphCheck, null)
|
|
486
|
+
let tag = t('history.tagAuto')
|
|
487
|
+
let tagCls = 'ab-tag'
|
|
488
|
+
if (kind === 'manual-pending') {
|
|
489
|
+
glyph = React.createElement('span', { className: 'ab-notice-glyph-warn' }, '◔')
|
|
490
|
+
tag = t('history.tagPending', { ticket: ticket })
|
|
491
|
+
tagCls = 'ab-tag-warn'
|
|
492
|
+
} else if (kind === 'manual-approved') {
|
|
493
|
+
glyph = React.createElement('span', { className: 'ab-notice-glyph-warn' }, '✓')
|
|
494
|
+
tag = t(ev.source === 'qq' ? 'history.tagQqAllow' : 'history.tagAllow', { ticket: ticket })
|
|
495
|
+
tagCls = 'ab-tag-warn'
|
|
496
|
+
} else if (kind === 'manual-rejected') {
|
|
497
|
+
glyph = React.createElement('span', { className: 'ab-notice-glyph-err' }, '✕')
|
|
498
|
+
tag = t('history.tagReject', { ticket: ticket })
|
|
499
|
+
tagCls = 'ab-tag-err'
|
|
500
|
+
} else if (isAutoReject(ev.verdict)) {
|
|
501
|
+
glyph = React.createElement('span', { className: 'ab-notice-glyph-err' }, '✕')
|
|
502
|
+
tag = verdictLabel(t, ev.verdict) || t('notice.rejectedDefault')
|
|
503
|
+
tagCls = 'ab-tag-err'
|
|
504
|
+
} else {
|
|
505
|
+
tag = verdictLabel(t, ev.verdict) || t('history.tagAuto')
|
|
506
|
+
}
|
|
507
|
+
const open = openId === ev.id
|
|
508
|
+
const args = ev.args || {}
|
|
509
|
+
const j = ev.judge || {}
|
|
510
|
+
return React.createElement('div', {
|
|
511
|
+
className: 'ab-row' + (open ? ' ab-row-open' : ''),
|
|
512
|
+
key: ev.id,
|
|
513
|
+
},
|
|
514
|
+
React.createElement('div', { className: 'ab-row-rail' },
|
|
515
|
+
glyph,
|
|
516
|
+
React.createElement('div', { className: 'ab-row-line' }),
|
|
517
|
+
),
|
|
518
|
+
React.createElement('div', { className: 'ab-row-body' },
|
|
519
|
+
React.createElement('button', {
|
|
520
|
+
type: 'button',
|
|
521
|
+
className: 'ab-row-head',
|
|
522
|
+
onClick: function () { setOpenId(open ? null : ev.id) },
|
|
523
|
+
},
|
|
524
|
+
React.createElement('div', { className: 'ab-row-top' },
|
|
525
|
+
React.createElement('span', { className: 'ab-row-tool' }, ev.tool || 'tool'),
|
|
526
|
+
React.createElement('span', { className: tagCls }, tag),
|
|
527
|
+
React.createElement('span', { className: 'ab-time' }, fmtTime(ev.ts)),
|
|
528
|
+
React.createElement('span', { className: 'ab-row-chev' }, '▾'),
|
|
529
|
+
),
|
|
530
|
+
React.createElement('div', { className: 'ab-row-reason' }, eventPreview(ev)),
|
|
531
|
+
),
|
|
532
|
+
open
|
|
533
|
+
? React.createElement('div', { className: 'ab-details' },
|
|
534
|
+
DetailSection(t('detail.request'), [
|
|
535
|
+
[t('detail.command'), args.command],
|
|
536
|
+
[t('detail.path'), args.file_path || args.path],
|
|
537
|
+
[t('detail.description'), args.description],
|
|
538
|
+
[t('detail.old'), args.old_string],
|
|
539
|
+
[t('detail.new'), args.new_string],
|
|
540
|
+
[t('detail.content'), args.content],
|
|
541
|
+
[t('detail.code'), args.code],
|
|
542
|
+
[t('detail.url'), args.url],
|
|
543
|
+
[t('detail.query'), args.query],
|
|
544
|
+
[t('detail.script'), args.script],
|
|
545
|
+
[t('detail.sql'), args.sql],
|
|
546
|
+
[t('detail.prompt'), args.prompt],
|
|
547
|
+
[t('detail.cwd'), ev.cwd],
|
|
548
|
+
[t('detail.sandbox'), ev.mode],
|
|
549
|
+
[t('detail.justification'), ev.justification || ev.reason],
|
|
550
|
+
]),
|
|
551
|
+
DetailSection(t('detail.audit'), [
|
|
552
|
+
[t('detail.pipe'), pathLabel(t, ev.path) || ev.path],
|
|
553
|
+
[t('detail.keyword'), ev.keyword],
|
|
554
|
+
[t('detail.judgeModel'), [j.provider, j.model, j.effort].filter(Boolean).join(' / ')],
|
|
555
|
+
[t('detail.judgeCategory'), j.failed
|
|
556
|
+
? t('detail.judgeFailed')
|
|
557
|
+
: (j.label && j.criterion ? j.criterion + ' · ' + j.label : (j.criterion || ev.category || ''))],
|
|
558
|
+
[t('detail.judgeAction'), actionLabel(t, j.action) || j.action],
|
|
559
|
+
[t('detail.judgeReason'), j.reason || ev.judgeReason],
|
|
560
|
+
[t('detail.judgeError'), j.error],
|
|
561
|
+
[t('detail.judgeRaw'), j.raw],
|
|
562
|
+
[t('detail.ticket'), ev.ticket != null ? '#' + ev.ticket : ''],
|
|
563
|
+
[t('detail.source'), ev.source],
|
|
564
|
+
]),
|
|
565
|
+
)
|
|
566
|
+
: null,
|
|
567
|
+
),
|
|
568
|
+
)
|
|
569
|
+
}),
|
|
570
|
+
),
|
|
571
|
+
)
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
/** 设置 → 自动审批。QQ 卡折在底部;恢复默认需连点两次。 */
|
|
575
|
+
function SettingsPage(props) {
|
|
576
|
+
const t = tFrom(props)
|
|
577
|
+
const rpc = props.rpc
|
|
578
|
+
const rootRef = React.useRef(null)
|
|
579
|
+
const [snapshot, setSnapshot] = React.useState(null)
|
|
580
|
+
const [error, setError] = React.useState(null)
|
|
581
|
+
const [busy, setBusy] = React.useState(false)
|
|
582
|
+
const [feedback, setFeedback] = React.useState(null)
|
|
583
|
+
const [newKeyword, setNewKeyword] = React.useState('')
|
|
584
|
+
const [newKeywordAction, setNewKeywordAction] = React.useState('human')
|
|
585
|
+
const [newCriterion, setNewCriterion] = React.useState({ id: '', label: '', description: '', action: 'human' })
|
|
586
|
+
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
|
+
const [models, setModels] = React.useState([])
|
|
593
|
+
const [efforts, setEfforts] = React.useState([])
|
|
594
|
+
const [judgeProvider, setJudgeProvider] = React.useState('')
|
|
595
|
+
const [judgeModel, setJudgeModel] = React.useState('')
|
|
596
|
+
const [judgeEffort, setJudgeEffort] = React.useState('')
|
|
597
|
+
const [judgePromptLang, setJudgePromptLang] = React.useState('zh')
|
|
598
|
+
const [presetSandbox, setPresetSandbox] = React.useState('workspace-write')
|
|
599
|
+
const [foldOpen, setFoldOpen] = React.useState({ keywords: false, criteria: false, qq: false })
|
|
600
|
+
const [kwEdit, setKwEdit] = React.useState(null)
|
|
601
|
+
const [resetArmed, setResetArmed] = React.useState(null)
|
|
602
|
+
|
|
603
|
+
const load = function (opts) {
|
|
604
|
+
const keepEdits = opts && opts.keepEdits
|
|
605
|
+
rpc('snapshot').then(function (data) {
|
|
606
|
+
setSnapshot(data)
|
|
607
|
+
if (!keepEdits) {
|
|
608
|
+
setTimeoutMs(String(data.config.judgeTimeoutMs))
|
|
609
|
+
setJudgeProvider((data.plugin.judge && data.plugin.judge.provider) || '')
|
|
610
|
+
setJudgeModel((data.plugin.judge && data.plugin.judge.model) || '')
|
|
611
|
+
setJudgeEffort((data.plugin.judge && data.plugin.judge.reasoningEffort) || '')
|
|
612
|
+
setJudgePromptLang((data.plugin && data.plugin.judgePromptLang) === 'en' ? 'en' : 'zh')
|
|
613
|
+
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
|
+
}
|
|
618
|
+
setError(null)
|
|
619
|
+
}).catch(function (e) {
|
|
620
|
+
setError(t('set.loadFailed', { error: String((e && e.message) || e) }))
|
|
621
|
+
})
|
|
622
|
+
}
|
|
623
|
+
React.useEffect(function () { load() }, [])
|
|
624
|
+
React.useEffect(function () {
|
|
625
|
+
if (!resetArmed) return
|
|
626
|
+
const disarm = setTimeout(function () { setResetArmed(null) }, 5000)
|
|
627
|
+
return function () { clearTimeout(disarm) }
|
|
628
|
+
}, [resetArmed])
|
|
629
|
+
|
|
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
|
+
React.useEffect(function () {
|
|
648
|
+
const provider = judgeProvider || (snapshot && snapshot.fallback && snapshot.fallback.provider)
|
|
649
|
+
if (!provider || !rpc) return
|
|
650
|
+
rpc('judge-catalog', { provider: provider }).then(function (data) {
|
|
651
|
+
setModels(data.models || [])
|
|
652
|
+
}).catch(function () { setModels([]) })
|
|
653
|
+
}, [judgeProvider, snapshot, rpc])
|
|
654
|
+
|
|
655
|
+
React.useEffect(function () {
|
|
656
|
+
const provider = judgeProvider || (snapshot && snapshot.fallback && snapshot.fallback.provider)
|
|
657
|
+
const model = judgeModel || (snapshot && snapshot.fallback && snapshot.fallback.model)
|
|
658
|
+
if (!provider || !model || !rpc) { setEfforts([]); return }
|
|
659
|
+
rpc('judge-info', { provider: provider, model: model }).then(function (data) {
|
|
660
|
+
setEfforts(data.efforts || [])
|
|
661
|
+
}).catch(function () { setEfforts([]) })
|
|
662
|
+
}, [judgeProvider, judgeModel, snapshot, rpc])
|
|
663
|
+
|
|
664
|
+
const showFeedback = function (msg, ok) {
|
|
665
|
+
setFeedback({ msg: String(msg), ok: ok !== false })
|
|
666
|
+
setTimeout(function () { setFeedback(null) }, 4000)
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
const run = function (endpoint, payload, okMsg) {
|
|
670
|
+
setBusy(true)
|
|
671
|
+
return rpc(endpoint, payload).then(function (res) {
|
|
672
|
+
load()
|
|
673
|
+
showFeedback(okMsg || t('set.hotOk'))
|
|
674
|
+
return res
|
|
675
|
+
}).catch(function (e) {
|
|
676
|
+
showFeedback(String((e && e.message) || e), false)
|
|
677
|
+
}).finally(function () { setBusy(false) })
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
if (!snapshot) {
|
|
681
|
+
return React.createElement('div', { className: 'ab-set' },
|
|
682
|
+
React.createElement('div', { className: error ? 'ab-set-err' : 'ab-set-ok' }, error || t('set.loading')))
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
const cfg = snapshot.config
|
|
686
|
+
const setup = snapshot.setup || { configured: false }
|
|
687
|
+
const predefined = snapshot.predefined || {}
|
|
688
|
+
const preDeny = new Set(predefined.rejectKeywords || predefined.denyKeywords || [])
|
|
689
|
+
const qq = snapshot.qq || {}
|
|
690
|
+
const plugin = snapshot.plugin || {}
|
|
691
|
+
const notify = plugin.notify || {}
|
|
692
|
+
const providers = snapshot.providers || []
|
|
693
|
+
const fallback = snapshot.fallback || {}
|
|
694
|
+
const judgeTimeoutLabel = Math.round((Number(cfg.judgeTimeoutMs) || 20000) / 1000) + 's'
|
|
695
|
+
const judgeRoute = (judgeProvider && judgeModel)
|
|
696
|
+
? (judgeProvider + ' / ' + judgeModel)
|
|
697
|
+
: (fallback.provider && fallback.model ? t('set.followDefault') : t('set.unconfigured'))
|
|
698
|
+
const scrollToStage = function (id) {
|
|
699
|
+
try {
|
|
700
|
+
const root = rootRef.current
|
|
701
|
+
const el = root && root.querySelector('[data-ab-stage="' + id + '"]')
|
|
702
|
+
if (!el) return
|
|
703
|
+
if (el.tagName === 'DETAILS') {
|
|
704
|
+
setFoldOpen(function (prev) {
|
|
705
|
+
const next = Object.assign({}, prev)
|
|
706
|
+
next[id] = true
|
|
707
|
+
return next
|
|
708
|
+
})
|
|
709
|
+
}
|
|
710
|
+
el.scrollIntoView({ behavior: 'smooth', block: 'center' })
|
|
711
|
+
} catch (e) {}
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
const resetBtn = function (kind, idleLabel, okMsg, value) {
|
|
715
|
+
const armed = resetArmed === kind
|
|
716
|
+
return React.createElement('button', {
|
|
717
|
+
type: 'button',
|
|
718
|
+
className: 'ab-set-btn' + (armed ? ' ab-set-btn-danger' : ''),
|
|
719
|
+
disabled: busy,
|
|
720
|
+
onClick: function () {
|
|
721
|
+
if (resetArmed !== kind) {
|
|
722
|
+
setResetArmed(kind)
|
|
723
|
+
return
|
|
724
|
+
}
|
|
725
|
+
setResetArmed(null)
|
|
726
|
+
run('rule-op', { op: 'reset', kind: kind, value: value }, okMsg)
|
|
727
|
+
},
|
|
728
|
+
}, armed ? t('set.resetConfirm') : idleLabel)
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
return React.createElement('div', { className: 'ab-set', ref: rootRef },
|
|
732
|
+
React.createElement('h3', { className: 'ab-set-title' }, t('set.title')),
|
|
733
|
+
React.createElement('p', { className: 'ab-set-intro' }, t('set.intro')),
|
|
734
|
+
(cfg && cfg.corrupt)
|
|
735
|
+
? React.createElement('div', { className: 'ab-set-err' }, t('set.allowlistCorrupt'))
|
|
736
|
+
: null,
|
|
737
|
+
snapshot.pluginCorrupt
|
|
738
|
+
? React.createElement('div', { className: 'ab-set-err' },
|
|
739
|
+
t('set.pluginCorrupt'),
|
|
740
|
+
React.createElement('button', {
|
|
741
|
+
type: 'button', className: 'ab-set-btn ab-set-btn-danger', disabled: busy,
|
|
742
|
+
onClick: function () { run('save-plugin', { overwriteCorrupt: true }, t('set.overwriteOk')) },
|
|
743
|
+
}, t('set.pluginCorruptOverwrite')),
|
|
744
|
+
)
|
|
745
|
+
: null,
|
|
746
|
+
(snapshot.qq && snapshot.qq.credsCorrupt)
|
|
747
|
+
? React.createElement('div', { className: 'ab-set-err' }, t('set.qqCorrupt'))
|
|
748
|
+
: null,
|
|
749
|
+
|
|
750
|
+
!setup.configured ? React.createElement('div', { className: 'ab-set-card' },
|
|
751
|
+
React.createElement('div', { className: 'ab-set-card-head' },
|
|
752
|
+
React.createElement('div', { className: 'ab-set-card-title' }, t('set.presetMissingTitle')),
|
|
753
|
+
React.createElement('p', { className: 'ab-set-card-sub' }, t('set.presetMissingSub'))),
|
|
754
|
+
React.createElement('div', { className: 'ab-set-row' },
|
|
755
|
+
React.createElement('button', {
|
|
756
|
+
type: 'button', className: 'ab-set-btn ab-set-btn-primary', disabled: busy,
|
|
757
|
+
onClick: function () { run('setup', {}, t('set.presetWrote')) },
|
|
758
|
+
}, t('set.presetWrite')),
|
|
759
|
+
React.createElement('span', { className: 'ab-set-note' }, t('set.presetWriteNote')),
|
|
760
|
+
),
|
|
761
|
+
) : null,
|
|
762
|
+
|
|
763
|
+
React.createElement('div', { className: 'ab-set-card' },
|
|
764
|
+
React.createElement('div', { className: 'ab-set-card-head' },
|
|
765
|
+
React.createElement('div', { className: 'ab-set-card-title' }, t('set.modeTitle'))),
|
|
766
|
+
React.createElement('div', { className: 'ab-set-row ab-set-row-choices' },
|
|
767
|
+
React.createElement('button', {
|
|
768
|
+
type: 'button',
|
|
769
|
+
className: 'ab-set-choice' + (presetSandbox === 'workspace-write' ? ' ab-set-choice-on' : ''),
|
|
770
|
+
onClick: function () { setPresetSandbox('workspace-write') },
|
|
771
|
+
},
|
|
772
|
+
React.createElement('span', { className: 'ab-set-choice-t' }, t('set.modeWs')),
|
|
773
|
+
React.createElement('span', { className: 'ab-set-choice-d' }, t('set.modeWsHint')),
|
|
774
|
+
),
|
|
775
|
+
React.createElement('button', {
|
|
776
|
+
type: 'button',
|
|
777
|
+
className: 'ab-set-choice' + (presetSandbox === 'read-only' ? ' ab-set-choice-on' : ''),
|
|
778
|
+
onClick: function () { setPresetSandbox('read-only') },
|
|
779
|
+
},
|
|
780
|
+
React.createElement('span', { className: 'ab-set-choice-t' }, t('set.modeRo')),
|
|
781
|
+
React.createElement('span', { className: 'ab-set-choice-d' }, t('set.modeRoHint')),
|
|
782
|
+
),
|
|
783
|
+
),
|
|
784
|
+
React.createElement('div', { className: 'ab-set-row' },
|
|
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
|
+
),
|
|
796
|
+
),
|
|
797
|
+
|
|
798
|
+
React.createElement('div', { className: 'ab-set-card' },
|
|
799
|
+
React.createElement('div', { className: 'ab-set-card-head' },
|
|
800
|
+
React.createElement('div', { className: 'ab-set-card-title' }, t('set.overview')),
|
|
801
|
+
React.createElement('p', { className: 'ab-set-card-sub' }, t('set.overviewSub'))),
|
|
802
|
+
React.createElement('div', { className: 'ab-set-steps' },
|
|
803
|
+
[
|
|
804
|
+
{
|
|
805
|
+
id: 'keywords', name: t('set.stepKeywords'),
|
|
806
|
+
value: countsLabel(t, (cfg.rejectKeywords || []).length, (cfg.humanKeywords || cfg.denyKeywords || []).length, (cfg.allowKeywords || []).length),
|
|
807
|
+
},
|
|
808
|
+
{ id: 'criteria', name: t('set.stepCriteria'), value: t('set.criteriaCount', { n: String((cfg.criteria || []).length) }) },
|
|
809
|
+
{ id: 'judge', name: t('set.stepJudge'), value: judgeRoute + ' · ' + judgeTimeoutLabel },
|
|
810
|
+
].map(function (s) {
|
|
811
|
+
return React.createElement('button', {
|
|
812
|
+
type: 'button', className: 'ab-set-step', key: s.id,
|
|
813
|
+
onClick: function () { scrollToStage(s.id) },
|
|
814
|
+
},
|
|
815
|
+
React.createElement('div', { className: 'ab-set-step-k' }, s.name),
|
|
816
|
+
React.createElement('div', { className: 'ab-set-step-v' }, s.value),
|
|
817
|
+
)
|
|
818
|
+
}),
|
|
819
|
+
),
|
|
820
|
+
React.createElement('div', { className: 'ab-set-invs' },
|
|
821
|
+
(cfg.criteria || []).map(function (c) {
|
|
822
|
+
return React.createElement('span', {
|
|
823
|
+
className: 'ab-set-inv ' + (c.action === 'reject' ? 'ab-set-inv-hard' : c.action === 'allow' ? 'ab-set-inv-ok' : 'ab-set-inv-mode'),
|
|
824
|
+
key: c.id, title: c.id,
|
|
825
|
+
}, (c.label || c.id) + ' · ' + (actionLabel(t, c.action) || c.action))
|
|
826
|
+
}),
|
|
827
|
+
),
|
|
828
|
+
),
|
|
829
|
+
|
|
830
|
+
React.createElement('details', {
|
|
831
|
+
className: 'ab-set-card ab-set-fold', 'data-ab-stage': 'keywords',
|
|
832
|
+
open: foldOpen.keywords,
|
|
833
|
+
onToggle: function (e) {
|
|
834
|
+
const open = e.currentTarget.open
|
|
835
|
+
setFoldOpen(function (prev) { return Object.assign({}, prev, { keywords: open }) })
|
|
836
|
+
},
|
|
837
|
+
},
|
|
838
|
+
React.createElement('summary', null,
|
|
839
|
+
React.createElement('div', { className: 'ab-set-card-head' },
|
|
840
|
+
React.createElement('div', { className: 'ab-set-card-title' },
|
|
841
|
+
t('set.stepKeywords'),
|
|
842
|
+
React.createElement('span', { className: 'ab-set-fold-chev' }, '▾')),
|
|
843
|
+
React.createElement('p', { className: 'ab-set-card-sub' }, t('set.kwSub'))),
|
|
844
|
+
React.createElement('div', { className: 'ab-set-fold-sum' },
|
|
845
|
+
countsLabel(t, (cfg.rejectKeywords || []).length, (cfg.humanKeywords || cfg.denyKeywords || []).length, (cfg.allowKeywords || []).length))),
|
|
846
|
+
React.createElement('div', { className: 'ab-set-fold-body' },
|
|
847
|
+
React.createElement('div', { className: 'ab-set-list' },
|
|
848
|
+
(function () {
|
|
849
|
+
const rows = []
|
|
850
|
+
;(cfg.rejectKeywords || []).forEach(function (k) { rows.push({ text: k, action: 'reject' }) })
|
|
851
|
+
;(cfg.humanKeywords || cfg.denyKeywords || []).forEach(function (k) { rows.push({ text: k, action: 'human' }) })
|
|
852
|
+
;(cfg.allowKeywords || []).forEach(function (k) { rows.push({ text: k, action: 'allow' }) })
|
|
853
|
+
if (rows.length === 0) return React.createElement('p', { className: 'ab-set-empty' }, t('set.empty'))
|
|
854
|
+
return rows.map(function (row) {
|
|
855
|
+
const commitKw = function (text, action) {
|
|
856
|
+
const next = String(text || '').trim()
|
|
857
|
+
if (!next) return
|
|
858
|
+
if (next === row.text && action === row.action) return
|
|
859
|
+
run('rule-op', { op: 'set', kind: 'keywords', value: { from: row.text, text: next, action: action } })
|
|
860
|
+
}
|
|
861
|
+
const editKey = row.action + ':' + row.text
|
|
862
|
+
return React.createElement('div', { className: 'ab-set-item ab-set-item-kw', key: editKey },
|
|
863
|
+
kwEdit === editKey
|
|
864
|
+
? React.createElement('input', {
|
|
865
|
+
className: 'ab-set-input ab-set-input-grow',
|
|
866
|
+
defaultValue: row.text,
|
|
867
|
+
autoFocus: true,
|
|
868
|
+
onBlur: function (e) {
|
|
869
|
+
commitKw(e.target.value, row.action)
|
|
870
|
+
setKwEdit(null)
|
|
871
|
+
},
|
|
872
|
+
onKeyDown: function (e) {
|
|
873
|
+
if (e.key === 'Enter') e.currentTarget.blur()
|
|
874
|
+
if (e.key === 'Escape') setKwEdit(null)
|
|
875
|
+
},
|
|
876
|
+
})
|
|
877
|
+
: React.createElement('span', {
|
|
878
|
+
className: 'ab-set-kw-text',
|
|
879
|
+
title: t('set.kwEditTitle'),
|
|
880
|
+
onClick: function () { setKwEdit(editKey) },
|
|
881
|
+
}, row.text),
|
|
882
|
+
preDeny.has(row.text) ? React.createElement('span', { className: 'ab-set-tag ab-set-tag-blue' }, t('set.presetTag')) : null,
|
|
883
|
+
React.createElement('select', {
|
|
884
|
+
className: 'ab-set-select', value: row.action,
|
|
885
|
+
onChange: function (e) { commitKw(row.text, e.target.value) },
|
|
886
|
+
},
|
|
887
|
+
React.createElement('option', { value: 'human' }, t('action.human')),
|
|
888
|
+
React.createElement('option', { value: 'allow' }, t('action.allow')),
|
|
889
|
+
React.createElement('option', { value: 'reject' }, t('action.reject')),
|
|
890
|
+
),
|
|
891
|
+
React.createElement('button', {
|
|
892
|
+
type: 'button',
|
|
893
|
+
className: 'ab-set-item-del' + (resetArmed === ('del-kw:' + row.text) ? ' ab-set-btn-danger' : ''),
|
|
894
|
+
title: t('set.delete'),
|
|
895
|
+
onClick: function () {
|
|
896
|
+
const key = 'del-kw:' + row.text
|
|
897
|
+
if (preDeny.has(row.text) && resetArmed !== key) {
|
|
898
|
+
setResetArmed(key)
|
|
899
|
+
return
|
|
900
|
+
}
|
|
901
|
+
setResetArmed(null)
|
|
902
|
+
run('rule-op', { op: 'remove', kind: 'keywords', value: { text: row.text } })
|
|
903
|
+
},
|
|
904
|
+
}, resetArmed === ('del-kw:' + row.text) ? t('set.confirm') : '✕'),
|
|
905
|
+
)
|
|
906
|
+
})
|
|
907
|
+
})(),
|
|
908
|
+
),
|
|
909
|
+
React.createElement('div', { className: 'ab-set-row' },
|
|
910
|
+
React.createElement('input', {
|
|
911
|
+
className: 'ab-set-input ab-set-input-grow', placeholder: t('set.kwPlaceholder'),
|
|
912
|
+
value: newKeyword, onChange: function (e) { setNewKeyword(e.target.value) },
|
|
913
|
+
}),
|
|
914
|
+
React.createElement('select', {
|
|
915
|
+
className: 'ab-set-select', value: newKeywordAction,
|
|
916
|
+
onChange: function (e) { setNewKeywordAction(e.target.value) },
|
|
917
|
+
},
|
|
918
|
+
React.createElement('option', { value: 'human' }, t('action.human')),
|
|
919
|
+
React.createElement('option', { value: 'allow' }, t('action.allow')),
|
|
920
|
+
React.createElement('option', { value: 'reject' }, t('action.reject')),
|
|
921
|
+
),
|
|
922
|
+
React.createElement('button', {
|
|
923
|
+
type: 'button', className: 'ab-set-btn', disabled: busy || !newKeyword.trim(),
|
|
924
|
+
onClick: function () {
|
|
925
|
+
run('rule-op', { op: 'add', kind: 'keywords', value: { text: newKeyword.trim(), action: newKeywordAction } })
|
|
926
|
+
setNewKeyword('')
|
|
927
|
+
},
|
|
928
|
+
}, t('set.add')),
|
|
929
|
+
),
|
|
930
|
+
React.createElement('div', { className: 'ab-set-row ab-set-foot' },
|
|
931
|
+
resetBtn('keywords', t('set.resetKeywords'), t('set.resetKeywordsOk')),
|
|
932
|
+
),
|
|
933
|
+
),
|
|
934
|
+
),
|
|
935
|
+
|
|
936
|
+
React.createElement('details', {
|
|
937
|
+
className: 'ab-set-card ab-set-fold', 'data-ab-stage': 'criteria',
|
|
938
|
+
open: foldOpen.criteria,
|
|
939
|
+
onToggle: function (e) {
|
|
940
|
+
const open = e.currentTarget.open
|
|
941
|
+
setFoldOpen(function (prev) { return Object.assign({}, prev, { criteria: open }) })
|
|
942
|
+
},
|
|
943
|
+
},
|
|
944
|
+
React.createElement('summary', null,
|
|
945
|
+
React.createElement('div', { className: 'ab-set-card-head' },
|
|
946
|
+
React.createElement('div', { className: 'ab-set-card-title' },
|
|
947
|
+
t('set.stepCriteria'),
|
|
948
|
+
React.createElement('span', { className: 'ab-set-fold-chev' }, '▾')),
|
|
949
|
+
React.createElement('p', { className: 'ab-set-card-sub' }, t('set.criteriaSub'))),
|
|
950
|
+
React.createElement('div', { className: 'ab-set-fold-sum' },
|
|
951
|
+
(function () {
|
|
952
|
+
const rows = cfg.criteria || []
|
|
953
|
+
const n = function (a) { return rows.filter(function (c) { return c.action === a }).length }
|
|
954
|
+
return countsLabel(t, n('reject'), n('human'), n('allow'))
|
|
955
|
+
})())),
|
|
956
|
+
React.createElement('div', { className: 'ab-set-fold-body' },
|
|
957
|
+
React.createElement('div', { className: 'ab-set-list ab-set-list-crit' },
|
|
958
|
+
(cfg.criteria || []).map(function (c) {
|
|
959
|
+
const commitCrit = function (patch) {
|
|
960
|
+
run('rule-op', { op: 'set', kind: 'criteria', value: Object.assign({ id: c.id }, patch) })
|
|
961
|
+
}
|
|
962
|
+
return React.createElement('div', { className: 'ab-set-item ab-set-item-crit', key: c.id },
|
|
963
|
+
React.createElement('div', { className: 'ab-set-crit-top' },
|
|
964
|
+
React.createElement('span', { className: 'ab-set-item-id' }, c.id),
|
|
965
|
+
React.createElement('select', {
|
|
966
|
+
className: 'ab-set-select', value: c.action || 'human',
|
|
967
|
+
onChange: function (e) { commitCrit({ action: e.target.value }) },
|
|
968
|
+
},
|
|
969
|
+
React.createElement('option', { value: 'human' }, t('action.human')),
|
|
970
|
+
React.createElement('option', { value: 'allow' }, t('action.allow')),
|
|
971
|
+
React.createElement('option', { value: 'reject' }, t('action.reject')),
|
|
972
|
+
),
|
|
973
|
+
c.id === 'other' ? null : React.createElement('button', {
|
|
974
|
+
type: 'button',
|
|
975
|
+
className: 'ab-set-item-del' + (resetArmed === ('del-c:' + c.id) ? ' ab-set-btn-danger' : ''),
|
|
976
|
+
onClick: function () {
|
|
977
|
+
const key = 'del-c:' + c.id
|
|
978
|
+
if (resetArmed !== key) {
|
|
979
|
+
setResetArmed(key)
|
|
980
|
+
return
|
|
981
|
+
}
|
|
982
|
+
setResetArmed(null)
|
|
983
|
+
run('rule-op', { op: 'remove', kind: 'criteria', value: { id: c.id } })
|
|
984
|
+
},
|
|
985
|
+
}, resetArmed === ('del-c:' + c.id) ? t('set.confirm') : '✕'),
|
|
986
|
+
),
|
|
987
|
+
React.createElement('div', { className: 'ab-set-item-fields' },
|
|
988
|
+
React.createElement('input', {
|
|
989
|
+
className: 'ab-set-input ab-set-input-ui',
|
|
990
|
+
defaultValue: c.label || '',
|
|
991
|
+
placeholder: t('set.labelPlaceholder'),
|
|
992
|
+
onBlur: function (e) {
|
|
993
|
+
const next = e.target.value.trim()
|
|
994
|
+
if (!next || next === c.label) return
|
|
995
|
+
commitCrit({ label: next })
|
|
996
|
+
},
|
|
997
|
+
onKeyDown: function (e) { if (e.key === 'Enter') e.currentTarget.blur() },
|
|
998
|
+
}),
|
|
999
|
+
React.createElement('textarea', {
|
|
1000
|
+
className: 'ab-set-textarea',
|
|
1001
|
+
defaultValue: c.description || '',
|
|
1002
|
+
placeholder: t('set.descPlaceholder'),
|
|
1003
|
+
rows: 3,
|
|
1004
|
+
ref: sizeTextarea,
|
|
1005
|
+
onInput: function (e) { sizeTextarea(e.currentTarget) },
|
|
1006
|
+
onBlur: function (e) {
|
|
1007
|
+
const next = e.target.value.trim()
|
|
1008
|
+
if (next === (c.description || '')) return
|
|
1009
|
+
commitCrit({ description: next })
|
|
1010
|
+
},
|
|
1011
|
+
}),
|
|
1012
|
+
),
|
|
1013
|
+
)
|
|
1014
|
+
}),
|
|
1015
|
+
),
|
|
1016
|
+
React.createElement('div', { className: 'ab-set-item ab-set-item-crit' },
|
|
1017
|
+
React.createElement('div', { className: 'ab-set-crit-top' },
|
|
1018
|
+
React.createElement('input', {
|
|
1019
|
+
className: 'ab-set-input ab-set-input-grow', placeholder: t('set.criterionId'),
|
|
1020
|
+
value: newCriterion.id,
|
|
1021
|
+
onChange: function (e) { setNewCriterion(Object.assign({}, newCriterion, { id: e.target.value })) },
|
|
1022
|
+
}),
|
|
1023
|
+
React.createElement('select', {
|
|
1024
|
+
className: 'ab-set-select', value: newCriterion.action,
|
|
1025
|
+
onChange: function (e) { setNewCriterion(Object.assign({}, newCriterion, { action: e.target.value })) },
|
|
1026
|
+
},
|
|
1027
|
+
React.createElement('option', { value: 'human' }, t('action.human')),
|
|
1028
|
+
React.createElement('option', { value: 'allow' }, t('action.allow')),
|
|
1029
|
+
React.createElement('option', { value: 'reject' }, t('action.reject')),
|
|
1030
|
+
),
|
|
1031
|
+
),
|
|
1032
|
+
React.createElement('div', { className: 'ab-set-item-fields' },
|
|
1033
|
+
React.createElement('input', {
|
|
1034
|
+
className: 'ab-set-input ab-set-input-ui', placeholder: t('set.labelPlaceholder'),
|
|
1035
|
+
value: newCriterion.label,
|
|
1036
|
+
onChange: function (e) { setNewCriterion(Object.assign({}, newCriterion, { label: e.target.value })) },
|
|
1037
|
+
}),
|
|
1038
|
+
React.createElement('textarea', {
|
|
1039
|
+
className: 'ab-set-textarea',
|
|
1040
|
+
placeholder: t('set.descPlaceholder'),
|
|
1041
|
+
rows: 2,
|
|
1042
|
+
value: newCriterion.description,
|
|
1043
|
+
onChange: function (e) { setNewCriterion(Object.assign({}, newCriterion, { description: e.target.value })) },
|
|
1044
|
+
ref: sizeTextarea,
|
|
1045
|
+
onInput: function (e) { sizeTextarea(e.currentTarget) },
|
|
1046
|
+
}),
|
|
1047
|
+
),
|
|
1048
|
+
React.createElement('div', { className: 'ab-set-row ab-set-foot' },
|
|
1049
|
+
React.createElement('button', {
|
|
1050
|
+
type: 'button', className: 'ab-set-btn ab-set-btn-primary',
|
|
1051
|
+
disabled: busy || !(newCriterion.id || newCriterion.label),
|
|
1052
|
+
onClick: function () {
|
|
1053
|
+
run('rule-op', { op: 'add', kind: 'criteria', value: newCriterion })
|
|
1054
|
+
setNewCriterion({ id: '', label: '', description: '', action: 'human' })
|
|
1055
|
+
},
|
|
1056
|
+
}, t('set.addCriterion')),
|
|
1057
|
+
),
|
|
1058
|
+
),
|
|
1059
|
+
React.createElement('div', { className: 'ab-set-row ab-set-foot' },
|
|
1060
|
+
resetBtn(
|
|
1061
|
+
'criteria',
|
|
1062
|
+
t('set.resetCriteria', { lang: t(judgePromptLang === 'en' ? 'set.judgeLangEn' : 'set.judgeLangZh') }),
|
|
1063
|
+
t('set.resetCriteriaOk', { lang: t(judgePromptLang === 'en' ? 'set.judgeLangEn' : 'set.judgeLangZh') }),
|
|
1064
|
+
{ lang: judgePromptLang },
|
|
1065
|
+
),
|
|
1066
|
+
),
|
|
1067
|
+
),
|
|
1068
|
+
),
|
|
1069
|
+
|
|
1070
|
+
React.createElement('div', { className: 'ab-set-card', 'data-ab-stage': 'judge' },
|
|
1071
|
+
React.createElement('div', { className: 'ab-set-card-head' },
|
|
1072
|
+
React.createElement('div', { className: 'ab-set-card-title' }, t('set.judgeTitle')),
|
|
1073
|
+
React.createElement('p', { className: 'ab-set-card-sub' },
|
|
1074
|
+
t('set.judgeSubLead') +
|
|
1075
|
+
(fallback.provider && fallback.model
|
|
1076
|
+
? t('set.judgeSubFallback', { provider: fallback.provider, model: fallback.model })
|
|
1077
|
+
: t('set.judgeSubNoFallback')) +
|
|
1078
|
+
t('set.judgeSubTail'))),
|
|
1079
|
+
React.createElement('div', { className: 'ab-set-row' },
|
|
1080
|
+
React.createElement('select', {
|
|
1081
|
+
className: 'ab-set-select', value: judgeProvider,
|
|
1082
|
+
onChange: function (e) { setJudgeProvider(e.target.value); setJudgeModel(''); setJudgeEffort('') },
|
|
1083
|
+
},
|
|
1084
|
+
React.createElement('option', { value: '' }, t('set.followProvider')),
|
|
1085
|
+
providers.map(function (p) {
|
|
1086
|
+
return React.createElement('option', { value: p.id, key: p.id }, p.name || p.id)
|
|
1087
|
+
}),
|
|
1088
|
+
),
|
|
1089
|
+
React.createElement('select', {
|
|
1090
|
+
className: 'ab-set-select', value: judgeModel,
|
|
1091
|
+
onChange: function (e) { setJudgeModel(e.target.value); setJudgeEffort('') },
|
|
1092
|
+
},
|
|
1093
|
+
React.createElement('option', { value: '' }, t('set.followModel')),
|
|
1094
|
+
models.map(function (m) {
|
|
1095
|
+
return React.createElement('option', { value: m.id, key: m.id }, m.name || m.id)
|
|
1096
|
+
}),
|
|
1097
|
+
),
|
|
1098
|
+
React.createElement('select', {
|
|
1099
|
+
className: 'ab-set-select', value: judgeEffort,
|
|
1100
|
+
onChange: function (e) { setJudgeEffort(e.target.value) },
|
|
1101
|
+
},
|
|
1102
|
+
React.createElement('option', { value: '' }, t('set.modelDefaultEffort')),
|
|
1103
|
+
React.createElement('option', { value: 'off' }, t('set.effortOff')),
|
|
1104
|
+
efforts.filter(function (e) { return e.id !== 'off' }).map(function (e) {
|
|
1105
|
+
return React.createElement('option', { value: e.id, key: e.id }, e.name || e.id)
|
|
1106
|
+
}),
|
|
1107
|
+
),
|
|
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
|
+
),
|
|
1118
|
+
React.createElement('div', { className: 'ab-set-row' },
|
|
1119
|
+
React.createElement('span', { className: 'ab-set-item-meta' }, t('set.judgeLang')),
|
|
1120
|
+
React.createElement('select', {
|
|
1121
|
+
className: 'ab-set-select', value: judgePromptLang,
|
|
1122
|
+
onChange: function (e) { setJudgePromptLang(e.target.value === 'en' ? 'en' : 'zh') },
|
|
1123
|
+
},
|
|
1124
|
+
React.createElement('option', { value: 'zh' }, t('set.judgeLangZh')),
|
|
1125
|
+
React.createElement('option', { value: 'en' }, t('set.judgeLangEn')),
|
|
1126
|
+
),
|
|
1127
|
+
React.createElement('span', { className: 'ab-set-note' }, t('set.judgeLangHint')),
|
|
1128
|
+
),
|
|
1129
|
+
React.createElement('div', { className: 'ab-set-row' },
|
|
1130
|
+
React.createElement('span', { className: 'ab-set-item-meta' }, t('set.judgeTimeoutMs')),
|
|
1131
|
+
React.createElement('input', { className: 'ab-set-input ab-set-input-num', type: 'number', min: 1000, value: timeoutMs, onChange: function (e) { setTimeoutMs(e.target.value) } }),
|
|
1132
|
+
React.createElement('button', {
|
|
1133
|
+
type: 'button', className: 'ab-set-btn', disabled: busy,
|
|
1134
|
+
onClick: function () { run('rule-op', { op: 'set', kind: 'judgeTimeoutMs', value: Number(timeoutMs) }) },
|
|
1135
|
+
}, t('set.save')),
|
|
1136
|
+
),
|
|
1137
|
+
),
|
|
1138
|
+
|
|
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
|
+
)
|
|
1249
|
+
}
|
|
1250
|
+
|
|
1251
|
+
const plugin = {
|
|
1252
|
+
inject: ['connection', 'slots', 'locale'],
|
|
1253
|
+
async apply(ctx) {
|
|
1254
|
+
const slots = ctx.slots
|
|
1255
|
+
const connection = ctx.connection
|
|
1256
|
+
ctx.effect(function () { return ctx.locale.register(NS, { zh: ZH, en: EN }) })
|
|
1257
|
+
const t = ctx.locale.bind(NS)
|
|
1258
|
+
const rpc = makeRpc(connection, t)
|
|
1259
|
+
|
|
1260
|
+
let styleEl = null
|
|
1261
|
+
try {
|
|
1262
|
+
styleEl = document.createElement('style')
|
|
1263
|
+
styleEl.setAttribute('data-plugin-css', 'dsh-auto-approve')
|
|
1264
|
+
styleEl.textContent = CSS
|
|
1265
|
+
document.head.appendChild(styleEl)
|
|
1266
|
+
} catch (e) {
|
|
1267
|
+
console.error('[dsh-auto-approve] 注入样式失败:' + String((e && e.message) || e))
|
|
1268
|
+
}
|
|
1269
|
+
ctx.effect(() => {
|
|
1270
|
+
return () => {
|
|
1271
|
+
if (styleEl && styleEl.parentNode) {
|
|
1272
|
+
try { styleEl.parentNode.removeChild(styleEl) } catch (e) {}
|
|
1273
|
+
}
|
|
1274
|
+
}
|
|
1275
|
+
})
|
|
1276
|
+
|
|
1277
|
+
slots.inject('conversation.input.dock', function () {
|
|
1278
|
+
return slots.register(
|
|
1279
|
+
{ 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 }) },
|
|
1281
|
+
)
|
|
1282
|
+
})
|
|
1283
|
+
|
|
1284
|
+
slots.inject('conversation.view', function () {
|
|
1285
|
+
return slots.register(
|
|
1286
|
+
{
|
|
1287
|
+
name: 'conversation.view',
|
|
1288
|
+
id: 'dsh-auto-approve.history',
|
|
1289
|
+
order: 20,
|
|
1290
|
+
locale: NS,
|
|
1291
|
+
label: function () { return t('slot.history') },
|
|
1292
|
+
inject: (sessionId) => ({ sessionId }),
|
|
1293
|
+
},
|
|
1294
|
+
function (props) { return React.createElement(HistoryView, { slotsProps: props, rpc: rpc }) },
|
|
1295
|
+
)
|
|
1296
|
+
})
|
|
1297
|
+
|
|
1298
|
+
slots.inject('settings.section', function () {
|
|
1299
|
+
return slots.register(
|
|
1300
|
+
{ 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 }) },
|
|
1302
|
+
)
|
|
1303
|
+
})
|
|
1304
|
+
},
|
|
1305
|
+
}
|
|
1306
|
+
|
|
1307
|
+
exports.apply = plugin.apply
|
|
1308
|
+
exports.inject = plugin.inject
|
|
1309
|
+
exports.name = 'dsh-auto-approve'
|
|
1310
|
+
return module.exports
|
|
1311
|
+
},
|
|
1312
|
+
})
|