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'
|
2026-03-17 03:39:48 +08:00
|
|
|
import { normalizeQuestions, buildLegacyQuestion } from '@/utils/questionUtils'
|
2026-03-07 23:28:25 +08:00
|
|
|
|
|
|
|
|
const extractQuestionPayload = (chunk) => {
|
|
|
|
|
const interruptInfo = chunk?.interrupt_info || {}
|
2026-03-17 03:39:48 +08:00
|
|
|
const rawQuestions = chunk?.questions || interruptInfo?.questions || []
|
2026-03-10 00:16:41 +08:00
|
|
|
const source = chunk?.source || interruptInfo?.source || 'interrupt'
|
2026-03-17 03:39:48 +08:00
|
|
|
let questions = normalizeQuestions(rawQuestions)
|
|
|
|
|
|
|
|
|
|
if (!questions.length) {
|
|
|
|
|
const legacyQuestion = buildLegacyQuestion(chunk, interruptInfo)
|
|
|
|
|
if (legacyQuestion) {
|
|
|
|
|
questions = [legacyQuestion]
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-03-07 23:28:25 +08:00
|
|
|
|
|
|
|
|
return {
|
2026-03-17 03:39:48 +08:00
|
|
|
questions,
|
|
|
|
|
source
|
2026-03-07 23:28:25 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-17 03:39:48 +08:00
|
|
|
const parseApprovedDecision = (answer) => {
|
|
|
|
|
const parseFromValue = (value) => {
|
|
|
|
|
if (typeof value === 'boolean') return value
|
|
|
|
|
if (typeof value === 'string') {
|
|
|
|
|
const normalized = value.trim().toLowerCase()
|
2026-03-20 21:18:13 +08:00
|
|
|
if (normalized === 'approve' || normalized === 'approved' || normalized === 'true')
|
|
|
|
|
return true
|
|
|
|
|
if (normalized === 'reject' || normalized === 'rejected' || normalized === 'false')
|
|
|
|
|
return false
|
2026-03-17 03:39:48 +08:00
|
|
|
}
|
|
|
|
|
return null
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const direct = parseFromValue(answer)
|
|
|
|
|
if (direct !== null) return direct
|
|
|
|
|
|
|
|
|
|
if (answer && typeof answer === 'object' && !Array.isArray(answer)) {
|
|
|
|
|
const values = Object.values(answer)
|
|
|
|
|
if (values.length === 1) {
|
|
|
|
|
return parseFromValue(values[0])
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return null
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-01 21:34:16 +08:00
|
|
|
export function useApproval({ getThreadState, resetOnGoingConv, fetchThreadMessages }) {
|
|
|
|
|
const approvalState = reactive({
|
|
|
|
|
showModal: false,
|
2026-03-17 03:39:48 +08:00
|
|
|
questions: [],
|
|
|
|
|
status: '',
|
2026-03-07 23:28:25 +08:00
|
|
|
threadId: null
|
2026-01-15 06:01:34 +08:00
|
|
|
})
|
2025-11-01 21:34:16 +08:00
|
|
|
|
2026-03-25 03:36:08 +08:00
|
|
|
const handleApproval = async (answer, currentAgentId, agentConfigId) => {
|
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-03-25 03:36:08 +08:00
|
|
|
if (!agentConfigId) {
|
|
|
|
|
message.error('缺少智能体配置,请重新选择配置后重试')
|
|
|
|
|
approvalState.showModal = false
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
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 requestBody = {
|
|
|
|
|
thread_id: threadId,
|
2026-03-25 03:36:08 +08:00
|
|
|
config: { agent_config_id: agentConfigId }
|
2026-03-07 23:28:25 +08:00
|
|
|
}
|
2025-11-01 21:34:16 +08:00
|
|
|
|
2026-03-17 03:39:48 +08:00
|
|
|
if (approvalState.status === 'human_approval_required') {
|
|
|
|
|
const approved = parseApprovedDecision(answer)
|
|
|
|
|
if (approved !== null) {
|
|
|
|
|
requestBody.approved = approved
|
|
|
|
|
} else {
|
|
|
|
|
requestBody.answer = answer
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
requestBody.answer = answer
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-01 21:34:16 +08:00
|
|
|
try {
|
2026-03-27 00:20:33 +08:00
|
|
|
const response = await agentApi.resumeAgentChat(threadId, requestBody, {
|
2026-03-07 23:28:25 +08:00
|
|
|
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-16 21:40:19 +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)
|
2026-03-17 03:39:48 +08:00
|
|
|
if (!payload.questions.length) return false
|
2026-03-07 23:28:25 +08:00
|
|
|
|
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-17 03:39:48 +08:00
|
|
|
approvalState.questions = payload.questions
|
|
|
|
|
approvalState.status = chunk.status || ''
|
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-17 03:39:48 +08:00
|
|
|
approvalState.questions = []
|
|
|
|
|
approvalState.status = ''
|
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
|
|
|
}
|
|
|
|
|
}
|