Class: ActionAgent::Api::SessionRecordingsController

Inherits:
BaseController show all
Defined in:
app/controllers/action_agent/api/session_recordings_controller.rb

Instance Method Summary collapse

Methods inherited from ActionAgent::ApplicationController

allow_unauthenticated_access

Instance Method Details

#actionsObject

GET /api/session_recordings/:id/actions Get the action timeline for playback



77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
# File 'app/controllers/action_agent/api/session_recordings_controller.rb', line 77

def actions
  actions = @recording.recording_actions.ordered

  # Support pagination for large recordings
  if params[:after_sequence].present?
    actions = actions.where("sequence > ?", params[:after_sequence].to_i)
  end

  limit = [ params[:limit]&.to_i || 100, 500 ].min
  actions = actions.limit(limit)

  render json: {
    actions: actions.map(&:as_json_for_api),
    has_more: actions.count == limit,
    total_actions: @recording.action_count
  }
end

#complete_sessionObject

POST /api/session_recordings/:id/complete Complete a user session recording



190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
# File 'app/controllers/action_agent/api/session_recordings_controller.rb', line 190

def complete_session
  recording = SessionRecording.find(params[:id])
  return not_found unless can_manage_recording?(recording)

  unless recording.recording?
    render json: { error: "Recording already completed" }, status: :unprocessable_entity
    return
  end

  # Record the completion action
  recording.record_action!(
    action_type: "completion",
    value: params[:completion_type] || "session_end",
    metadata: {
      email_submitted: params[:email_submitted],
      success: params[:success]
    }
  )

  recording.complete!

  render json: {
    recording_id: recording.id,
    status: recording.status,
    action_count: recording.action_count,
    duration_ms: recording.duration_ms
  }
end

#destroyObject

DELETE /api/session_recordings/:id



252
253
254
255
256
257
258
259
260
261
262
263
# File 'app/controllers/action_agent/api/session_recordings_controller.rb', line 252

def destroy
  @recording = SessionRecording.find(params[:id])

  # Only allow deletion of own recordings (or any if admin)
  unless can_manage_recording?(@recording)
    render json: { error: "Not authorized" }, status: :forbidden
    return
  end

  @recording.destroy!
  render json: { message: "Recording deleted" }
end

#exportObject

POST /api/session_recordings/:id/export Export recording as VCR cassette



118
119
120
121
122
123
124
125
126
127
# File 'app/controllers/action_agent/api/session_recordings_controller.rb', line 118

def export
  format = params[:format] || "json"

  cassette = build_cassette(@recording, format)

  render json: {
    cassette: cassette,
    filename: "#{@recording.name}_recording.#{format}"
  }
end

#handoffObject

POST /api/session_recordings/:id/handoff Get handoff state to continue where agent left off



221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
# File 'app/controllers/action_agent/api/session_recordings_controller.rb', line 221

def handoff
  handoff_state = @recording.["handoff_state"]

  unless handoff_state
    render json: { error: "No handoff state available" }, status: :unprocessable_entity
    return
  end

  # Create a new recording for the user's continuation
  continuation = SessionRecording.create!(
    sandbox_session: @recording.sandbox_session,
    agent_run: @recording.agent_run,
    name: "#{@recording.name}_continuation",
    status: :recording,
    metadata: {
      parent_recording_id: @recording.id,
      handoff_from: @recording.action_count,
      started_at: Time.current.iso8601,
      user_id: current_user&.id
    }
  )

  render json: {
    handoff_state: handoff_state,
    continuation_recording_id: continuation.id,
    parent_recording: recording_summary(@recording),
    message: "Ready to continue from action #{@recording.action_count}"
  }
end

#indexObject

GET /api/session_recordings List recordings with optional filters



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
# File 'app/controllers/action_agent/api/session_recordings_controller.rb', line 18

