forked from BasedHardware/omi
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathChatFirstAnalytics.swift
More file actions
189 lines (173 loc) · 5.48 KB
/
Copy pathChatFirstAnalytics.swift
File metadata and controls
189 lines (173 loc) · 5.48 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
import Foundation
/// Closed, content-free analytics schema for the universal Chat-first
/// experience. The associated values are all finite enums or bounded numeric
/// buckets: callers cannot put titles, entity IDs, question answers, URLs,
/// prompts, transcripts, or raw errors into a payload.
enum ChatFirstAnalyticsEvent: Equatable, Sendable {
enum Route: String, CaseIterable, Sendable {
case chat
case conversations
case tasks
case goals
case memories
case more
case dailyRecap = "daily-recap"
}
enum RouteOrigin: String, CaseIterable, Sendable {
case shellLaunch = "shell_launch"
case sidebar
case chatDeeplink = "chat_deeplink"
case more
}
enum RichBlockKind: String, CaseIterable, Sendable {
case taskCard = "task_card"
case goalLink = "goal_link"
case captureLink = "capture_link"
case conversationLink = "conversation_link"
case memoryLink = "memory_link"
case questionCard = "question_card"
}
enum RichBlockOutcome: String, CaseIterable, Sendable {
case rendered
case acted
case stalePlaceholder = "stale_placeholder"
case rejected
}
enum RichBlockAction: String, CaseIterable, Sendable {
case none
case open
case toggle
case select
case copyLink = "copy_link"
}
enum QuestionLifecycle: String, CaseIterable, Sendable {
case shown
case answered
case retiredUnseen = "retired_unseen"
case deferred
}
enum TaskMutationLifecycle: String, CaseIterable, Sendable {
case attempt
case success
case rollback
}
enum TaskMutation: String, CaseIterable, Sendable {
case create
case completion
case rename
case schedule
}
enum CapabilityOutcome: String, CaseIterable, Sendable {
case enabled
case disabled
case unavailable
case projectionRejected = "projection_rejected"
}
enum CapabilityGenerationBucket: String, CaseIterable, Sendable {
case none
case zeroToNine = "0_9"
case tenToNinetyNine = "10_99"
case hundredPlus = "100_plus"
static func bucket(for generation: Int?) -> Self {
guard let generation, generation >= 0 else { return .none }
switch generation {
case 0...9: return .zeroToNine
case 10...99: return .tenToNinetyNine
default: return .hundredPlus
}
}
}
enum CapabilityErrorClass: String, CaseIterable, Sendable {
case none
case unavailable
case ownerChanged = "owner_changed"
case invalidControl = "invalid_control"
case projectionRejected = "projection_rejected"
}
case routeEntered(route: Route, origin: RouteOrigin)
case richBlock(kind: RichBlockKind, outcome: RichBlockOutcome, action: RichBlockAction)
case question(lifecycle: QuestionLifecycle)
case taskMutation(lifecycle: TaskMutationLifecycle, mutation: TaskMutation)
case capabilityResolution(
outcome: CapabilityOutcome,
generationBucket: CapabilityGenerationBucket,
errorClass: CapabilityErrorClass
)
}
/// The only map accepted by `AnalyticsManager` for Chat-first events. String
/// keys are private to this mapper and values come exclusively from the enums
/// above, so a UI cannot accidentally widen the event contract with free text.
struct ChatFirstAnalyticsPayload {
let eventName: String
let properties: [String: String]
}
extension ChatFirstAnalyticsEvent {
var analyticsPayload: ChatFirstAnalyticsPayload {
switch self {
case .routeEntered(let route, let origin):
return ChatFirstAnalyticsPayload(
eventName: "chat_first_route_entered",
properties: [
"route": route.rawValue,
"origin": origin.rawValue,
"telemetry_schema_version": "1",
]
)
case .richBlock(let kind, let outcome, let action):
return ChatFirstAnalyticsPayload(
eventName: "chat_first_rich_block",
properties: [
"kind": kind.rawValue,
"outcome": outcome.rawValue,
"action": action.rawValue,
"telemetry_schema_version": "1",
]
)
case .question(let lifecycle):
return ChatFirstAnalyticsPayload(
eventName: "chat_first_question",
properties: [
"lifecycle": lifecycle.rawValue,
"telemetry_schema_version": "1",
]
)
case .taskMutation(let lifecycle, let mutation):
return ChatFirstAnalyticsPayload(
eventName: "chat_first_task_mutation",
properties: [
"lifecycle": lifecycle.rawValue,
"mutation": mutation.rawValue,
"telemetry_schema_version": "1",
]
)
case .capabilityResolution(let outcome, let generationBucket, let errorClass):
return ChatFirstAnalyticsPayload(
eventName: "chat_first_capability_resolution",
properties: [
"outcome": outcome.rawValue,
"generation_bucket": generationBucket.rawValue,
"error_class": errorClass.rawValue,
"telemetry_schema_version": "1",
]
)
}
}
}
/// Converts the existing owner-safe task result into the only terminal shape
/// Chat-first may record. It deliberately collapses transport and owner detail.
enum ChatFirstTaskMutationTelemetry {
static func terminalLifecycle(
for outcome: TasksStore.TaskUpdateOutcome
) -> ChatFirstAnalyticsEvent.TaskMutationLifecycle {
switch outcome {
case .updated:
return .success
case .preservedLocalAfterRemoteFailure,
.rolledBackAfterRemoteFailure,
.rollbackFailed,
.localWriteFailed,
.ownerChanged:
return .rollback
}
}
}