gitee-ai-employee 1.2.0 → 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.
Files changed (3) hide show
  1. package/dsh/client.js +608 -608
  2. package/dsh/index.js +1894 -1848
  3. package/package.json +61 -61
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,[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
- })
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
+ })