2026-01-15 06:01:34 +08:00
|
|
|
import { reactive } from 'vue'
|
|
|
|
|
import { message } from 'ant-design-vue'
|
|
|
|
|
import { handleChatError } from '@/utils/errorHandler'
|
|
|
|
|
import { agentApi } from '@/apis'
|
2025-11-01 21:34:16 +08:00
|
|
|
|
2026-03-07 23:28:25 +08:00
|
|
|
const normalizeOptions = (rawOptions) => {
|
|
|
|
|
if (!Array.isArray(rawOptions)) return []
|
|
|
|
|
return rawOptions
|
|
|
|
|
.map((item) => {
|
|
|
|
|
if (item && typeof item === 'object') {
|
|
|
|
|
const label = String(item.label || item.value || '').trim()
|
|
|
|
|
const value = String(item.value || item.label || '').trim()
|
|
|
|
|
return label && value ? { label, value } : null
|
|
|
|
|
}
|
|
|
|
|
const text = String(item || '').trim()
|
|
|
|
|
return text ? { label: text, value: text } : null
|
|
|
|
|
})
|
|
|
|
|
.filter(Boolean)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const toLegacyApprovalOptions = () => [
|
|
|
|
|
{ label: '批准 (Recommended)', value: 'approve' },
|
|
|
|
|
{ label: '拒绝', value: 'reject' }
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
const extractQuestionPayload = (chunk) => {
|
|
|
|
|
const interruptInfo = chunk?.interrupt_info || {}
|
|
|
|
|
const rawOptions =
|
|
|
|
|
chunk?.options || interruptInfo?.options || (interruptInfo?.operation ? toLegacyApprovalOptions() : [])
|
|
|
|
|
const options = normalizeOptions(rawOptions)
|
|
|
|
|
const operation = chunk?.operation || interruptInfo?.operation || ''
|
|
|
|
|
|
|
|
|
|
const source = chunk?.source || interruptInfo?.source || (operation ? 'get_approved_user_goal' : 'interrupt')
|
|
|
|
|
const multiSelect = Boolean(chunk?.multi_select ?? interruptInfo?.multi_select ?? false)
|
|
|
|
|
let allowOther = Boolean(chunk?.allow_other ?? interruptInfo?.allow_other ?? true)
|
|
|
|
|
const questionId = chunk?.question_id || interruptInfo?.question_id || ''
|
|
|
|
|
const question = chunk?.question || interruptInfo?.question || '请选择一个选项'
|
|
|
|
|
const legacyMode = source === 'get_approved_user_goal' && options.length === 2
|
|
|
|
|
if (source === 'get_approved_user_goal') {
|
|
|
|
|
allowOther = false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
questionId,
|
|
|
|
|
question,
|
|
|
|
|
options,
|
|
|
|
|
multiSelect,
|
|
|
|
|
allowOther,
|
|
|
|
|
source,
|
|
|
|
|
operation,
|
|
|
|
|
legacyMode
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const inferApprovedFromAnswer = (answer) => {
|
|
|
|
|
if (answer === 'approve') return true
|
|
|
|
|
if (answer === 'reject') return false
|
|
|
|
|
return null
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-01 21:34:16 +08:00
|
|
|
export function useApproval({ getThreadState, resetOnGoingConv, fetchThreadMessages }) {
|
|
|
|
|
const approvalState = reactive({
|
|
|
|
|
showModal: false,
|
2026-03-07 23:28:25 +08:00
|
|
|
questionId: '',
|
2025-11-01 21:34:16 +08:00
|
|
|
question: '',
|
|
|
|
|
operation: '',
|
2026-03-07 23:28:25 +08:00
|
|
|
options: [],
|
|
|
|
|
multiSelect: false,
|
|
|
|
|
allowOther: true,
|
|
|
|
|
source: '',
|
|
|
|
|
legacyMode: false,
|
|
|
|
|
threadId: null
|
2026-01-15 06:01:34 +08:00
|
|
|
})
|
2025-11-01 21:34:16 +08:00
|
|
|
|
2026-03-07 23:28:25 +08:00
|
|
|
const handleApproval = async (answer, currentAgentId, agentConfigId = null) => {
|
2026-01-15 06:01:34 +08:00
|
|
|
const threadId = approvalState.threadId
|
2025-11-01 21:34:16 +08:00
|
|
|
if (!threadId) {
|
2026-03-07 23:28:25 +08:00
|
|
|
message.error('无效的提问请求')
|
2026-01-15 06:01:34 +08:00
|
|
|
approvalState.showModal = false
|
|
|
|
|
return
|
2025-11-01 21:34:16 +08:00
|
|
|
}
|
|
|
|
|
|
2026-01-15 06:01:34 +08:00
|
|
|
const threadState = getThreadState(threadId)
|
2025-11-01 21:34:16 +08:00
|
|
|
if (!threadState) {
|
2026-01-15 06:01:34 +08:00
|
|
|
message.error('无法找到对应的对话线程')
|
|
|
|
|
approvalState.showModal = false
|
|
|
|
|
return
|
2025-11-01 21:34:16 +08:00
|
|
|
}
|
|
|
|
|
|
2026-01-15 06:01:34 +08:00
|
|
|
approvalState.showModal = false
|
2025-11-01 21:34:16 +08:00
|
|
|
|
|
|
|
|
if (threadState.streamAbortController) {
|
2026-01-15 06:01:34 +08:00
|
|
|
threadState.streamAbortController.abort()
|
|
|
|
|
threadState.streamAbortController = null
|
2025-11-01 21:34:16 +08:00
|
|
|
}
|
|
|
|
|
|
2026-01-15 06:01:34 +08:00
|
|
|
threadState.isStreaming = true
|
|
|
|
|
resetOnGoingConv(threadId)
|
|
|
|
|
threadState.streamAbortController = new AbortController()
|
2025-11-01 21:34:16 +08:00
|
|
|
|
2026-03-07 23:28:25 +08:00
|
|
|
const approved = inferApprovedFromAnswer(answer)
|
|
|
|
|
const requestBody = {
|
|
|
|
|
thread_id: threadId,
|
|
|
|
|
answer,
|
|
|
|
|
config: agentConfigId ? { agent_config_id: agentConfigId } : {}
|
|
|
|
|
}
|
|
|
|
|
if (approved !== null) {
|
|
|
|
|
requestBody.approved = approved
|
|
|
|
|
}
|
2025-11-01 21:34:16 +08:00
|
|
|
|
|
|
|
|
try {
|
2026-03-07 23:28:25 +08:00
|
|
|
const response = await agentApi.resumeAgentChat(currentAgentId, requestBody, {
|
|
|
|
|
signal: threadState.streamAbortController?.signal
|
|
|
|
|
})
|
2025-11-01 21:34:16 +08:00
|
|
|
|
|
|
|
|
if (!response.ok) {
|
2026-01-15 06:01:34 +08:00
|
|
|
const errorText = await response.text()
|
|
|
|
|
throw new Error(`HTTP error! status: ${response.status}, details: ${errorText}`)
|
2025-11-01 21:34:16 +08:00
|
|
|
}
|
|
|
|
|
|
2026-03-07 23:28:25 +08:00
|
|
|
return response
|
2025-11-01 21:34:16 +08:00
|
|
|
} catch (error) {
|
|
|
|
|
if (error.name !== 'AbortError') {
|
2026-01-15 06:01:34 +08:00
|
|
|
handleChatError(error, 'resume')
|
|
|
|
|
message.error(`恢复对话失败: ${error.message || '未知错误'}`)
|
2025-11-01 21:34:16 +08:00
|
|
|
}
|
2026-01-15 06:01:34 +08:00
|
|
|
threadState.isStreaming = false
|
|
|
|
|
threadState.streamAbortController = null
|
2026-03-07 23:28:25 +08:00
|
|
|
throw error
|
2025-11-01 21:34:16 +08:00
|
|
|
}
|
2026-01-15 06:01:34 +08:00
|
|
|
}
|
2025-11-01 21:34:16 +08:00
|
|
|
|
|
|
|
|
const processApprovalInStream = (chunk, threadId, currentAgentId) => {
|
2026-03-07 23:28:25 +08:00
|
|
|
if (chunk.status !== 'ask_user_question_required' && chunk.status !== 'human_approval_required') {
|
2026-01-15 06:01:34 +08:00
|
|
|
return false
|
2025-11-01 21:34:16 +08:00
|
|
|
}
|
|
|
|
|
|
2026-01-15 06:01:34 +08:00
|
|
|
const threadState = getThreadState(threadId)
|
|
|
|
|
if (!threadState) return false
|
2025-11-01 21:34:16 +08:00
|
|
|
|
2026-03-07 23:28:25 +08:00
|
|
|
const payload = extractQuestionPayload(chunk)
|
|
|
|
|
if (!payload.options.length) {
|
|
|
|
|
payload.options = toLegacyApprovalOptions()
|
|
|
|
|
payload.legacyMode = true
|
|
|
|
|
payload.allowOther = false
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-15 06:01:34 +08:00
|
|
|
threadState.isStreaming = false
|
2025-11-01 21:34:16 +08:00
|
|
|
|
2026-01-15 06:01:34 +08:00
|
|
|
approvalState.showModal = true
|
2026-03-07 23:28:25 +08:00
|
|
|
approvalState.questionId = payload.questionId
|
|
|
|
|
approvalState.question = payload.question
|
|
|
|
|
approvalState.operation = payload.operation
|
|
|
|
|
approvalState.options = payload.options
|
|
|
|
|
approvalState.multiSelect = payload.multiSelect
|
|
|
|
|
approvalState.allowOther = payload.allowOther
|
|
|
|
|
approvalState.source = payload.source
|
|
|
|
|
approvalState.legacyMode = payload.legacyMode
|
2026-01-15 06:01:34 +08:00
|
|
|
approvalState.threadId = chunk.thread_id || threadId
|
2025-11-01 21:34:16 +08:00
|
|
|
|
2026-03-07 23:28:25 +08:00
|
|
|
fetchThreadMessages({ agentId: currentAgentId, threadId })
|
2025-11-01 21:34:16 +08:00
|
|
|
|
2026-03-07 23:28:25 +08:00
|
|
|
return true
|
2026-01-15 06:01:34 +08:00
|
|
|
}
|
2025-11-01 21:34:16 +08:00
|
|
|
|
|
|
|
|
const resetApprovalState = () => {
|
2026-01-15 06:01:34 +08:00
|
|
|
approvalState.showModal = false
|
2026-03-07 23:28:25 +08:00
|
|
|
approvalState.questionId = ''
|
2026-01-15 06:01:34 +08:00
|
|
|
approvalState.question = ''
|
|
|
|
|
approvalState.operation = ''
|
2026-03-07 23:28:25 +08:00
|
|
|
approvalState.options = []
|
|
|
|
|
approvalState.multiSelect = false
|
|
|
|
|
approvalState.allowOther = true
|
|
|
|
|
approvalState.source = ''
|
|
|
|
|
approvalState.legacyMode = false
|
2026-01-15 06:01:34 +08:00
|
|
|
approvalState.threadId = null
|
|
|
|
|
}
|
2025-11-01 21:34:16 +08:00
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
approvalState,
|
|
|
|
|
handleApproval,
|
|
|
|
|
processApprovalInStream,
|
|
|
|
|
resetApprovalState
|
2026-01-15 06:01:34 +08:00
|
|
|
}
|
|
|
|
|
}
|