def index
  # Recordings the caller owns, plus the shared demo. Ownership is a
  # real column rather than a JSON metadata key, so this works on
  # every adapter.
  recordings = owned(SessionRecording).or(SessionRecording.where(name: "lander_demo")).recent

  # Filter by status
  recordings = recordings.where(status: params[:status]) if params[:status].present?

  # Filter by agent
  if params[:agent_id].present?
    recordings = recordings.joins(:agent_run)
                           .where(agent_runs: { agent_id: params[:agent_id] })
  end

  # Filter by sandbox session
  if params[:sandbox_session_id].present?
    recordings = recordings.where(sandbox_session_id: params[:sandbox_session_id])
  end

  # Pagination
  page = (params[:page] || 1).to_i
  per_page = [ (params[:per_page] || 20).to_i, 100 ].min
  offset = (page - 1) * per_page

  total = recordings.count
  recordings = recordings.offset(offset).limit(per_page)

  render json: {
    recordings: recordings.map { |r| recording_summary(r) },
    pagination: {
      page: page,
      per_page: per_page,
      total: total,
      total_pages: (total.to_f / per_page).ceil
    }
  }
end

#recentObject

GET /api/session_recordings/recent Get recent recordings for the current user



59
60
61
62
63
64
65
# File 'app/controllers/action_agent/api/session_recordings_controller.rb', line 59

def recent
  recordings = owned(SessionRecording).recent.limit(10)

  render json: {
    recordings: recordings.map { |r| recording_summary(r) }
  }
end

#record_actionObject

POST /api/session_recordings/:id/record_action Record a user action in an active session



165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
# File 'app/controllers/action_agent/api/session_recordings_controller.rb', line 165

def record_action
  recording = SessionRecording.find(params[:id])
  return not_found unless can_manage_recording?(recording)

  unless recording.recording?
    render json: { error: "Recording already completed" }, status: :unprocessable_entity
    return
  end

  action = recording.record_action!(
    action_type: params[:action_type],
    selector: params[:selector],
    value: params[:value],
    metadata: params[:metadata]&.to_unsafe_h || {}
  )

  render json: {
    action_id: action.id,
    sequence: action.sequence,
    timestamp_ms: action.timestamp_ms
  }
end

#showObject

GET /api/session_recordings/:id Get full recording details for playback



69
70
71
72
73
# File 'app/controllers/action_agent/api/session_recordings_controller.rb', line 69

def show
  render json: {
    recording: recording_detail(@recording)
  }
end

#snapshotObject

GET /api/session_recordings/:id/snapshot/:action_id Get a specific snapshot (screenshot or DOM)



97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
# File 'app/controllers/action_agent/api/session_recordings_controller.rb', line 97

def snapshot
  action = @recording.recording_actions.find(params[:action_id])

  snapshot_type = params[:type] || "screenshot"

  case snapshot_type
  when "screenshot"
    url = action.screenshot_url
    render json: { url: url, type: "screenshot" }
  when "dom"
    content = action.dom_snapshot_content
    render json: { content: content, type: "dom" }
  else
    render json: { error: "Unknown snapshot type" }, status: :bad_request
  end
rescue ActiveRecord::RecordNotFound
  render json: { error: "Action not found" }, status: :not_found
end

#start_user_sessionObject

POST /api/session_recordings/start_user_session Start a new user takeover session for analytics



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
# File 'app/controllers/action_agent/api/session_recordings_controller.rb', line 131

def start_user_session
  recording = SessionRecording.start_user_session!(
    visitor_id: params[:visitor_id] || generate_visitor_id,
    parent_demo_id: params[:parent_demo_id],
    page_url: params[:page_url]
  )

  # Set user agent from request
  recording.update!(
    metadata: recording..merge(
      user_agent: request.user_agent,
      ip_hash: Digest::SHA256.hexdigest(request.remote_ip.to_s)[0..16]
    )
  )

  # Record the handoff action
  recording.record_action!(
    action_type: "handoff",
    value: "User took over from agent demo",
    metadata: {
      source: "lander_demo",
      step: params[:step] || 4
    }
  )

  render json: {
    recording_id: recording.id,
    visitor_id: recording.["visitor_id"],
    message: "User session started"
  }, status: :created
end