101 lines
3.2 KiB
Rust
101 lines
3.2 KiB
Rust
use crate::*;
|
|
|
|
/// AI 任务事件类型。
|
|
///
|
|
/// 事件表用于给订阅端和 BFF 增量消费状态变化;正式任务真相仍以
|
|
/// `ai_task`、`ai_task_stage`、`ai_text_chunk` 和 `ai_result_reference` 为准。
|
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, SpacetimeType)]
|
|
pub enum AiTaskEventKind {
|
|
TaskCreated,
|
|
TaskStatusChanged,
|
|
StageStarted,
|
|
StageCompleted,
|
|
TextChunkAppended,
|
|
ResultReferenceAttached,
|
|
}
|
|
|
|
#[spacetimedb::table(
|
|
accessor = ai_task_event,
|
|
public,
|
|
event,
|
|
index(accessor = by_ai_task_event_task_id, btree(columns = [task_id])),
|
|
index(accessor = by_ai_task_event_owner_user_id, btree(columns = [owner_user_id]))
|
|
)]
|
|
pub struct AiTaskEvent {
|
|
#[primary_key]
|
|
pub(crate) event_id: String,
|
|
pub(crate) task_id: String,
|
|
pub(crate) owner_user_id: String,
|
|
pub(crate) event_kind: AiTaskEventKind,
|
|
pub(crate) task_status: Option<AiTaskStatus>,
|
|
pub(crate) stage_kind: Option<AiTaskStageKind>,
|
|
pub(crate) text_chunk_row_id: Option<String>,
|
|
pub(crate) result_reference_row_id: Option<String>,
|
|
pub(crate) occurred_at: Timestamp,
|
|
}
|
|
|
|
pub(crate) fn emit_ai_task_event(
|
|
ctx: &ReducerContext,
|
|
task: &AiTaskSnapshot,
|
|
event_kind: AiTaskEventKind,
|
|
stage_kind: Option<AiTaskStageKind>,
|
|
text_chunk_row_id: Option<String>,
|
|
result_reference_row_id: Option<String>,
|
|
occurred_at_micros: i64,
|
|
) {
|
|
let suffix = match event_kind {
|
|
AiTaskEventKind::TaskCreated => "created".to_string(),
|
|
AiTaskEventKind::TaskStatusChanged => format!("status_{}", task.status.as_event_slug()),
|
|
AiTaskEventKind::StageStarted => {
|
|
format!("stage_started_{}", stage_kind_slug(stage_kind))
|
|
}
|
|
AiTaskEventKind::StageCompleted => {
|
|
format!("stage_completed_{}", stage_kind_slug(stage_kind))
|
|
}
|
|
AiTaskEventKind::TextChunkAppended => {
|
|
format!(
|
|
"chunk_{}",
|
|
text_chunk_row_id.as_deref().unwrap_or("unknown")
|
|
)
|
|
}
|
|
AiTaskEventKind::ResultReferenceAttached => {
|
|
format!(
|
|
"result_{}",
|
|
result_reference_row_id.as_deref().unwrap_or("unknown")
|
|
)
|
|
}
|
|
};
|
|
|
|
ctx.db.ai_task_event().insert(AiTaskEvent {
|
|
event_id: format!("aievt_{}_{}_{}", task.task_id, occurred_at_micros, suffix),
|
|
task_id: task.task_id.clone(),
|
|
owner_user_id: task.owner_user_id.clone(),
|
|
event_kind,
|
|
task_status: Some(task.status),
|
|
stage_kind,
|
|
text_chunk_row_id,
|
|
result_reference_row_id,
|
|
occurred_at: Timestamp::from_micros_since_unix_epoch(occurred_at_micros),
|
|
});
|
|
}
|
|
|
|
fn stage_kind_slug(stage_kind: Option<AiTaskStageKind>) -> &'static str {
|
|
stage_kind.map(AiTaskStageKind::as_str).unwrap_or("unknown")
|
|
}
|
|
|
|
trait AiTaskStatusEventSlug {
|
|
fn as_event_slug(self) -> &'static str;
|
|
}
|
|
|
|
impl AiTaskStatusEventSlug for AiTaskStatus {
|
|
fn as_event_slug(self) -> &'static str {
|
|
match self {
|
|
Self::Pending => "pending",
|
|
Self::Running => "running",
|
|
Self::Completed => "completed",
|
|
Self::Failed => "failed",
|
|
Self::Cancelled => "cancelled",
|
|
}
|
|
}
|
|
}
|