192 lines
5.5 KiB
JavaScript
192 lines
5.5 KiB
JavaScript
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
|
|
}
|