Files
TrueGrowth/packages/drawnix/src/utils/task-utils.ts
jiam 5119ac0ef8
Some checks failed
CI / main (push) Has been cancelled
CI / release-e2e (push) Has been cancelled
Sync latest TrueGrowth updates
2026-07-08 02:03:18 +08:00

209 lines
5.6 KiB
TypeScript

/**
* Task Utility Functions
*
* Provides helper functions for task management including ID generation,
* status checking, timeout detection, and duration formatting.
*/
import { Task, TaskStatus, TaskType } from '../types/task.types';
import { TASK_TIMEOUT } from '../constants/TASK_CONSTANTS';
import { isAsyncImageModel } from '../constants/model-config';
import { generateUUID, formatDate } from '@aitu/utils';
/**
* Generates a unique task ID using UUID v4 algorithm
*
* @returns A unique task identifier string
*
* @example
* generateTaskId() // Returns "550e8400-e29b-41d4-a716-446655440000"
*/
export function generateTaskId(): string {
return generateUUID();
}
/**
* Checks if a task is in an active state (pending or processing)
*
* @param task - The task to check
* @returns True if the task is active, false otherwise
*
* @example
* isTaskActive({ status: 'processing' }) // Returns true
* isTaskActive({ status: 'completed' }) // Returns false
*/
export function isTaskActive(task: Task): boolean {
return task.status === TaskStatus.PENDING ||
task.status === TaskStatus.PROCESSING;
}
function hasAsyncImageInvocationRoute(
task: Pick<Task, 'invocationRoute'>
): boolean {
const binding = task.invocationRoute?.binding;
return (
task.invocationRoute?.operation === 'image' &&
(binding?.protocol === 'openai.async.media' ||
binding?.requestSchema === 'openai.async.image.form')
);
}
/**
* Checks whether an image task can resume polling after page reload.
*
* Dynamic provider models may not be listed in ASYNC_IMAGE_MODEL_IDS, so the
* persisted invocation binding is the source of truth once a remoteId exists.
*/
export function isResumableAsyncImageTask(
task: Pick<Task, 'type' | 'remoteId' | 'params' | 'invocationRoute'>
): boolean {
return task.type === TaskType.IMAGE && Boolean(task.remoteId);
}
/**
* Checks if a task has exceeded its timeout limit
*
* @param task - The task to check
* @returns True if the task has timed out, false otherwise
*
* @example
* const task = { type: 'image', startedAt: Date.now() - 16 * 60 * 1000 };
* isTaskTimeout(task) // Returns true (started > 15 minutes ago)
*/
export function isTaskTimeout(task: Task): boolean {
if (!task.startedAt || task.status !== TaskStatus.PROCESSING) {
return false;
}
const timeout = getTaskTimeout(task.type);
const elapsed = Date.now() - task.startedAt;
return elapsed > timeout;
}
/**
* Gets the timeout duration for a specific task type
*
* @param taskType - The type of task
* @returns Timeout duration in milliseconds
*
* @example
* getTaskTimeout('image') // Returns 900000 (15 minutes)
* getTaskTimeout('video') // Returns 1800000 (30 minutes)
*/
export function getTaskTimeout(taskType: TaskType): number {
return TASK_TIMEOUT[taskType.toUpperCase() as keyof typeof TASK_TIMEOUT] || TASK_TIMEOUT.IMAGE;
}
/**
* Formats a duration in milliseconds to a human-readable string
*
* @param ms - Duration in milliseconds
* @returns Formatted duration string
*
* @example
* formatTaskDuration(1500) // Returns "1s"
* formatTaskDuration(65000) // Returns "1m 5s"
* formatTaskDuration(3665000) // Returns "1h 1m 5s"
*/
export function formatTaskDuration(ms: number): string {
const seconds = Math.floor(ms / 1000);
const minutes = Math.floor(seconds / 60);
const hours = Math.floor(minutes / 60);
if (hours > 0) {
return `${hours}h ${minutes % 60}m ${seconds % 60}s`;
} else if (minutes > 0) {
return `${minutes}m ${seconds % 60}s`;
} else {
return `${seconds}s`;
}
}
/**
* Calculates the elapsed time since a task was created
*
* @param task - The task to calculate elapsed time for
* @returns Elapsed time in milliseconds
*/
export function getTaskElapsedTime(task: Task): number {
if (task.completedAt) {
return task.completedAt - task.createdAt;
}
return Date.now() - task.createdAt;
}
/**
* Formats a timestamp to a localized date-time string
*
* @param timestamp - Unix timestamp in milliseconds
* @returns Formatted date-time string (e.g., "2025-12-18 14:30:25")
*
* @example
* formatDateTime(Date.now()) // Returns "2025-12-18 14:30:25"
*/
export const formatDateTime = formatDate;
export interface TaskTimelineItem {
label: string;
value: string;
}
function isValidTimestamp(timestamp: number | undefined): timestamp is number {
return (
typeof timestamp === 'number' &&
Number.isFinite(timestamp) &&
timestamp > 0
);
}
/**
* Builds the labeled absolute times shown in task-center rows.
*/
export function getTaskTimelineItems(task: Task): TaskTimelineItem[] {
const items: TaskTimelineItem[] = [];
if (isValidTimestamp(task.createdAt)) {
items.push({
label: '创建',
value: formatDateTime(task.createdAt),
});
}
const completedAt = isValidTimestamp(task.completedAt)
? task.completedAt
: task.updatedAt;
if (task.status === TaskStatus.COMPLETED && isValidTimestamp(completedAt)) {
items.push({
label: '完成',
value: formatDateTime(completedAt),
});
return items;
}
if (
(task.status === TaskStatus.FAILED ||
task.status === TaskStatus.CANCELLED) &&
isValidTimestamp(task.updatedAt)
) {
items.push({
label: task.status === TaskStatus.FAILED ? '失败' : '取消',
value: formatDateTime(task.updatedAt),
});
return items;
}
if (
task.status === TaskStatus.PROCESSING &&
isValidTimestamp(task.updatedAt) &&
task.updatedAt !== task.createdAt
) {
items.push({
label: '更新',
value: formatDateTime(task.updatedAt),
});
}
return items;
}