Files
Genarrative/server-rs/crates/shared-contracts/src/agc_analytics.rs
T
lhk229 1e186369c9
Project CI / AI game creator shell Rust crates (push) Successful in 1m24s
Project CI / AI game creator shell Rust smoke (push) Successful in 1m56s
Project CI / AI game creator shell Rust lane 1/2 (push) Has been cancelled
Project CI / Frontend tests (push) Has been cancelled
Project CI / Backend tests (push) Has been cancelled
Project CI / Repository checks (push) Has been cancelled
Project CI / AI game creator shell web tests (push) Has been cancelled
Project CI / AI game creator shell Rust lane 2/2 (push) Has been cancelled
Project CI / Native shell tests (push) Has been cancelled
客户端埋点设置 (#446)
Reviewed-on: https://git.genarrative.world/git/GenarrativeAI/Genarrative/pulls/446
Co-authored-by: Linghong <ink29535@proton.me>
Co-committed-by: Linghong <ink29535@proton.me>
2026-09-23 00:09:12 +08:00

283 lines
7.1 KiB
Rust

//! 客户端埋点上传合同;字段与本地 schema v1 保持一致。
use serde::{Deserialize, Deserializer, Serialize};
use serde_json::Value;
fn required_nullable<'de, D, T>(deserializer: D) -> Result<Option<T>, D::Error>
where
D: Deserializer<'de>,
T: Deserialize<'de>,
{
Option::<T>::deserialize(deserializer)
}
macro_rules! values {
($name:ident { $($variant:ident),+ $(,)? }) => {
#[derive(Clone, Copy, Debug, Deserialize, Serialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum $name { $($variant),+ }
};
}
values!(Source {
Editor,
Direct,
DesignAgent,
AssetCanvas,
ResourceEditor,
UiEditor,
Manual,
System
});
values!(Status { Success, Failed });
values!(EntrySource {
DirectLaunch,
ProjectAssociation,
AppRestore
});
values!(SessionEndReason {
UserExit,
AppRestart
});
values!(FocusReason {
InitialFocus,
WindowFocus,
Restore,
AccountChange
});
values!(BlurReason {
WindowBlur,
Minimized,
AppExit,
SystemSuspend,
AccountChange
});
values!(CreationSource {
HomeGame,
HomeDesign,
Template,
SelectedDirectory
});
values!(OpenSource {
Create,
Picker,
Recent,
AppRestore,
ProjectAssociation
});
values!(AgentType {
GameAgent,
DesignAgent
});
values!(RunSource {
UserSubmit,
UserContinue,
Clarification,
Approval,
UserRetry
});
values!(RunEndReason {
Finished,
WaitingForUser,
WaitingForApproval,
Failed
});
values!(ErrorCode {
ProviderAuthFailed,
ProviderRateLimited,
ProviderUnavailable,
ProviderTimeout,
ProviderInvalidResponse,
LocalIoFailed,
RuntimeFailed,
RuntimeErrorUnclassified
});
values!(RevisionSource {
Agent,
AssetCanvas,
ResourceEditor,
UiEditor,
ManualEdit,
SystemProjection
});
values!(ChangeKind {
Code,
Asset,
Ui,
DesignDocument,
Mixed
});
values!(PreviewSource {
User,
Agent,
AutoRestore
});
values!(SaveSource {
Manual,
Auto,
Checkpoint
});
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(deny_unknown_fields)]
pub struct SessionStart {
pub entry_source: EntrySource,
#[serde(deserialize_with = "required_nullable")]
pub first_project_id: Option<String>,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(deny_unknown_fields)]
pub struct SessionEnd {
pub end_reason: SessionEndReason,
#[serde(deserialize_with = "required_nullable")]
pub session_duration_ms: Option<u64>,
#[serde(deserialize_with = "required_nullable")]
pub last_project_id: Option<String>,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(deny_unknown_fields)]
pub struct FocusStart {
pub focus_interval_id: String,
pub focus_reason: FocusReason,
#[serde(deserialize_with = "required_nullable")]
pub active_project_id: Option<String>,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(deny_unknown_fields)]
pub struct FocusEnd {
pub focus_interval_id: String,
pub blur_reason: BlurReason,
#[serde(deserialize_with = "required_nullable")]
pub focus_duration_ms: Option<u64>,
#[serde(deserialize_with = "required_nullable")]
pub active_project_id: Option<String>,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(deny_unknown_fields)]
pub struct ProjectCreated {
pub creation_source: CreationSource,
#[serde(skip_serializing_if = "Option::is_none")]
pub project_template_id: Option<String>,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(deny_unknown_fields)]
pub struct ProjectOpened {
pub open_source: OpenSource,
#[serde(deserialize_with = "required_nullable")]
pub is_first_open: Option<bool>,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(deny_unknown_fields)]
pub struct EmptyProperties {}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(deny_unknown_fields)]
pub struct RunFinished {
pub agent_type: AgentType,
pub run_source: RunSource,
#[serde(deserialize_with = "required_nullable")]
pub duration_ms: Option<u64>,
pub retry_index: u64,
#[serde(deserialize_with = "required_nullable")]
pub output_change_detected: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub revision_id: Option<String>,
pub end_reason: RunEndReason,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(deny_unknown_fields)]
pub struct RevisionCreated {
pub revision_id: String,
pub revision_source: RevisionSource,
pub change_kind: ChangeKind,
#[serde(skip_serializing_if = "Option::is_none")]
pub files_changed_count: Option<u64>,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(deny_unknown_fields)]
pub struct PreviewReady {
pub preview_source: PreviewSource,
pub preview_version: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub ready_duration_ms: Option<u64>,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(deny_unknown_fields)]
pub struct ProjectSaved {
pub save_source: SaveSource,
#[serde(skip_serializing_if = "Option::is_none")]
pub revision_id: Option<String>,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(
tag = "event_name",
content = "properties",
rename_all = "snake_case",
deny_unknown_fields
)]
pub enum EventData {
EditorSessionStart(SessionStart),
EditorSessionEnd(SessionEnd),
EditorFocusStart(FocusStart),
EditorFocusEnd(FocusEnd),
ProjectCreateSuccess(ProjectCreated),
ProjectOpen(ProjectOpened),
CreativeTaskSubmit(EmptyProperties),
AgentRunCompleted(RunFinished),
AgentRunFailed(RunFinished),
ProjectRevisionCreated(RevisionCreated),
PreviewReady(PreviewReady),
ProjectSave(ProjectSaved),
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(deny_unknown_fields)]
pub struct Event {
pub schema_version: u32,
pub event_id: String,
pub event_name: String,
pub event_time: String,
#[serde(deserialize_with = "required_nullable")]
pub user_id: Option<String>,
pub editor_session_id: String,
#[serde(deserialize_with = "required_nullable")]
pub project_id: Option<String>,
#[serde(deserialize_with = "required_nullable")]
pub creative_task_id: Option<String>,
#[serde(deserialize_with = "required_nullable")]
pub agent_run_id: Option<String>,
#[serde(deserialize_with = "required_nullable")]
pub agent_turn_id: Option<String>,
#[serde(deserialize_with = "required_nullable")]
pub status: Option<Status>,
#[serde(deserialize_with = "required_nullable")]
pub error_code: Option<ErrorCode>,
pub source: Source,
pub client_version: String,
pub properties: Value,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(deny_unknown_fields)]
pub struct AgcAnalyticsBatch {
pub schema_version: u32,
pub batch_id: String,
pub destination_origin: String,
pub user_id: String,
pub events: Vec<Event>,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(deny_unknown_fields)]
pub struct AgcAnalyticsAcknowledgement {
pub acknowledged_batch_ids: Vec<String>,
pub event_count: u32,
}