import { resolveInlineTravelApplicationRequest } from './workbenchAiApplicationGateModel.js' export const WORKBENCH_AI_INTENT_SOURCE_MODEL = 'llm_function_call' export const WORKBENCH_AI_INTENT_SOURCE_RULE_FALLBACK = 'rule_fallback' export const WORKBENCH_AI_INTENT_CONFIDENCE_THRESHOLD = 0.6 export const WORKBENCH_AI_STEP_BUILD_APPLICATION_PREVIEW = 'build_application_preview' export const WORKBENCH_AI_STEP_VALIDATE_REQUIRED_FIELDS = 'validate_required_fields' export const WORKBENCH_AI_STEP_SAVE_APPLICATION_DRAFT = 'save_application_draft' export const WORKBENCH_AI_STEP_RUN_DUPLICATE_PRECHECK = 'run_duplicate_precheck' export const WORKBENCH_AI_STEP_SUBMIT_APPLICATION = 'submit_application' const TRAVEL_APPLICATION_INTENT = 'create_travel_application' function normalizePromptAction(prompt = '') { const compact = String(prompt || '').replace(/\s+/g, '') if (/直接提交|提交申请|确认提交|提交审批/.test(compact)) { return 'submit' } if (/保存草稿|保存|存草稿|先保存/.test(compact)) { return 'save_draft' } return 'preview' } function normalizePlannerSource(value = '') { return String(value || '').trim() === WORKBENCH_AI_INTENT_SOURCE_MODEL ? WORKBENCH_AI_INTENT_SOURCE_MODEL : WORKBENCH_AI_INTENT_SOURCE_RULE_FALLBACK } function normalizeSlotKey(key = '') { const normalized = String(key || '').trim() if (['time_range', 'business_time', 'occurred_date', 'application_time'].includes(normalized)) { return 'timeRange' } if (['transport_mode', 'transportType', 'transport_type', 'trafficMode'].includes(normalized)) { return 'transportMode' } if (['business_reason', 'businessPurpose', 'purpose'].includes(normalized)) { return 'reason' } return normalized } function normalizeSlots(rawSlots = {}) { if (!rawSlots || typeof rawSlots !== 'object') { return {} } return Object.entries(rawSlots).reduce((slots, [key, value]) => { const normalizedKey = normalizeSlotKey(key) const normalizedValue = String(value || '').trim() if (normalizedKey && normalizedValue) { slots[normalizedKey] = normalizedValue } return slots }, {}) } const ONTOLOGY_FIELD_ALIASES = { business_time: 'time_range', occurred_date: 'time_range', application_time: 'time_range', transportType: 'transport_mode', transport_type: 'transport_mode', trafficMode: 'transport_mode', business_reason: 'reason', businessPurpose: 'reason', purpose: 'reason' } const SUPPORTED_ONTOLOGY_FIELDS = new Set([ 'expense_type', 'time_range', 'location', 'reason', 'amount', 'transport_mode', 'attachments', 'customer_name', 'merchant_name', 'department_name', 'employee_name', 'employee_no' ]) function normalizeOntologyFields(rawFields = {}) { if (!rawFields || typeof rawFields !== 'object') { return {} } return Object.entries(rawFields).reduce((fields, [key, value]) => { const normalizedKey = ONTOLOGY_FIELD_ALIASES[String(key || '').trim()] || String(key || '').trim() const normalizedValue = String(value || '').trim() if (SUPPORTED_ONTOLOGY_FIELDS.has(normalizedKey) && normalizedValue) { fields[normalizedKey] = normalizedValue } return fields }, {}) } function buildApplicationSteps(requestedAction = 'preview') { const steps = [ WORKBENCH_AI_STEP_BUILD_APPLICATION_PREVIEW, WORKBENCH_AI_STEP_VALIDATE_REQUIRED_FIELDS ] if (requestedAction === 'submit') { steps.push( WORKBENCH_AI_STEP_RUN_DUPLICATE_PRECHECK, WORKBENCH_AI_STEP_SUBMIT_APPLICATION ) } else if (requestedAction === 'save_draft') { steps.push(WORKBENCH_AI_STEP_SAVE_APPLICATION_DRAFT) } return steps } function normalizeServerApplicationSteps(rawSteps = []) { if (!Array.isArray(rawSteps)) { return [] } const mappedSteps = rawSteps .map((step) => String(step?.action_type || step?.actionType || '').trim()) .map((actionType) => { if (actionType === WORKBENCH_AI_STEP_BUILD_APPLICATION_PREVIEW) { return WORKBENCH_AI_STEP_BUILD_APPLICATION_PREVIEW } if (actionType === WORKBENCH_AI_STEP_VALIDATE_REQUIRED_FIELDS) { return WORKBENCH_AI_STEP_VALIDATE_REQUIRED_FIELDS } if (actionType === WORKBENCH_AI_STEP_SAVE_APPLICATION_DRAFT) { return WORKBENCH_AI_STEP_SAVE_APPLICATION_DRAFT } if (actionType === WORKBENCH_AI_STEP_RUN_DUPLICATE_PRECHECK) { return WORKBENCH_AI_STEP_RUN_DUPLICATE_PRECHECK } if (actionType === WORKBENCH_AI_STEP_SUBMIT_APPLICATION) { return WORKBENCH_AI_STEP_SUBMIT_APPLICATION } return '' }) .filter(Boolean) return [...new Set(mappedSteps)] } function resolveModelTasks(rawPlan = {}) { return Array.isArray(rawPlan?.tasks) ? rawPlan.tasks : [] } function isModelTravelApplicationTask(task = {}) { if (!task || typeof task !== 'object') { return false } const taskType = String(task?.task_type || task?.taskType || '').trim() const assignedAgent = String(task?.assigned_agent || task?.assignedAgent || '').trim() return taskType === 'expense_application' || assignedAgent === 'application_assistant' } function findModelTravelApplicationTask(rawPlan = {}) { return resolveModelTasks(rawPlan).find(isModelTravelApplicationTask) || null } function resolveModelRemainingTasks(rawPlan = {}, selectedTask = null) { const tasks = resolveModelTasks(rawPlan) const selectedIndex = tasks.findIndex((task) => task === selectedTask) if (selectedIndex < 0) { return [] } return tasks.slice(selectedIndex + 1).filter((task) => { if (!task || typeof task !== 'object') { return false } const taskType = String(task?.task_type || task?.taskType || '').trim() const assignedAgent = String(task?.assigned_agent || task?.assignedAgent || '').trim() return Boolean(taskType || assignedAgent) }) } function resolveCandidateFlows(rawPlan = {}) { const pendingFlow = rawPlan?.pending_flow_confirmation || rawPlan?.pendingFlowConfirmation || {} const pendingCandidates = pendingFlow?.candidate_flows || pendingFlow?.candidateFlows const rootCandidates = rawPlan?.candidate_flows || rawPlan?.candidateFlows if (Array.isArray(pendingCandidates)) { return pendingCandidates } return Array.isArray(rootCandidates) ? rootCandidates : [] } function findSingleApplicationCandidateFlow(rawPlan = {}) { const pendingFlow = rawPlan?.pending_flow_confirmation || rawPlan?.pendingFlowConfirmation || {} if (String(pendingFlow?.status || '').trim() !== 'pending') { return null } const candidateFlows = resolveCandidateFlows(rawPlan) if (candidateFlows.length !== 1) { return null } const [candidate] = candidateFlows const flowId = String(candidate?.flow_id || candidate?.flowId || '').trim() const label = String(candidate?.label || '').trim() if (flowId === 'travel_application' && /先发起出差申请/.test(label)) { return candidate } return null } export function normalizeWorkbenchAiIntentPlan(rawPlan = {}, options = {}) { const prompt = String(options.prompt || rawPlan?.sourceText || rawPlan?.source_text || '').trim() const task = findModelTravelApplicationTask(rawPlan) if (!task) { const candidateFlow = findSingleApplicationCandidateFlow(rawPlan) if (!candidateFlow) { return null } const ontologyFields = normalizeOntologyFields(candidateFlow.ontology_fields || candidateFlow.ontologyFields) const requestedAction = normalizePromptAction(prompt) return { source: normalizePlannerSource(rawPlan.planning_source || rawPlan.planningSource), intent: TRAVEL_APPLICATION_INTENT, requestedAction, confidence: Number(candidateFlow.confidence || rawPlan.confidence || 0), sourceText: prompt, ontologyFields, slots: normalizeSlots(ontologyFields), missingFields: Array.isArray(candidateFlow.missing_fields || candidateFlow.missingFields) ? candidateFlow.missing_fields || candidateFlow.missingFields : [], steps: buildApplicationSteps(requestedAction) } } const rawOntologyFields = task.ontology_fields || task.ontologyFields || rawPlan.slots const ontologyFields = normalizeOntologyFields(rawOntologyFields) const requestedAction = String( task.requested_action || task.requestedAction || rawPlan.requested_action || rawPlan.requestedAction || '' ).trim() || normalizePromptAction(prompt) const serverSteps = normalizeServerApplicationSteps(task.action_steps || task.actionSteps) return { source: normalizePlannerSource(rawPlan.planning_source || rawPlan.planningSource), intent: TRAVEL_APPLICATION_INTENT, requestedAction, confidence: Number(task.confidence || rawPlan.confidence || 0), sourceText: prompt, ontologyFields, slots: normalizeSlots(ontologyFields), missingFields: Array.isArray(task.missing_fields || task.missingFields) ? task.missing_fields || task.missingFields : [], steps: serverSteps.length ? serverSteps : buildApplicationSteps(requestedAction), stewardRemainingTasks: resolveModelRemainingTasks(rawPlan, task) } } export function buildRuleFallbackWorkbenchAiIntentPlan(prompt = '') { const request = resolveInlineTravelApplicationRequest(prompt) if (!request) { return null } const requestedAction = request.requestedSubmit ? 'submit' : normalizePromptAction(prompt) return { source: WORKBENCH_AI_INTENT_SOURCE_RULE_FALLBACK, intent: TRAVEL_APPLICATION_INTENT, requestedAction, confidence: 0.72, sourceText: request.sourceText, ontologyFields: {}, slots: {}, missingFields: [], steps: buildApplicationSteps(requestedAction) } } export function shouldRequestWorkbenchAiIntentPlan(prompt = '') { const compact = String(prompt || '').replace(/\s+/g, '') if (!compact) { return false } if (compact.length < 2 || /^[\d\s.,,。::;;!?!?-]+$/.test(compact)) { return false } if (!WORKBENCH_AI_BUSINESS_KEYWORD_PATTERN.test(compact)) { return false } return true } const WORKBENCH_AI_BUSINESS_KEYWORD_PATTERN = ( /报销|报账|出差|差旅|申请|审批|审核|报销单|申请单|草稿|删除|提交|保存|查|看|找|列出|发起|新建|创建|驳回|退回|通过|多少|标准|制度|规则|政策/ ) export function resolveExecutableTravelApplicationPlan(plan = null) { if (!plan || plan.intent !== TRAVEL_APPLICATION_INTENT) { return null } if (!Array.isArray(plan.steps) || !plan.steps.includes(WORKBENCH_AI_STEP_BUILD_APPLICATION_PREVIEW)) { return null } const requestedSubmit = plan.steps.includes(WORKBENCH_AI_STEP_SUBMIT_APPLICATION) const request = { expenseType: 'travel', expenseTypeLabel: '差旅费', sourceText: String(plan.sourceText || '').trim(), ontologyFields: normalizeOntologyFields(plan.ontologyFields || {}), autoSubmit: false, autoSaveDraft: plan.steps.includes(WORKBENCH_AI_STEP_SAVE_APPLICATION_DRAFT), requestedSubmit, submitRequiresConfirmation: requestedSubmit } const stewardRemainingTasks = Array.isArray(plan.stewardRemainingTasks) ? plan.stewardRemainingTasks : [] if (stewardRemainingTasks.length) { request.stewardRemainingTasks = stewardRemainingTasks } return request } export function isLowConfidenceTravelApplicationPlan(plan = null) { if (!plan || plan.intent !== TRAVEL_APPLICATION_INTENT) { return false } if (plan.source === WORKBENCH_AI_INTENT_SOURCE_RULE_FALLBACK) { return false } if (plan.requestedAction === 'submit' || plan.requestedAction === 'save_draft') { return false } const confidence = Number(plan.confidence) if (!Number.isFinite(confidence)) { return false } return confidence < WORKBENCH_AI_INTENT_CONFIDENCE_THRESHOLD }