chore: 初始版本提交 - 简化架构 + 轮询改造
- 移除 Motia Streams 实时通信,改用 3 秒轮询 - 简化前端代码,移除冗余组件 - 简化后端架构,准备 FastAPI 重构 - 更新 pixi.toml 环境配置 - 保留 bttoxin_digger_v5_repro 作为参考文档 Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
1
motia-backend/streams/.gitkeep
Normal file
1
motia-backend/streams/.gitkeep
Normal file
@@ -0,0 +1 @@
|
||||
# Streams directory for Motia stream definitions
|
||||
62
motia-backend/streams/taskLog.ts
Normal file
62
motia-backend/streams/taskLog.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
/**
|
||||
* TaskLog Stream Definition
|
||||
*
|
||||
* Manages log entries for each task execution.
|
||||
* Uses sessionId as groupId for per-task log isolation.
|
||||
* Retains recent N entries for state recovery.
|
||||
*
|
||||
* @see Requirements 4.2, 4.6
|
||||
*/
|
||||
|
||||
import { defineStream } from '@motia/core'
|
||||
import { z } from 'zod'
|
||||
import type { StepId } from './taskState'
|
||||
|
||||
// Log level enum
|
||||
export const LogLevelSchema = z.enum(['INFO', 'WARN', 'ERROR'])
|
||||
export type LogLevel = z.infer<typeof LogLevelSchema>
|
||||
|
||||
// Task log entry schema
|
||||
export const TaskLogEntrySchema = z.object({
|
||||
sessionId: z.string(),
|
||||
stepId: z.string(), // StepId type
|
||||
ts: z.string(), // ISO timestamp
|
||||
level: LogLevelSchema,
|
||||
message: z.string(),
|
||||
seq: z.number(), // Log sequence number for ordering
|
||||
})
|
||||
export type TaskLogEntry = z.infer<typeof TaskLogEntrySchema>
|
||||
|
||||
// Maximum number of log entries to retain per task
|
||||
export const MAX_LOG_ENTRIES = 1000
|
||||
|
||||
/**
|
||||
* Create a new log entry
|
||||
*/
|
||||
export function createLogEntry(
|
||||
sessionId: string,
|
||||
stepId: StepId,
|
||||
level: LogLevel,
|
||||
message: string,
|
||||
seq: number
|
||||
): TaskLogEntry {
|
||||
return {
|
||||
sessionId,
|
||||
stepId,
|
||||
ts: new Date().toISOString(),
|
||||
level,
|
||||
message,
|
||||
seq,
|
||||
}
|
||||
}
|
||||
|
||||
// Define the taskLog stream
|
||||
export const taskLogStream = defineStream({
|
||||
name: 'taskLog',
|
||||
schema: TaskLogEntrySchema,
|
||||
persistence: {
|
||||
enabled: true,
|
||||
// Retain recent entries for state recovery
|
||||
maxEntries: MAX_LOG_ENTRIES,
|
||||
},
|
||||
})
|
||||
60
motia-backend/streams/taskQueue.ts
Normal file
60
motia-backend/streams/taskQueue.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
/**
|
||||
* TaskQueue Stream Definition
|
||||
*
|
||||
* Manages global task queue state for concurrency control.
|
||||
* Tracks running tasks count and queued task list.
|
||||
*
|
||||
* @see Requirements 10.1, 10.4
|
||||
*/
|
||||
|
||||
import { defineStream } from '@motia/core'
|
||||
import { z } from 'zod'
|
||||
|
||||
// Maximum concurrent tasks
|
||||
export const MAX_CONCURRENT = 4
|
||||
|
||||
// Task queue state schema
|
||||
export const TaskQueueStateSchema = z.object({
|
||||
runningCount: z.number().min(0).max(MAX_CONCURRENT),
|
||||
maxConcurrent: z.number().default(MAX_CONCURRENT),
|
||||
queue: z.array(z.string()), // sessionId list in FIFO order
|
||||
})
|
||||
export type TaskQueueState = z.infer<typeof TaskQueueStateSchema>
|
||||
|
||||
/**
|
||||
* Create initial queue state
|
||||
*/
|
||||
export function createInitialQueueState(): TaskQueueState {
|
||||
return {
|
||||
runningCount: 0,
|
||||
maxConcurrent: MAX_CONCURRENT,
|
||||
queue: [],
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a new task can run immediately
|
||||
*/
|
||||
export function canRunImmediately(state: TaskQueueState): boolean {
|
||||
return state.runningCount < state.maxConcurrent
|
||||
}
|
||||
|
||||
/**
|
||||
* Get queue position for a session (1-based, null if not in queue)
|
||||
*/
|
||||
export function getQueuePosition(
|
||||
state: TaskQueueState,
|
||||
sessionId: string
|
||||
): number | null {
|
||||
const index = state.queue.indexOf(sessionId)
|
||||
return index === -1 ? null : index + 1
|
||||
}
|
||||
|
||||
// Define the taskQueue stream (global, single instance)
|
||||
export const taskQueueStream = defineStream({
|
||||
name: 'taskQueue',
|
||||
schema: TaskQueueStateSchema,
|
||||
persistence: {
|
||||
enabled: true,
|
||||
},
|
||||
})
|
||||
126
motia-backend/streams/taskState.ts
Normal file
126
motia-backend/streams/taskState.ts
Normal file
@@ -0,0 +1,126 @@
|
||||
/**
|
||||
* TaskState Stream Definition
|
||||
*
|
||||
* Manages the state of each task, including overall status and step statuses.
|
||||
* Uses sessionId as groupId for per-task state isolation.
|
||||
*
|
||||
* @see Requirements 5.2, 2.3
|
||||
*/
|
||||
|
||||
import { defineStream } from '@motia/core'
|
||||
import { z } from 'zod'
|
||||
|
||||
// Step status enum
|
||||
export const StepStatusSchema = z.enum([
|
||||
'PENDING',
|
||||
'RUNNING',
|
||||
'SUCCESS',
|
||||
'FAILED',
|
||||
'SKIPPED',
|
||||
])
|
||||
export type StepStatus = z.infer<typeof StepStatusSchema>
|
||||
|
||||
// Overall task status enum
|
||||
export const TaskOverallStatusSchema = z.enum([
|
||||
'QUEUED',
|
||||
'PENDING',
|
||||
'RUNNING',
|
||||
'SUCCESS',
|
||||
'FAILED',
|
||||
'EXPIRED',
|
||||
])
|
||||
export type TaskOverallStatus = z.infer<typeof TaskOverallStatusSchema>
|
||||
|
||||
// Step ID enum
|
||||
export const StepIdSchema = z.enum([
|
||||
'upload',
|
||||
'digger',
|
||||
'shotter',
|
||||
'plot',
|
||||
'done',
|
||||
])
|
||||
export type StepId = z.infer<typeof StepIdSchema>
|
||||
|
||||
// Task step schema
|
||||
export const TaskStepSchema = z.object({
|
||||
stepId: StepIdSchema,
|
||||
name: z.string(),
|
||||
status: StepStatusSchema,
|
||||
startAt: z.string().nullable(),
|
||||
endAt: z.string().nullable(),
|
||||
durationMs: z.number().nullable(),
|
||||
summary: z.string().nullable(),
|
||||
error: z.string().nullable(),
|
||||
})
|
||||
export type TaskStep = z.infer<typeof TaskStepSchema>
|
||||
|
||||
// Task state schema
|
||||
export const TaskStateSchema = z.object({
|
||||
sessionId: z.string(),
|
||||
status: TaskOverallStatusSchema,
|
||||
queuePosition: z.number().nullable(),
|
||||
createdAt: z.string(),
|
||||
startedAt: z.string().nullable(),
|
||||
completedAt: z.string().nullable(),
|
||||
expiresAt: z.string(),
|
||||
error: z.string().nullable(),
|
||||
steps: z.array(TaskStepSchema),
|
||||
resultBundle: z.string().nullable(),
|
||||
})
|
||||
export type TaskState = z.infer<typeof TaskStateSchema>
|
||||
|
||||
|
||||
// Default step names (English)
|
||||
export const DEFAULT_STEP_NAMES: Record<StepId, string> = {
|
||||
upload: 'File Upload',
|
||||
digger: 'BtToxin Digger',
|
||||
shotter: 'Shotter Scoring',
|
||||
plot: 'Generate Report',
|
||||
done: 'Package Complete',
|
||||
}
|
||||
|
||||
// Step order for validation
|
||||
export const STEP_ORDER: StepId[] = ['upload', 'digger', 'shotter', 'plot', 'done']
|
||||
|
||||
/**
|
||||
* Create initial task state with all steps set to PENDING
|
||||
*/
|
||||
export function createInitialTaskState(
|
||||
sessionId: string,
|
||||
queuePosition: number | null = null
|
||||
): TaskState {
|
||||
const now = new Date()
|
||||
const expiresAt = new Date(now.getTime() + 30 * 24 * 60 * 60 * 1000) // 30 days
|
||||
|
||||
return {
|
||||
sessionId,
|
||||
status: queuePosition !== null ? 'QUEUED' : 'PENDING',
|
||||
queuePosition,
|
||||
createdAt: now.toISOString(),
|
||||
startedAt: null,
|
||||
completedAt: null,
|
||||
expiresAt: expiresAt.toISOString(),
|
||||
error: null,
|
||||
steps: STEP_ORDER.map((stepId) => ({
|
||||
stepId,
|
||||
name: DEFAULT_STEP_NAMES[stepId],
|
||||
status: 'PENDING' as StepStatus,
|
||||
startAt: null,
|
||||
endAt: null,
|
||||
durationMs: null,
|
||||
summary: null,
|
||||
error: null,
|
||||
})),
|
||||
resultBundle: null,
|
||||
}
|
||||
}
|
||||
|
||||
// Define the taskState stream
|
||||
export const taskStateStream = defineStream({
|
||||
name: 'taskState',
|
||||
schema: TaskStateSchema,
|
||||
persistence: {
|
||||
enabled: true,
|
||||
// State is persisted per sessionId (groupId)
|
||||
},
|
||||
})
|
||||
Reference in New Issue
Block a user