@ai-wendao/ant 0.4.13 → 0.4.15
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/README.md +8 -4
- package/client.js +493 -385
- package/conversation.js +115 -8
- package/delegation.js +83 -154
- package/index.js +26 -3
- package/package.json +1 -1
package/client.js
CHANGED
|
@@ -20,12 +20,13 @@ window.__ModuleLoader__.load({
|
|
|
20
20
|
const PLUGIN_INSTALL_RECORD_PREFIX = 'ant-reviewed-plugin-install:'
|
|
21
21
|
const ANT_SETTINGS_TAB_STORAGE_KEY = 'ant-settings-tab'
|
|
22
22
|
const ANT_SETTINGS_TABS = [
|
|
23
|
-
['connection', '团队连接'], ['models', '团队模型'], ['
|
|
24
|
-
['
|
|
23
|
+
['connection', '团队连接'], ['models', '团队模型'], ['collaboration', '协作'],
|
|
24
|
+
['local', '本机与任务'], ['updates', '关于'],
|
|
25
25
|
]
|
|
26
26
|
const loadAntSettingsTab = () => {
|
|
27
27
|
try {
|
|
28
28
|
const saved = localStorage.getItem(ANT_SETTINGS_TAB_STORAGE_KEY)
|
|
29
|
+
if (saved === 'bot-chat' || saved === 'capability') return 'collaboration'
|
|
29
30
|
return ANT_SETTINGS_TABS.some(([id]) => id === saved) ? saved : 'connection'
|
|
30
31
|
} catch { return 'connection' }
|
|
31
32
|
}
|
|
@@ -405,7 +406,7 @@ window.__ModuleLoader__.load({
|
|
|
405
406
|
h('span', null, bindingSummary),
|
|
406
407
|
!bindingReadyForSummary ? h('button', { type: 'button', onClick: () => selectSettingsTab('connection'),
|
|
407
408
|
style: secondaryButtonStyle }, binding.state === 'error' ? '检查连接' : '查看连接设置')
|
|
408
|
-
: activeTab === 'connection' ? h('button', { type: 'button', onClick: () => selectSettingsTab('
|
|
409
|
+
: activeTab === 'connection' ? h('button', { type: 'button', onClick: () => selectSettingsTab('collaboration'),
|
|
409
410
|
style: secondaryButtonStyle }, '设置或管理机器人') : null),
|
|
410
411
|
h('div', { style: { position: 'sticky', top: 0, zIndex: 8, display: 'grid', gap: 8, padding: '6px 0',
|
|
411
412
|
backgroundColor: 'var(--dsw-alias-bg-base)' } },
|
|
@@ -472,17 +473,20 @@ window.__ModuleLoader__.load({
|
|
|
472
473
|
const [repositoryCacheLimitGib, setRepositoryCacheLimitGib] = useState(10)
|
|
473
474
|
const [botId, setBotId] = useState('')
|
|
474
475
|
const [trustedOrigin, setTrustedOrigin] = useState('')
|
|
475
|
-
const [bindingCode, setBindingCode] = useState('')
|
|
476
476
|
const [conversationKind, setConversationKind] = useState('dm')
|
|
477
477
|
const [groupId, setGroupId] = useState('')
|
|
478
|
-
const [
|
|
478
|
+
const [ruleDescription, setRuleDescription] = useState('')
|
|
479
|
+
const [triggerPhrases, setTriggerPhrases] = useState('')
|
|
480
|
+
const recognizedBaselineRef = useRef(null)
|
|
481
|
+
const recognizedDescriptionRef = useRef(null)
|
|
482
|
+
const [recognitionBusy, setRecognitionBusy] = useState(false)
|
|
479
483
|
const [title, setTitle] = useState('')
|
|
480
484
|
const [intent, setIntent] = useState('')
|
|
481
|
-
const [examples, setExamples] = useState('')
|
|
482
485
|
const [taskPrompt, setTaskPrompt] = useState('')
|
|
483
486
|
const [runner, setRunner] = useState('dsh')
|
|
484
487
|
const [reviewers, setReviewers] = useState(['dsh'])
|
|
485
488
|
const [parameters, setParameters] = useState([])
|
|
489
|
+
const [crossReviewOpen, setCrossReviewOpen] = useState(false)
|
|
486
490
|
const [giteaToken, setGiteaToken] = useState('')
|
|
487
491
|
const [tokenConfigured, setTokenConfigured] = useState(false)
|
|
488
492
|
const [senderScope, setSenderScope] = useState('any')
|
|
@@ -550,23 +554,41 @@ window.__ModuleLoader__.load({
|
|
|
550
554
|
: !selectedEnrollmentBound ? '请先把机器人绑定到这台 DSH,再修改私聊设置。'
|
|
551
555
|
: !dmChatSettingAvailable ? '当前团队服务端尚未提供私聊开关,请先升级服务端。'
|
|
552
556
|
: dmChatEnabled
|
|
553
|
-
? '
|
|
554
|
-
: '新私聊消息不会交给 DSH
|
|
557
|
+
? '允许此机器人接收私聊;任务仍按已开放的规则执行。'
|
|
558
|
+
: '新私聊消息不会交给 DSH;已提交任务仍会在原私聊回复。已开放能力不撤销,重新开启后未过期能力恢复触发。'
|
|
555
559
|
const availableGroups = groups.filter((group) => group.bot_fingerprint === selectedBot?.fingerprint)
|
|
556
|
-
const selectedChatGroup = availableGroups.find((group) => group.group_id === chatGroupId)
|
|
557
560
|
const groupChatLoadFailed = loadFailures.includes('groups')
|
|
558
561
|
const activeCards = cards.filter((card) => !card.revoked_at && card.expires_at * 1000 > Date.now())
|
|
559
|
-
const selectedGroupHasTasks = Boolean(selectedBot && chatGroupId && (
|
|
560
|
-
activeCards.some((card) => card.bot_fingerprint === selectedBot.fingerprint
|
|
561
|
-
&& card.conversation_kind === 'group' && card.conversation_id === chatGroupId)))
|
|
562
562
|
const connectedBots = bots.filter((bot) => bot.available && bot.connected)
|
|
563
563
|
const authorizationLoadError = loadFailures.includes('cards')
|
|
564
564
|
const enrollmentLoadError = loadFailures.includes('enrolled-bots')
|
|
565
565
|
const enrolledCount = enrolled.filter((item) => item.device_bound_to_current
|
|
566
566
|
&& connectedBots.some((bot) => bot.fingerprint === item.bot_fingerprint)).length
|
|
567
567
|
const selectedTaskCount = activeCards.filter((card) => card.bot_fingerprint === selectedBot?.fingerprint).length
|
|
568
|
+
const legacyTriggerCardCount = activeCards.filter((card) => card.bot_fingerprint === selectedBot?.fingerprint
|
|
569
|
+
&& (!Array.isArray(card.trigger_phrases) || !card.trigger_phrases.length)).length
|
|
570
|
+
const collaborationStatus = loading ? '正在读取机器人与任务状态…'
|
|
571
|
+
: botLoadError ? '暂时无法读取机器人状态。请刷新后再试。'
|
|
572
|
+
: connectedBots.length === 0 ? '尚未发现已连接的机器人。请先在 DSH-IM 中连接飞书机器人。'
|
|
573
|
+
: selectedBot && !botConnected ? `“${selectedBot.name}”当前离线;请先在 DSH-IM 恢复连接。`
|
|
574
|
+
: enrollmentLoadError ? selectedBot
|
|
575
|
+
? `“${selectedBot.name}”已连接;暂时无法确认协作是否已启用,请刷新状态。`
|
|
576
|
+
: '机器人已连接;协作启用状态暂未读取,请刷新状态。'
|
|
577
|
+
: selectedBot && !selectedEnrollmentBound ? selectedEnrollment
|
|
578
|
+
? `“${selectedBot.name}”已连接;原协作配置尚未绑定到这台 DSH,请在本机重新启用。若原设备令牌曾被撤销,旧任务权限已关闭,绑定后需重新开放。`
|
|
579
|
+
: `“${selectedBot.name}”已连接;请先启用本机协作。`
|
|
580
|
+
: enrolledCount === 0 ? '机器人已连接;请选择并启用协作。'
|
|
581
|
+
: !selectedBot ? '已有协作机器人;请选择一台,查看已开放的能力和聊天范围。'
|
|
582
|
+
: authorizationLoadError ? `“${selectedBot.name}”已启用协作;已开放任务暂未读取,请刷新状态。`
|
|
583
|
+
: !dmChatEnabled ? `“${selectedBot.name}”已启用协作;私聊已关闭,群聊仍按各自设置工作。`
|
|
584
|
+
: selectedTaskCount === 0 ? `“${selectedBot.name}”已启用协作;下一步开放一项能力。你也可以先私聊自测普通聊天。`
|
|
585
|
+
: `“${selectedBot.name}”已启用协作 · ${selectedTaskCount} 项能力已开放。可以私聊自测。`
|
|
568
586
|
const nativeReviewerSelected = reviewers.some((item) => ['codex', 'claude-code'].includes(item))
|
|
569
587
|
const hasPrParameter = parameters.some((item) => item.kind === 'gitea_pr')
|
|
588
|
+
const hasSelectedPrRule = activeCards.some((card) => card.bot_fingerprint === selectedBot?.fingerprint
|
|
589
|
+
&& Array.isArray(card.parameters) && card.parameters.some((rule) => rule.kind === 'gitea_pr'))
|
|
590
|
+
const showPrSecuritySettings = Boolean(selectedBot && (hasPrParameter || hasSelectedPrRule))
|
|
591
|
+
const selectedGroupSummary = availableGroups.find((group) => group.group_id === groupId)?.group_code || groupId
|
|
570
592
|
const codexAvailable = executors.some((item) => item.id === 'codex-readonly' && item.available)
|
|
571
593
|
const claudeCodeAvailable = executors.some((item) => item.id === 'claude-code' && item.available)
|
|
572
594
|
const dshAvailable = executors.some((item) => item.id === 'dsh' && item.available)
|
|
@@ -582,23 +604,57 @@ window.__ModuleLoader__.load({
|
|
|
582
604
|
}
|
|
583
605
|
const requesterSummary = senderScope === 'allowlist'
|
|
584
606
|
? `仅指定成员:${senderIds.trim() || '尚未填写成员 ID'}`
|
|
585
|
-
: conversationKind === 'group' ?
|
|
607
|
+
: conversationKind === 'group' ? '群内所有成员' : '任何能私聊此机器人的人'
|
|
586
608
|
const resultRouteSummary = conversationKind === 'group' ? '回复到原群(群内可见)' : '回复到发起任务的私聊'
|
|
609
|
+
const ruleTriggerSummary = triggerPhrases.split('\n').map((item) => item.trim()).filter(Boolean)
|
|
610
|
+
const ruleSummaryReady = Boolean(title || intent || ruleTriggerSummary.length || parameters.length || taskPrompt)
|
|
611
|
+
const parameterSummary = (rule) => {
|
|
612
|
+
const parts = [rule.required === false ? '可选' : '必填']
|
|
613
|
+
const choices = Array.isArray(rule.choices) ? rule.choices
|
|
614
|
+
: typeof rule.choices === 'string' ? rule.choices.split(/[,,\n]/u).map((item) => item.trim()).filter(Boolean) : []
|
|
615
|
+
if (rule.kind === 'gitea_pr' && rule.repository) parts.push(`仓库 ${rule.repository}`)
|
|
616
|
+
else if (rule.kind === 'choice' && choices.length) parts.push(`选项 ${choices.join('、')}`)
|
|
617
|
+
else if (rule.kind === 'integer') {
|
|
618
|
+
const minValue = rule.min == null || rule.min === '' ? null : Number(rule.min)
|
|
619
|
+
const maxValue = rule.max == null || rule.max === '' ? null : Number(rule.max)
|
|
620
|
+
const minimum = minValue != null && Number.isFinite(minValue) ? minValue : null
|
|
621
|
+
const maximum = maxValue != null && Number.isFinite(maxValue) ? maxValue : null
|
|
622
|
+
if (minimum != null && maximum != null) parts.push(`范围 ${minimum}–${maximum}`)
|
|
623
|
+
else if (minimum != null) parts.push(`最少 ${minimum}`)
|
|
624
|
+
else if (maximum != null) parts.push(`最多 ${maximum}`)
|
|
625
|
+
} else if (rule.kind === 'text' && rule.max != null && rule.max !== ''
|
|
626
|
+
&& Number.isFinite(Number(rule.max))) {
|
|
627
|
+
parts.push(`文字最多 ${Number(rule.max)} 字节`)
|
|
628
|
+
}
|
|
629
|
+
return `${rule.description?.trim() || rule.name}(${parts.join(';')})`
|
|
630
|
+
}
|
|
631
|
+
const recognizedInputsSummary = parameters.length
|
|
632
|
+
? `请求时还需提供:${parameters.map(parameterSummary).join(';')}`
|
|
633
|
+
: '无需额外参数,按请求中的内容处理'
|
|
634
|
+
const ruleSummaryNarrative = ruleSummaryReady
|
|
635
|
+
? `${ruleTriggerSummary.length ? `消息以“${ruleTriggerSummary.join('”或“')}”开头时,` : ''}${intent.trim() || title.trim()}${taskPrompt.trim() ? `;${taskPrompt.trim()}` : ''};${recognizedInputsSummary}`
|
|
636
|
+
: '识别后会用一段摘要呈现系统理解的任务。'
|
|
587
637
|
const executorSummary = runner === 'codex' ? '本机 Codex · PR 快照'
|
|
588
638
|
: runner === 'claude-code' ? '本机 Claude Code · PR 快照' : 'DSH · 当前授权模型'
|
|
589
|
-
const
|
|
590
|
-
: '当前会话中 DSH 实际可见的内容'
|
|
639
|
+
const crossReviewSummary = reviewers.filter((item) => item !== runner).map(reviewerLabel).join('、')
|
|
591
640
|
const cardBlockReason = enrollmentLoadError ? '暂时无法核对机器人是否已启用协作,请刷新状态后再试。'
|
|
592
641
|
: !botConnected || !selectedEnrollmentBound ? '先在上方把机器人绑定到这台 DSH。'
|
|
642
|
+
: loadFailures.includes('settings') || defaults?.task_rule_schema_version !== 2
|
|
643
|
+
? '团队服务端尚未支持触发规则的持久化与执行前核验;请先升级 ANT 服务端。'
|
|
644
|
+
: !ruleDescription.trim() ? '请先用自然语言描述要做的任务。'
|
|
645
|
+
: recognizedDescriptionRef.current !== null && recognizedDescriptionRef.current !== ruleDescription.trim()
|
|
646
|
+
? '描述已修改,请重新识别,让执行规则与描述保持一致。'
|
|
647
|
+
: !title.trim() || !intent.trim() || !taskPrompt.trim() ? '先识别任务描述并检查摘要,再开放任务。'
|
|
648
|
+
: !triggerPhrases.trim() ? '描述里还没有明确写出何时触发。请补充触发方式并重新识别。'
|
|
593
649
|
: conversationKind === 'group' && loadFailures.includes('groups') ? '暂时无法核对目标群,请刷新状态后再试。'
|
|
594
|
-
: conversationKind === 'group' && !groupId ? '
|
|
595
|
-
: senderScope === 'allowlist' && !senderIds.trim() ? '
|
|
650
|
+
: conversationKind === 'group' && !groupId ? '请在“请求范围”中选择目标群;若列表为空,让群成员先 @机器人,再刷新群列表。'
|
|
651
|
+
: senderScope === 'allowlist' && !senderIds.trim() ? '请填写可请求成员的 IM ID。'
|
|
596
652
|
: parameters.some((rule) => rule.kind === 'gitea_pr') && !tokenConfigured ? '请先在“本机与任务”中保存本机 Gitea 只读令牌。'
|
|
597
|
-
|
|
598
|
-
? '
|
|
653
|
+
: parameters.some((rule) => rule.kind === 'gitea_pr') && !selectedEnrollment?.gitea_origin
|
|
654
|
+
? 'PR 来源尚未信任。请到“本机与任务”设置可信 Gitea 来源。'
|
|
599
655
|
: parameters.some((rule) => rule.kind === 'gitea_pr')
|
|
600
656
|
&& normalizeServiceUrl(trustedOrigin) !== selectedEnrollment?.gitea_origin
|
|
601
|
-
? '
|
|
657
|
+
? '请先在“本机与任务”保存可信 Gitea 来源;任务描述中的 PR 链接会使用这个来源。'
|
|
602
658
|
: nativeReviewerSelected && (parameters.length !== 1 || parameters[0].kind !== 'gitea_pr')
|
|
603
659
|
? 'Codex 和 Claude Code 目前只支持单个 Gitea PR 审查任务。'
|
|
604
660
|
: nativeReviewerSelected && !parameters[0].required
|
|
@@ -613,57 +669,115 @@ window.__ModuleLoader__.load({
|
|
|
613
669
|
const loadNames = { bots: '机器人', 'enrolled-bots': '协作启用状态', groups: '群聊列表',
|
|
614
670
|
cards: '任务授权', settings: '团队默认值', executors: '本机执行器',
|
|
615
671
|
'local-settings': '本机设置', status: '最近任务' }
|
|
616
|
-
const updateParameter = (index, patch) => setParameters((current) => current.map((rule, i) => i === index ? { ...rule, ...patch } : rule))
|
|
617
672
|
const changeReviewer = (id, enabled) => {
|
|
618
673
|
const next = enabled ? [...reviewers, id] : reviewers.filter((item) => item !== id)
|
|
619
674
|
if (!next.length) { setMessage('至少保留一位审查 Agent。'); return }
|
|
620
675
|
setReviewers([...new Set(next)])
|
|
621
676
|
if (!next.includes(runner)) setRunner(next[0])
|
|
622
677
|
}
|
|
623
|
-
const
|
|
678
|
+
const selectRunner = (id) => {
|
|
679
|
+
setRunner(id)
|
|
680
|
+
setReviewers((current) => current.length > 1
|
|
681
|
+
? [...new Set([...current, id])]
|
|
682
|
+
: [id])
|
|
683
|
+
}
|
|
684
|
+
const startNewCard = (context = {}) => {
|
|
624
685
|
setEditingCardId(null); setEditingExpiresAt(null)
|
|
625
686
|
setDraftNotice('')
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
687
|
+
recognizedBaselineRef.current = null
|
|
688
|
+
recognizedDescriptionRef.current = null
|
|
689
|
+
setRuleDescription('')
|
|
690
|
+
setTriggerPhrases('')
|
|
691
|
+
setTitle(''); setIntent(''); setTaskPrompt(''); setRunner('dsh')
|
|
692
|
+
setReviewers(['dsh']); setCrossReviewOpen(false)
|
|
693
|
+
setParameters([]); setConversationKind(context.conversationKind || 'dm')
|
|
694
|
+
setGroupId(context.groupId || '')
|
|
629
695
|
setSenderScope('any'); setSenderIds(''); setDays(7); setCreateOpen(true)
|
|
696
|
+
if (context.botId) setBotId(context.botId)
|
|
630
697
|
}
|
|
698
|
+
const taskRuleDraftFromCard = (card, title) => ({
|
|
699
|
+
title: title || '', intent: card.intent || '', task_prompt: card.task_prompt || '',
|
|
700
|
+
trigger_phrases: Array.isArray(card.trigger_phrases) ? card.trigger_phrases.map((item) => item.trim()).filter(Boolean) : [],
|
|
701
|
+
parameters: (card.parameters || []).map((rule) => ({
|
|
702
|
+
name: rule.name, kind: rule.kind, required: rule.required,
|
|
703
|
+
description: rule.description || '', min: rule.min == null ? '' : String(rule.min),
|
|
704
|
+
max: rule.max == null ? '' : String(rule.max),
|
|
705
|
+
choices: Array.isArray(rule.choices) ? rule.choices.join(', ') : String(rule.choices ?? ''),
|
|
706
|
+
gitea_origin: rule.gitea_origin || '', repository: rule.repository || '',
|
|
707
|
+
})),
|
|
708
|
+
})
|
|
631
709
|
const editCard = (card, copy = false) => {
|
|
632
710
|
setBotId(card.bot_id)
|
|
633
711
|
setTrustedOrigin(enrolled.find((bot) => bot.bot_id === card.bot_id)?.gitea_origin || '')
|
|
634
712
|
setEditingCardId(copy ? null : card.id); setEditingExpiresAt(copy ? null : card.expires_at)
|
|
635
|
-
|
|
713
|
+
const taskDraft = taskRuleDraftFromCard(card, copy ? `${card.title.slice(0, 94)}(副本)` : card.title)
|
|
714
|
+
recognizedBaselineRef.current = taskDraft
|
|
715
|
+
const existingDescription = card.rule_description || card.intent || ''
|
|
716
|
+
recognizedDescriptionRef.current = existingDescription.trim() || null
|
|
717
|
+
const legacyTriggerRule = !Array.isArray(card.trigger_phrases) || !card.trigger_phrases.length
|
|
718
|
+
setDraftNotice(legacyTriggerRule
|
|
719
|
+
? '这项旧任务没有明确触发方式;新版插件不会按它启动任务。请升级 ANT 服务端后重新描述、识别并开放规则。'
|
|
720
|
+
: '')
|
|
721
|
+
setRuleDescription(existingDescription)
|
|
722
|
+
setTriggerPhrases(taskDraft.trigger_phrases.join('\n'))
|
|
723
|
+
setTitle(taskDraft.title)
|
|
636
724
|
if (copy) setDays(7)
|
|
637
|
-
setIntent(
|
|
638
|
-
setTaskPrompt(
|
|
725
|
+
setIntent(taskDraft.intent)
|
|
726
|
+
setTaskPrompt(taskDraft.task_prompt); setRunner(card.runner)
|
|
639
727
|
setReviewers(Array.isArray(card.reviewers) && card.reviewers.length ? card.reviewers : [card.runner])
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
description: rule.description || '', min: rule.min == null ? '' : String(rule.min),
|
|
643
|
-
max: rule.max == null ? '' : String(rule.max), choices: (rule.choices || []).join(', '),
|
|
644
|
-
gitea_origin: rule.gitea_origin || '', repository: rule.repository || '',
|
|
645
|
-
})))
|
|
728
|
+
setCrossReviewOpen(false)
|
|
729
|
+
setParameters(taskDraft.parameters)
|
|
646
730
|
setConversationKind(card.conversation_kind); setGroupId(card.conversation_id || '')
|
|
647
731
|
setSenderScope(card.sender_scope); setSenderIds((card.sender_ids || []).join(', '))
|
|
648
732
|
setCreateOpen(true)
|
|
649
733
|
}
|
|
650
|
-
const
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
734
|
+
const applyRuleDraft = (draft) => {
|
|
735
|
+
setTitle(draft.title)
|
|
736
|
+
setIntent(draft.intent)
|
|
737
|
+
setTriggerPhrases(draft.trigger_phrases.join('\n'))
|
|
738
|
+
setTaskPrompt(draft.task_prompt)
|
|
739
|
+
setParameters(draft.parameters)
|
|
740
|
+
}
|
|
741
|
+
const identifyRule = async () => {
|
|
742
|
+
if (!ruleDescription.trim()) { setMessage('先描述这项任务何时触发、要做什么以及涉及哪些资料。'); return }
|
|
743
|
+
setRecognitionBusy(true); setMessage('')
|
|
744
|
+
try {
|
|
745
|
+
const response = await fetch('/api/ant-teams/delegation/recognize', {
|
|
746
|
+
method: 'POST', headers: { 'content-type': 'application/json' },
|
|
747
|
+
body: JSON.stringify({ description: ruleDescription }),
|
|
748
|
+
})
|
|
749
|
+
const result = await readLocalJsonResponse(response)
|
|
750
|
+
if (!response.ok) throw new Error(result.message || '规则识别失败。')
|
|
751
|
+
const candidate = {
|
|
752
|
+
title: typeof result.title === 'string' ? result.title : '',
|
|
753
|
+
intent: typeof result.intent === 'string' ? result.intent : '',
|
|
754
|
+
trigger_phrases: Array.isArray(result.trigger_phrases) ? result.trigger_phrases : [],
|
|
755
|
+
task_prompt: typeof result.task_prompt === 'string' ? result.task_prompt : '',
|
|
756
|
+
parameters: Array.isArray(result.parameters) ? result.parameters.map((rule) => ({
|
|
757
|
+
name: typeof rule?.name === 'string' ? rule.name : '',
|
|
758
|
+
kind: typeof rule?.kind === 'string' ? rule.kind : 'text',
|
|
759
|
+
required: rule?.required !== false,
|
|
760
|
+
description: typeof rule?.description === 'string' ? rule.description : '',
|
|
761
|
+
min: rule?.min == null ? '' : String(rule.min),
|
|
762
|
+
max: rule?.max == null ? '' : String(rule.max),
|
|
763
|
+
choices: Array.isArray(rule?.choices) ? rule.choices.join(', ')
|
|
764
|
+
: typeof rule?.choices === 'string' ? rule.choices : '',
|
|
765
|
+
gitea_origin: typeof rule?.gitea_origin === 'string' ? rule.gitea_origin : '',
|
|
766
|
+
repository: typeof rule?.repository === 'string' ? rule.repository : '',
|
|
767
|
+
})) : [],
|
|
768
|
+
}
|
|
769
|
+
if (!candidate.title || !candidate.intent || !candidate.task_prompt || !candidate.trigger_phrases.length) {
|
|
770
|
+
throw new Error('识别结果不完整。请在描述中写清楚何时执行,再重新识别。')
|
|
771
|
+
}
|
|
772
|
+
const previous = recognizedBaselineRef.current
|
|
773
|
+
applyRuleDraft(candidate)
|
|
774
|
+
recognizedBaselineRef.current = candidate
|
|
775
|
+
recognizedDescriptionRef.current = ruleDescription.trim()
|
|
776
|
+
setMessage(previous
|
|
777
|
+
? '已根据新描述更新草稿,请检查摘要和授权范围。'
|
|
778
|
+
: '已识别为任务草稿。检查摘要和授权范围;此操作不会开放任务。')
|
|
779
|
+
} catch (error) { setMessage(error.message) }
|
|
780
|
+
finally { setRecognitionBusy(false) }
|
|
667
781
|
}
|
|
668
782
|
const saveGiteaToken = async () => {
|
|
669
783
|
if (!giteaToken.trim()) return
|
|
@@ -717,29 +831,12 @@ window.__ModuleLoader__.load({
|
|
|
717
831
|
} catch (error) { setMessage(error.message) }
|
|
718
832
|
finally { setBusy(false) }
|
|
719
833
|
}
|
|
720
|
-
const
|
|
721
|
-
if (!selectedBot || !selectedEnrollmentBound) return
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
const response = await fetch('/api/ant-teams/delegation/owner-challenge', {
|
|
725
|
-
method: 'POST', headers: { 'content-type': 'application/json' },
|
|
726
|
-
body: JSON.stringify({ bot_id: selectedBot.bot_id, bot_fingerprint: selectedBot.fingerprint }),
|
|
727
|
-
})
|
|
728
|
-
const result = await readLocalJsonResponse(response)
|
|
729
|
-
if (!response.ok) throw new Error(result.message || '无法获取身份绑定码。')
|
|
730
|
-
setBindingCode(result.code)
|
|
731
|
-
setMessage('')
|
|
732
|
-
} catch (error) { setMessage(error.message) }
|
|
733
|
-
finally { setBusy(false) }
|
|
734
|
-
}
|
|
735
|
-
const saveGroupChat = async (enabled) => {
|
|
736
|
-
if (!selectedBot || !selectedEnrollmentBound || !chatGroupId.trim()) return
|
|
737
|
-
if (!enabled && selectedGroupHasTasks) {
|
|
738
|
-
setMessage('这个群还有开放能力。请先停止这些能力,再关闭群聊。')
|
|
739
|
-
return
|
|
740
|
-
}
|
|
834
|
+
const saveGroupChat = async (enabled, targetGroupId) => {
|
|
835
|
+
if (!selectedBot || !selectedEnrollmentBound || !targetGroupId.trim()) return
|
|
836
|
+
const targetHasTasks = activeCards.some((card) => card.bot_fingerprint === selectedBot.fingerprint
|
|
837
|
+
&& card.conversation_kind === 'group' && card.conversation_id === targetGroupId)
|
|
741
838
|
if (!window.confirm(enabled
|
|
742
|
-
?
|
|
839
|
+
? targetHasTasks
|
|
743
840
|
? '这个群当前因任务授权可以聊天。要在任务到期后仍允许普通聊天吗?聊天会使用团队模型额度。'
|
|
744
841
|
: '允许这个群与机器人自然聊天?群成员仍不能执行未开放的能力,聊天会使用团队模型额度。'
|
|
745
842
|
: '关闭这个群的普通聊天?已有能力不会自动撤销。')) return
|
|
@@ -748,7 +845,7 @@ window.__ModuleLoader__.load({
|
|
|
748
845
|
const response = await fetch('/api/ant-teams/delegation/groups', {
|
|
749
846
|
method: 'PUT', headers: { 'content-type': 'application/json' },
|
|
750
847
|
body: JSON.stringify({ bot_id: selectedBot.bot_id,
|
|
751
|
-
bot_fingerprint: selectedBot.fingerprint, group_id:
|
|
848
|
+
bot_fingerprint: selectedBot.fingerprint, group_id: targetGroupId.trim(), enabled }),
|
|
752
849
|
})
|
|
753
850
|
if (!response.ok) {
|
|
754
851
|
const result = await readLocalJsonResponse(response)
|
|
@@ -762,8 +859,8 @@ window.__ModuleLoader__.load({
|
|
|
762
859
|
const saveDmChat = async (enabled) => {
|
|
763
860
|
if (!selectedBot || !selectedEnrollmentBound || !dmChatSettingAvailable) return
|
|
764
861
|
if (!window.confirm(enabled
|
|
765
|
-
? '
|
|
766
|
-
: '关闭后,新私聊消息不会进入 DSH
|
|
862
|
+
? '开启后,此机器人会在私聊中正常对话;私聊任务仍逐项按已开放规则核验。'
|
|
863
|
+
: '关闭后,新私聊消息不会进入 DSH,包括普通聊天、任务请求和状态查询。关闭前已提交的任务不会被撤销,仍按原私聊回复。已开放的私聊能力不会删除;重新开启后,未过期的能力会恢复触发。群聊设置不受影响。')) return
|
|
767
864
|
setBusy(true)
|
|
768
865
|
try {
|
|
769
866
|
const response = await fetch('/api/ant-teams/delegation/dm-chat', {
|
|
@@ -808,19 +905,33 @@ window.__ModuleLoader__.load({
|
|
|
808
905
|
})
|
|
809
906
|
}
|
|
810
907
|
const goToSetupStep = (index) => {
|
|
811
|
-
if (index === 0) return focusAfterTabChange('
|
|
812
|
-
if (index === 1) return focusAfterTabChange('
|
|
813
|
-
|
|
908
|
+
if (index === 0) return focusAfterTabChange('collaboration', selectedBot ? 'ant-bot-selector' : 'ant-chat-entry')
|
|
909
|
+
if (index === 1) return focusAfterTabChange('collaboration',
|
|
910
|
+
selectedEnrollmentBound && botConnected ? 'ant-group-task-status' : 'ant-chat-scope-panel')
|
|
911
|
+
if (index === 2 && (authorizationLoadError || selectedTaskCount > 0)) {
|
|
912
|
+
return focusAfterTabChange('collaboration', 'ant-capability-list')
|
|
913
|
+
}
|
|
914
|
+
if (index === 2) {
|
|
915
|
+
startNewCard()
|
|
916
|
+
return focusAfterTabChange('collaboration', 'ant-new-task-summary')
|
|
917
|
+
}
|
|
814
918
|
setCreateOpen(true)
|
|
815
|
-
focusAfterTabChange('
|
|
919
|
+
focusAfterTabChange('collaboration', 'ant-authorization-review')
|
|
816
920
|
}
|
|
921
|
+
const collaborationGridStyle = {
|
|
922
|
+
display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(min(100%, 300px), 1fr))', gap: 12,
|
|
923
|
+
}
|
|
924
|
+
const collaborationSettingsGridStyle = {
|
|
925
|
+
...collaborationGridStyle, gridTemplateColumns: 'repeat(auto-fit, minmax(min(100%, 420px), 1fr))',
|
|
926
|
+
}
|
|
927
|
+
const collaborationCardStyle = { ...settingsPanelStyle, padding: 12, gap: 10 }
|
|
817
928
|
const setupStepper = h('section', { 'aria-label': '首次设置引导', style: { display: 'grid', gap: 6 } },
|
|
818
929
|
h('p', { style: mutedTextStyle }, '首次设置可按顺序完成;你也可以直接切换页面。普通聊天不需要先开放任务。'),
|
|
819
930
|
h('ol', { 'aria-label': '设置步骤', style: { display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(min(100%, 150px), 1fr))',
|
|
820
931
|
gap: 8, padding: 0, margin: 0, listStyle: 'none' } },
|
|
821
932
|
...[
|
|
822
933
|
{ label: '机器人和设备', detail: !selectedBot ? '选择已连接的机器人' : !botConnected ? '检查 DSH-IM 连接' : selectedEnrollmentBound ? '已绑定到这台 DSH' : '绑定到这台 DSH' },
|
|
823
|
-
{ label: '聊天入口', detail: !selectedBot || !selectedEnrollmentBound ? '绑定设备后设置私聊和群聊' : groupChatLoadFailed ? '
|
|
934
|
+
{ label: '聊天入口', detail: !selectedBot || !selectedEnrollmentBound ? '绑定设备后设置私聊和群聊' : groupChatLoadFailed ? '群列表暂不可用,请刷新状态' : `${dmChatEnabled ? '私聊可用' : '私聊已关闭'} · ${availableGroups.length} 个已发现群` },
|
|
824
935
|
{ label: '开放能力', detail: !botConnected || !selectedEnrollmentBound ? '完成机器人绑定后再开放' : authorizationLoadError ? '授权列表暂不可读,请刷新状态' : selectedTaskCount ? `${selectedTaskCount} 项已开放,可查看或停止` : '按需要创建任务' },
|
|
825
936
|
{ label: '复核授权', detail: authorizationLoadError ? '先确认已保存授权' : createOpen ? '检查请求范围后确认' : selectedTaskCount ? '查看已开放范围' : '请求人、执行方和期限' },
|
|
826
937
|
].map((step, index) => {
|
|
@@ -838,32 +949,15 @@ window.__ModuleLoader__.load({
|
|
|
838
949
|
h('span', { style: { fontSize: 12, color: mutedTextStyle.color } }, state)),
|
|
839
950
|
h('span', { style: mutedTextStyle }, step.detail)))
|
|
840
951
|
})))
|
|
841
|
-
const chatScopePanel = h('
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
h('div', { style: { display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(min(100%, 240px), 1fr))', gap: 8 } },
|
|
845
|
-
h('div', { style: { display: 'grid', gap: 5, padding: 10, borderRadius: 8,
|
|
846
|
-
border: '1px solid var(--dsw-alias-border-l2)', backgroundColor: 'var(--dsw-alias-bg-layer-1)' } },
|
|
847
|
-
h('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: 8 } },
|
|
848
|
-
h('label', { style: { display: 'flex', gap: 8, alignItems: 'center' } },
|
|
849
|
-
h('input', { type: 'checkbox', checked: dmChatEnabled,
|
|
850
|
-
disabled: busy || !selectedBot || !selectedEnrollmentBound
|
|
851
|
-
|| enrollmentLoadError || !dmChatSettingAvailable,
|
|
852
|
-
onChange: (event) => void saveDmChat(event.target.checked),
|
|
853
|
-
'aria-label': '允许机器人私聊' }), h('strong', null, '私聊')),
|
|
854
|
-
h('span', { style: { ...mutedTextStyle, whiteSpace: 'nowrap' } }, dmChatStatus)),
|
|
855
|
-
h('p', { style: mutedTextStyle }, dmChatDescription)),
|
|
856
|
-
h('div', { style: { display: 'grid', gap: 5, padding: 10, borderRadius: 8,
|
|
952
|
+
const chatScopePanel = h('section', { id: 'ant-chat-scope-panel', tabIndex: -1, style: collaborationCardStyle },
|
|
953
|
+
h('strong', null, '私聊入口'),
|
|
954
|
+
h('label', { style: { display: 'flex', gap: 8, alignItems: 'center', padding: 10, borderRadius: 8,
|
|
857
955
|
border: '1px solid var(--dsw-alias-border-l2)', backgroundColor: 'var(--dsw-alias-bg-layer-1)' } },
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
onChange: (event) => void saveGroupChat(event.target.checked),
|
|
864
|
-
'aria-label': '允许所选群进行普通聊天' }), h('strong', null, '群聊')),
|
|
865
|
-
h('span', { style: { ...mutedTextStyle, whiteSpace: 'nowrap' } }, `${availableGroups.length} 个已发现`)),
|
|
866
|
-
h('p', { style: mutedTextStyle }, '选择一个群后可单独启用;不影响私聊设置,群任务仍需单独授权。'))))
|
|
956
|
+
h('input', { type: 'checkbox', checked: dmChatEnabled,
|
|
957
|
+
disabled: busy || !selectedBot || !selectedEnrollmentBound || enrollmentLoadError || !dmChatSettingAvailable,
|
|
958
|
+
onChange: (event) => void saveDmChat(event.target.checked), 'aria-label': '允许机器人私聊' }),
|
|
959
|
+
h('strong', null, '允许私聊普通聊天'), h('span', { style: mutedTextStyle }, dmChatStatus)),
|
|
960
|
+
h('p', { style: mutedTextStyle }, dmChatDescription))
|
|
867
961
|
const createCard = async (event) => {
|
|
868
962
|
event.preventDefault()
|
|
869
963
|
setDraftNotice('')
|
|
@@ -871,37 +965,40 @@ window.__ModuleLoader__.load({
|
|
|
871
965
|
if (editingCardId && (!editingExpiresAt || editingExpiresAt <= Math.floor(Date.now() / 1000))) {
|
|
872
966
|
setMessage('这项能力已到期,无法继续编辑;请返回后重新开放一项能力。'); return
|
|
873
967
|
}
|
|
874
|
-
if (!title.trim() || !intent.trim() || !taskPrompt.trim()) {
|
|
875
|
-
setMessage('
|
|
968
|
+
if (!ruleDescription.trim() || !title.trim() || !intent.trim() || !taskPrompt.trim()) {
|
|
969
|
+
setMessage('请填写任务描述并点击“识别规则”,检查摘要后再开放。'); return
|
|
876
970
|
}
|
|
877
971
|
const bytes = (value) => new TextEncoder().encode(value).length
|
|
878
|
-
if (bytes(
|
|
879
|
-
|
|
972
|
+
if (bytes(ruleDescription.trim()) > 4000 || bytes(title.trim()) > 100
|
|
973
|
+
|| bytes(intent.trim()) > 1000 || bytes(taskPrompt.trim()) > 4000) {
|
|
974
|
+
setMessage('任务描述或识别摘要过长,请缩短描述并重新识别。'); return
|
|
880
975
|
}
|
|
881
|
-
const
|
|
882
|
-
if (
|
|
883
|
-
|
|
976
|
+
const triggerLines = triggerPhrases.split('\n').map((item) => item.normalize('NFKC').trim()).filter(Boolean)
|
|
977
|
+
if (!triggerLines.length || triggerLines.length > 8 || triggerLines.some((item) => bytes(item) > 120)
|
|
978
|
+
|| new Set(triggerLines.map((item) => item.toLowerCase())).size !== triggerLines.length) {
|
|
979
|
+
setMessage('没能从描述中识别出清楚的触发方式。请写明消息以什么开头时执行,再重新识别。'); return
|
|
884
980
|
}
|
|
885
981
|
const normalized = parameters.map((rule) => ({ name: rule.name.trim(), kind: rule.kind,
|
|
886
982
|
description: rule.description.trim() || null,
|
|
887
|
-
required: rule.required, min: rule.min === '' ? null : Number(rule.min),
|
|
888
|
-
max: rule.max === '' ? null : Number(rule.max),
|
|
889
|
-
choices: rule.kind === 'choice' ? rule.choices
|
|
983
|
+
required: rule.required, min: rule.min == null || rule.min === '' ? null : Number(rule.min),
|
|
984
|
+
max: rule.max == null || rule.max === '' ? null : Number(rule.max),
|
|
985
|
+
choices: rule.kind === 'choice' ? (Array.isArray(rule.choices) ? rule.choices
|
|
986
|
+
: String(rule.choices ?? '').split(/[,,\n]/u)).map((item) => item.trim()).filter(Boolean) : null,
|
|
890
987
|
gitea_origin: rule.kind === 'gitea_pr' ? selectedEnrollment?.gitea_origin || null : null,
|
|
891
988
|
repository: rule.kind === 'gitea_pr' ? rule.repository.trim().toLowerCase() : null }))
|
|
892
989
|
if (normalized.some((rule) => !/^[A-Za-z][A-Za-z0-9_]{0,31}$/u.test(rule.name))) {
|
|
893
|
-
setMessage('
|
|
990
|
+
setMessage('没能从描述中整理出有效的请求信息。请说明同事需要提供什么,再重新识别。'); return
|
|
894
991
|
}
|
|
895
992
|
if (new Set(normalized.map((rule) => rule.name)).size !== normalized.length) {
|
|
896
|
-
setMessage('
|
|
993
|
+
setMessage('请求信息识别有重复项。请修改任务描述并重新识别。'); return
|
|
897
994
|
}
|
|
898
995
|
if (normalized.some((rule) => rule.description && bytes(rule.description) > 100)) {
|
|
899
|
-
setMessage('
|
|
996
|
+
setMessage('请求信息说明过长。请缩短任务描述并重新识别。'); return
|
|
900
997
|
}
|
|
901
998
|
if (normalized.some((rule) => rule.kind === 'choice' && (!rule.choices?.length || rule.choices.length > 32
|
|
902
999
|
|| rule.choices.some((choice) => bytes(choice) > 100)
|
|
903
1000
|
|| new Set(rule.choices).size !== rule.choices.length))) {
|
|
904
|
-
setMessage('
|
|
1001
|
+
setMessage('描述中的可选范围不清楚或重复。请修改描述并重新识别。'); return
|
|
905
1002
|
}
|
|
906
1003
|
if (normalized.some((rule) => (rule.min != null && !Number.isInteger(rule.min))
|
|
907
1004
|
|| (rule.max != null && !Number.isInteger(rule.max))
|
|
@@ -909,10 +1006,10 @@ window.__ModuleLoader__.load({
|
|
|
909
1006
|
|| (rule.kind === 'text' && rule.max != null && (rule.max < 1 || rule.max > 4096))
|
|
910
1007
|
|| (rule.kind === 'gitea_pr' && ((rule.min != null && rule.min < 1)
|
|
911
1008
|
|| (rule.max != null && rule.max > 1_000_000_000))))) {
|
|
912
|
-
setMessage('
|
|
1009
|
+
setMessage('描述中的请求范围无效。请明确范围或提供完整 PR 链接,再重新识别。'); return
|
|
913
1010
|
}
|
|
914
1011
|
if (normalized.some((rule) => rule.kind === 'gitea_pr' && (!rule.gitea_origin || !rule.repository))) {
|
|
915
|
-
setMessage('PR
|
|
1012
|
+
setMessage('没能从描述中识别 PR 来源或仓库。请补充完整 PR 链接或仓库名,再重新识别。'); return
|
|
916
1013
|
}
|
|
917
1014
|
const validRepository = (value) => {
|
|
918
1015
|
const parts = value.split('/')
|
|
@@ -920,14 +1017,14 @@ window.__ModuleLoader__.load({
|
|
|
920
1017
|
&& /^[A-Za-z0-9_.-]{1,100}$/u.test(part))
|
|
921
1018
|
}
|
|
922
1019
|
if (normalized.some((rule) => rule.kind === 'gitea_pr' && !validRepository(rule.repository))) {
|
|
923
|
-
setMessage('
|
|
1020
|
+
setMessage('仓库名无法识别。请在描述中写明“团队名/仓库名”或粘贴完整 PR 链接,再重新识别。'); return
|
|
924
1021
|
}
|
|
925
1022
|
if (!reviewers.length || !reviewers.includes(runner)) {
|
|
926
1023
|
setMessage('请至少选择一位审查 Agent,并设置普通请求的默认执行方。'); return
|
|
927
1024
|
}
|
|
928
1025
|
if (nativeReviewerSelected && (normalized.length !== 1
|
|
929
1026
|
|| normalized[0].kind !== 'gitea_pr' || !normalized[0].required)) {
|
|
930
|
-
setMessage('
|
|
1027
|
+
setMessage('Codex 和 Claude Code 目前只支持审查一个 PR。请在任务描述中写明 PR 链接,再重新识别。'); return
|
|
931
1028
|
}
|
|
932
1029
|
const scope = conversationKind === 'group' ? `群 ${groupId}` : '私聊'
|
|
933
1030
|
const resultDestination = conversationKind === 'group' ? '原群(群内可见)' : '发起任务的私聊'
|
|
@@ -940,8 +1037,8 @@ window.__ModuleLoader__.load({
|
|
|
940
1037
|
? `普通请求默认由 ${reviewerLabel(runner)} 执行;请求人明确说“交叉 review”时,会并行启动 ${reviewers.map(reviewerLabel).join('、')},分别返回审查结果。${reviewers.map((item) => executionDetails[item]).join(' ')}`
|
|
941
1038
|
: executionDetails[reviewers[0]]
|
|
942
1039
|
const actionDescription = editingCardId
|
|
943
|
-
? `更新“${title}”后,${requesterSummary}可在${scope}
|
|
944
|
-
: `开放“${title}”后,${requesterSummary}可在${scope}
|
|
1040
|
+
? `更新“${title}”后,${requesterSummary}可在${scope}按任务描述中的规则发起请求。旧待确认请求将失效,已提交任务继续使用提交时的配置。结果${resultDestination}。到期时间保持为 ${new Date(editingExpiresAt * 1000).toLocaleString('zh-CN')}。`
|
|
1041
|
+
: `开放“${title}”后,${requesterSummary}可在${scope}按任务描述中的规则发起请求,有效期 ${days} 天;结果${resultDestination}。`
|
|
945
1042
|
if (!window.confirm(`${actionDescription}${executionInfo}请确认。`)) return
|
|
946
1043
|
setBusy(true)
|
|
947
1044
|
try {
|
|
@@ -949,7 +1046,8 @@ window.__ModuleLoader__.load({
|
|
|
949
1046
|
method: editingCardId ? 'PUT' : 'POST', headers: { 'content-type': 'application/json' },
|
|
950
1047
|
body: JSON.stringify({ ...(editingCardId ? { id: editingCardId } : {}),
|
|
951
1048
|
bot_id: selectedBot.bot_id, bot_fingerprint: selectedBot.fingerprint,
|
|
952
|
-
title,
|
|
1049
|
+
title, rule_description: ruleDescription.trim(), trigger_phrases: triggerLines,
|
|
1050
|
+
intent, examples: [],
|
|
953
1051
|
parameters: normalized, task_prompt: taskPrompt, runner, reviewers, workspace: null,
|
|
954
1052
|
sender_scope: senderScope, sender_ids: senderScope === 'allowlist'
|
|
955
1053
|
? senderIds.split(/[\s,,]+/u).filter(Boolean) : [], conversation_kind: conversationKind,
|
|
@@ -957,6 +1055,9 @@ window.__ModuleLoader__.load({
|
|
|
957
1055
|
expires_at: editingCardId ? editingExpiresAt : Math.floor(Date.now() / 1000) + Number(days) * 86400 }),
|
|
958
1056
|
})
|
|
959
1057
|
const result = await readLocalJsonResponse(response)
|
|
1058
|
+
if (!response.ok && result.code === 'IM_TASK_RULE_REQUIRED') {
|
|
1059
|
+
throw new Error('当前 ANT 插件版本不支持任务触发规则,请升级插件后再开放任务。')
|
|
1060
|
+
}
|
|
960
1061
|
if (!response.ok && reviewers.includes('claude-code') && result.code === 'INVALID_REQUEST') {
|
|
961
1062
|
throw new Error('当前团队服务端可能尚不支持 Claude Code 审查配置。请管理员升级服务端后刷新重试。')
|
|
962
1063
|
}
|
|
@@ -976,13 +1077,17 @@ window.__ModuleLoader__.load({
|
|
|
976
1077
|
}
|
|
977
1078
|
throw new Error('当前团队服务端尚不支持多 Agent 审查配置;请管理员升级 ANT 服务端后重试。')
|
|
978
1079
|
}
|
|
979
|
-
|
|
1080
|
+
recognizedBaselineRef.current = null
|
|
1081
|
+
recognizedDescriptionRef.current = null
|
|
1082
|
+
setRuleDescription(''); setTriggerPhrases('')
|
|
1083
|
+
setTitle(''); setIntent(''); setTaskPrompt(''); setParameters([]); setRunner('dsh'); setReviewers(['dsh'])
|
|
1084
|
+
setCrossReviewOpen(false)
|
|
980
1085
|
setEditingCardId(null); setEditingExpiresAt(null)
|
|
981
1086
|
await load()
|
|
982
1087
|
setCreateOpen(false)
|
|
983
1088
|
setMessage(editingCardId
|
|
984
1089
|
? '能力配置已更新。新请求将使用新配置,已提交任务保留原配置。'
|
|
985
|
-
: '
|
|
1090
|
+
: '任务已开放。机器人会按已确认的描述判断请求;缺少必要信息时会向请求人追问。')
|
|
986
1091
|
} catch (error) { setMessage(error.message) }
|
|
987
1092
|
finally { setBusy(false) }
|
|
988
1093
|
}
|
|
@@ -1000,29 +1105,36 @@ window.__ModuleLoader__.load({
|
|
|
1000
1105
|
}
|
|
1001
1106
|
const copyCardToDraft = (card) => {
|
|
1002
1107
|
setEditingCardId(null); setEditingExpiresAt(null)
|
|
1108
|
+
setCrossReviewOpen(false)
|
|
1109
|
+
const hasTriggerRule = Array.isArray(card.trigger_phrases) && card.trigger_phrases.length > 0
|
|
1110
|
+
const copiedDescription = card.rule_description || card.intent || ''
|
|
1111
|
+
recognizedDescriptionRef.current = hasTriggerRule ? copiedDescription.trim() || null : null
|
|
1112
|
+
setDraftNotice(hasTriggerRule
|
|
1113
|
+
? '已复制到新草稿。复制不保留原有效期;请检查任务描述和请求范围。'
|
|
1114
|
+
: '复制的旧任务没有明确触发方式。请修改描述并重新识别,再在支持触发规则的服务端开放;副本不保留原有效期。')
|
|
1003
1115
|
const cardBot = bots.find((bot) => bot.fingerprint === card.bot_fingerprint)
|
|
1004
1116
|
const cardEnrollment = enrolled.find((bot) => bot.bot_fingerprint === card.bot_fingerprint)
|
|
1005
1117
|
const titleChars = Array.from(`复制:${card.title || '未命名能力'}`)
|
|
1006
1118
|
while (new TextEncoder().encode(titleChars.join('')).length > 100) titleChars.pop()
|
|
1119
|
+
const taskDraft = taskRuleDraftFromCard(card, titleChars.join(''))
|
|
1120
|
+
recognizedBaselineRef.current = hasTriggerRule ? taskDraft : null
|
|
1007
1121
|
setBotId(card.bot_id || cardBot?.bot_id || '')
|
|
1008
1122
|
setTrustedOrigin(cardEnrollment?.gitea_origin || '')
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1123
|
+
setRuleDescription(copiedDescription)
|
|
1124
|
+
setTriggerPhrases(taskDraft.trigger_phrases.join('\n'))
|
|
1125
|
+
setTitle(taskDraft.title)
|
|
1126
|
+
setIntent(taskDraft.intent)
|
|
1127
|
+
setTaskPrompt(taskDraft.task_prompt)
|
|
1013
1128
|
setRunner(['dsh', 'codex', 'claude-code'].includes(card.runner) ? card.runner : 'dsh')
|
|
1014
1129
|
setReviewers(Array.isArray(card.reviewers) && card.reviewers.length ? card.reviewers : [card.runner || 'dsh'])
|
|
1015
|
-
setParameters(
|
|
1016
|
-
...rule, min: String(rule.min ?? ''), max: String(rule.max ?? ''), choices: Array.isArray(rule.choices) ? rule.choices.join(',') : String(rule.choices ?? ''),
|
|
1017
|
-
})) : [])
|
|
1130
|
+
setParameters(taskDraft.parameters)
|
|
1018
1131
|
setConversationKind(card.conversation_kind === 'group' ? 'group' : 'dm')
|
|
1019
1132
|
setGroupId(card.conversation_id || '')
|
|
1020
1133
|
setSenderScope(card.sender_scope === 'allowlist' ? 'allowlist' : 'any')
|
|
1021
1134
|
setSenderIds(Array.isArray(card.sender_ids) ? card.sender_ids.join('\n') : '')
|
|
1022
1135
|
setDays(7)
|
|
1023
|
-
setDraftNotice('已复制到新草稿。复制不保留原有效期;开放前请重新核对群、请求人和执行方,默认有效期为 7 天。')
|
|
1024
1136
|
setCreateOpen(true)
|
|
1025
|
-
onSelectTab('
|
|
1137
|
+
onSelectTab('collaboration')
|
|
1026
1138
|
}
|
|
1027
1139
|
const cancelTask = async (task) => {
|
|
1028
1140
|
if (!window.confirm(`取消任务 ${task.task_id}?已经发生的读取不能撤回。`)) return
|
|
@@ -1036,120 +1148,112 @@ window.__ModuleLoader__.load({
|
|
|
1036
1148
|
} catch (error) { setMessage(error.message) }
|
|
1037
1149
|
finally { setBusy(false) }
|
|
1038
1150
|
}
|
|
1151
|
+
const robotCard = h('section', { style: collaborationCardStyle, 'aria-label': '机器人和设备' },
|
|
1152
|
+
h('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: 8 } },
|
|
1153
|
+
h('strong', null, '机器人和设备'),
|
|
1154
|
+
h('button', { type: 'button', disabled: loading || busy,
|
|
1155
|
+
onClick: () => void load().catch((error) => setMessage(error.message)), style: secondaryButtonStyle },
|
|
1156
|
+
loading ? '读取中…' : '刷新状态')),
|
|
1157
|
+
h('p', { role: 'status', style: mutedTextStyle }, collaborationStatus),
|
|
1158
|
+
bots.length ? h('label', null, '我的机器人', h('select', { id: 'ant-bot-selector', value: botId,
|
|
1159
|
+
onChange: (event) => { setBotId(event.target.value); setGroupId('');
|
|
1160
|
+
setTrustedOrigin(enrolled.find((bot) => bot.bot_id === event.target.value)?.gitea_origin || '') }, style: inputStyle },
|
|
1161
|
+
h('option', { value: '' }, '请选择机器人'),
|
|
1162
|
+
...bots.map((bot) => h('option', { key: bot.bot_id, value: bot.bot_id,
|
|
1163
|
+
disabled: !bot.available || !bot.connected }, `${bot.name}${bot.available && bot.connected ? '' : '(未连接)'}`)))) : null,
|
|
1164
|
+
connectedBots.length === 0 && !loading && !botLoadError ? h('div', { style: { display: 'flex', gap: 8, alignItems: 'center', flexWrap: 'wrap' } },
|
|
1165
|
+
h('button', { type: 'button', onClick: openImSettings, style: secondaryButtonStyle }, '打开 DSH-IM 设置'),
|
|
1166
|
+
h('span', { style: mutedTextStyle }, '先在 DSH 的插件列表安装并连接飞书机器人。')) : null,
|
|
1167
|
+
selectedBot && !botConnected && connectedBots.length ? h('button', { type: 'button', onClick: openImSettings,
|
|
1168
|
+
style: secondaryButtonStyle }, '检查 DSH-IM 连接') : null,
|
|
1169
|
+
selectedBot && !selectedEnrollmentBound ? h('button', { type: 'button', disabled: busy || !botConnected || enrollmentLoadError,
|
|
1170
|
+
onClick: () => void enrollBot(), style: primaryButtonStyle },
|
|
1171
|
+
selectedEnrollment ? '绑定原协作配置到这台 DSH' : '启用并绑定到这台 DSH') : null)
|
|
1172
|
+
const trustedOriginCard = showPrSecuritySettings ? h('section', { style: collaborationCardStyle },
|
|
1173
|
+
h('strong', null, 'PR 读取来源(可选)'),
|
|
1174
|
+
h('p', { style: mutedTextStyle }, '只有任务需要读取 Gitea PR 时才需设置。PR 链接、仓库和编号写在任务描述里;这里仅登记本机允许读取的 Gitea 来源。'),
|
|
1175
|
+
h('label', null, '机器人', h('select', { 'aria-label': 'Gitea 来源对应机器人', value: botId,
|
|
1176
|
+
onChange: (event) => { setBotId(event.target.value); setTrustedOrigin(
|
|
1177
|
+
enrolled.find((bot) => bot.bot_id === event.target.value)?.gitea_origin || '') }, style: inputStyle },
|
|
1178
|
+
h('option', { value: '' }, '请选择机器人'),
|
|
1179
|
+
...bots.map((bot) => h('option', { key: bot.bot_id, value: bot.bot_id,
|
|
1180
|
+
disabled: !bot.available || !bot.connected }, `${bot.name}${bot.available && bot.connected ? '' : '(未连接)'}`)))),
|
|
1181
|
+
selectedBot ? h('div', { style: { display: 'flex', gap: 8, flexWrap: 'wrap' } },
|
|
1182
|
+
h('input', { 'aria-label': '可信 Gitea 地址', type: 'url', value: trustedOrigin,
|
|
1183
|
+
onChange: (event) => setTrustedOrigin(event.target.value), placeholder: 'https://git.example.com',
|
|
1184
|
+
style: { ...inputStyle, flex: '1 1 220px' } }),
|
|
1185
|
+
h('button', { type: 'button', disabled: busy || !trustedOrigin.trim() || (!selectedEnrollmentBound && !botConnected),
|
|
1186
|
+
onClick: () => void (selectedEnrollmentBound ? saveTrustedOrigin() : enrollBot()), style: secondaryButtonStyle },
|
|
1187
|
+
selectedEnrollmentBound ? '保存可信来源' : botConnected ? '启用并保存来源' : '先连接机器人'))
|
|
1188
|
+
: null) : null
|
|
1189
|
+
const groupTaskStatusPanel = selectedEnrollmentBound && botConnected ? h('section', {
|
|
1190
|
+
id: 'ant-group-task-status', role: 'region', 'aria-label': '各群聊天与任务状态', tabIndex: -1,
|
|
1191
|
+
style: { ...collaborationCardStyle, gap: 10 } },
|
|
1192
|
+
h('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: 8 } },
|
|
1193
|
+
h('strong', null, '群聊'),
|
|
1194
|
+
h('button', { type: 'button', disabled: busy || loading,
|
|
1195
|
+
onClick: () => void load().catch((error) => setMessage(error.message)), style: secondaryButtonStyle },
|
|
1196
|
+
loading ? '读取中…' : '刷新群列表')),
|
|
1197
|
+
h('p', { style: mutedTextStyle }, groupChatLoadFailed
|
|
1198
|
+
? '群列表暂不可用,请刷新状态后重试。'
|
|
1199
|
+
: availableGroups.length ? '每个群分别控制普通聊天和任务;关闭普通聊天不会撤销已开放规则。'
|
|
1200
|
+
: '先在目标群 @机器人,再刷新状态发现群。'),
|
|
1201
|
+
availableGroups.length ? h('div', { id: 'ant-group-card-grid', style: collaborationGridStyle },
|
|
1202
|
+
...availableGroups.map((group) => {
|
|
1203
|
+
const taskCount = activeCards.filter((card) => card.bot_fingerprint === selectedBot.fingerprint
|
|
1204
|
+
&& card.conversation_kind === 'group' && card.conversation_id === group.group_id).length
|
|
1205
|
+
return h('article', { key: group.group_id, style: { ...collaborationCardStyle, padding: 10, gap: 8 } },
|
|
1206
|
+
h('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: 8 } },
|
|
1207
|
+
h('strong', null, group.group_code),
|
|
1208
|
+
h('span', { style: mutedTextStyle }, `已开放任务 ${taskCount} 项`)),
|
|
1209
|
+
h('label', { style: { display: 'flex', alignItems: 'center', gap: 8 } },
|
|
1210
|
+
h('input', { type: 'checkbox', checked: group.chat_enabled,
|
|
1211
|
+
disabled: busy || groupChatLoadFailed,
|
|
1212
|
+
onChange: (event) => void saveGroupChat(event.target.checked, group.group_id),
|
|
1213
|
+
'aria-label': '允许群 ' + group.group_code + ' 普通聊天' }), '普通聊天'),
|
|
1214
|
+
h('button', { type: 'button', disabled: busy, style: secondaryButtonStyle,
|
|
1215
|
+
onClick: () => { startNewCard({ conversationKind: 'group', groupId: group.group_id }); focusAfterTabChange('collaboration', 'ant-new-task-summary') } },
|
|
1216
|
+
'为此群开放任务'))
|
|
1217
|
+
})) : h('div', { style: infoNoticeStyle },
|
|
1218
|
+
h('strong', null, '还没有发现群'),
|
|
1219
|
+
h('span', { style: mutedTextStyle }, '在目标群 @机器人后刷新列表。'))) : null
|
|
1039
1220
|
return h('section', { style: { ...settingsPanelStyle,
|
|
1040
|
-
display: ['
|
|
1041
|
-
'aria-label': '
|
|
1221
|
+
display: ['collaboration', 'local'].includes(activeTab) ? 'grid' : 'none' },
|
|
1222
|
+
'aria-label': '群聊协作设置' },
|
|
1042
1223
|
message ? h('p', { role: 'status', style: mutedTextStyle }, message) : null,
|
|
1043
1224
|
h('div', { id: 'ant-chat-entry', role: 'region', 'aria-label': '聊天入口和机器人状态', tabIndex: -1,
|
|
1044
|
-
style: { display: activeTab === '
|
|
1045
|
-
h('
|
|
1046
|
-
|
|
1047
|
-
|
|
1225
|
+
style: { display: activeTab === 'collaboration' ? 'grid' : 'none', gap: 12 } },
|
|
1226
|
+
h('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'start', gap: 12, flexWrap: 'wrap' } },
|
|
1227
|
+
h('div', { style: { display: 'grid', gap: 4 } },
|
|
1228
|
+
h('h3', { style: sectionTitleStyle }, '协作总览'),
|
|
1229
|
+
h('p', { style: mutedTextStyle }, '选择机器人,设置聊天入口,再按需要开放任务规则。聊天和任务授权分别控制。')),
|
|
1230
|
+
h('span', { style: { ...mutedTextStyle, padding: '4px 9px', borderRadius: 999,
|
|
1231
|
+
backgroundColor: 'var(--dsw-alias-bg-layer-2)' } }, selectedBot ? selectedBot.name : '尚未选择机器人')),
|
|
1232
|
+
legacyTriggerCardCount ? h('p', { role: 'status', style: { ...mutedTextStyle,
|
|
1233
|
+
color: 'var(--dsw-alias-state-warning-primary, var(--dsw-alias-label-secondary))' } },
|
|
1234
|
+
'当前有 ' + legacyTriggerCardCount + ' 项旧任务规则没有明确触发方式;新版插件不会按这些旧规则触发任务。升级 ANT 服务端后,再重新描述并开放需要的规则。') : null,
|
|
1048
1235
|
loadFailures.length ? h('p', { role: 'status', style: mutedTextStyle },
|
|
1049
1236
|
`部分协作信息暂时无法读取:${loadFailures.map((path) => loadNames[path]).join('、')}。已读取的信息仍显示,请稍后刷新状态。`) : null,
|
|
1050
|
-
h('div', {
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
: selectedBot && !botConnected ? `“${selectedBot.name}”当前离线;请先在 DSH-IM 恢复连接。`
|
|
1055
|
-
: enrollmentLoadError ? selectedBot
|
|
1056
|
-
? `“${selectedBot.name}”已连接;暂时无法确认协作是否已启用,请刷新状态。`
|
|
1057
|
-
: '机器人已连接;协作启用状态暂未读取,请刷新状态。'
|
|
1058
|
-
: selectedBot && !selectedEnrollmentBound ? selectedEnrollment
|
|
1059
|
-
? `“${selectedBot.name}”已连接;原协作配置尚未绑定到这台 DSH,请在本机重新启用。若原设备令牌曾被撤销,旧任务权限已关闭,绑定后需重新开放。`
|
|
1060
|
-
: `“${selectedBot.name}”已连接;请先启用本机协作。`
|
|
1061
|
-
: enrolledCount === 0 ? '机器人已连接;请选择并启用协作。'
|
|
1062
|
-
: !selectedBot ? '已有协作机器人;请选择一台,查看已开放的能力和聊天范围。'
|
|
1063
|
-
: authorizationLoadError ? `“${selectedBot.name}”已启用协作;已开放任务暂未读取,请刷新状态。`
|
|
1064
|
-
: !dmChatEnabled ? `“${selectedBot.name}”已启用协作;私聊已关闭,群聊仍按各自设置工作。`
|
|
1065
|
-
: selectedTaskCount === 0 ? `“${selectedBot.name}”已启用协作;下一步开放一项能力。你也可以先私聊自测普通聊天。`
|
|
1066
|
-
: `“${selectedBot.name}”已启用协作 · ${selectedTaskCount} 项能力已开放。可以私聊自测。`),
|
|
1067
|
-
h('button', { type: 'button', disabled: loading || busy,
|
|
1068
|
-
onClick: () => void load().catch((error) => setMessage(error.message)),
|
|
1069
|
-
style: { ...secondaryButtonStyle, whiteSpace: 'nowrap' } },
|
|
1070
|
-
loading ? '正在刷新…' : '刷新状态')),
|
|
1071
|
-
bots.length ? h('label', null, '我的机器人', h('select', { id: 'ant-bot-selector', value: botId,
|
|
1072
|
-
onChange: (event) => { setBotId(event.target.value); setGroupId(''); setChatGroupId(''); setBindingCode('');
|
|
1073
|
-
setTrustedOrigin(enrolled.find((bot) => bot.bot_id === event.target.value)?.gitea_origin || '') }, style: inputStyle },
|
|
1074
|
-
h('option', { value: '' }, '请选择机器人'),
|
|
1075
|
-
...bots.map((bot) => h('option', { key: bot.bot_id, value: bot.bot_id,
|
|
1076
|
-
disabled: !bot.available || !bot.connected }, `${bot.name}${bot.available && bot.connected ? '' : '(未连接)'}`)))) : null,
|
|
1077
|
-
connectedBots.length === 0 && !loading && !botLoadError ? h('button', { type: 'button',
|
|
1078
|
-
onClick: openImSettings, style: secondaryButtonStyle }, '打开 DSH-IM 设置') : null,
|
|
1079
|
-
connectedBots.length === 0 && !loading && !botLoadError ? h('span', { style: mutedTextStyle },
|
|
1080
|
-
'若尚未安装 DSH-IM,请先到 DSH 的插件列表安装并连接机器人。') : null,
|
|
1081
|
-
selectedBot && !botConnected && connectedBots.length ? h('button', { type: 'button', onClick: openImSettings,
|
|
1082
|
-
style: secondaryButtonStyle }, '检查 DSH-IM 连接') : null,
|
|
1083
|
-
selectedBot && !selectedEnrollmentBound ? h('button', { type: 'button', disabled: busy || !botConnected || enrollmentLoadError,
|
|
1084
|
-
onClick: () => void enrollBot(), style: secondaryButtonStyle },
|
|
1085
|
-
selectedEnrollment ? '绑定原协作配置到这台 DSH' : '启用并绑定到这台 DSH') : null,
|
|
1086
|
-
selectedBot ? h('div', { style: { display: 'grid', gap: 8, padding: 10,
|
|
1087
|
-
border: '1px solid var(--dsw-alias-border-l2)', borderRadius: 8 } },
|
|
1088
|
-
h('strong', null, `可信 Gitea 地址 · ${selectedBot.name}`),
|
|
1089
|
-
h('p', { style: mutedTextStyle }, '为这个机器人登记一次。PR 能力只需配置仓库和 PR 编号,不必重复填写地址。'),
|
|
1090
|
-
h('div', { style: { display: 'flex', gap: 8, flexWrap: 'wrap' } },
|
|
1091
|
-
h('input', { 'aria-label': '可信 Gitea 地址', type: 'url', value: trustedOrigin,
|
|
1092
|
-
onChange: (event) => setTrustedOrigin(event.target.value), placeholder: 'https://git.example.com',
|
|
1093
|
-
style: { ...inputStyle, flex: '1 1 280px' } }),
|
|
1094
|
-
h('button', { type: 'button', disabled: busy || !trustedOrigin.trim() || (!selectedEnrollmentBound && !botConnected),
|
|
1095
|
-
onClick: () => void (selectedEnrollmentBound ? saveTrustedOrigin() : enrollBot()), style: secondaryButtonStyle },
|
|
1096
|
-
selectedEnrollmentBound ? '保存可信地址' : botConnected ? '绑定机器人并保存地址' : '先连接机器人'))) : null,
|
|
1097
|
-
selectedBot && selectedEnrollmentBound ? h('div', { role: 'region', 'aria-label': '群任务所有者身份验证',
|
|
1098
|
-
style: { display: 'grid', gap: 8, padding: 10, border: '1px solid var(--dsw-alias-border-l2)', borderRadius: 8,
|
|
1099
|
-
backgroundColor: 'var(--dsw-alias-bg-layer-1)' } },
|
|
1100
|
-
h('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: 8 } },
|
|
1101
|
-
h('strong', null, '群任务所有者身份'),
|
|
1102
|
-
h('span', { role: 'status', style: { ...mutedTextStyle, color: selectedEnrollment.owner_bound
|
|
1103
|
-
? 'var(--dsw-alias-state-success-primary)' : 'var(--dsw-alias-state-warning-primary, var(--dsw-alias-label-secondary))' } },
|
|
1104
|
-
selectedEnrollment.owner_bound ? '已绑定' : '尚未绑定')),
|
|
1105
|
-
h('p', { style: mutedTextStyle }, '绑定只针对当前机器人,所有群共用。只有允许群成员请求已开放能力时才需要;普通群聊无需绑定。绑定后,群任务仍需你在目标群按机器人提示确认。'),
|
|
1106
|
-
h('div', { style: { display: 'flex', alignItems: 'center', gap: 8, flexWrap: 'wrap' } },
|
|
1107
|
-
h('button', { type: 'button', disabled: busy || !botConnected || enrollmentLoadError,
|
|
1108
|
-
onClick: () => void issueBindingCode(), style: secondaryButtonStyle },
|
|
1109
|
-
selectedEnrollment.owner_bound ? '重新获取绑定码' : '获取绑定码'),
|
|
1110
|
-
bindingCode ? h('span', { role: 'status', style: mutedTextStyle },
|
|
1111
|
-
`请你本人私聊“${selectedBot.name}”发送“绑定身份 ${bindingCode}”。绑定码 5 分钟有效。`) : null)) : null,
|
|
1237
|
+
h('div', { id: 'ant-collaboration-settings-grid', style: collaborationSettingsGridStyle },
|
|
1238
|
+
robotCard,
|
|
1239
|
+
chatScopePanel,
|
|
1240
|
+
),
|
|
1112
1241
|
setupStepper,
|
|
1113
|
-
|
|
1242
|
+
groupTaskStatusPanel,
|
|
1114
1243
|
selectedEnrollmentBound && botConnected ? h('p', { style: mutedTextStyle },
|
|
1115
1244
|
dmChatEnabled
|
|
1116
|
-
? `自测:私聊“${selectedBot.name}
|
|
1117
|
-
: '
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
h('span', { role: 'status', style: mutedTextStyle }, groupChatLoadFailed
|
|
1122
|
-
? '群聊列表与当前权限暂时无法读取,请刷新后再更改;仍可填写群 ID。'
|
|
1123
|
-
: availableGroups.length
|
|
1124
|
-
? '从机器人已发现的群中选择;仍可手动填写群 ID。允许普通聊天不等于允许群成员执行任务。'
|
|
1125
|
-
: '先在目标群 @机器人并刷新列表;若平台暂未返回群列表,也可以手动填写群 ID。'),
|
|
1126
|
-
h('div', { style: { display: 'grid', gridTemplateColumns: 'minmax(0, 1fr) auto', gap: 8, alignItems: 'end' } },
|
|
1127
|
-
h('label', { style: { display: 'grid', gap: 5 } },
|
|
1128
|
-
h('span', { style: mutedTextStyle }, '选择已记录的群'),
|
|
1129
|
-
h('select', { 'aria-label': '已记录的群', value: availableGroups.some((group) => group.group_id === chatGroupId) ? chatGroupId : '',
|
|
1130
|
-
onChange: (event) => setChatGroupId(event.target.value), style: inputStyle },
|
|
1131
|
-
h('option', { value: '' }, '选择已发现的群聊'),
|
|
1132
|
-
...availableGroups.map((group) => h('option', { key: group.group_id, value: group.group_id },
|
|
1133
|
-
`${group.group_code} · ${group.chat_enabled ? '已允许普通聊天' : '未允许普通聊天'}`)))),
|
|
1134
|
-
h('button', { type: 'button', disabled: busy || !selectedEnrollmentBound,
|
|
1135
|
-
onClick: () => void load().catch((error) => setMessage(error.message)), style: secondaryButtonStyle }, '刷新群列表')),
|
|
1136
|
-
h('label', { style: { display: 'grid', gap: 5 } },
|
|
1137
|
-
h('span', { style: mutedTextStyle }, '群 ID(平台未返回群名时可手动填写)'),
|
|
1138
|
-
h('input', { 'aria-label': '群 ID', value: chatGroupId, maxLength: 256,
|
|
1139
|
-
onChange: (event) => setChatGroupId(event.target.value.trim()),
|
|
1140
|
-
placeholder: '输入群 ID', style: inputStyle })),
|
|
1141
|
-
selectedChatGroup?.chat_enabled && selectedGroupHasTasks ? h('span', { style: mutedTextStyle },
|
|
1142
|
-
'本群还有开放能力;关闭普通聊天前请先停止这些能力。') : null) : null),
|
|
1143
|
-
h('div', { id: 'ant-capability-list', role: 'region', 'aria-label': '已开放能力列表', tabIndex: -1,
|
|
1144
|
-
style: { display: activeTab === 'capability' ? 'grid' : 'none', gap: 12 } },
|
|
1245
|
+
? `自测:私聊“${selectedBot.name}”发送“你能做什么”;发起任务后可问“刚才的任务怎么样了”。`
|
|
1246
|
+
: '私聊当前已关闭;如要恢复,请在上方开启。') : null,
|
|
1247
|
+
),
|
|
1248
|
+
h('div', { id: 'ant-capability-list', role: 'region', 'aria-label': '已开放任务规则', tabIndex: -1,
|
|
1249
|
+
style: { display: activeTab === 'collaboration' ? 'grid' : 'none', gap: 12 } },
|
|
1145
1250
|
h('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', gap: 10, flexWrap: 'wrap' } },
|
|
1146
|
-
h('h4', { style: sectionTitleStyle },
|
|
1147
|
-
h('div', { role: 'toolbar', 'aria-label': '
|
|
1148
|
-
h('button', { type: 'button', onClick: () => {
|
|
1149
|
-
style: primaryButtonStyle }, '
|
|
1150
|
-
h('button', { type: 'button', onClick: () => { preparePrExample(); onSelectTab('capability') },
|
|
1151
|
-
style: secondaryButtonStyle }, '使用 PR 差异审查示例'))),
|
|
1251
|
+
h('h4', { style: sectionTitleStyle }, '我的任务规则 · ' + (authorizationLoadError ? '暂未读取' : activeCards.length)),
|
|
1252
|
+
h('div', { role: 'toolbar', 'aria-label': '任务规则操作', style: { display: 'flex', gap: 8, flexWrap: 'wrap' } },
|
|
1253
|
+
h('button', { type: 'button', onClick: () => { startNewCard(); focusAfterTabChange('collaboration', 'ant-new-task-summary') },
|
|
1254
|
+
style: primaryButtonStyle }, '新建任务规则'))),
|
|
1152
1255
|
h('p', { style: mutedTextStyle }, '当前一项能力只能绑定一个范围:私聊,或某一个群。若要给其他群或私聊开放相同任务,目前需分别建立能力卡;它们的请求人和有效期独立。'),
|
|
1256
|
+
h('div', { id: 'ant-capability-cards', style: collaborationGridStyle },
|
|
1153
1257
|
...activeCards.map((card) => {
|
|
1154
1258
|
const expiresAt = Number(card.expires_at) * 1000
|
|
1155
1259
|
const remainingDays = Math.max(1, Math.ceil((expiresAt - Date.now()) / 86_400_000))
|
|
@@ -1158,46 +1262,46 @@ window.__ModuleLoader__.load({
|
|
|
1158
1262
|
? `群 ${groups.find((group) => group.group_id === card.conversation_id)?.group_code || card.conversation_id}` : '私聊'
|
|
1159
1263
|
const requester = card.sender_scope === 'allowlist' ? '指定成员可请求'
|
|
1160
1264
|
: card.conversation_kind === 'group' ? '群成员可请求' : '可私聊者可请求'
|
|
1161
|
-
const parametersText = (card.parameters || []).map((rule) => {
|
|
1162
|
-
const constraint = rule.kind === 'gitea_pr' && rule.repository ? `仓库 ${rule.repository}`
|
|
1163
|
-
: rule.kind === 'choice' ? `选项 ${(rule.choices || []).join('、')}`
|
|
1164
|
-
: ['integer', 'gitea_pr'].includes(rule.kind)
|
|
1165
|
-
? `范围 ${rule.min ?? 1}–${rule.max ?? '无上限'}` : ''
|
|
1166
|
-
return `${rule.name}:${rule.description || rule.kind}${rule.required ? '(必填)' : '(可选)'}${constraint ? ` · ${constraint}` : ''}`
|
|
1167
|
-
}).join(';')
|
|
1168
1265
|
const examplesText = Array.isArray(card.examples) ? card.examples.join('、') : ''
|
|
1169
1266
|
const requesterIds = card.sender_scope === 'allowlist' && Array.isArray(card.sender_ids) ? card.sender_ids.join('、') : ''
|
|
1170
1267
|
const remainingDaysLabel = remainingDays <= 7 ? 'var(--dsw-alias-state-warning-primary, var(--dsw-alias-label-secondary))'
|
|
1171
1268
|
: 'var(--dsw-alias-label-secondary)'
|
|
1172
|
-
return h('
|
|
1173
|
-
|
|
1174
|
-
h('div', { style: { display: 'grid', gap: 4, minWidth: 0 } },
|
|
1269
|
+
return h('article', { key: card.id, style: { ...collaborationCardStyle, padding: 12, gap: 8 } },
|
|
1270
|
+
h('div', { style: { display: 'grid', gap: 6, minWidth: 0 } },
|
|
1175
1271
|
h('div', { style: { display: 'flex', alignItems: 'center', gap: 8, flexWrap: 'wrap' } },
|
|
1176
1272
|
h('strong', { style: { overflowWrap: 'anywhere' } }, card.title || '未命名能力'),
|
|
1177
1273
|
h('span', { style: { ...mutedTextStyle, padding: '2px 8px', borderRadius: 999,
|
|
1178
1274
|
backgroundColor: 'var(--dsw-alias-bg-layer-2)', color: remainingDaysLabel, whiteSpace: 'nowrap' } }, expiry)),
|
|
1179
1275
|
h('span', { style: mutedTextStyle }, `${cardReviewerSummary(card)} · ${conversation} · ${requester} · 结果回复原${card.conversation_kind === 'group' ? '群' : '私聊'}`),
|
|
1276
|
+
card.rule_description
|
|
1277
|
+
? h('span', { style: { ...mutedTextStyle, overflowWrap: 'anywhere' } }, card.rule_description)
|
|
1278
|
+
: h('span', { role: 'status', style: { ...mutedTextStyle, color: 'var(--dsw-alias-state-warning-primary, var(--dsw-alias-label-secondary))' } },
|
|
1279
|
+
'旧规则没有明确触发方式;新版插件不会按它启动任务。升级 ANT 服务端后需重新描述并开放。'),
|
|
1180
1280
|
h('details', null,
|
|
1181
|
-
h('summary', { style: { cursor: 'pointer', color: 'var(--dsw-alias-link)', fontSize: 13 } }, '
|
|
1281
|
+
h('summary', { style: { cursor: 'pointer', color: 'var(--dsw-alias-link)', fontSize: 13 } }, '查看规则说明'),
|
|
1182
1282
|
h('div', { style: { display: 'grid', gap: 4, paddingTop: 6 } },
|
|
1183
|
-
card.
|
|
1184
|
-
parametersText ? h('p', { style: { ...mutedTextStyle, overflowWrap: 'anywhere' } }, `请求参数:${parametersText}`)
|
|
1185
|
-
: h('p', { style: mutedTextStyle }, '请求参数:无'),
|
|
1283
|
+
card.rule_description ? h('p', { style: { ...mutedTextStyle, overflowWrap: 'anywhere' } }, card.rule_description) : null,
|
|
1186
1284
|
requesterIds ? h('p', { style: { ...mutedTextStyle, overflowWrap: 'anywhere' } }, `允许请求的 IM 用户:${requesterIds}`) : null,
|
|
1187
1285
|
examplesText ? h('p', { style: mutedTextStyle }, `请求示例:${examplesText}`) : null,
|
|
1286
|
+
Array.isArray(card.parameters) && card.parameters.length
|
|
1287
|
+
? h('p', { style: { ...mutedTextStyle, overflowWrap: 'anywhere' } },
|
|
1288
|
+
`请求时还需提供:${card.parameters.map(parameterSummary).join(';')}`)
|
|
1289
|
+
: h('p', { style: mutedTextStyle }, '无需额外参数,按请求中的内容处理'),
|
|
1188
1290
|
card.task_prompt ? h('p', { style: mutedTextStyle }, `处理要求:${card.task_prompt}`) : null))),
|
|
1189
|
-
h('div', { style: { display: 'flex', gap: 6, flexWrap: 'wrap', justifyContent: 'flex-end'
|
|
1291
|
+
h('div', { style: { display: 'flex', gap: 6, flexWrap: 'wrap', justifyContent: 'flex-end', paddingTop: 4,
|
|
1292
|
+
borderTop: '1px solid var(--dsw-alias-border-l2)' } },
|
|
1190
1293
|
h('button', { type: 'button', disabled: busy, onClick: () => editCard(card), style: secondaryButtonStyle }, '编辑'),
|
|
1191
1294
|
h('button', { type: 'button', disabled: busy, onClick: () => copyCardToDraft(card), style: secondaryButtonStyle }, '复制'),
|
|
1192
1295
|
h('button', { type: 'button', disabled: busy, onClick: () => void revokeCard(card), style: secondaryButtonStyle }, '停止')))
|
|
1193
1296
|
}),
|
|
1194
|
-
activeCards.length === 0 ? h('div', { style: {
|
|
1297
|
+
activeCards.length === 0 ? h('div', { style: { ...collaborationCardStyle, gridColumn: '1 / -1', justifyItems: 'start' } },
|
|
1195
1298
|
h('p', { style: mutedTextStyle }, authorizationLoadError
|
|
1196
1299
|
? '暂时无法确认已开放的能力。刷新授权状态后再试;列表未核实前请勿重复创建。'
|
|
1197
|
-
: '
|
|
1300
|
+
: '还没有开放任务。先描述要做什么,再设置请求范围和有效期。'),
|
|
1198
1301
|
authorizationLoadError ? h('button', { type: 'button', disabled: loading,
|
|
1199
1302
|
onClick: () => void load().catch((error) => setMessage(error.message)), style: secondaryButtonStyle },
|
|
1200
1303
|
loading ? '正在刷新…' : '刷新授权状态') : null) : null),
|
|
1304
|
+
),
|
|
1201
1305
|
h('div', { role: 'region', 'aria-label': '最近任务', style: { display: activeTab === 'local' ? 'grid' : 'none', gap: 12 } },
|
|
1202
1306
|
h('h4', { style: sectionTitleStyle }, '最近任务'),
|
|
1203
1307
|
...(tasks.filter((task) => task.task_id).length ? tasks.filter((task) => task.task_id)
|
|
@@ -1219,13 +1323,13 @@ window.__ModuleLoader__.load({
|
|
|
1219
1323
|
})
|
|
1220
1324
|
: [h('p', { key: 'none', style: mutedTextStyle }, loadFailures.includes('status')
|
|
1221
1325
|
? '任务记录暂时无法读取,请刷新状态。' : '暂无任务。')])),
|
|
1222
|
-
h('div', { role: 'region', 'aria-label': '
|
|
1326
|
+
h('div', { role: 'region', 'aria-label': '任务规则编辑器', style: { display: activeTab === 'collaboration' ? 'grid' : 'none' } },
|
|
1223
1327
|
h('details', { id: 'ant-new-task', open: createOpen,
|
|
1224
1328
|
onToggle: (event) => setCreateOpen(event.currentTarget.open),
|
|
1225
1329
|
style: { border: '1px solid var(--dsw-alias-border-l2)', borderRadius: 8, padding: 12 } },
|
|
1226
1330
|
h('summary', { id: 'ant-new-task-summary', style: { cursor: 'pointer', fontWeight: 600 } }, editingCardId ? '编辑能力配置' : '开放一项能力'),
|
|
1227
|
-
h('div', { style: { display: 'grid', gridTemplateColumns: '
|
|
1228
|
-
gap:
|
|
1331
|
+
h('div', { style: { display: 'grid', gridTemplateColumns: 'minmax(0, 1fr)',
|
|
1332
|
+
gap: 12, alignItems: 'start', marginTop: 12 } },
|
|
1229
1333
|
h('form', { id: 'ant-capability-create-form', onSubmit: createCard, style: { display: 'grid', gap: 9, minWidth: 0 } },
|
|
1230
1334
|
editingCardId ? h('p', { role: 'status', style: mutedTextStyle },
|
|
1231
1335
|
`保存会生成新的授权版本并停止旧版本;到期时间保持不变:${new Date(editingExpiresAt * 1000).toLocaleString('zh-CN')}。已提交任务继续使用原配置。`) : null,
|
|
@@ -1240,132 +1344,108 @@ window.__ModuleLoader__.load({
|
|
|
1240
1344
|
'这是尚未绑定到设备的机器人。请在要继续使用它的电脑重新启用;Gitea 地址和配置记录会保留。若旧设备访问曾被撤销,原任务权限不会恢复,需要重新开放。') : null,
|
|
1241
1345
|
selectedBot && conversationKind === 'group' && !selectedBot.history_available ? h('p', { role: 'status', style: mutedTextStyle },
|
|
1242
1346
|
'这个机器人当前不能读取平台群历史;群聊只能参考实际送达的消息和本机近期记录。请检查 DSH-IM 权限。') : null,
|
|
1243
|
-
h('label', null, '
|
|
1244
|
-
onChange: (event) =>
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
h('
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
h('
|
|
1252
|
-
h('
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
h('
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
onChange: (event) =>
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
h('
|
|
1309
|
-
|
|
1310
|
-
: '
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
h('
|
|
1316
|
-
|
|
1317
|
-
onChange: (event) => setGroupId(event.target.value), style: inputStyle },
|
|
1318
|
-
h('option', { value: '' }, '让群成员先 @ 机器人,再刷新列表'),
|
|
1319
|
-
...availableGroups.map((group) => h('option', { key: group.group_id, value: group.group_id },
|
|
1320
|
-
`识别码 ${group.group_code} · ${group.group_id}`)))) : null,
|
|
1321
|
-
conversationKind === 'group' ? h('button', { type: 'button', disabled: busy,
|
|
1322
|
-
onClick: () => void load().catch((error) => setMessage(error.message)), style: secondaryButtonStyle }, '刷新群列表') : null,
|
|
1323
|
-
h('label', null, '谁可以请求', h('select', { value: senderScope,
|
|
1324
|
-
onChange: (event) => setSenderScope(event.target.value), style: inputStyle },
|
|
1325
|
-
h('option', { value: 'any' }, conversationKind === 'group' ? '此群任何成员' : '任何能私聊此机器人者'),
|
|
1326
|
-
h('option', { value: 'allowlist' }, '指定 IM 用户 ID'))),
|
|
1327
|
-
senderScope === 'allowlist' ? h('input', { 'aria-label': '可请求的 IM 用户 ID', required: true, value: senderIds,
|
|
1328
|
-
onChange: (event) => setSenderIds(event.target.value), placeholder: '多个 ID 用逗号分隔', style: inputStyle }) : null,
|
|
1329
|
-
editingCardId ? h('p', { style: mutedTextStyle }, `有效期:${new Date(editingExpiresAt * 1000).toLocaleString('zh-CN')}(编辑时保持不变)`)
|
|
1330
|
-
: h('label', null, '有效期', h('select', { value: days, onChange: (event) => setDays(Number(event.target.value)), style: inputStyle },
|
|
1331
|
-
h('option', { value: 1 }, '1 天'), h('option', { value: 7 }, '7 天'), h('option', { value: 30 }, '30 天')))),
|
|
1347
|
+
h('label', null, '描述规则', h('textarea', { required: true, maxLength: 4000, rows: 4, value: ruleDescription,
|
|
1348
|
+
onChange: (event) => setRuleDescription(event.target.value),
|
|
1349
|
+
placeholder: '用日常的话说明何时执行、要参考哪些资料和结果怎么写。例如:群里有人以“审查 PR”开头并贴出完整 PR 链接时,检查代码差异,列出可定位的问题,不修改代码。', style: inputStyle })),
|
|
1350
|
+
h('p', { style: mutedTextStyle }, '描述只写任务何时执行和怎么处理;执行方由下方选项决定,未触发消息是否普通聊天由上方聊天开关决定。'),
|
|
1351
|
+
h('div', { style: { display: 'flex', gap: 10, alignItems: 'center', flexWrap: 'wrap' } },
|
|
1352
|
+
h('button', { type: 'button', disabled: recognitionBusy || busy || !ruleDescription.trim(),
|
|
1353
|
+
onClick: () => void identifyRule(), style: secondaryButtonStyle }, recognitionBusy ? '正在识别…' : ruleSummaryReady ? '重新识别' : '识别规则'),
|
|
1354
|
+
h('span', { style: mutedTextStyle }, '每次点击调用一次团队模型并计入用量;只整理草稿,不会开放任务。改完描述后可重新识别。')),
|
|
1355
|
+
h('section', { role: 'region', 'aria-label': '识别结果摘要', style: { ...panelStyle, padding: 10, gap: 7 } },
|
|
1356
|
+
h('div', { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 8 } },
|
|
1357
|
+
h('strong', null, ruleSummaryReady ? (title || '识别结果') : '识别结果预览'),
|
|
1358
|
+
h('span', { style: mutedTextStyle }, ruleSummaryReady ? '草稿 · 尚未开放' : '填写描述后可识别')),
|
|
1359
|
+
h('p', { style: { ...mutedTextStyle, overflowWrap: 'anywhere' } }, ruleSummaryNarrative),
|
|
1360
|
+
h('p', { style: mutedTextStyle }, ruleSummaryReady
|
|
1361
|
+
? '草稿未开放。检查这段理解;有误就改上方描述并重新识别。'
|
|
1362
|
+
: '填写描述后识别;识别只生成草稿,不会开放任务。')),
|
|
1363
|
+
h('section', { style: { ...panelStyle, padding: 10, gap: 9, display: 'grid',
|
|
1364
|
+
gridTemplateColumns: 'repeat(auto-fit, minmax(min(100%, 280px), 1fr))' } },
|
|
1365
|
+
h('strong', { style: { gridColumn: '1 / -1' } }, '通用授权'),
|
|
1366
|
+
h('p', { style: { ...mutedTextStyle, gridColumn: '1 / -1' } },
|
|
1367
|
+
'上方描述规则任务要做什么;这里限定谁能请求、在哪里请求、由谁执行和授权多久。'),
|
|
1368
|
+
h('label', null, '执行方', h('select', { value: runner, onChange: (event) => selectRunner(event.target.value),
|
|
1369
|
+
style: inputStyle },
|
|
1370
|
+
h('option', { value: 'dsh', disabled: !dshAvailable && runner !== 'dsh' }, 'DSH · 当前团队模型'),
|
|
1371
|
+
h('option', { value: 'codex', disabled: (!codexAvailable || !hasPrParameter) && runner !== 'codex' }, 'Codex · PR 快照只读'),
|
|
1372
|
+
h('option', { value: 'claude-code', disabled: (!claudeCodeAvailable || !hasPrParameter) && runner !== 'claude-code' }, 'Claude Code · 本机权限')),
|
|
1373
|
+
h('span', { style: mutedTextStyle }, runner === 'codex'
|
|
1374
|
+
? 'Codex 使用本机登录,仅读取已核验的 PR 快照。'
|
|
1375
|
+
: runner === 'claude-code' ? 'Claude Code 沿用这台 DSH 的权限设置。'
|
|
1376
|
+
: 'DSH 使用获准团队模型处理会话中可见或已提供的内容。')),
|
|
1377
|
+
h('details', { id: 'ant-cross-review-details', open: crossReviewOpen,
|
|
1378
|
+
onToggle: (event) => setCrossReviewOpen(event.currentTarget.open),
|
|
1379
|
+
style: { gridColumn: '1 / -1', border: '1px solid var(--dsw-alias-border-l2)', borderRadius: 8, padding: 10 } },
|
|
1380
|
+
h('summary', { style: { cursor: 'pointer', fontWeight: 600 } }, crossReviewSummary
|
|
1381
|
+
? `交叉审查:${crossReviewSummary}` : '交叉审查(可选)'),
|
|
1382
|
+
h('p', { style: mutedTextStyle }, '普通请求只运行所选执行方;请求人明确提出“交叉 review”时才并行运行所选 Agent。'),
|
|
1383
|
+
h('div', { style: { display: 'flex', gap: 14, flexWrap: 'wrap' } },
|
|
1384
|
+
h('label', null, h('input', { type: 'checkbox', 'aria-label': '审查 Agent DSH', checked: reviewers.includes('dsh'),
|
|
1385
|
+
disabled: !dshAvailable && !reviewers.includes('dsh'),
|
|
1386
|
+
onChange: (event) => changeReviewer('dsh', event.target.checked) }), ' DSH'),
|
|
1387
|
+
h('label', null, h('input', { type: 'checkbox', 'aria-label': '审查 Agent Codex', checked: reviewers.includes('codex'),
|
|
1388
|
+
disabled: (!codexAvailable || !hasPrParameter) && !reviewers.includes('codex'),
|
|
1389
|
+
onChange: (event) => changeReviewer('codex', event.target.checked) }), ' Codex'),
|
|
1390
|
+
h('label', null, h('input', { type: 'checkbox', 'aria-label': '审查 Agent Claude Code', checked: reviewers.includes('claude-code'),
|
|
1391
|
+
disabled: (!claudeCodeAvailable || !hasPrParameter) && !reviewers.includes('claude-code'),
|
|
1392
|
+
onChange: (event) => changeReviewer('claude-code', event.target.checked) }), ' Claude Code')),
|
|
1393
|
+
!hasPrParameter ? h('p', { style: mutedTextStyle }, '从描述识别出 PR 任务后,才能使用 Codex 或 Claude Code。') : null),
|
|
1394
|
+
nativePrUnavailableReason ? h('p', { role: 'status', style: { ...mutedTextStyle, gridColumn: '1 / -1' } },
|
|
1395
|
+
`${nativePrUnavailableReason},已禁用 Codex/Claude Code 完整快照选项;可选择 DSH 差异审查。`) : null,
|
|
1396
|
+
h('label', null, '使用场景', h('select', { value: conversationKind,
|
|
1397
|
+
onChange: (event) => { setConversationKind(event.target.value); setGroupId('') }, style: inputStyle },
|
|
1398
|
+
h('option', { value: 'dm' }, '私聊'), h('option', { value: 'group' }, '指定群聊'))),
|
|
1399
|
+
conversationKind === 'group' ? h('div', { style: { display: 'grid', gridTemplateColumns: 'minmax(0, 1fr) auto',
|
|
1400
|
+
gap: 8, alignItems: 'end' } },
|
|
1401
|
+
h('label', null, '群聊', h('select', { required: true, value: groupId,
|
|
1402
|
+
onChange: (event) => setGroupId(event.target.value), style: inputStyle },
|
|
1403
|
+
h('option', { value: '' }, '让群成员先 @ 机器人,再刷新列表'),
|
|
1404
|
+
...availableGroups.map((group) => h('option', { key: group.group_id, value: group.group_id },
|
|
1405
|
+
`识别码 ${group.group_code}`)))),
|
|
1406
|
+
h('button', { type: 'button', disabled: busy,
|
|
1407
|
+
onClick: () => void load().catch((error) => setMessage(error.message)), style: secondaryButtonStyle }, '刷新群列表')) : null,
|
|
1408
|
+
h('label', null, '谁可以请求', h('select', { value: senderScope,
|
|
1409
|
+
onChange: (event) => setSenderScope(event.target.value), style: inputStyle },
|
|
1410
|
+
h('option', { value: 'any' }, conversationKind === 'group' ? '此群任何成员' : '任何能私聊此机器人者'),
|
|
1411
|
+
h('option', { value: 'allowlist' }, '指定 IM 用户 ID'))),
|
|
1412
|
+
senderScope === 'allowlist' ? h('label', null, '可请求成员 ID', h('input', { 'aria-label': '可请求的 IM 用户 ID',
|
|
1413
|
+
required: true, value: senderIds, onChange: (event) => setSenderIds(event.target.value),
|
|
1414
|
+
placeholder: '多个 ID 用逗号分隔', style: inputStyle })) : null,
|
|
1415
|
+
editingCardId ? h('p', { style: { ...mutedTextStyle, alignSelf: 'end' } },
|
|
1416
|
+
`有效期:${new Date(editingExpiresAt * 1000).toLocaleString('zh-CN')}(编辑时保持不变)`)
|
|
1417
|
+
: h('label', null, '有效期', h('select', { value: days, onChange: (event) => setDays(Number(event.target.value)), style: inputStyle },
|
|
1418
|
+
h('option', { value: 1 }, '1 天'), h('option', { value: 7 }, '7 天'), h('option', { value: 30 }, '30 天'))),
|
|
1419
|
+
nativeReviewerSelected ? h('p', { style: { ...mutedTextStyle, gridColumn: '1 / -1' } },
|
|
1420
|
+
'本机 PR 审查需要 Gitea 只读权限 read:repository 和 read:user;ANT 不会把令牌交给执行方。') : null),
|
|
1332
1421
|
),
|
|
1333
1422
|
h('aside', { id: 'ant-authorization-review', role: 'region', 'aria-label': '开放前复核', tabIndex: -1,
|
|
1334
|
-
style: { ...settingsPanelStyle, padding: 14, gap: 10,
|
|
1335
|
-
position: 'sticky', top: 12, alignSelf: 'start' } },
|
|
1423
|
+
style: { ...settingsPanelStyle, padding: 14, gap: 10, alignSelf: 'stretch' } },
|
|
1336
1424
|
h('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: 8 } },
|
|
1337
|
-
h('h4', { style: sectionTitleStyle }, '
|
|
1425
|
+
h('h4', { style: sectionTitleStyle }, '开放前看一眼'),
|
|
1338
1426
|
h('span', { style: { ...mutedTextStyle, padding: '3px 8px', borderRadius: 999,
|
|
1339
1427
|
backgroundColor: 'var(--dsw-alias-bg-layer-2)' } }, '草稿 · 未开放')),
|
|
1340
|
-
h('p', { style: mutedTextStyle }, '
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
['触发位置', conversationKind === 'group' ? `群 ${groupId || '尚未选择'}` : '私聊'],
|
|
1344
|
-
['可请求人', requesterSummary],
|
|
1345
|
-
['执行方', executorSummary],
|
|
1346
|
-
['读取资料', resourceSummary],
|
|
1347
|
-
['有效期', editingCardId ? new Date(editingExpiresAt * 1000).toLocaleString('zh-CN') : `${days} 天`],
|
|
1348
|
-
['回复位置', resultRouteSummary],
|
|
1349
|
-
].map(([label, value]) => h('div', { key: label, style: { display: 'grid', gridTemplateColumns: '84px minmax(0, 1fr)', gap: 8 } },
|
|
1350
|
-
h('dt', { style: mutedTextStyle }, label),
|
|
1351
|
-
h('dd', { style: { margin: 0, minWidth: 0, overflowWrap: 'anywhere' } }, value)))),
|
|
1428
|
+
h('p', { style: mutedTextStyle }, `这项任务将在${conversationKind === 'group'
|
|
1429
|
+
? `群“${selectedGroupSummary || '尚未选择'}”` : '私聊'}开放给${requesterSummary},由${executorSummary}执行;`
|
|
1430
|
+
+ `有效期 ${editingCardId ? new Date(editingExpiresAt * 1000).toLocaleString('zh-CN') : `${days} 天`},${resultRouteSummary}。`),
|
|
1352
1431
|
cardBlockReason ? h('p', { role: 'status', style: { ...mutedTextStyle,
|
|
1353
1432
|
color: 'var(--dsw-alias-state-warning-primary, var(--dsw-alias-label-secondary))' } }, cardBlockReason) : null,
|
|
1354
1433
|
h('button', { type: 'submit', form: 'ant-capability-create-form', disabled: busy || Boolean(cardBlockReason),
|
|
1355
1434
|
style: { ...primaryButtonStyle, width: '100%' } }, editingCardId ? '保存能力修改' : '确认并开放能力'))))),
|
|
1356
1435
|
h('div', { role: 'region', 'aria-label': '本机处理设置', style: { display: activeTab === 'local' ? 'grid' : 'none', gap: 12 } },
|
|
1357
|
-
|
|
1358
|
-
|
|
1436
|
+
trustedOriginCard,
|
|
1437
|
+
showPrSecuritySettings ? h('section', { style: { ...settingsPanelStyle, padding: 14 } },
|
|
1438
|
+
h('strong', null, 'PR 只读凭据(本机)'),
|
|
1359
1439
|
h('p', { style: mutedTextStyle }, tokenConfigured
|
|
1360
1440
|
? '只读访问令牌已保存在这台 DSH 的安全凭据中;不会同步到 ANT 服务端或发送给执行器。'
|
|
1361
|
-
: 'PR
|
|
1441
|
+
: '读取 Gitea PR 需要只读访问令牌。令牌只保存在这台 DSH,不提交给 ANT 服务端或执行器。'),
|
|
1362
1442
|
h('div', { style: { display: 'flex', gap: 8, flexWrap: 'wrap' } },
|
|
1363
1443
|
h('input', { 'aria-label': 'Gitea 只读访问令牌', type: 'password', autoComplete: 'new-password', value: giteaToken,
|
|
1364
1444
|
onChange: (event) => setGiteaToken(event.target.value),
|
|
1365
1445
|
placeholder: tokenConfigured ? '已保存;输入新令牌可替换' : '粘贴 Gitea 只读令牌',
|
|
1366
1446
|
style: { ...inputStyle, flex: '1 1 280px' } }),
|
|
1367
1447
|
h('button', { type: 'button', disabled: busy || !giteaToken.trim(),
|
|
1368
|
-
onClick: () => void saveGiteaToken(), style: secondaryButtonStyle }, tokenConfigured ? '替换本机令牌' : '保存本机令牌'))),
|
|
1448
|
+
onClick: () => void saveGiteaToken(), style: secondaryButtonStyle }, tokenConfigured ? '替换本机令牌' : '保存本机令牌'))) : null,
|
|
1369
1449
|
h('details', { style: { border: '1px solid var(--dsw-alias-border-l2)', borderRadius: 8, padding: 12 } },
|
|
1370
1450
|
h('summary', { style: { cursor: 'pointer', fontWeight: 600 } }, '本机处理设置'),
|
|
1371
1451
|
h('p', { style: mutedTextStyle }, '本机并发上限只影响这台电脑接收的新任务;PR 仓库缓存上限按这台 DSH 的长期仓库缓存总量计算,不包括任务临时工作目录。'),
|
|
@@ -1390,7 +1470,19 @@ window.__ModuleLoader__.load({
|
|
|
1390
1470
|
h('button', { disabled: busy, style: secondaryButtonStyle }, '保存本机处理设置'),
|
|
1391
1471
|
h('p', { style: { ...mutedTextStyle, flex: '1 1 100%', margin: 0 } },
|
|
1392
1472
|
'默认 10 GiB,范围 1–100 GiB,按长期仓库文件逻辑大小计算(不含临时工作区和文件系统分配开销)。日常不会淘汰仍启用能力的缓存;停用或到期能力会在刷新时回收。单次拉取若实际越界,为恢复预算会清理这项能力的仓库缓存(旧 PR 可重新下载)。')),
|
|
1393
|
-
|
|
1473
|
+
),
|
|
1474
|
+
h('section', { style: { ...settingsPanelStyle, padding: 14, gap: 8 } },
|
|
1475
|
+
h('strong', null, '执行方状态与权限边界'),
|
|
1476
|
+
h('p', { style: mutedTextStyle }, '这里显示本机可用情况;具体哪些执行方能处理某项任务,由“协作”页中的规则决定。'),
|
|
1477
|
+
loadFailures.includes('executors') ? h('p', { role: 'status', style: mutedTextStyle }, '暂时无法读取执行方状态,请刷新后重试。') : null,
|
|
1478
|
+
...executors.filter((item) => ['dsh', 'codex-readonly', 'claude-code'].includes(item.id)).map((item) => h('div', {
|
|
1479
|
+
key: item.id, style: { display: 'grid', gap: 3, paddingTop: 8, borderTop: '1px solid var(--dsw-alias-border-l2)' } },
|
|
1480
|
+
h('strong', null, item.title + ' · ' + (item.available ? '可用' : item.reason || '未启用')),
|
|
1481
|
+
h('span', { style: mutedTextStyle }, item.id === 'codex-readonly'
|
|
1482
|
+
? '读取固定到 PR Head 的快照,使用固定只读配置;Gitea 令牌不会交给 Codex。'
|
|
1483
|
+
: item.id === 'claude-code'
|
|
1484
|
+
? '读取相同 PR 快照,但沿用本机 DSH 权限设置,不具备 Codex 路径的固定只读隔离。'
|
|
1485
|
+
: '处理授权任务提供的内容,不调用本机工具。'))))))
|
|
1394
1486
|
}
|
|
1395
1487
|
|
|
1396
1488
|
function SkillCenterPage({ remote, onBack, onAll, onSkills, onPlugins, openBundle,
|
|
@@ -2090,18 +2182,7 @@ window.__ModuleLoader__.load({
|
|
|
2090
2182
|
? provider.apiMode === 'responses' ? 'ant-team-openai-responses' : 'ant-team-openai-chat-completions'
|
|
2091
2183
|
: provider.providerId === 'anthropic-default'
|
|
2092
2184
|
? 'ant-team-anthropic' : `ant-team-${provider.protocol}-${provider.providerId}`
|
|
2093
|
-
const
|
|
2094
|
-
const ops = []
|
|
2095
|
-
let previousRoutes = []
|
|
2096
|
-
try {
|
|
2097
|
-
const parsed = JSON.parse(localStorage.getItem(PROVIDER_ROUTES_STORAGE_KEY) || '[]')
|
|
2098
|
-
if (Array.isArray(parsed)) previousRoutes = parsed.filter((route) => typeof route === 'string')
|
|
2099
|
-
} catch {}
|
|
2100
|
-
const knownRoutes = new Set([...previousRoutes, 'ant-team-openai', 'ant-team-openai-chat-completions',
|
|
2101
|
-
'ant-team-openai-responses', 'ant-team-anthropic'])
|
|
2102
|
-
for (const route of knownRoutes) {
|
|
2103
|
-
if (!routes.includes(route)) ops.push({ op: 'unset', path: ['providers', route] })
|
|
2104
|
-
}
|
|
2185
|
+
const routeConfigurations = new Map()
|
|
2105
2186
|
for (const provider of providers) {
|
|
2106
2187
|
const route = routeForProvider(provider)
|
|
2107
2188
|
const api = provider.protocol === 'openai'
|
|
@@ -2112,13 +2193,40 @@ window.__ModuleLoader__.load({
|
|
|
2112
2193
|
: provider.protocol === 'openai'
|
|
2113
2194
|
? new URL(`/model-gateway/openai/providers/${encodeURIComponent(provider.providerId)}/v1`, normalized).toString()
|
|
2114
2195
|
: new URL(`/model-gateway/anthropic/providers/${encodeURIComponent(provider.providerId)}`, normalized).toString()
|
|
2115
|
-
|
|
2196
|
+
routeConfigurations.set(route, {
|
|
2116
2197
|
displayName: provider.providerName,
|
|
2117
2198
|
apiKeyEnv: 'ANT_MODEL_API_KEY',
|
|
2118
2199
|
api,
|
|
2119
2200
|
baseURL,
|
|
2120
2201
|
models: provider.models.map((model) => ({ id: model.model_id, name: model.display_name })),
|
|
2121
|
-
}
|
|
2202
|
+
})
|
|
2203
|
+
if (provider.protocol === 'openai' && provider.providerId === 'openai-default') {
|
|
2204
|
+
const aliases = provider.apiMode === 'responses'
|
|
2205
|
+
? ['ant-team-openai-chat-completions', 'ant-team-openai'] : ['ant-team-openai']
|
|
2206
|
+
const legacyChat = {
|
|
2207
|
+
displayName: `${provider.providerName} · Chat(旧会话兼容)`,
|
|
2208
|
+
apiKeyEnv: 'ANT_MODEL_API_KEY',
|
|
2209
|
+
api: 'openai-completions',
|
|
2210
|
+
baseURL: new URL('/model-gateway/openai/v1', normalized).toString(),
|
|
2211
|
+
models: provider.models.map((model) => ({ id: model.model_id, name: model.display_name })),
|
|
2212
|
+
}
|
|
2213
|
+
for (const alias of aliases) routeConfigurations.set(alias, legacyChat)
|
|
2214
|
+
}
|
|
2215
|
+
}
|
|
2216
|
+
const routes = [...routeConfigurations.keys()]
|
|
2217
|
+
const ops = []
|
|
2218
|
+
let previousRoutes = []
|
|
2219
|
+
try {
|
|
2220
|
+
const parsed = JSON.parse(localStorage.getItem(PROVIDER_ROUTES_STORAGE_KEY) || '[]')
|
|
2221
|
+
if (Array.isArray(parsed)) previousRoutes = parsed.filter((route) => typeof route === 'string')
|
|
2222
|
+
} catch {}
|
|
2223
|
+
const knownRoutes = new Set([...previousRoutes, 'ant-team-openai', 'ant-team-openai-chat-completions',
|
|
2224
|
+
'ant-team-openai-responses', 'ant-team-anthropic'])
|
|
2225
|
+
for (const route of knownRoutes) {
|
|
2226
|
+
if (!routes.includes(route)) ops.push({ op: 'unset', path: ['providers', route] })
|
|
2227
|
+
}
|
|
2228
|
+
for (const [route, value] of routeConfigurations) {
|
|
2229
|
+
ops.push({ op: 'set', path: ['providers', route], value })
|
|
2122
2230
|
}
|
|
2123
2231
|
const settingsWrite = await remote.settings.mutate('llm-pi-ai', ops, undefined)
|
|
2124
2232
|
if (!settingsWrite.ok) throw new Error(settingsWrite.error?.message || 'DSH 无法更新团队模型设置。')
|