gitee-ai-employee 1.2.1 → 1.2.2
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/dsh/client.js +608 -608
- package/dsh/index.js +22 -5
- package/package.json +1 -1
package/dsh/client.js
CHANGED
|
@@ -1,608 +1,608 @@
|
|
|
1
|
-
// Browser half of the Gitee AI employee dsh plugin: a settings card in the
|
|
2
|
-
// installed-plugins list (设置 → 已安装插件). The card reuses the exact
|
|
3
|
-
// PluginCard.module.css class names injected by @deepseek-ai/dsh-client-ui
|
|
4
|
-
// -settings-plugins (YyYd_a_*), so it renders pixel-identical to the built-in
|
|
5
|
-
// Shell / Agent loop / Web search cards — a border, a 12px radius, a big bold
|
|
6
|
-
// name header with a description, a collapsing body of fields, and a
|
|
7
|
-
// discard/save footer.
|
|
8
|
-
//
|
|
9
|
-
// Hand-written in the lazy-CJS bundle protocol (window.__ModuleLoader__.load
|
|
10
|
-
// with a factory returning cordis-plugin exports), so no build step and no
|
|
11
|
-
// imports from dsh client packages — the same zero-dependency stance as the
|
|
12
|
-
// host half. `slots` is optional (registered lazily via ctx.inject), so a
|
|
13
|
-
// non-web profile simply never renders the card.
|
|
14
|
-
window.__ModuleLoader__.load({
|
|
15
|
-
id: 'gitee-ai-employee',
|
|
16
|
-
factory: (require) => {
|
|
17
|
-
var module = { exports: {} }
|
|
18
|
-
var exports = module.exports
|
|
19
|
-
|
|
20
|
-
function labels() {
|
|
21
|
-
return {
|
|
22
|
-
title: 'Gitee AI 员工',
|
|
23
|
-
description: 'issue @员工 → AI 开发 → PR → 自动关闭',
|
|
24
|
-
expand: '展开',
|
|
25
|
-
collapse: '收起',
|
|
26
|
-
unsaved: '未保存',
|
|
27
|
-
loading: '加载中…',
|
|
28
|
-
loadFailed: '配置加载失败',
|
|
29
|
-
discard: '放弃',
|
|
30
|
-
save: '保存',
|
|
31
|
-
saving: '保存中…',
|
|
32
|
-
savedDone: '保存完成,已生效',
|
|
33
|
-
readOnly: '只读',
|
|
34
|
-
token: 'Gitee 私人令牌',
|
|
35
|
-
tokenHint: '留空保持原值',
|
|
36
|
-
bot: '机器人账号',
|
|
37
|
-
botHint: 'issue 里 @ 它才触发',
|
|
38
|
-
workRoot: '仓库工作目录',
|
|
39
|
-
repos: '监听仓库',
|
|
40
|
-
reposHint: '每行一个 owner/repo',
|
|
41
|
-
poll: '启用定时轮询',
|
|
42
|
-
interval: '轮询间隔(毫秒)',
|
|
43
|
-
merge: '自动合并 PR',
|
|
44
|
-
close: '成功后自动关闭 issue',
|
|
45
|
-
status: '查看任务状态',
|
|
46
|
-
scan: '代码安全扫描',
|
|
47
|
-
scanEnable: '启用定时扫描(全部仓库排队持续扫描,去重幂等)',
|
|
48
|
-
scanReposLabel: '扫描仓库',
|
|
49
|
-
scanReposHint: '每行一个 owner/repo
|
|
50
|
-
scanSeverity: '最低上报级别',
|
|
51
|
-
scanSeverityHint: 'critical / high / medium / low / none',
|
|
52
|
-
scanOneIssue: '一次扫描合并为一个 issue',
|
|
53
|
-
scanConcurrency: '同时扫描仓库数(1~8,默认 3,自动排队)',
|
|
54
|
-
scanPromptsLabel: '自定义提示词(JSON)',
|
|
55
|
-
scanPromptsHint: '{"id":{"name":"名称","prompt":"内容"}},同 id 覆盖内置,留空用内置',
|
|
56
|
-
scanInterval: '扫描间隔(毫秒)',
|
|
57
|
-
scanFetch: '一键获取我的仓库',
|
|
58
|
-
scanFetching: '获取中…',
|
|
59
|
-
scanNow: '立即扫描全部仓库',
|
|
60
|
-
scanning: '触发中…',
|
|
61
|
-
scanDone: '已触发,结果见 /gitee-ai/scan',
|
|
62
|
-
scanFailed: '触发失败',
|
|
63
|
-
scanNeedRepos: '请先填写扫描仓库',
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
function ConfigCard(react, ui) {
|
|
68
|
-
var h = react.createElement
|
|
69
|
-
var Input = ui && ui.Input
|
|
70
|
-
var Chevron = ui && ui.IconChevronDownOutline14
|
|
71
|
-
|
|
72
|
-
// 复用内置 settings-plugins 注入的字段样式类(At1oFq_*)
|
|
73
|
-
var fieldRow = (label, hint, control, key, groupName) =>
|
|
74
|
-
h(
|
|
75
|
-
groupName ? 'div' : 'label',
|
|
76
|
-
{
|
|
77
|
-
key: key,
|
|
78
|
-
role: groupName ? 'group' : undefined,
|
|
79
|
-
'aria-label': groupName || undefined,
|
|
80
|
-
className: 'At1oFq_field',
|
|
81
|
-
style: { display: 'flex', flexDirection: 'column', gap: '6px', padding: '12px 0' },
|
|
82
|
-
},
|
|
83
|
-
h('div', { className: 'At1oFq_head' },
|
|
84
|
-
h('span', { className: 'At1oFq_label' }, label),
|
|
85
|
-
hint
|
|
86
|
-
? h('span', { className: 'At1oFq_badgeMuted', style: { fontSize: '11px', lineHeight: '17px', color: 'var(--dsw-alias-label-tertiary)' } }, hint)
|
|
87
|
-
: null,
|
|
88
|
-
),
|
|
89
|
-
control,
|
|
90
|
-
)
|
|
91
|
-
|
|
92
|
-
// 自定义开关行:左侧标签,右侧紧凑的 iOS 风格 toggle
|
|
93
|
-
var toggleRow = (label, hint, checked, onChange, key) =>
|
|
94
|
-
h(
|
|
95
|
-
'div',
|
|
96
|
-
{
|
|
97
|
-
key: key,
|
|
98
|
-
className: 'At1oFq_field',
|
|
99
|
-
style: { display: 'flex', flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', gap: '12px', padding: '10px 0' },
|
|
100
|
-
},
|
|
101
|
-
h('div', { style: { minWidth: 0, display: 'flex', flexDirection: 'column', gap: '2px' } },
|
|
102
|
-
h('span', {
|
|
103
|
-
className: 'At1oFq_label',
|
|
104
|
-
style: { margin: 0, fontSize: '13px', fontWeight: 500, color: 'var(--dsw-alias-label-primary)' },
|
|
105
|
-
}, label),
|
|
106
|
-
hint
|
|
107
|
-
? h('span', { style: { fontSize: '12px', color: 'var(--dsw-alias-label-tertiary)', lineHeight: 1.4 } }, hint)
|
|
108
|
-
: null,
|
|
109
|
-
),
|
|
110
|
-
h(Switch, { checked: !!checked, onChange, label }),
|
|
111
|
-
)
|
|
112
|
-
|
|
113
|
-
// 紧凑圆形开关(轨道 + 滑块),用 DSH 主题 token 着色
|
|
114
|
-
function Switch(props) {
|
|
115
|
-
var on = !!props.checked
|
|
116
|
-
return h(
|
|
117
|
-
'button',
|
|
118
|
-
{
|
|
119
|
-
type: 'button',
|
|
120
|
-
role: 'switch',
|
|
121
|
-
'aria-checked': on,
|
|
122
|
-
'aria-label': props.label,
|
|
123
|
-
onClick: () => props.onChange(!on),
|
|
124
|
-
style: {
|
|
125
|
-
appearance: 'none',
|
|
126
|
-
flex: 'none',
|
|
127
|
-
width: '36px',
|
|
128
|
-
height: '20px',
|
|
129
|
-
borderRadius: '999px',
|
|
130
|
-
border: 0,
|
|
131
|
-
padding: 0,
|
|
132
|
-
cursor: 'pointer',
|
|
133
|
-
position: 'relative',
|
|
134
|
-
background: on
|
|
135
|
-
? 'var(--dsw-alias-brand-primary, #2563eb)'
|
|
136
|
-
: 'var(--dsw-alias-bg-module-platform, rgba(127,127,127,0.35))',
|
|
137
|
-
transition: 'background .16s',
|
|
138
|
-
outline: 'none',
|
|
139
|
-
boxShadow: 'inset 0 0 0 1px rgba(127,127,127,0.25)',
|
|
140
|
-
},
|
|
141
|
-
},
|
|
142
|
-
h('span', {
|
|
143
|
-
style: {
|
|
144
|
-
position: 'absolute',
|
|
145
|
-
top: '2px',
|
|
146
|
-
left: on ? '18px' : '2px',
|
|
147
|
-
width: '16px',
|
|
148
|
-
height: '16px',
|
|
149
|
-
borderRadius: '999px',
|
|
150
|
-
background: '#fff',
|
|
151
|
-
boxShadow: '0 1px 2px rgba(0,0,0,0.35)',
|
|
152
|
-
transition: 'left .16s',
|
|
153
|
-
},
|
|
154
|
-
}),
|
|
155
|
-
)
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
// 平衡:onChange 里用 ref 读最新 draft,避免闭包陈旧
|
|
159
|
-
var draftRef = { current: null }
|
|
160
|
-
|
|
161
|
-
return function GiteeCard() {
|
|
162
|
-
var t = labels()
|
|
163
|
-
var openState = react.useState(false)
|
|
164
|
-
var summaryState = react.useState(null)
|
|
165
|
-
var draftState = react.useState(null)
|
|
166
|
-
var busyState = react.useState(false)
|
|
167
|
-
var noteState = react.useState('')
|
|
168
|
-
var failedState = react.useState(false)
|
|
169
|
-
var dirtyState = react.useState(false)
|
|
170
|
-
var open = openState[0]
|
|
171
|
-
var summary = summaryState[0]
|
|
172
|
-
var draft = draftState[0]
|
|
173
|
-
var busy = busyState[0]
|
|
174
|
-
var note = noteState[0]
|
|
175
|
-
var failed = failedState[0]
|
|
176
|
-
var dirty = dirtyState[0]
|
|
177
|
-
var scanBusyState = react.useState(false)
|
|
178
|
-
var scanNoteState = react.useState('')
|
|
179
|
-
var scanBusy = scanBusyState[0]
|
|
180
|
-
var scanNote = scanNoteState[0]
|
|
181
|
-
var fetchBusyState = react.useState(false)
|
|
182
|
-
var fetchNoteState = react.useState('')
|
|
183
|
-
var fetchBusy = fetchBusyState[0]
|
|
184
|
-
var fetchNote = fetchNoteState[0]
|
|
185
|
-
|
|
186
|
-
draftRef.current = draft
|
|
187
|
-
|
|
188
|
-
var seedDraft = (config) => ({
|
|
189
|
-
botName: (config && config.botName) || '',
|
|
190
|
-
workRoot: (config && config.workRoot) || '',
|
|
191
|
-
watchRepos: ((config && config.watchRepos) || []).join('\n'),
|
|
192
|
-
pollEnabled: !!(config && config.pollEnabled),
|
|
193
|
-
pollIntervalMs: (config && config.pollIntervalMs) || 60000,
|
|
194
|
-
autoMerge: !!(config && config.autoMerge),
|
|
195
|
-
autoCloseIssue: !!(config && config.autoCloseIssue),
|
|
196
|
-
tokenConfigured: !!(config && config.tokenConfigured),
|
|
197
|
-
githubTokenConfigured: !!(config && config.githubTokenConfigured),
|
|
198
|
-
scanEnabled: !!(config && config.scanEnabled),
|
|
199
|
-
scanRepos: ((config && config.scanRepos) || []).join('\n'),
|
|
200
|
-
scanMinSeverity: (config && config.scanMinSeverity) || 'medium',
|
|
201
|
-
scanOneIssuePerRun: (config && config.scanOneIssuePerRun) !== false,
|
|
202
|
-
scanPrompts: (config && config.scanPrompts && Object.keys(config.scanPrompts).length)
|
|
203
|
-
? JSON.stringify(config.scanPrompts, null, 2) : '',
|
|
204
|
-
scanIntervalMs: (config && config.scanIntervalMs) || 21600000,
|
|
205
|
-
scanConcurrency: (config && config.scanConcurrency) || 3,
|
|
206
|
-
})
|
|
207
|
-
|
|
208
|
-
var load = react.useCallback(() => {
|
|
209
|
-
fetch('/gitee-ai/config')
|
|
210
|
-
.then((r) => r.json().then((body) => {
|
|
211
|
-
if (!r.ok) throw new Error(body.error || 'load failed')
|
|
212
|
-
return body
|
|
213
|
-
}))
|
|
214
|
-
.then((body) => {
|
|
215
|
-
summaryState[1](body.config)
|
|
216
|
-
var d = seedDraft(body.config)
|
|
217
|
-
draftRef.current = d
|
|
218
|
-
draftState[1](d)
|
|
219
|
-
noteState[1]('')
|
|
220
|
-
failedState[1](false)
|
|
221
|
-
})
|
|
222
|
-
.catch((error) => {
|
|
223
|
-
noteState[1](String(error.message ? error.message : error))
|
|
224
|
-
failedState[1](true)
|
|
225
|
-
})
|
|
226
|
-
}, [])
|
|
227
|
-
|
|
228
|
-
react.useEffect(() => {
|
|
229
|
-
if (open && summary === null) load()
|
|
230
|
-
}, [open, summary, load])
|
|
231
|
-
|
|
232
|
-
var setDraft = (d) => { draftState[1](d); dirtyState[1](true) }
|
|
233
|
-
var setDraftFromControl = (patch) => {
|
|
234
|
-
var d = Object.assign({}, draftRef.current, patch)
|
|
235
|
-
draftRef.current = d
|
|
236
|
-
draftState[1](d)
|
|
237
|
-
dirtyState[1](true)
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
var save = () => {
|
|
241
|
-
if (!draft) return
|
|
242
|
-
var prompts = {}
|
|
243
|
-
if (draft.scanPrompts && draft.scanPrompts.trim()) {
|
|
244
|
-
try {
|
|
245
|
-
var parsed = JSON.parse(draft.scanPrompts)
|
|
246
|
-
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
|
|
247
|
-
for (var id in parsed) {
|
|
248
|
-
var pv = parsed[id] || {}
|
|
249
|
-
var nm = String(pv.name || '').trim()
|
|
250
|
-
var pt = String(pv.prompt || '').trim()
|
|
251
|
-
if (nm && pt) prompts[id] = { name: nm, prompt: pt }
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
} catch (e) {
|
|
255
|
-
noteState[1]('提示词 JSON 格式错误,请检查')
|
|
256
|
-
failedState[1](true)
|
|
257
|
-
return
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
busyState[1](true)
|
|
261
|
-
failedState[1](false)
|
|
262
|
-
noteState[1]('')
|
|
263
|
-
var payload = {
|
|
264
|
-
...((draft.token && draft.token.trim()) ? { giteeToken: draft.token.trim() } : {}),
|
|
265
|
-
...((draft.githubToken && draft.githubToken.trim()) ? { githubToken: draft.githubToken.trim() } : {}),
|
|
266
|
-
botName: draft.botName,
|
|
267
|
-
workRoot: draft.workRoot,
|
|
268
|
-
watchRepos: (draft.watchRepos || '').split(/\r?\n/).map((s) => s.trim()).filter(Boolean),
|
|
269
|
-
pollEnabled: draft.pollEnabled,
|
|
270
|
-
pollIntervalMs: Number(draft.pollIntervalMs) || 60000,
|
|
271
|
-
autoMerge: draft.autoMerge,
|
|
272
|
-
autoCloseIssue: draft.autoCloseIssue,
|
|
273
|
-
scanEnabled: !!draft.scanEnabled,
|
|
274
|
-
scanRepos: (draft.scanRepos || '').split(/\r?\n/).map((s) => s.trim()).filter(Boolean),
|
|
275
|
-
scanMinSeverity: draft.scanMinSeverity || 'medium',
|
|
276
|
-
scanOneIssuePerRun: draft.scanOneIssuePerRun !== false,
|
|
277
|
-
scanPrompts: prompts,
|
|
278
|
-
scanIntervalMs: Number(draft.scanIntervalMs) || 21600000,
|
|
279
|
-
scanConcurrency: Number(draft.scanConcurrency) || 3,
|
|
280
|
-
}
|
|
281
|
-
fetch('/gitee-ai/config', {
|
|
282
|
-
method: 'POST',
|
|
283
|
-
headers: { 'Content-Type': 'application/json' },
|
|
284
|
-
body: JSON.stringify(payload),
|
|
285
|
-
})
|
|
286
|
-
.then((r) => r.json().then((body) => {
|
|
287
|
-
if (!r.ok) throw new Error(body.error || 'save failed')
|
|
288
|
-
return body
|
|
289
|
-
}))
|
|
290
|
-
.then((body) => {
|
|
291
|
-
noteState[1](t.savedDone)
|
|
292
|
-
summaryState[1](body.config)
|
|
293
|
-
var d = seedDraft(body.config)
|
|
294
|
-
draftRef.current = d
|
|
295
|
-
draftState[1](d)
|
|
296
|
-
dirtyState[1](false)
|
|
297
|
-
})
|
|
298
|
-
.catch((error) => {
|
|
299
|
-
noteState[1](String(error.message ? error.message : error))
|
|
300
|
-
failedState[1](true)
|
|
301
|
-
})
|
|
302
|
-
.finally(() => busyState[1](false))
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
var discard = () => {
|
|
306
|
-
if (!summary) return
|
|
307
|
-
var d = seedDraft(summary)
|
|
308
|
-
draftRef.current = d
|
|
309
|
-
draftState[1](d)
|
|
310
|
-
dirtyState[1](false)
|
|
311
|
-
noteState[1]('')
|
|
312
|
-
failedState[1](false)
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
// 立即扫描:对 draft 里的 scanRepos 逐个 POST /gitee-ai/scan(服务端自动排队,按并发上限持续扫描)
|
|
316
|
-
var scanNow = () => {
|
|
317
|
-
if (!draft) return
|
|
318
|
-
var repos = (draft.scanRepos || '').split(/\r?\n/).map((s) => s.trim()).filter(Boolean)
|
|
319
|
-
if (!repos.length) { scanNoteState[1](t.scanNeedRepos); return }
|
|
320
|
-
scanBusyState[1](true)
|
|
321
|
-
scanNoteState[1]('')
|
|
322
|
-
var accepted = 0
|
|
323
|
-
var skipped = 0
|
|
324
|
-
var tasks = repos.map(function (spec) {
|
|
325
|
-
var platform = /^github:/i.test(spec) ? 'github' : /^gitee:/i.test(spec) ? 'gitee' : 'gitee'
|
|
326
|
-
var rest = spec.replace(/^(github|gitee):/i, '')
|
|
327
|
-
var idx = rest.indexOf('/')
|
|
328
|
-
var owner = rest.slice(0, idx)
|
|
329
|
-
var repo = rest.slice(idx + 1)
|
|
330
|
-
if (!owner || !repo) return Promise.resolve({ accepted: false })
|
|
331
|
-
return fetch('/gitee-ai/scan', {
|
|
332
|
-
method: 'POST',
|
|
333
|
-
headers: { 'Content-Type': 'application/json' },
|
|
334
|
-
body: JSON.stringify({ platform: platform, owner: owner, repo: repo, dryRun: false }),
|
|
335
|
-
}).then(function (r) { return r.json().catch(function () { return { accepted: false } }) })
|
|
336
|
-
.then(function (b) {
|
|
337
|
-
if (b && b.accepted) accepted++
|
|
338
|
-
else skipped++
|
|
339
|
-
return b
|
|
340
|
-
})
|
|
341
|
-
})
|
|
342
|
-
Promise.all(tasks)
|
|
343
|
-
.then(function () {
|
|
344
|
-
scanNoteState[1]('已排队 ' + accepted + ' 个' + (skipped ? '(跳过 ' + skipped + ' 个:正在扫/已在队列)' : '') + ',同时最多扫 ' + (draft.scanConcurrency || 3) + ' 个')
|
|
345
|
-
})
|
|
346
|
-
.catch(function () { scanNoteState[1](t.scanFailed) })
|
|
347
|
-
.finally(function () { scanBusyState[1](false) })
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
// 一键获取我的仓库:拉取有权限的仓库(含所属组织),追加进 scanRepos 供人工勾选
|
|
351
|
-
var normLine = (s) => String(s || '').replace(/^(gitee|github):/i, '').toLowerCase()
|
|
352
|
-
var fetchMyRepos = () => {
|
|
353
|
-
if (fetchBusy) return
|
|
354
|
-
fetchBusyState[1](true)
|
|
355
|
-
fetchNoteState[1]('')
|
|
356
|
-
fetch('/gitee-ai/my-repos')
|
|
357
|
-
.then((r) => r.json().catch(() => ({ ok: false, error: '响应异常' })))
|
|
358
|
-
.then((b) => {
|
|
359
|
-
if (!b.ok || !Array.isArray(b.repos)) { fetchNoteState[1]('获取失败:' + ((b && b.error) || '未知错误')); return }
|
|
360
|
-
var cur = (draftRef.current.scanRepos || '').split(/\r?\n/).map((s) => s.trim()).filter(Boolean)
|
|
361
|
-
var seen = {}
|
|
362
|
-
cur.forEach((s) => { seen[normLine(s)] = true })
|
|
363
|
-
var lines = cur.slice()
|
|
364
|
-
b.repos.forEach((x) => {
|
|
365
|
-
var line = x.platform + ':' + x.owner + '/' + x.repo
|
|
366
|
-
if (!seen[normLine(line)]) { lines.push(line); seen[normLine(line)] = true }
|
|
367
|
-
})
|
|
368
|
-
setDraftFromControl({ scanRepos: lines.join('\n') })
|
|
369
|
-
fetchNoteState[1]('已填入 ' + b.repos.length + ' 个仓库(含既有保留,去掉不需要的再保存)')
|
|
370
|
-
})
|
|
371
|
-
.catch((e) => fetchNoteState[1]('获取失败:' + String(e.message || e)))
|
|
372
|
-
.finally(() => fetchBusyState[1](false))
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
// ── 头部(大标题 + 描述 + 展开箭头)──
|
|
376
|
-
var statusLine = summary
|
|
377
|
-
? '轮询' + (summary.pollEnabled ? ' 开 · ' + Math.round((summary.pollIntervalMs || 0) / 1000) + 's' : ' 关')
|
|
378
|
-
+ ' · 仓库 ' + ((summary.watchRepos || []).length) + ' 个'
|
|
379
|
-
+ ' · 扫描' + (summary.scanEnabled ? ' 开' : ' 关')
|
|
380
|
-
+ (summary.tokenConfigured ? '' : ' · 未配置令牌')
|
|
381
|
-
: t.description
|
|
382
|
-
|
|
383
|
-
var chevronEl = Chevron
|
|
384
|
-
? h(Chevron, {
|
|
385
|
-
className: 'YyYd_a_chevron' + (open ? ' YyYd_a_chevronOpen' : ''),
|
|
386
|
-
})
|
|
387
|
-
: h('span', {
|
|
388
|
-
className: 'YyYd_a_chevron' + (open ? ' YyYd_a_chevronOpen' : ''),
|
|
389
|
-
style: { display: 'inline-flex' },
|
|
390
|
-
}, '▾')
|
|
391
|
-
|
|
392
|
-
var header = h(
|
|
393
|
-
'button',
|
|
394
|
-
{
|
|
395
|
-
type: 'button',
|
|
396
|
-
className: 'YyYd_a_header',
|
|
397
|
-
'aria-expanded': open,
|
|
398
|
-
'aria-label': (open ? t.collapse : t.expand) + ': ' + t.title,
|
|
399
|
-
onClick: () => openState[1](!open),
|
|
400
|
-
},
|
|
401
|
-
h('span', { className: 'YyYd_a_headText' },
|
|
402
|
-
h('span', { className: 'YyYd_a_name' }, t.title),
|
|
403
|
-
h('span', { className: 'YyYd_a_description' }, statusLine),
|
|
404
|
-
),
|
|
405
|
-
dirty ? h('span', { className: 'YyYd_a_pending' }, t.unsaved) : null,
|
|
406
|
-
chevronEl,
|
|
407
|
-
)
|
|
408
|
-
|
|
409
|
-
// ── 主体(字段 + 底部保存)──
|
|
410
|
-
var body = null
|
|
411
|
-
if (open) {
|
|
412
|
-
if (!summary || !draft) {
|
|
413
|
-
body = h(
|
|
414
|
-
'div',
|
|
415
|
-
{ className: 'YyYd_a_body', style: { padding: '12px 16px', color: 'var(--dsw-alias-label-tertiary)', fontSize: '13px' } },
|
|
416
|
-
note || t.loading,
|
|
417
|
-
)
|
|
418
|
-
} else {
|
|
419
|
-
var controls = []
|
|
420
|
-
controls.push(fieldRow(t.token, draft.tokenConfigured ? t.tokenHint : undefined, h(Input, {
|
|
421
|
-
key: 'token',
|
|
422
|
-
type: 'password',
|
|
423
|
-
placeholder: draft.tokenConfigured ? '••••••••(留空保持原值)' : '输入 Gitee 私人令牌',
|
|
424
|
-
value: (draft.token !== undefined ? draft.token : ''),
|
|
425
|
-
onChange: (e) => setDraftFromControl({ token: e.target.value }),
|
|
426
|
-
}), 'token'))
|
|
427
|
-
controls.push(fieldRow('GitHub 令牌(可选)', '留空保持原值;用于 github: 前缀的仓库', h(Input, {
|
|
428
|
-
key: 'githubToken',
|
|
429
|
-
type: 'password',
|
|
430
|
-
placeholder: draft.githubTokenConfigured ? '••••••••(留空保持原值)' : '输入 GitHub 私人令牌(可选)',
|
|
431
|
-
value: (draft.githubToken !== undefined ? draft.githubToken : ''),
|
|
432
|
-
onChange: (e) => setDraftFromControl({ githubToken: e.target.value }),
|
|
433
|
-
}), 'githubToken'))
|
|
434
|
-
controls.push(fieldRow(t.bot, t.botHint, h(Input, {
|
|
435
|
-
key: 'bot',
|
|
436
|
-
value: draft.botName || '',
|
|
437
|
-
onChange: (e) => setDraftFromControl({ botName: e.target.value }),
|
|
438
|
-
}), 'bot'))
|
|
439
|
-
controls.push(fieldRow(t.workRoot, undefined, h(Input, {
|
|
440
|
-
key: 'workRoot',
|
|
441
|
-
value: draft.workRoot || '',
|
|
442
|
-
onChange: (e) => setDraftFromControl({ workRoot: e.target.value }),
|
|
443
|
-
}), 'workRoot'))
|
|
444
|
-
controls.push(fieldRow(t.repos, t.reposHint, h('textarea', {
|
|
445
|
-
key: 'repos',
|
|
446
|
-
rows: 3,
|
|
447
|
-
className: 'At1oFq_input',
|
|
448
|
-
style: { height: 'auto', padding: '8px 12px', resize: 'vertical', lineHeight: 1.5 },
|
|
449
|
-
value: draft.watchRepos || '',
|
|
450
|
-
onChange: (e) => setDraftFromControl({ watchRepos: e.target.value }),
|
|
451
|
-
}), 'repos'))
|
|
452
|
-
controls.push(toggleRow(t.poll, undefined, draft.pollEnabled, (v) => setDraftFromControl({ pollEnabled: v }), 'poll'))
|
|
453
|
-
controls.push(fieldRow(t.interval, undefined, h(Input, {
|
|
454
|
-
key: 'interval',
|
|
455
|
-
type: 'number',
|
|
456
|
-
value: draft.pollIntervalMs,
|
|
457
|
-
onChange: (e) => setDraftFromControl({ pollIntervalMs: e.target.value }),
|
|
458
|
-
}), 'interval'))
|
|
459
|
-
controls.push(toggleRow(t.merge, undefined, draft.autoMerge, (v) => setDraftFromControl({ autoMerge: v }), 'merge'))
|
|
460
|
-
controls.push(toggleRow(t.close, undefined, draft.autoCloseIssue, (v) => setDraftFromControl({ autoCloseIssue: v }), 'close'))
|
|
461
|
-
|
|
462
|
-
// ── 代码安全扫描区(v1.2)──
|
|
463
|
-
controls.push(h('div', { key: 'scanTitle', className: 'At1oFq_label', style: { margin: '16px 0 4px', fontSize: '13px', fontWeight: 600, color: 'var(--dsw-alias-label-primary)' } }, t.scan))
|
|
464
|
-
controls.push(toggleRow(t.scanEnable, undefined, draft.scanEnabled, (v) => setDraftFromControl({ scanEnabled: v }), 'scanEnable'))
|
|
465
|
-
controls.push(fieldRow(t.scanReposLabel, t.scanReposHint, h('textarea', {
|
|
466
|
-
key: 'scanRepos', rows: 3,
|
|
467
|
-
className: 'At1oFq_input',
|
|
468
|
-
style: { height: 'auto', padding: '8px 12px', resize: 'vertical', lineHeight: 1.5 },
|
|
469
|
-
value: draft.scanRepos || '',
|
|
470
|
-
onChange: (e) => setDraftFromControl({ scanRepos: e.target.value }),
|
|
471
|
-
}), 'scanRepos'))
|
|
472
|
-
controls.push(h('div', { key: 'fetchReposRow', style: { display: 'flex', gap: '8px', alignItems: 'center', padding: '4px 0', flexWrap: 'wrap' } },
|
|
473
|
-
h('button', {
|
|
474
|
-
type: 'button',
|
|
475
|
-
onClick: fetchMyRepos,
|
|
476
|
-
disabled: fetchBusy,
|
|
477
|
-
style: { background: '#2f6fed', color: '#fff', border: 0, borderRadius: '8px', padding: '6px 12px', fontSize: '12px', cursor: 'pointer' },
|
|
478
|
-
}, fetchBusy ? t.scanFetching : t.scanFetch),
|
|
479
|
-
fetchNote ? h('span', { style: { fontSize: '12px', color: 'var(--dsw-alias-label-secondary)' } }, fetchNote) : null,
|
|
480
|
-
))
|
|
481
|
-
controls.push(fieldRow(t.scanSeverity, t.scanSeverityHint, h('select', {
|
|
482
|
-
key: 'scanSeverity',
|
|
483
|
-
className: 'At1oFq_input',
|
|
484
|
-
style: { padding: '8px 12px' },
|
|
485
|
-
value: draft.scanMinSeverity || 'medium',
|
|
486
|
-
onChange: (e) => setDraftFromControl({ scanMinSeverity: e.target.value }),
|
|
487
|
-
}, ['critical', 'high', 'medium', 'low', 'none'].map((s) => h('option', { key: s, value: s }, s))), 'scanSeverity'))
|
|
488
|
-
controls.push(toggleRow(t.scanOneIssue, undefined, draft.scanOneIssuePerRun, (v) => setDraftFromControl({ scanOneIssuePerRun: v }), 'scanOneIssue'))
|
|
489
|
-
controls.push(fieldRow(t.scanConcurrency, undefined, h(Input, {
|
|
490
|
-
key: 'scanConcurrency', type: 'number', min: 1, max: 8,
|
|
491
|
-
value: draft.scanConcurrency,
|
|
492
|
-
onChange: (e) => setDraftFromControl({ scanConcurrency: e.target.value }),
|
|
493
|
-
}), 'scanConcurrency'))
|
|
494
|
-
controls.push(fieldRow(t.scanPromptsLabel, t.scanPromptsHint, h('textarea', {
|
|
495
|
-
key: 'scanPrompts', rows: 4,
|
|
496
|
-
className: 'At1oFq_input',
|
|
497
|
-
style: { height: 'auto', padding: '8px 12px', resize: 'vertical', lineHeight: 1.4, fontFamily: 'Consolas,monospace', fontSize: '11px' },
|
|
498
|
-
value: draft.scanPrompts || '',
|
|
499
|
-
onChange: (e) => setDraftFromControl({ scanPrompts: e.target.value }),
|
|
500
|
-
}), 'scanPrompts'))
|
|
501
|
-
controls.push(fieldRow(t.scanInterval, undefined, h(Input, {
|
|
502
|
-
key: 'scanInterval', type: 'number',
|
|
503
|
-
value: draft.scanIntervalMs,
|
|
504
|
-
onChange: (e) => setDraftFromControl({ scanIntervalMs: e.target.value }),
|
|
505
|
-
}), 'scanInterval'))
|
|
506
|
-
controls.push(h('div', { key: 'scanNowRow', style: { display: 'flex', gap: '8px', alignItems: 'center', padding: '8px 0', flexWrap: 'wrap' } },
|
|
507
|
-
h('button', {
|
|
508
|
-
type: 'button',
|
|
509
|
-
onClick: scanNow,
|
|
510
|
-
disabled: scanBusy,
|
|
511
|
-
style: { background: 'var(--dsw-alias-danger, #dc2626)', color: '#fff', border: 0, borderRadius: '8px', padding: '8px 14px', fontSize: '13px', cursor: 'pointer' },
|
|
512
|
-
}, scanBusy ? t.scanning : t.scanNow),
|
|
513
|
-
scanNote ? h('span', { style: { fontSize: '12px', color: 'var(--dsw-alias-label-secondary)' } }, scanNote) : null,
|
|
514
|
-
))
|
|
515
|
-
|
|
516
|
-
body = h(
|
|
517
|
-
'div',
|
|
518
|
-
{ className: 'YyYd_a_body' },
|
|
519
|
-
h('a', {
|
|
520
|
-
href: '/gitee-ai/settings',
|
|
521
|
-
target: '_blank',
|
|
522
|
-
rel: 'noreferrer',
|
|
523
|
-
style: { display: 'inline-block', marginTop: '12px', fontSize: '12px', color: 'var(--dsw-alias-label-secondary)', textDecoration: 'underline' },
|
|
524
|
-
}, t.status + ' ↗'),
|
|
525
|
-
controls,
|
|
526
|
-
note
|
|
527
|
-
? h('p', {
|
|
528
|
-
className: failed ? 'YyYd_a_failed' : 'YyYd_a_readOnly',
|
|
529
|
-
role: 'status',
|
|
530
|
-
style: failed ? {} : { margin: '8px 0 0', fontSize: '12px', lineHeight: '1.5' },
|
|
531
|
-
}, note)
|
|
532
|
-
: null,
|
|
533
|
-
h('div', { className: 'YyYd_a_footer' },
|
|
534
|
-
h('button', {
|
|
535
|
-
type: 'button',
|
|
536
|
-
className: 'YyYd_a_discard',
|
|
537
|
-
disabled: !dirty || busy,
|
|
538
|
-
onClick: discard,
|
|
539
|
-
}, t.discard),
|
|
540
|
-
h('button', {
|
|
541
|
-
type: 'button',
|
|
542
|
-
className: 'YyYd_a_save',
|
|
543
|
-
disabled: busy,
|
|
544
|
-
onClick: save,
|
|
545
|
-
}, busy ? t.saving : t.save),
|
|
546
|
-
),
|
|
547
|
-
)
|
|
548
|
-
}
|
|
549
|
-
}
|
|
550
|
-
|
|
551
|
-
return h(
|
|
552
|
-
'li',
|
|
553
|
-
{ className: 'YyYd_a_card' + (open ? ' YyYd_a_cardOpen' : '') },
|
|
554
|
-
header,
|
|
555
|
-
body,
|
|
556
|
-
)
|
|
557
|
-
}
|
|
558
|
-
}
|
|
559
|
-
|
|
560
|
-
function registerCard(ctx) {
|
|
561
|
-
if (typeof ctx.inject !== 'function') return
|
|
562
|
-
ctx.inject(['slots'], (scope) => {
|
|
563
|
-
fetch('/gitee-ai/config')
|
|
564
|
-
.then((response) => {
|
|
565
|
-
if (response.status === 404) return
|
|
566
|
-
try {
|
|
567
|
-
mountCard(scope)
|
|
568
|
-
} catch (error) {
|
|
569
|
-
console.error('[gitee-ai] settings card skipped: ' + error)
|
|
570
|
-
}
|
|
571
|
-
})
|
|
572
|
-
.catch(() => {})
|
|
573
|
-
})
|
|
574
|
-
}
|
|
575
|
-
|
|
576
|
-
function mountCard(ctx) {
|
|
577
|
-
var react
|
|
578
|
-
try {
|
|
579
|
-
react = require('react')
|
|
580
|
-
} catch (error) {
|
|
581
|
-
console.error('[gitee-ai] settings card skipped: ' + error)
|
|
582
|
-
return
|
|
583
|
-
}
|
|
584
|
-
var ui
|
|
585
|
-
try {
|
|
586
|
-
ui = require('@deepseek-ai/dsh-client-ui-primitives')
|
|
587
|
-
} catch (error) {
|
|
588
|
-
ui = {}
|
|
589
|
-
}
|
|
590
|
-
var Card = ConfigCard(react, ui)
|
|
591
|
-
ctx.slots.inject('settings.plugin.item', function* () {
|
|
592
|
-
yield ctx.slots.register({ name: 'settings.plugin.item', id: 'gitee-ai-employee', key: 'gitee-ai-employee', order: 30 }, Card)
|
|
593
|
-
})
|
|
594
|
-
}
|
|
595
|
-
|
|
596
|
-
function apply(ctx) {
|
|
597
|
-
registerCard(ctx)
|
|
598
|
-
if (typeof ctx.effect === 'function') {
|
|
599
|
-
ctx.effect(() => () => {}, 'gitee-ai: settings card')
|
|
600
|
-
}
|
|
601
|
-
}
|
|
602
|
-
|
|
603
|
-
exports.apply = apply
|
|
604
|
-
// `slots` is optional, so it is not required here: registerCard checks.
|
|
605
|
-
exports.inject = []
|
|
606
|
-
return module.exports
|
|
607
|
-
},
|
|
608
|
-
})
|
|
1
|
+
// Browser half of the Gitee AI employee dsh plugin: a settings card in the
|
|
2
|
+
// installed-plugins list (设置 → 已安装插件). The card reuses the exact
|
|
3
|
+
// PluginCard.module.css class names injected by @deepseek-ai/dsh-client-ui
|
|
4
|
+
// -settings-plugins (YyYd_a_*), so it renders pixel-identical to the built-in
|
|
5
|
+
// Shell / Agent loop / Web search cards — a border, a 12px radius, a big bold
|
|
6
|
+
// name header with a description, a collapsing body of fields, and a
|
|
7
|
+
// discard/save footer.
|
|
8
|
+
//
|
|
9
|
+
// Hand-written in the lazy-CJS bundle protocol (window.__ModuleLoader__.load
|
|
10
|
+
// with a factory returning cordis-plugin exports), so no build step and no
|
|
11
|
+
// imports from dsh client packages — the same zero-dependency stance as the
|
|
12
|
+
// host half. `slots` is optional (registered lazily via ctx.inject), so a
|
|
13
|
+
// non-web profile simply never renders the card.
|
|
14
|
+
window.__ModuleLoader__.load({
|
|
15
|
+
id: 'gitee-ai-employee',
|
|
16
|
+
factory: (require) => {
|
|
17
|
+
var module = { exports: {} }
|
|
18
|
+
var exports = module.exports
|
|
19
|
+
|
|
20
|
+
function labels() {
|
|
21
|
+
return {
|
|
22
|
+
title: 'Gitee AI 员工',
|
|
23
|
+
description: 'issue @员工 → AI 开发 → PR → 自动关闭',
|
|
24
|
+
expand: '展开',
|
|
25
|
+
collapse: '收起',
|
|
26
|
+
unsaved: '未保存',
|
|
27
|
+
loading: '加载中…',
|
|
28
|
+
loadFailed: '配置加载失败',
|
|
29
|
+
discard: '放弃',
|
|
30
|
+
save: '保存',
|
|
31
|
+
saving: '保存中…',
|
|
32
|
+
savedDone: '保存完成,已生效',
|
|
33
|
+
readOnly: '只读',
|
|
34
|
+
token: 'Gitee 私人令牌',
|
|
35
|
+
tokenHint: '留空保持原值',
|
|
36
|
+
bot: '机器人账号',
|
|
37
|
+
botHint: 'issue 里 @ 它才触发',
|
|
38
|
+
workRoot: '仓库工作目录',
|
|
39
|
+
repos: '监听仓库',
|
|
40
|
+
reposHint: '每行一个 owner/repo',
|
|
41
|
+
poll: '启用定时轮询',
|
|
42
|
+
interval: '轮询间隔(毫秒)',
|
|
43
|
+
merge: '自动合并 PR',
|
|
44
|
+
close: '成功后自动关闭 issue',
|
|
45
|
+
status: '查看任务状态',
|
|
46
|
+
scan: '代码安全扫描',
|
|
47
|
+
scanEnable: '启用定时扫描(全部仓库排队持续扫描,去重幂等)',
|
|
48
|
+
scanReposLabel: '扫描仓库',
|
|
49
|
+
scanReposHint: '每行一个 owner/repo 或完整克隆地址(https://gitee.com/owner/repo.git、git@github.com:owner/repo.git 均可),[gitee:|github:] 前缀可选',
|
|
50
|
+
scanSeverity: '最低上报级别',
|
|
51
|
+
scanSeverityHint: 'critical / high / medium / low / none',
|
|
52
|
+
scanOneIssue: '一次扫描合并为一个 issue',
|
|
53
|
+
scanConcurrency: '同时扫描仓库数(1~8,默认 3,自动排队)',
|
|
54
|
+
scanPromptsLabel: '自定义提示词(JSON)',
|
|
55
|
+
scanPromptsHint: '{"id":{"name":"名称","prompt":"内容"}},同 id 覆盖内置,留空用内置',
|
|
56
|
+
scanInterval: '扫描间隔(毫秒)',
|
|
57
|
+
scanFetch: '一键获取我的仓库',
|
|
58
|
+
scanFetching: '获取中…',
|
|
59
|
+
scanNow: '立即扫描全部仓库',
|
|
60
|
+
scanning: '触发中…',
|
|
61
|
+
scanDone: '已触发,结果见 /gitee-ai/scan',
|
|
62
|
+
scanFailed: '触发失败',
|
|
63
|
+
scanNeedRepos: '请先填写扫描仓库',
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function ConfigCard(react, ui) {
|
|
68
|
+
var h = react.createElement
|
|
69
|
+
var Input = ui && ui.Input
|
|
70
|
+
var Chevron = ui && ui.IconChevronDownOutline14
|
|
71
|
+
|
|
72
|
+
// 复用内置 settings-plugins 注入的字段样式类(At1oFq_*)
|
|
73
|
+
var fieldRow = (label, hint, control, key, groupName) =>
|
|
74
|
+
h(
|
|
75
|
+
groupName ? 'div' : 'label',
|
|
76
|
+
{
|
|
77
|
+
key: key,
|
|
78
|
+
role: groupName ? 'group' : undefined,
|
|
79
|
+
'aria-label': groupName || undefined,
|
|
80
|
+
className: 'At1oFq_field',
|
|
81
|
+
style: { display: 'flex', flexDirection: 'column', gap: '6px', padding: '12px 0' },
|
|
82
|
+
},
|
|
83
|
+
h('div', { className: 'At1oFq_head' },
|
|
84
|
+
h('span', { className: 'At1oFq_label' }, label),
|
|
85
|
+
hint
|
|
86
|
+
? h('span', { className: 'At1oFq_badgeMuted', style: { fontSize: '11px', lineHeight: '17px', color: 'var(--dsw-alias-label-tertiary)' } }, hint)
|
|
87
|
+
: null,
|
|
88
|
+
),
|
|
89
|
+
control,
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
// 自定义开关行:左侧标签,右侧紧凑的 iOS 风格 toggle
|
|
93
|
+
var toggleRow = (label, hint, checked, onChange, key) =>
|
|
94
|
+
h(
|
|
95
|
+
'div',
|
|
96
|
+
{
|
|
97
|
+
key: key,
|
|
98
|
+
className: 'At1oFq_field',
|
|
99
|
+
style: { display: 'flex', flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', gap: '12px', padding: '10px 0' },
|
|
100
|
+
},
|
|
101
|
+
h('div', { style: { minWidth: 0, display: 'flex', flexDirection: 'column', gap: '2px' } },
|
|
102
|
+
h('span', {
|
|
103
|
+
className: 'At1oFq_label',
|
|
104
|
+
style: { margin: 0, fontSize: '13px', fontWeight: 500, color: 'var(--dsw-alias-label-primary)' },
|
|
105
|
+
}, label),
|
|
106
|
+
hint
|
|
107
|
+
? h('span', { style: { fontSize: '12px', color: 'var(--dsw-alias-label-tertiary)', lineHeight: 1.4 } }, hint)
|
|
108
|
+
: null,
|
|
109
|
+
),
|
|
110
|
+
h(Switch, { checked: !!checked, onChange, label }),
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
// 紧凑圆形开关(轨道 + 滑块),用 DSH 主题 token 着色
|
|
114
|
+
function Switch(props) {
|
|
115
|
+
var on = !!props.checked
|
|
116
|
+
return h(
|
|
117
|
+
'button',
|
|
118
|
+
{
|
|
119
|
+
type: 'button',
|
|
120
|
+
role: 'switch',
|
|
121
|
+
'aria-checked': on,
|
|
122
|
+
'aria-label': props.label,
|
|
123
|
+
onClick: () => props.onChange(!on),
|
|
124
|
+
style: {
|
|
125
|
+
appearance: 'none',
|
|
126
|
+
flex: 'none',
|
|
127
|
+
width: '36px',
|
|
128
|
+
height: '20px',
|
|
129
|
+
borderRadius: '999px',
|
|
130
|
+
border: 0,
|
|
131
|
+
padding: 0,
|
|
132
|
+
cursor: 'pointer',
|
|
133
|
+
position: 'relative',
|
|
134
|
+
background: on
|
|
135
|
+
? 'var(--dsw-alias-brand-primary, #2563eb)'
|
|
136
|
+
: 'var(--dsw-alias-bg-module-platform, rgba(127,127,127,0.35))',
|
|
137
|
+
transition: 'background .16s',
|
|
138
|
+
outline: 'none',
|
|
139
|
+
boxShadow: 'inset 0 0 0 1px rgba(127,127,127,0.25)',
|
|
140
|
+
},
|
|
141
|
+
},
|
|
142
|
+
h('span', {
|
|
143
|
+
style: {
|
|
144
|
+
position: 'absolute',
|
|
145
|
+
top: '2px',
|
|
146
|
+
left: on ? '18px' : '2px',
|
|
147
|
+
width: '16px',
|
|
148
|
+
height: '16px',
|
|
149
|
+
borderRadius: '999px',
|
|
150
|
+
background: '#fff',
|
|
151
|
+
boxShadow: '0 1px 2px rgba(0,0,0,0.35)',
|
|
152
|
+
transition: 'left .16s',
|
|
153
|
+
},
|
|
154
|
+
}),
|
|
155
|
+
)
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// 平衡:onChange 里用 ref 读最新 draft,避免闭包陈旧
|
|
159
|
+
var draftRef = { current: null }
|
|
160
|
+
|
|
161
|
+
return function GiteeCard() {
|
|
162
|
+
var t = labels()
|
|
163
|
+
var openState = react.useState(false)
|
|
164
|
+
var summaryState = react.useState(null)
|
|
165
|
+
var draftState = react.useState(null)
|
|
166
|
+
var busyState = react.useState(false)
|
|
167
|
+
var noteState = react.useState('')
|
|
168
|
+
var failedState = react.useState(false)
|
|
169
|
+
var dirtyState = react.useState(false)
|
|
170
|
+
var open = openState[0]
|
|
171
|
+
var summary = summaryState[0]
|
|
172
|
+
var draft = draftState[0]
|
|
173
|
+
var busy = busyState[0]
|
|
174
|
+
var note = noteState[0]
|
|
175
|
+
var failed = failedState[0]
|
|
176
|
+
var dirty = dirtyState[0]
|
|
177
|
+
var scanBusyState = react.useState(false)
|
|
178
|
+
var scanNoteState = react.useState('')
|
|
179
|
+
var scanBusy = scanBusyState[0]
|
|
180
|
+
var scanNote = scanNoteState[0]
|
|
181
|
+
var fetchBusyState = react.useState(false)
|
|
182
|
+
var fetchNoteState = react.useState('')
|
|
183
|
+
var fetchBusy = fetchBusyState[0]
|
|
184
|
+
var fetchNote = fetchNoteState[0]
|
|
185
|
+
|
|
186
|
+
draftRef.current = draft
|
|
187
|
+
|
|
188
|
+
var seedDraft = (config) => ({
|
|
189
|
+
botName: (config && config.botName) || '',
|
|
190
|
+
workRoot: (config && config.workRoot) || '',
|
|
191
|
+
watchRepos: ((config && config.watchRepos) || []).join('\n'),
|
|
192
|
+
pollEnabled: !!(config && config.pollEnabled),
|
|
193
|
+
pollIntervalMs: (config && config.pollIntervalMs) || 60000,
|
|
194
|
+
autoMerge: !!(config && config.autoMerge),
|
|
195
|
+
autoCloseIssue: !!(config && config.autoCloseIssue),
|
|
196
|
+
tokenConfigured: !!(config && config.tokenConfigured),
|
|
197
|
+
githubTokenConfigured: !!(config && config.githubTokenConfigured),
|
|
198
|
+
scanEnabled: !!(config && config.scanEnabled),
|
|
199
|
+
scanRepos: ((config && config.scanRepos) || []).join('\n'),
|
|
200
|
+
scanMinSeverity: (config && config.scanMinSeverity) || 'medium',
|
|
201
|
+
scanOneIssuePerRun: (config && config.scanOneIssuePerRun) !== false,
|
|
202
|
+
scanPrompts: (config && config.scanPrompts && Object.keys(config.scanPrompts).length)
|
|
203
|
+
? JSON.stringify(config.scanPrompts, null, 2) : '',
|
|
204
|
+
scanIntervalMs: (config && config.scanIntervalMs) || 21600000,
|
|
205
|
+
scanConcurrency: (config && config.scanConcurrency) || 3,
|
|
206
|
+
})
|
|
207
|
+
|
|
208
|
+
var load = react.useCallback(() => {
|
|
209
|
+
fetch('/gitee-ai/config')
|
|
210
|
+
.then((r) => r.json().then((body) => {
|
|
211
|
+
if (!r.ok) throw new Error(body.error || 'load failed')
|
|
212
|
+
return body
|
|
213
|
+
}))
|
|
214
|
+
.then((body) => {
|
|
215
|
+
summaryState[1](body.config)
|
|
216
|
+
var d = seedDraft(body.config)
|
|
217
|
+
draftRef.current = d
|
|
218
|
+
draftState[1](d)
|
|
219
|
+
noteState[1]('')
|
|
220
|
+
failedState[1](false)
|
|
221
|
+
})
|
|
222
|
+
.catch((error) => {
|
|
223
|
+
noteState[1](String(error.message ? error.message : error))
|
|
224
|
+
failedState[1](true)
|
|
225
|
+
})
|
|
226
|
+
}, [])
|
|
227
|
+
|
|
228
|
+
react.useEffect(() => {
|
|
229
|
+
if (open && summary === null) load()
|
|
230
|
+
}, [open, summary, load])
|
|
231
|
+
|
|
232
|
+
var setDraft = (d) => { draftState[1](d); dirtyState[1](true) }
|
|
233
|
+
var setDraftFromControl = (patch) => {
|
|
234
|
+
var d = Object.assign({}, draftRef.current, patch)
|
|
235
|
+
draftRef.current = d
|
|
236
|
+
draftState[1](d)
|
|
237
|
+
dirtyState[1](true)
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
var save = () => {
|
|
241
|
+
if (!draft) return
|
|
242
|
+
var prompts = {}
|
|
243
|
+
if (draft.scanPrompts && draft.scanPrompts.trim()) {
|
|
244
|
+
try {
|
|
245
|
+
var parsed = JSON.parse(draft.scanPrompts)
|
|
246
|
+
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
|
|
247
|
+
for (var id in parsed) {
|
|
248
|
+
var pv = parsed[id] || {}
|
|
249
|
+
var nm = String(pv.name || '').trim()
|
|
250
|
+
var pt = String(pv.prompt || '').trim()
|
|
251
|
+
if (nm && pt) prompts[id] = { name: nm, prompt: pt }
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
} catch (e) {
|
|
255
|
+
noteState[1]('提示词 JSON 格式错误,请检查')
|
|
256
|
+
failedState[1](true)
|
|
257
|
+
return
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
busyState[1](true)
|
|
261
|
+
failedState[1](false)
|
|
262
|
+
noteState[1]('')
|
|
263
|
+
var payload = {
|
|
264
|
+
...((draft.token && draft.token.trim()) ? { giteeToken: draft.token.trim() } : {}),
|
|
265
|
+
...((draft.githubToken && draft.githubToken.trim()) ? { githubToken: draft.githubToken.trim() } : {}),
|
|
266
|
+
botName: draft.botName,
|
|
267
|
+
workRoot: draft.workRoot,
|
|
268
|
+
watchRepos: (draft.watchRepos || '').split(/\r?\n/).map((s) => s.trim()).filter(Boolean),
|
|
269
|
+
pollEnabled: draft.pollEnabled,
|
|
270
|
+
pollIntervalMs: Number(draft.pollIntervalMs) || 60000,
|
|
271
|
+
autoMerge: draft.autoMerge,
|
|
272
|
+
autoCloseIssue: draft.autoCloseIssue,
|
|
273
|
+
scanEnabled: !!draft.scanEnabled,
|
|
274
|
+
scanRepos: (draft.scanRepos || '').split(/\r?\n/).map((s) => s.trim()).filter(Boolean),
|
|
275
|
+
scanMinSeverity: draft.scanMinSeverity || 'medium',
|
|
276
|
+
scanOneIssuePerRun: draft.scanOneIssuePerRun !== false,
|
|
277
|
+
scanPrompts: prompts,
|
|
278
|
+
scanIntervalMs: Number(draft.scanIntervalMs) || 21600000,
|
|
279
|
+
scanConcurrency: Number(draft.scanConcurrency) || 3,
|
|
280
|
+
}
|
|
281
|
+
fetch('/gitee-ai/config', {
|
|
282
|
+
method: 'POST',
|
|
283
|
+
headers: { 'Content-Type': 'application/json' },
|
|
284
|
+
body: JSON.stringify(payload),
|
|
285
|
+
})
|
|
286
|
+
.then((r) => r.json().then((body) => {
|
|
287
|
+
if (!r.ok) throw new Error(body.error || 'save failed')
|
|
288
|
+
return body
|
|
289
|
+
}))
|
|
290
|
+
.then((body) => {
|
|
291
|
+
noteState[1](t.savedDone)
|
|
292
|
+
summaryState[1](body.config)
|
|
293
|
+
var d = seedDraft(body.config)
|
|
294
|
+
draftRef.current = d
|
|
295
|
+
draftState[1](d)
|
|
296
|
+
dirtyState[1](false)
|
|
297
|
+
})
|
|
298
|
+
.catch((error) => {
|
|
299
|
+
noteState[1](String(error.message ? error.message : error))
|
|
300
|
+
failedState[1](true)
|
|
301
|
+
})
|
|
302
|
+
.finally(() => busyState[1](false))
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
var discard = () => {
|
|
306
|
+
if (!summary) return
|
|
307
|
+
var d = seedDraft(summary)
|
|
308
|
+
draftRef.current = d
|
|
309
|
+
draftState[1](d)
|
|
310
|
+
dirtyState[1](false)
|
|
311
|
+
noteState[1]('')
|
|
312
|
+
failedState[1](false)
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// 立即扫描:对 draft 里的 scanRepos 逐个 POST /gitee-ai/scan(服务端自动排队,按并发上限持续扫描)
|
|
316
|
+
var scanNow = () => {
|
|
317
|
+
if (!draft) return
|
|
318
|
+
var repos = (draft.scanRepos || '').split(/\r?\n/).map((s) => s.trim()).filter(Boolean)
|
|
319
|
+
if (!repos.length) { scanNoteState[1](t.scanNeedRepos); return }
|
|
320
|
+
scanBusyState[1](true)
|
|
321
|
+
scanNoteState[1]('')
|
|
322
|
+
var accepted = 0
|
|
323
|
+
var skipped = 0
|
|
324
|
+
var tasks = repos.map(function (spec) {
|
|
325
|
+
var platform = /^github:/i.test(spec) ? 'github' : /^gitee:/i.test(spec) ? 'gitee' : 'gitee'
|
|
326
|
+
var rest = spec.replace(/^(github|gitee):/i, '')
|
|
327
|
+
var idx = rest.indexOf('/')
|
|
328
|
+
var owner = rest.slice(0, idx)
|
|
329
|
+
var repo = rest.slice(idx + 1)
|
|
330
|
+
if (!owner || !repo) return Promise.resolve({ accepted: false })
|
|
331
|
+
return fetch('/gitee-ai/scan', {
|
|
332
|
+
method: 'POST',
|
|
333
|
+
headers: { 'Content-Type': 'application/json' },
|
|
334
|
+
body: JSON.stringify({ platform: platform, owner: owner, repo: repo, dryRun: false }),
|
|
335
|
+
}).then(function (r) { return r.json().catch(function () { return { accepted: false } }) })
|
|
336
|
+
.then(function (b) {
|
|
337
|
+
if (b && b.accepted) accepted++
|
|
338
|
+
else skipped++
|
|
339
|
+
return b
|
|
340
|
+
})
|
|
341
|
+
})
|
|
342
|
+
Promise.all(tasks)
|
|
343
|
+
.then(function () {
|
|
344
|
+
scanNoteState[1]('已排队 ' + accepted + ' 个' + (skipped ? '(跳过 ' + skipped + ' 个:正在扫/已在队列)' : '') + ',同时最多扫 ' + (draft.scanConcurrency || 3) + ' 个')
|
|
345
|
+
})
|
|
346
|
+
.catch(function () { scanNoteState[1](t.scanFailed) })
|
|
347
|
+
.finally(function () { scanBusyState[1](false) })
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// 一键获取我的仓库:拉取有权限的仓库(含所属组织),追加进 scanRepos 供人工勾选
|
|
351
|
+
var normLine = (s) => String(s || '').replace(/^(gitee|github):/i, '').toLowerCase()
|
|
352
|
+
var fetchMyRepos = () => {
|
|
353
|
+
if (fetchBusy) return
|
|
354
|
+
fetchBusyState[1](true)
|
|
355
|
+
fetchNoteState[1]('')
|
|
356
|
+
fetch('/gitee-ai/my-repos')
|
|
357
|
+
.then((r) => r.json().catch(() => ({ ok: false, error: '响应异常' })))
|
|
358
|
+
.then((b) => {
|
|
359
|
+
if (!b.ok || !Array.isArray(b.repos)) { fetchNoteState[1]('获取失败:' + ((b && b.error) || '未知错误')); return }
|
|
360
|
+
var cur = (draftRef.current.scanRepos || '').split(/\r?\n/).map((s) => s.trim()).filter(Boolean)
|
|
361
|
+
var seen = {}
|
|
362
|
+
cur.forEach((s) => { seen[normLine(s)] = true })
|
|
363
|
+
var lines = cur.slice()
|
|
364
|
+
b.repos.forEach((x) => {
|
|
365
|
+
var line = x.platform + ':' + x.owner + '/' + x.repo
|
|
366
|
+
if (!seen[normLine(line)]) { lines.push(line); seen[normLine(line)] = true }
|
|
367
|
+
})
|
|
368
|
+
setDraftFromControl({ scanRepos: lines.join('\n') })
|
|
369
|
+
fetchNoteState[1]('已填入 ' + b.repos.length + ' 个仓库(含既有保留,去掉不需要的再保存)')
|
|
370
|
+
})
|
|
371
|
+
.catch((e) => fetchNoteState[1]('获取失败:' + String(e.message || e)))
|
|
372
|
+
.finally(() => fetchBusyState[1](false))
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// ── 头部(大标题 + 描述 + 展开箭头)──
|
|
376
|
+
var statusLine = summary
|
|
377
|
+
? '轮询' + (summary.pollEnabled ? ' 开 · ' + Math.round((summary.pollIntervalMs || 0) / 1000) + 's' : ' 关')
|
|
378
|
+
+ ' · 仓库 ' + ((summary.watchRepos || []).length) + ' 个'
|
|
379
|
+
+ ' · 扫描' + (summary.scanEnabled ? ' 开' : ' 关')
|
|
380
|
+
+ (summary.tokenConfigured ? '' : ' · 未配置令牌')
|
|
381
|
+
: t.description
|
|
382
|
+
|
|
383
|
+
var chevronEl = Chevron
|
|
384
|
+
? h(Chevron, {
|
|
385
|
+
className: 'YyYd_a_chevron' + (open ? ' YyYd_a_chevronOpen' : ''),
|
|
386
|
+
})
|
|
387
|
+
: h('span', {
|
|
388
|
+
className: 'YyYd_a_chevron' + (open ? ' YyYd_a_chevronOpen' : ''),
|
|
389
|
+
style: { display: 'inline-flex' },
|
|
390
|
+
}, '▾')
|
|
391
|
+
|
|
392
|
+
var header = h(
|
|
393
|
+
'button',
|
|
394
|
+
{
|
|
395
|
+
type: 'button',
|
|
396
|
+
className: 'YyYd_a_header',
|
|
397
|
+
'aria-expanded': open,
|
|
398
|
+
'aria-label': (open ? t.collapse : t.expand) + ': ' + t.title,
|
|
399
|
+
onClick: () => openState[1](!open),
|
|
400
|
+
},
|
|
401
|
+
h('span', { className: 'YyYd_a_headText' },
|
|
402
|
+
h('span', { className: 'YyYd_a_name' }, t.title),
|
|
403
|
+
h('span', { className: 'YyYd_a_description' }, statusLine),
|
|
404
|
+
),
|
|
405
|
+
dirty ? h('span', { className: 'YyYd_a_pending' }, t.unsaved) : null,
|
|
406
|
+
chevronEl,
|
|
407
|
+
)
|
|
408
|
+
|
|
409
|
+
// ── 主体(字段 + 底部保存)──
|
|
410
|
+
var body = null
|
|
411
|
+
if (open) {
|
|
412
|
+
if (!summary || !draft) {
|
|
413
|
+
body = h(
|
|
414
|
+
'div',
|
|
415
|
+
{ className: 'YyYd_a_body', style: { padding: '12px 16px', color: 'var(--dsw-alias-label-tertiary)', fontSize: '13px' } },
|
|
416
|
+
note || t.loading,
|
|
417
|
+
)
|
|
418
|
+
} else {
|
|
419
|
+
var controls = []
|
|
420
|
+
controls.push(fieldRow(t.token, draft.tokenConfigured ? t.tokenHint : undefined, h(Input, {
|
|
421
|
+
key: 'token',
|
|
422
|
+
type: 'password',
|
|
423
|
+
placeholder: draft.tokenConfigured ? '••••••••(留空保持原值)' : '输入 Gitee 私人令牌',
|
|
424
|
+
value: (draft.token !== undefined ? draft.token : ''),
|
|
425
|
+
onChange: (e) => setDraftFromControl({ token: e.target.value }),
|
|
426
|
+
}), 'token'))
|
|
427
|
+
controls.push(fieldRow('GitHub 令牌(可选)', '留空保持原值;用于 github: 前缀的仓库', h(Input, {
|
|
428
|
+
key: 'githubToken',
|
|
429
|
+
type: 'password',
|
|
430
|
+
placeholder: draft.githubTokenConfigured ? '••••••••(留空保持原值)' : '输入 GitHub 私人令牌(可选)',
|
|
431
|
+
value: (draft.githubToken !== undefined ? draft.githubToken : ''),
|
|
432
|
+
onChange: (e) => setDraftFromControl({ githubToken: e.target.value }),
|
|
433
|
+
}), 'githubToken'))
|
|
434
|
+
controls.push(fieldRow(t.bot, t.botHint, h(Input, {
|
|
435
|
+
key: 'bot',
|
|
436
|
+
value: draft.botName || '',
|
|
437
|
+
onChange: (e) => setDraftFromControl({ botName: e.target.value }),
|
|
438
|
+
}), 'bot'))
|
|
439
|
+
controls.push(fieldRow(t.workRoot, undefined, h(Input, {
|
|
440
|
+
key: 'workRoot',
|
|
441
|
+
value: draft.workRoot || '',
|
|
442
|
+
onChange: (e) => setDraftFromControl({ workRoot: e.target.value }),
|
|
443
|
+
}), 'workRoot'))
|
|
444
|
+
controls.push(fieldRow(t.repos, t.reposHint, h('textarea', {
|
|
445
|
+
key: 'repos',
|
|
446
|
+
rows: 3,
|
|
447
|
+
className: 'At1oFq_input',
|
|
448
|
+
style: { height: 'auto', padding: '8px 12px', resize: 'vertical', lineHeight: 1.5 },
|
|
449
|
+
value: draft.watchRepos || '',
|
|
450
|
+
onChange: (e) => setDraftFromControl({ watchRepos: e.target.value }),
|
|
451
|
+
}), 'repos'))
|
|
452
|
+
controls.push(toggleRow(t.poll, undefined, draft.pollEnabled, (v) => setDraftFromControl({ pollEnabled: v }), 'poll'))
|
|
453
|
+
controls.push(fieldRow(t.interval, undefined, h(Input, {
|
|
454
|
+
key: 'interval',
|
|
455
|
+
type: 'number',
|
|
456
|
+
value: draft.pollIntervalMs,
|
|
457
|
+
onChange: (e) => setDraftFromControl({ pollIntervalMs: e.target.value }),
|
|
458
|
+
}), 'interval'))
|
|
459
|
+
controls.push(toggleRow(t.merge, undefined, draft.autoMerge, (v) => setDraftFromControl({ autoMerge: v }), 'merge'))
|
|
460
|
+
controls.push(toggleRow(t.close, undefined, draft.autoCloseIssue, (v) => setDraftFromControl({ autoCloseIssue: v }), 'close'))
|
|
461
|
+
|
|
462
|
+
// ── 代码安全扫描区(v1.2)──
|
|
463
|
+
controls.push(h('div', { key: 'scanTitle', className: 'At1oFq_label', style: { margin: '16px 0 4px', fontSize: '13px', fontWeight: 600, color: 'var(--dsw-alias-label-primary)' } }, t.scan))
|
|
464
|
+
controls.push(toggleRow(t.scanEnable, undefined, draft.scanEnabled, (v) => setDraftFromControl({ scanEnabled: v }), 'scanEnable'))
|
|
465
|
+
controls.push(fieldRow(t.scanReposLabel, t.scanReposHint, h('textarea', {
|
|
466
|
+
key: 'scanRepos', rows: 3,
|
|
467
|
+
className: 'At1oFq_input',
|
|
468
|
+
style: { height: 'auto', padding: '8px 12px', resize: 'vertical', lineHeight: 1.5 },
|
|
469
|
+
value: draft.scanRepos || '',
|
|
470
|
+
onChange: (e) => setDraftFromControl({ scanRepos: e.target.value }),
|
|
471
|
+
}), 'scanRepos'))
|
|
472
|
+
controls.push(h('div', { key: 'fetchReposRow', style: { display: 'flex', gap: '8px', alignItems: 'center', padding: '4px 0', flexWrap: 'wrap' } },
|
|
473
|
+
h('button', {
|
|
474
|
+
type: 'button',
|
|
475
|
+
onClick: fetchMyRepos,
|
|
476
|
+
disabled: fetchBusy,
|
|
477
|
+
style: { background: '#2f6fed', color: '#fff', border: 0, borderRadius: '8px', padding: '6px 12px', fontSize: '12px', cursor: 'pointer' },
|
|
478
|
+
}, fetchBusy ? t.scanFetching : t.scanFetch),
|
|
479
|
+
fetchNote ? h('span', { style: { fontSize: '12px', color: 'var(--dsw-alias-label-secondary)' } }, fetchNote) : null,
|
|
480
|
+
))
|
|
481
|
+
controls.push(fieldRow(t.scanSeverity, t.scanSeverityHint, h('select', {
|
|
482
|
+
key: 'scanSeverity',
|
|
483
|
+
className: 'At1oFq_input',
|
|
484
|
+
style: { padding: '8px 12px' },
|
|
485
|
+
value: draft.scanMinSeverity || 'medium',
|
|
486
|
+
onChange: (e) => setDraftFromControl({ scanMinSeverity: e.target.value }),
|
|
487
|
+
}, ['critical', 'high', 'medium', 'low', 'none'].map((s) => h('option', { key: s, value: s }, s))), 'scanSeverity'))
|
|
488
|
+
controls.push(toggleRow(t.scanOneIssue, undefined, draft.scanOneIssuePerRun, (v) => setDraftFromControl({ scanOneIssuePerRun: v }), 'scanOneIssue'))
|
|
489
|
+
controls.push(fieldRow(t.scanConcurrency, undefined, h(Input, {
|
|
490
|
+
key: 'scanConcurrency', type: 'number', min: 1, max: 8,
|
|
491
|
+
value: draft.scanConcurrency,
|
|
492
|
+
onChange: (e) => setDraftFromControl({ scanConcurrency: e.target.value }),
|
|
493
|
+
}), 'scanConcurrency'))
|
|
494
|
+
controls.push(fieldRow(t.scanPromptsLabel, t.scanPromptsHint, h('textarea', {
|
|
495
|
+
key: 'scanPrompts', rows: 4,
|
|
496
|
+
className: 'At1oFq_input',
|
|
497
|
+
style: { height: 'auto', padding: '8px 12px', resize: 'vertical', lineHeight: 1.4, fontFamily: 'Consolas,monospace', fontSize: '11px' },
|
|
498
|
+
value: draft.scanPrompts || '',
|
|
499
|
+
onChange: (e) => setDraftFromControl({ scanPrompts: e.target.value }),
|
|
500
|
+
}), 'scanPrompts'))
|
|
501
|
+
controls.push(fieldRow(t.scanInterval, undefined, h(Input, {
|
|
502
|
+
key: 'scanInterval', type: 'number',
|
|
503
|
+
value: draft.scanIntervalMs,
|
|
504
|
+
onChange: (e) => setDraftFromControl({ scanIntervalMs: e.target.value }),
|
|
505
|
+
}), 'scanInterval'))
|
|
506
|
+
controls.push(h('div', { key: 'scanNowRow', style: { display: 'flex', gap: '8px', alignItems: 'center', padding: '8px 0', flexWrap: 'wrap' } },
|
|
507
|
+
h('button', {
|
|
508
|
+
type: 'button',
|
|
509
|
+
onClick: scanNow,
|
|
510
|
+
disabled: scanBusy,
|
|
511
|
+
style: { background: 'var(--dsw-alias-danger, #dc2626)', color: '#fff', border: 0, borderRadius: '8px', padding: '8px 14px', fontSize: '13px', cursor: 'pointer' },
|
|
512
|
+
}, scanBusy ? t.scanning : t.scanNow),
|
|
513
|
+
scanNote ? h('span', { style: { fontSize: '12px', color: 'var(--dsw-alias-label-secondary)' } }, scanNote) : null,
|
|
514
|
+
))
|
|
515
|
+
|
|
516
|
+
body = h(
|
|
517
|
+
'div',
|
|
518
|
+
{ className: 'YyYd_a_body' },
|
|
519
|
+
h('a', {
|
|
520
|
+
href: '/gitee-ai/settings',
|
|
521
|
+
target: '_blank',
|
|
522
|
+
rel: 'noreferrer',
|
|
523
|
+
style: { display: 'inline-block', marginTop: '12px', fontSize: '12px', color: 'var(--dsw-alias-label-secondary)', textDecoration: 'underline' },
|
|
524
|
+
}, t.status + ' ↗'),
|
|
525
|
+
controls,
|
|
526
|
+
note
|
|
527
|
+
? h('p', {
|
|
528
|
+
className: failed ? 'YyYd_a_failed' : 'YyYd_a_readOnly',
|
|
529
|
+
role: 'status',
|
|
530
|
+
style: failed ? {} : { margin: '8px 0 0', fontSize: '12px', lineHeight: '1.5' },
|
|
531
|
+
}, note)
|
|
532
|
+
: null,
|
|
533
|
+
h('div', { className: 'YyYd_a_footer' },
|
|
534
|
+
h('button', {
|
|
535
|
+
type: 'button',
|
|
536
|
+
className: 'YyYd_a_discard',
|
|
537
|
+
disabled: !dirty || busy,
|
|
538
|
+
onClick: discard,
|
|
539
|
+
}, t.discard),
|
|
540
|
+
h('button', {
|
|
541
|
+
type: 'button',
|
|
542
|
+
className: 'YyYd_a_save',
|
|
543
|
+
disabled: busy,
|
|
544
|
+
onClick: save,
|
|
545
|
+
}, busy ? t.saving : t.save),
|
|
546
|
+
),
|
|
547
|
+
)
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
return h(
|
|
552
|
+
'li',
|
|
553
|
+
{ className: 'YyYd_a_card' + (open ? ' YyYd_a_cardOpen' : '') },
|
|
554
|
+
header,
|
|
555
|
+
body,
|
|
556
|
+
)
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
function registerCard(ctx) {
|
|
561
|
+
if (typeof ctx.inject !== 'function') return
|
|
562
|
+
ctx.inject(['slots'], (scope) => {
|
|
563
|
+
fetch('/gitee-ai/config')
|
|
564
|
+
.then((response) => {
|
|
565
|
+
if (response.status === 404) return
|
|
566
|
+
try {
|
|
567
|
+
mountCard(scope)
|
|
568
|
+
} catch (error) {
|
|
569
|
+
console.error('[gitee-ai] settings card skipped: ' + error)
|
|
570
|
+
}
|
|
571
|
+
})
|
|
572
|
+
.catch(() => {})
|
|
573
|
+
})
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
function mountCard(ctx) {
|
|
577
|
+
var react
|
|
578
|
+
try {
|
|
579
|
+
react = require('react')
|
|
580
|
+
} catch (error) {
|
|
581
|
+
console.error('[gitee-ai] settings card skipped: ' + error)
|
|
582
|
+
return
|
|
583
|
+
}
|
|
584
|
+
var ui
|
|
585
|
+
try {
|
|
586
|
+
ui = require('@deepseek-ai/dsh-client-ui-primitives')
|
|
587
|
+
} catch (error) {
|
|
588
|
+
ui = {}
|
|
589
|
+
}
|
|
590
|
+
var Card = ConfigCard(react, ui)
|
|
591
|
+
ctx.slots.inject('settings.plugin.item', function* () {
|
|
592
|
+
yield ctx.slots.register({ name: 'settings.plugin.item', id: 'gitee-ai-employee', key: 'gitee-ai-employee', order: 30 }, Card)
|
|
593
|
+
})
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
function apply(ctx) {
|
|
597
|
+
registerCard(ctx)
|
|
598
|
+
if (typeof ctx.effect === 'function') {
|
|
599
|
+
ctx.effect(() => () => {}, 'gitee-ai: settings card')
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
exports.apply = apply
|
|
604
|
+
// `slots` is optional, so it is not required here: registerCard checks.
|
|
605
|
+
exports.inject = []
|
|
606
|
+
return module.exports
|
|
607
|
+
},
|
|
608
|
+
})
|
package/dsh/index.js
CHANGED
|
@@ -289,18 +289,35 @@ export default {
|
|
|
289
289
|
return s === "github" ? "github" : "gitee";
|
|
290
290
|
}
|
|
291
291
|
|
|
292
|
-
// watchRepos 行格式:
|
|
293
|
-
// config.defaultPlatform
|
|
292
|
+
// watchRepos/scanRepos 行格式:
|
|
293
|
+
// "github:owner/repo" / "gitee:owner/repo" / 无前缀按 config.defaultPlatform 判定;
|
|
294
|
+
// 也兼容完整克隆地址:https://gitee.com/owner/repo.git、git@gitee.com:owner/repo.git、
|
|
295
|
+
// https://github.com/owner/repo.git(按 host 推断平台,显式前缀优先);
|
|
296
|
+
// owner/repo 末尾的 .git 与多余斜杠会自动剥除。
|
|
294
297
|
function parseRepoSpec(spec) {
|
|
295
|
-
const s = String(spec || "").trim();
|
|
298
|
+
const s = String(spec || "").trim().replace(/\/+$/, "");
|
|
299
|
+
if (!s) return null;
|
|
296
300
|
let platform = normPlatform(config.defaultPlatform || "gitee");
|
|
297
301
|
let rest = s;
|
|
302
|
+
let prefixed = false;
|
|
298
303
|
const m = /^(github|gitee):(.+)$/i.exec(s);
|
|
299
|
-
if (m) { platform = normPlatform(m[1]); rest = m[2].trim(); }
|
|
304
|
+
if (m) { prefixed = true; platform = normPlatform(m[1]); rest = m[2].trim().replace(/\/+$/, ""); }
|
|
305
|
+
// 完整克隆地址(host / owner / repo 三段提取)
|
|
306
|
+
const urlM = /^(?:https?:\/\/|git@|ssh:\/\/git@|git:\/\/)([^\/:#]+)[:\/]([^\/]+)\/([^\/]+?)(?:\.git)?\/?$/i.exec(rest);
|
|
307
|
+
if (urlM) {
|
|
308
|
+
const host = urlM[1].toLowerCase();
|
|
309
|
+
const owner = urlM[2].trim();
|
|
310
|
+
const repo = urlM[3].trim();
|
|
311
|
+
if (!prefixed) platform = host.includes("github.") ? "github" : "gitee";
|
|
312
|
+
if (!owner || !repo || /[\s.]/.test(owner) || /[\s\\]/.test(repo)) return null;
|
|
313
|
+
return { platform, owner, repo };
|
|
314
|
+
}
|
|
315
|
+
// 以 http(s):// 开头但没匹配成完整地址(缺仓库段等)→ 拒绝,避免拆出畸形 owner/repo
|
|
316
|
+
if (/^https?:\/\//i.test(rest)) return null;
|
|
300
317
|
const idx = rest ? rest.indexOf("/") : -1;
|
|
301
318
|
if (idx <= 0 || idx === rest.length - 1) return null;
|
|
302
319
|
const owner = rest.slice(0, idx).trim();
|
|
303
|
-
const repo = rest.slice(idx + 1).trim();
|
|
320
|
+
const repo = rest.slice(idx + 1).trim().replace(/\.git$/, "");
|
|
304
321
|
if (!owner || !repo || /[\s.]/.test(owner) || /[\s\\]/.test(repo)) return null;
|
|
305
322
|
return { platform, owner, repo };
|
|
306
323
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gitee-ai-employee",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.2",
|
|
4
4
|
"description": "Gitee/GitHub AI 员工:issue 里 @ 机器人自动开发并提 PR;v1.2 新增代码安全扫描——配置仓库地址后按内置/自定义提示词扫描漏洞,去重后提交 issue。An issue-driven AI developer for DeepSeek Harness (Gitee & GitHub) with optional static security scanning (dedup + issue reporting).",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|