Files
X-Financial/web/src/views/scripts/travelReimbursementFlowTiming.js

192 lines
5.5 KiB
JavaScript
Raw Normal View History

export const FLOW_RUN_DETAIL_REFRESH_TIMEOUT_MS = 3000
const FLOW_DURATION_MS_FIELDS = [
'duration_ms',
'elapsed_ms',
'latency_ms',
'total_duration_ms',
'execution_time_ms'
]
const FLOW_DURATION_SECOND_FIELDS = [
'duration_seconds',
'elapsed_seconds',
'latency_seconds',
'execution_time_seconds'
]
const FLOW_DURATION_AUTO_FIELDS = ['duration', 'elapsed', 'latency', 'execution_time']
const FLOW_STARTED_AT_FIELDS = ['started_at', 'start_time', 'created_at', 'queued_at']
const FLOW_FINISHED_AT_FIELDS = ['finished_at', 'completed_at', 'ended_at', 'end_time', 'updated_at']
export function formatFlowDuration(ms) {
if (ms === null || ms === undefined || ms === '') {
return '--'
}
const numericValue = Number(ms)
if (!Number.isFinite(numericValue) || numericValue <= 0) {
return '--'
}
if (numericValue < 1000) {
return `${Math.max(0.1, numericValue / 1000).toFixed(1)}s`
}
if (numericValue < 10000) {
return `${(numericValue / 1000).toFixed(1)}s`
}
return `${Math.round(numericValue / 1000)}s`
}
function parseFlowTimestamp(value) {
if (value === null || value === undefined || value === '') {
return 0
}
if (typeof value === 'number' && Number.isFinite(value)) {
return value > 0 && value < 10000000000 ? Math.round(value * 1000) : Math.round(value)
}
const timestamp = new Date(value).getTime()
return Number.isFinite(timestamp) ? timestamp : 0
}
function normalizeDurationValue(value, unit = 'ms') {
if (value === null || value === undefined || value === '') {
return null
}
let numericValue = Number(value)
let normalizedUnit = unit
if (typeof value === 'string') {
const text = value.trim()
const match = text.match(/^(\d+(?:\.\d+)?)\s*(ms|毫秒|s|秒)?$/i)
if (match) {
numericValue = Number(match[1])
if (match[2]) {
normalizedUnit = ['s', '秒'].includes(match[2].toLowerCase()) ? 'seconds' : 'ms'
}
}
}
if (!Number.isFinite(numericValue) || numericValue <= 0) {
return null
}
if (normalizedUnit === 'seconds') {
return Math.round(numericValue * 1000)
}
if (normalizedUnit === 'auto') {
return Math.round(numericValue <= 300 ? numericValue * 1000 : numericValue)
}
return Math.round(numericValue)
}
function readFirstDurationField(source, fields, unit) {
if (!source || typeof source !== 'object') {
return null
}
for (const field of fields) {
if (!Object.prototype.hasOwnProperty.call(source, field)) {
continue
}
const durationMs = normalizeDurationValue(source[field], unit)
if (durationMs) {
return durationMs
}
}
return null
}
function resolveDurationFromFields(source) {
return (
readFirstDurationField(source, FLOW_DURATION_MS_FIELDS, 'ms')
|| readFirstDurationField(source, FLOW_DURATION_SECOND_FIELDS, 'seconds')
|| readFirstDurationField(source, FLOW_DURATION_AUTO_FIELDS, 'auto')
)
}
function readFirstTimestampField(source, fields) {
if (!source || typeof source !== 'object') {
return 0
}
for (const field of fields) {
const timestamp = parseFlowTimestamp(source[field])
if (timestamp) {
return timestamp
}
}
return 0
}
export function resolveStartedTimestamp(source) {
return readFirstTimestampField(source, FLOW_STARTED_AT_FIELDS)
}
export function resolveFinishedTimestamp(source) {
return readFirstTimestampField(source, FLOW_FINISHED_AT_FIELDS)
}
function resolveTimeRangeDurationMs(source) {
const startedAt = resolveStartedTimestamp(source)
const finishedAt = resolveFinishedTimestamp(source)
return finishedAt > startedAt ? finishedAt - startedAt : null
}
export function resolveSemanticPhaseDurations(run) {
const runStart = resolveStartedTimestamp(run)
const toolCalls = Array.isArray(run?.tool_calls) ? run.tool_calls : []
const firstToolStartedAt = toolCalls
.map((item) => resolveStartedTimestamp(item))
.filter((value) => value > 0)
.sort((left, right) => left - right)[0] || 0
const runFinishedAt = resolveFinishedTimestamp(run)
const semanticFinishedAt = firstToolStartedAt || runFinishedAt
if (!runStart || !semanticFinishedAt || semanticFinishedAt <= runStart) {
return { intentMs: null, extractionMs: null }
}
const totalMs = semanticFinishedAt - runStart
const intentMs = Math.max(120, Math.round(totalMs * 0.35))
const extractionMs = Math.max(160, totalMs - intentMs)
return {
intentMs,
extractionMs
}
}
export function resolveToolCallDurationMs(toolCall, index, toolCalls, run) {
const response = toolCall?.response_json && typeof toolCall.response_json === 'object'
? toolCall.response_json
: {}
const explicitDuration = resolveDurationFromFields(toolCall)
|| resolveTimeRangeDurationMs(toolCall)
|| resolveDurationFromFields(response)
|| resolveTimeRangeDurationMs(response)
if (explicitDuration) {
return explicitDuration
}
const startedAt = resolveStartedTimestamp(toolCall)
if (!startedAt) {
return null
}
const nextStartedAt = resolveStartedTimestamp(toolCalls[index + 1])
const runFinishedAt = resolveFinishedTimestamp(run)
const finishedAt = nextStartedAt > startedAt ? nextStartedAt : (runFinishedAt > startedAt ? runFinishedAt : 0)
if (!finishedAt || finishedAt <= startedAt) {
return null
}
return finishedAt - startedAt
}
export function summarizeVisibleToolText(value) {
const text = String(value || '')
.replace(/\|[^\n]*\|/g, '')
.replace(/\*\*/g, '')
.split('\n')
.map((line) => line.trim())
.find(Boolean) || ''
if (!text) {
return ''
}
return text.length > 80 ? `${text.slice(0, 80)}...` : text
}