chore: 初始版本提交 - 简化架构 + 轮询改造

- 移除 Motia Streams 实时通信,改用 3 秒轮询
- 简化前端代码,移除冗余组件
- 简化后端架构,准备 FastAPI 重构
- 更新 pixi.toml 环境配置
- 保留 bttoxin_digger_v5_repro 作为参考文档

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
zly
2026-01-13 16:50:09 +08:00
parent 4c9a7d0978
commit fe353fc0bc
134 changed files with 1237947 additions and 2518 deletions

View File

@@ -0,0 +1 @@
# Streams directory for Motia stream definitions

View 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,
},
})

View 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,
},
})

View 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)
},
})