- 新增数字员工财务报告生成、邮件投递与渲染调度器 - 引入员工画像扫描调度与定时提醒任务 - 完善财务看板快照、排行口径与部门人员占比计算 - 优化数字员工工作看板仪表盘与技能目录 - 增强前端总览页图表、工作台摘要与顶部导航栏交互 - 新增差旅申请规划推动提醒与报销创建会话状态管理 - 补充财务报告、看板调度、数字员工工作记录测试覆盖
321 lines
9.4 KiB
JavaScript
321 lines
9.4 KiB
JavaScript
import {
|
|
formatAgentRunElapsed,
|
|
formatAgentRunProgress,
|
|
resolveAgentRunHeartbeat,
|
|
resolveAgentRunStatus
|
|
} from '../../utils/agentRunMonitor.js'
|
|
|
|
const SOURCE_LABELS = {
|
|
schedule: '定时任务',
|
|
system_event: '系统事件',
|
|
user_message: '用户触发'
|
|
}
|
|
|
|
const KNOWLEDGE_JOB_TYPES = new Set([
|
|
'knowledge_index_sync',
|
|
'llm_wiki_sync',
|
|
'llm_wiki_rule_formation',
|
|
'finance_policy_knowledge_organize'
|
|
])
|
|
|
|
export const VISIBLE_DIGITAL_EMPLOYEE_WORK_TASK_TYPES = new Set([
|
|
'finance_dashboard_snapshot',
|
|
'digital_employee_reminder_scan',
|
|
'employee_behavior_profile_scan',
|
|
'department_expense_baseline_accumulate',
|
|
'budget_overrun_precontrol_evaluate',
|
|
'multi_evidence_consistency_evaluate',
|
|
'travel_spatiotemporal_consistency_evaluate',
|
|
'global_risk_scan',
|
|
'finance_policy_knowledge_organize'
|
|
])
|
|
|
|
const DAILY_COMPACT_TASK_TYPES = new Set([
|
|
'finance_dashboard_snapshot'
|
|
])
|
|
|
|
const TASK_TYPE_LABELS = {
|
|
finance_dashboard_snapshot: '财务经营快照沉淀',
|
|
digital_employee_reminder_scan: '定时提醒与待办扫描',
|
|
global_risk_scan: '财务风险图谱巡检',
|
|
employee_behavior_profile_scan: '员工行为画像巡检',
|
|
department_expense_baseline_accumulate: '部门费用基线沉淀',
|
|
budget_overrun_precontrol_evaluate: '预算占用与超标预警',
|
|
multi_evidence_consistency_evaluate: '单据多凭证一致性评估',
|
|
travel_spatiotemporal_consistency_evaluate: '差旅时空一致性评估',
|
|
risk_clue_collect: '风险线索归集',
|
|
finance_policy_knowledge_organize: '知识制度整理',
|
|
knowledge_index_sync: '知识制度整理',
|
|
llm_wiki_sync: '知识制度整理',
|
|
llm_wiki_rule_formation: '知识制度整理'
|
|
}
|
|
|
|
const TASK_CODE_TO_TYPE = {
|
|
'task.hermes.finance_dashboard_snapshot': 'finance_dashboard_snapshot',
|
|
'task.hermes.digital_employee_reminder_scan': 'digital_employee_reminder_scan',
|
|
'task.hermes.global_risk_scan': 'global_risk_scan',
|
|
'task.hermes.employee_behavior_profile_scan': 'employee_behavior_profile_scan',
|
|
'task.hermes.department_expense_baseline_accumulate': 'department_expense_baseline_accumulate',
|
|
'task.hermes.budget_overrun_precontrol_evaluate': 'budget_overrun_precontrol_evaluate',
|
|
'task.hermes.multi_evidence_consistency_evaluate': 'multi_evidence_consistency_evaluate',
|
|
'task.hermes.travel_spatiotemporal_consistency_evaluate': 'travel_spatiotemporal_consistency_evaluate',
|
|
'task.hermes.finance_policy_knowledge_organize': 'finance_policy_knowledge_organize',
|
|
'task.hermes.risk_rule_discovery': 'risk_clue_collect'
|
|
}
|
|
|
|
function toObject(value) {
|
|
return value && typeof value === 'object' && !Array.isArray(value) ? value : {}
|
|
}
|
|
|
|
function normalizeTaskType(value) {
|
|
const normalized = String(value || '').trim()
|
|
if (!normalized) {
|
|
return ''
|
|
}
|
|
return TASK_CODE_TO_TYPE[normalized] || normalized
|
|
}
|
|
|
|
function resolveTaskTypeFromToolName(value) {
|
|
const name = String(value || '').trim()
|
|
if (name.includes('financial_risk_graph')) {
|
|
return 'global_risk_scan'
|
|
}
|
|
if (name.includes('finance_dashboard_snapshot') || name.includes('finance_dashboard')) {
|
|
return 'finance_dashboard_snapshot'
|
|
}
|
|
if (name.includes('digital_employee_reminder') || name.includes('reminder')) {
|
|
return 'digital_employee_reminder_scan'
|
|
}
|
|
if (name.includes('employee_behavior_profile')) {
|
|
return 'employee_behavior_profile_scan'
|
|
}
|
|
if (name.includes('finance_policy_knowledge')) {
|
|
return 'finance_policy_knowledge_organize'
|
|
}
|
|
if (name.includes('risk_clue')) {
|
|
return 'risk_clue_collect'
|
|
}
|
|
return ''
|
|
}
|
|
|
|
export function formatWorkRecordDateTime(value) {
|
|
if (!value) {
|
|
return '未结束'
|
|
}
|
|
|
|
const date = new Date(value)
|
|
if (Number.isNaN(date.getTime())) {
|
|
return String(value)
|
|
}
|
|
|
|
return date.toLocaleString('zh-CN', { hour12: false })
|
|
}
|
|
|
|
export function formatWorkRecordSummary(summary) {
|
|
const text = String(summary || '').trim()
|
|
if (!text) {
|
|
return '暂无摘要。'
|
|
}
|
|
if (text.length <= 64) {
|
|
return text
|
|
}
|
|
return `${text.slice(0, 64)}...`
|
|
}
|
|
|
|
export function resolveWorkRecordSourceLabel(source) {
|
|
return SOURCE_LABELS[source] || source || '未标记'
|
|
}
|
|
|
|
export function resolveWorkRecordTaskType(run) {
|
|
const routeJson = toObject(run?.route_json)
|
|
const routeCandidates = [
|
|
routeJson.job_type,
|
|
routeJson.task_type,
|
|
routeJson.report_type,
|
|
routeJson.task_code
|
|
].map(normalizeTaskType)
|
|
|
|
for (const candidate of routeCandidates) {
|
|
if (candidate) {
|
|
return candidate
|
|
}
|
|
}
|
|
|
|
for (const toolCall of run?.tool_calls || []) {
|
|
const requestJson = toObject(toolCall?.request_json)
|
|
const responseJson = toObject(toolCall?.response_json)
|
|
const candidates = [
|
|
requestJson.task_type,
|
|
requestJson.job_type,
|
|
responseJson.report_type,
|
|
responseJson.task_type,
|
|
responseJson.job_type,
|
|
resolveTaskTypeFromToolName(toolCall?.tool_name)
|
|
].map(normalizeTaskType)
|
|
|
|
const matched = candidates.find(Boolean)
|
|
if (matched) {
|
|
return matched
|
|
}
|
|
}
|
|
|
|
return ''
|
|
}
|
|
|
|
export function isVisibleDigitalEmployeeWorkRecord(run) {
|
|
const taskType = resolveWorkRecordTaskType(run)
|
|
return VISIBLE_DIGITAL_EMPLOYEE_WORK_TASK_TYPES.has(taskType)
|
|
}
|
|
|
|
function resolveWorkRecordDayKey(run) {
|
|
const date = new Date(run?.started_at || run?.finished_at || '')
|
|
if (Number.isNaN(date.getTime())) {
|
|
return 'unknown'
|
|
}
|
|
return date.toISOString().slice(0, 10)
|
|
}
|
|
|
|
export function compactDigitalEmployeeWorkRecords(items = []) {
|
|
const rows = []
|
|
const compactedKeys = new Set()
|
|
|
|
for (const run of items) {
|
|
const taskType = resolveWorkRecordTaskType(run)
|
|
if (!VISIBLE_DIGITAL_EMPLOYEE_WORK_TASK_TYPES.has(taskType)) {
|
|
continue
|
|
}
|
|
|
|
if (DAILY_COMPACT_TASK_TYPES.has(taskType)) {
|
|
const key = `${taskType}:${resolveWorkRecordDayKey(run)}`
|
|
if (compactedKeys.has(key)) {
|
|
continue
|
|
}
|
|
compactedKeys.add(key)
|
|
}
|
|
|
|
rows.push(run)
|
|
}
|
|
|
|
return rows
|
|
}
|
|
|
|
export function resolveWorkRecordTaskLabel(run) {
|
|
const taskType = resolveWorkRecordTaskType(run)
|
|
return TASK_TYPE_LABELS[taskType] || ''
|
|
}
|
|
|
|
export function resolveWorkRecordProductKind(run) {
|
|
const taskType = resolveWorkRecordTaskType(run)
|
|
if (taskType === 'finance_dashboard_snapshot') {
|
|
return 'finance_snapshot'
|
|
}
|
|
if (taskType === 'digital_employee_reminder_scan') {
|
|
return 'reminder_scan'
|
|
}
|
|
if (taskType === 'global_risk_scan') {
|
|
return 'risk_graph'
|
|
}
|
|
if (taskType === 'employee_behavior_profile_scan') {
|
|
return 'employee_profile'
|
|
}
|
|
if (taskType === 'risk_clue_collect') {
|
|
return 'risk_clue'
|
|
}
|
|
if (KNOWLEDGE_JOB_TYPES.has(taskType)) {
|
|
return 'knowledge'
|
|
}
|
|
return ''
|
|
}
|
|
|
|
export function extractWorkRecordToolSummary(run) {
|
|
const taskType = resolveWorkRecordTaskType(run)
|
|
const toolCalls = Array.isArray(run?.tool_calls) ? run.tool_calls : []
|
|
const matchedCall = toolCalls.find((toolCall) => {
|
|
const requestJson = toObject(toolCall?.request_json)
|
|
const responseJson = toObject(toolCall?.response_json)
|
|
const candidates = [
|
|
requestJson.task_type,
|
|
requestJson.job_type,
|
|
responseJson.report_type,
|
|
responseJson.task_type,
|
|
responseJson.job_type,
|
|
resolveTaskTypeFromToolName(toolCall?.tool_name)
|
|
].map(normalizeTaskType)
|
|
return candidates.includes(taskType)
|
|
}) || toolCalls[0]
|
|
|
|
const responseJson = toObject(matchedCall?.response_json)
|
|
const nestedSummary = toObject(responseJson.summary)
|
|
return Object.keys(nestedSummary).length ? nestedSummary : responseJson
|
|
}
|
|
|
|
export function resolveWorkRecordModuleLabel(run) {
|
|
const routeJson = run?.route_json || {}
|
|
const taskLabel = resolveWorkRecordTaskLabel(run)
|
|
if (taskLabel) {
|
|
return taskLabel
|
|
}
|
|
if (KNOWLEDGE_JOB_TYPES.has(routeJson.job_type)) {
|
|
return '知识制度整理'
|
|
}
|
|
if (routeJson.selected_agent) {
|
|
return '数字员工'
|
|
}
|
|
if (routeJson.folder) {
|
|
return String(routeJson.folder)
|
|
}
|
|
return resolveWorkRecordSourceLabel(run?.source)
|
|
}
|
|
|
|
export function resolveWorkRecordTitle(run) {
|
|
const routeJson = run?.route_json || {}
|
|
const taskLabel = resolveWorkRecordTaskLabel(run)
|
|
if (taskLabel) {
|
|
const suffix = String(routeJson.task_name || routeJson.folder || '本次运行').trim()
|
|
return suffix && suffix !== taskLabel ? `${taskLabel} · ${suffix}` : taskLabel
|
|
}
|
|
if (KNOWLEDGE_JOB_TYPES.has(routeJson.job_type)) {
|
|
return `知识制度整理 · ${routeJson.folder || '未指定目录'}`
|
|
}
|
|
return `数字员工调用 · ${resolveWorkRecordModuleLabel(run)}`
|
|
}
|
|
|
|
export function resolveWorkRecordStatusLabel(run) {
|
|
return resolveAgentRunStatus(run).label
|
|
}
|
|
|
|
export function resolveWorkRecordStatusTone(run) {
|
|
return resolveAgentRunStatus(run).tone
|
|
}
|
|
|
|
export function resolveWorkRecordStatusNote(run) {
|
|
const statusInfo = resolveAgentRunStatus(run)
|
|
if (statusInfo.note) {
|
|
return statusInfo.note
|
|
}
|
|
|
|
const heartbeat = resolveAgentRunHeartbeat(run)
|
|
if (heartbeat.at !== null) {
|
|
return `最后心跳 ${formatWorkRecordDateTime(heartbeat.at)}`
|
|
}
|
|
|
|
return '暂无额外状态'
|
|
}
|
|
|
|
export function resolveWorkRecordSummaryMeta(run) {
|
|
const statusInfo = resolveAgentRunStatus(run)
|
|
const progressText = formatAgentRunProgress(run)
|
|
const elapsedLabel = run?.status === 'running' ? '已运行' : '耗时'
|
|
const elapsedText = formatAgentRunElapsed(run)
|
|
const parts = [`阶段 ${statusInfo.phaseLabel}`]
|
|
|
|
if (progressText) {
|
|
parts.push(progressText)
|
|
}
|
|
if (elapsedText !== '—') {
|
|
parts.push(`${elapsedLabel} ${elapsedText}`)
|
|
}
|
|
|
|
return parts.join(' · ')
|
|
}
|