| | |
| | | const OUTBOUND_JOBS_KEY = 'outbound_call_jobs'; |
| | | |
| | | // 活跃状态列表 |
| | | const ACTIVE_STATUSES = ['Scheduling', 'InProgress', 'Calling', 'Ringing', 'Answered']; |
| | | const ACTIVE_STATUSES = ['Scheduling', 'Executing', 'Paused', 'Drafted', 'InProgress', 'Calling', 'Ringing', 'Answered']; |
| | | |
| | | // Scheduling 状态 - 此状态变化不需要调用更新API |
| | | const SCHEDULING_STATUS = 'Scheduling'; |
| | | const BACKEND_STATUSES = ['Scheduling', 'Executing', 'Succeeded', 'Paused', 'Failed', 'Cancelled', 'Drafted']; |
| | | const STATUS_TO_BACKEND = { |
| | | InProgress: 'Executing', |
| | | Calling: 'Executing', |
| | | Ringing: 'Executing', |
| | | Answered: 'Executing' |
| | | }; |
| | | const isActiveStatus = (status) => !status || ACTIVE_STATUSES.includes(status); |
| | | |
| | | /** |
| | | * 智能外呼通话显示组件 |
| | |
| | | } |
| | | }); |
| | | |
| | | // 获取成功任务的 personId 集合 |
| | | const successPersonIds = new Set(successJobs.map(job => job.personId)); |
| | | |
| | | // 过滤掉已有成功任务的 personId 对应的失败任务(成功任务优先) |
| | | const filteredFailedJobs = uniqueFailedJobs.filter(job => !successPersonIds.has(job.personId)); |
| | | |
| | | // 合并所有任务 |
| | | return [...successJobs, ...uniqueFailedJobs]; |
| | | return [...successJobs, ...filteredFailedJobs]; |
| | | } catch (err) { |
| | | console.error('读取外呼任务失败:', err); |
| | | return []; |
| | |
| | | const results = await Promise.all( |
| | | jobsToUpdate.map(async (job) => { |
| | | try { |
| | | const statusToUpdate = job.backendStatus || job.newStatus; |
| | | if (!statusToUpdate) { |
| | | return { success: false, job }; |
| | | } |
| | | await OutboundBotAPIService.updateCallStatus({ |
| | | jobId: job.jobId, |
| | | callStatus: job.newStatus |
| | | callStatus: statusToUpdate |
| | | }); |
| | | console.log(`状态更新成功: ${job.jobId} -> ${job.newStatus}`); |
| | | console.log(`状态更新成功: ${job.jobId} -> ${statusToUpdate}`); |
| | | return { success: true, job }; |
| | | } catch (err) { |
| | | console.error(`状态更新失败: ${job.jobId}`, err); |
| | |
| | | const now = Date.now(); |
| | | return jobs.filter(job => { |
| | | // 检查是否为活跃状态 |
| | | if (ACTIVE_STATUSES.includes(job.callStatus)) { |
| | | if (isActiveStatus(job.callStatus)) { |
| | | // 检查是否超时(2小时) |
| | | const elapsed = now - (job.pollStartTime || job.startTime || now); |
| | | if (elapsed > 2 * 60 * 60 * 1000) { |
| | |
| | | const storedJobs = loadJobsFromStorage(); |
| | | |
| | | // 分离成功任务和失败任务 |
| | | const successJobs = storedJobs.filter(job => !job.errorCode && ACTIVE_STATUSES.includes(job.callStatus)); |
| | | const successJobs = storedJobs.filter(job => !job.errorCode && isActiveStatus(job.callStatus)); |
| | | const failedJobs = storedJobs.filter(job => job.errorCode > 0); |
| | | |
| | | if (successJobs.length === 0) { |
| | |
| | | |
| | | if (response?.data) { |
| | | const newStatus = response.data.callStatus; |
| | | if (!newStatus) { |
| | | return job; |
| | | } |
| | | const backendStatus = STATUS_TO_BACKEND[newStatus] || newStatus; |
| | | |
| | | // 如果状态发生变化,更新任务 |
| | | if (newStatus !== job.callStatus) { |
| | | console.log(`任务 ${job.jobId} 状态更新: ${job.callStatus} -> ${newStatus}`); |
| | | |
| | | // 检查是否需要调用后端更新API(排除Scheduling状态) |
| | | if (job.callStatus !== SCHEDULING_STATUS) { |
| | | if (backendStatus !== SCHEDULING_STATUS && BACKEND_STATUSES.includes(backendStatus)) { |
| | | jobsNeedBackendUpdate.push({ |
| | | ...job, |
| | | newStatus |
| | | newStatus, |
| | | backendStatus |
| | | }); |
| | | } |
| | | |
| | | // 如果是终态,可以从轮询中移除 |
| | | if (!ACTIVE_STATUSES.includes(newStatus)) { |
| | | if (!isActiveStatus(newStatus)) { |
| | | console.log(`任务 ${job.jobId} 达到终态: ${newStatus}`); |
| | | return null; // 标记为删除 |
| | | } |
| | |
| | | |
| | | // 定时轮询通话状态 |
| | | useEffect(() => { |
| | | // 组件挂载时设置为 true |
| | | isMountedRef.current = true; |
| | | |
| | | // 初始加载 |
| | | fetchCallStatus(); |
| | | |
| | | // 设置轮询定时器(10秒间隔) |
| | | const interval = setInterval(fetchCallStatus, POLL_INTERVAL); |
| | | |
| | | // 监听外呼任务更新事件(立即刷新) |
| | | const handleOutboundJobsUpdated = () => { |
| | | console.log('收到外呼任务更新事件,立即刷新'); |
| | | fetchCallStatus(); |
| | | }; |
| | | window.addEventListener('outbound-jobs-updated', handleOutboundJobsUpdated); |
| | | |
| | | // 清理函数 |
| | | return () => { |
| | | clearInterval(interval); |
| | | window.removeEventListener('outbound-jobs-updated', handleOutboundJobsUpdated); |
| | | isMountedRef.current = false; |
| | | }; |
| | | }, [fetchCallStatus]); |
| | |
| | | // 其他状态的任务正常显示 |
| | | return true; |
| | | }); |
| | | |
| | | // 如果没有活跃任务且不可见,不渲染任何内容 |
| | | if (activeCalls.length === 0 && !isVisible) { |
| | | return null; |
| | | } |
| | | |
| | | // 如果最小化,显示AI客服图标 |
| | | if (isMinimized) { |
| | |
| | | {call.errorCode > 0 ? ( |
| | | // 失败任务显示 |
| | | <span> |
| | | {call.perClassName || '联系人'} |
| | | {call.perTypeName || '联系人'} |
| | | {call.trueName && `(${call.trueName})`}: |
| | | {call.message} |
| | | </span> |
| | | ) : ( |
| | | // 成功任务显示 |
| | | // 成功任务显示 - 使用 perTypeName 字段(申请方当事人/被申请方当事人) |
| | | <span> |
| | | 正在与{call.perClassName || '申请方'}({call.trueName || call.personId})电话沟通中... |
| | | 正在与{call.perTypeName || '申请方当事人'}({call.trueName || call.personId})电话沟通中... |
| | | </span> |
| | | )} |
| | | </div> |
| | |
| | | ); |
| | | }; |
| | | |
| | | export default OutboundCallWidget; |
| | | export default OutboundCallWidget; |