- useWorkbenchAiApplicationPreviewFlow/useWorkbenchAiActionRouter/useWorkbenchAiCommandIntents 支持 task1 完成后自动推进 task2,确认按钮直接拉起申请预览,草稿/提交成功后继续推进下一 task - workbenchAiIntentPlannerModel/workbenchAiMessageModel/workbenchAiCommandIntentModel 适配多 task 意图规划与消息结构 - aiApplicationPreviewActions/aiApplicationPrecheckModel/aiExpenseDraftModel/aiWorkbenchConversationStore 草稿与会话存储适配 - PersonalWorkbenchAiMode 与样式适配,更新 preview-actions/expense-draft/conversation-store/fast-preview/action-router/command-intent/intent-planner 测试
336 lines
12 KiB
JavaScript
336 lines
12 KiB
JavaScript
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
|
||
}
|