Class: GammaClient
- Inherits:
-
Object
- Object
- GammaClient
- Defined in:
- app/services/gamma_client.rb
Overview
Faraday-backed client for the Gamma Generate API.
Gamma is an AI-powered presentation/document/webpage creator.
Generations are asynchronous: POST /generations returns a generationId,
which is then polled via GET /generations/:id until status == "completed".
Credentials: stored in Rails credentials under gamma: { api_key: ... }
Defined Under Namespace
Classes: Response
Constant Summary collapse
- BASE_URL =
'https://public-api.gamma.app/v1.0'- POLL_INTERVAL_SECONDS =
Polling configuration
4- DEFAULT_TIMEOUT_SECONDS =
120
Instance Method Summary collapse
-
#create_and_wait(timeout: DEFAULT_TIMEOUT_SECONDS, **generation_params) ⇒ Response
Submit a generation and poll until it completes or times out.
-
#create_from_template(gamma_id:, prompt:, theme_id: nil, folder_ids: nil, export_as: nil, image_options: nil, sharing_options: nil) ⇒ Response
Rework an existing gamma using it as a template.
-
#create_generation(input_text:, text_mode: 'generate', format: 'presentation', theme_id: nil, num_cards: nil, card_split: 'auto', additional_instructions: nil, folder_ids: nil, export_as: nil, text_options: nil, image_options: nil, card_options: nil, sharing_options: nil) ⇒ Response
Submit a new generation request.
-
#fetch_pdf_url(gamma_id:) ⇒ Response
Fetch a PDF export URL for any gamma, identified by its gammaId or generationId.
-
#get_generation(generation_id) ⇒ Response
Poll for the status of a generation.
-
#initialize(api_key: nil) ⇒ GammaClient
constructor
A new instance of GammaClient.
-
#list_folders(query: nil, limit: 50, after: nil) ⇒ Response
Retrieve folders in your workspace.
-
#list_themes(query: nil, limit: 50, after: nil) ⇒ Response
Retrieve available themes in your workspace.
-
#rework_and_wait(timeout: DEFAULT_TIMEOUT_SECONDS, **template_params) ⇒ Response
Rework an existing gamma and wait for the new one to complete.
Constructor Details
#initialize(api_key: nil) ⇒ GammaClient
Returns a new instance of GammaClient.
25 26 27 28 29 30 |
# File 'app/services/gamma_client.rb', line 25 def initialize(api_key: nil) @api_key = api_key || Heatwave::Configuration.fetch(:gamma, :api_key) raise ArgumentError, 'Gamma API key not configured' if @api_key.blank? @connection = build_connection end |
Instance Method Details
#create_and_wait(timeout: DEFAULT_TIMEOUT_SECONDS, **generation_params) ⇒ Response
Submit a generation and poll until it completes or times out.
Suitable for synchronous use inside a RubyLLM tool call.
311 312 313 314 315 316 |
# File 'app/services/gamma_client.rb', line 311 def create_and_wait(timeout: DEFAULT_TIMEOUT_SECONDS, **generation_params) submit = create_generation(**generation_params) return submit unless submit.success? poll_until_complete(submit.data[:generation_id], timeout: timeout) end |
#create_from_template(gamma_id:, prompt:, theme_id: nil, folder_ids: nil, export_as: nil, image_options: nil, sharing_options: nil) ⇒ Response
Rework an existing gamma using it as a template.
Creates a NEW gamma based on the original — the original is not modified.
256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 |
# File 'app/services/gamma_client.rb', line 256 def create_from_template( gamma_id:, prompt:, theme_id: nil, folder_ids: nil, export_as: nil, image_options: nil, sharing_options: nil ) payload = { gammaId: gamma_id, prompt: prompt } payload[:themeId] = theme_id if theme_id.present? payload[:folderIds] = folder_ids if folder_ids.present? payload[:exportAs] = export_as if export_as.present? payload[:imageOptions] = if .present? payload[:sharingOptions] = if .present? Rails.logger.info("[GammaClient] Creating from template gammaId=#{gamma_id}") response = @connection.post('generations/from-template') do |req| req.body = payload end if response.success? generation_id = response.body['generationId'] Rails.logger.info("[GammaClient] Template generation submitted: #{generation_id}") Response.new(success: true, data: { generation_id: generation_id }, error: nil) else error = extract_error(response) Rails.logger.error("[GammaClient] Create from template failed (#{response.status}): #{error}") Response.new(success: false, data: nil, error: error) end rescue Faraday::TimeoutError Response.new(success: false, data: nil, error: 'Gamma API request timed out') rescue Faraday::Error => e Rails.logger.error("[GammaClient] Connection error: #{e.}") Response.new(success: false, data: nil, error: "Connection error: #{e.}") end |
#create_generation(input_text:, text_mode: 'generate', format: 'presentation', theme_id: nil, num_cards: nil, card_split: 'auto', additional_instructions: nil, folder_ids: nil, export_as: nil, text_options: nil, image_options: nil, card_options: nil, sharing_options: nil) ⇒ Response
Submit a new generation request.
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 |
# File 'app/services/gamma_client.rb', line 48 def create_generation( input_text:, text_mode: 'generate', format: 'presentation', theme_id: nil, num_cards: nil, card_split: 'auto', additional_instructions: nil, folder_ids: nil, export_as: nil, text_options: nil, image_options: nil, card_options: nil, sharing_options: nil ) payload = { inputText: input_text, textMode: text_mode, format: format, cardSplit: card_split } payload[:themeId] = theme_id if theme_id.present? payload[:numCards] = num_cards if num_cards.present? payload[:additionalInstructions] = additional_instructions if additional_instructions.present? payload[:folderIds] = folder_ids if folder_ids.present? payload[:exportAs] = export_as if export_as.present? payload[:textOptions] = if .present? payload[:imageOptions] = if .present? payload[:cardOptions] = if .present? payload[:sharingOptions] = if .present? Rails.logger.info("[GammaClient] Creating generation: format=#{format} textMode=#{text_mode}") response = @connection.post('generations') do |req| req.body = payload end if response.success? generation_id = response.body['generationId'] Rails.logger.info("[GammaClient] Generation submitted: #{generation_id}") Response.new(success: true, data: { generation_id: generation_id }, error: nil) else error = extract_error(response) Rails.logger.error("[GammaClient] Create failed (#{response.status}): #{error}") Response.new(success: false, data: nil, error: error) end rescue Faraday::TimeoutError Response.new(success: false, data: nil, error: 'Gamma API request timed out') rescue Faraday::Error => e Rails.logger.error("[GammaClient] Connection error: #{e.}") Response.new(success: false, data: nil, error: "Connection error: #{e.}") end |
#fetch_pdf_url(gamma_id:) ⇒ Response
Fetch a PDF export URL for any gamma, identified by its gammaId or generationId.
Strategy:
- Call GET /generations/id — if the original generation included exportAs: "pdf",
the export_url is already available. - If not (gamma was created without export, or this is a gammaId), create a PDF
export via create_from_template with a content-preserving prompt.
210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 |
# File 'app/services/gamma_client.rb', line 210 def fetch_pdf_url(gamma_id:) # Step 1: check if a previous generation already has an export URL existing = get_generation(gamma_id) if existing.success? && existing.data[:export_url].present? Rails.logger.info("[GammaClient] Using cached export URL for #{gamma_id}") return Response.new(success: true, data: { pdf_url: existing.data[:export_url] }, error: nil) end # Step 2: generate a new PDF export using the gamma as a template. # This only works if the gamma is marked as a "Template" in the Gamma workspace. Rails.logger.info("[GammaClient] No cached PDF for #{gamma_id} — attempting PDF export via template") result = rework_and_wait( gamma_id: gamma_id, prompt: 'Export this as a PDF preserving all content exactly as-is.', export_as: 'pdf' ) if result.success? && result.data[:export_url].present? Response.new(success: true, data: { pdf_url: result.data[:export_url] }, error: nil) elsif result.success? Response.new(success: false, data: nil, error: 'PDF export was not returned by Gamma. Try specifying export_as: "pdf" when creating the presentation.') elsif result.error.to_s.downcase.include?('access denied') || result.error.to_s.downcase.include?('template') Response.new( success: false, data: nil, error: 'TEMPLATE_ACCESS_DENIED' ) else result end end |
#get_generation(generation_id) ⇒ Response
Poll for the status of a generation.
106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 |
# File 'app/services/gamma_client.rb', line 106 def get_generation(generation_id) response = @connection.get("generations/#{generation_id}") if response.success? body = response.body data = { status: body['status'], gamma_url: body['gammaUrl'], export_url: body['exportUrl'], credits: body['credits'] }.compact Response.new(success: true, data: data, error: nil) else error = extract_error(response) Rails.logger.error("[GammaClient] Poll failed (#{response.status}): #{error}") Response.new(success: false, data: nil, error: error) end rescue Faraday::TimeoutError Response.new(success: false, data: nil, error: 'Gamma API poll timed out') rescue Faraday::Error => e Response.new(success: false, data: nil, error: "Connection error: #{e.}") end |
#list_folders(query: nil, limit: 50, after: nil) ⇒ Response
Retrieve folders in your workspace.
Supports cursor-based pagination and name search.
176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 |
# File 'app/services/gamma_client.rb', line 176 def list_folders(query: nil, limit: 50, after: nil) Rails.logger.info('[GammaClient] Listing folders') response = @connection.get('folders') do |req| req.params['query'] = query if query.present? req.params['limit'] = limit req.params['after'] = after if after.present? end if response.success? body = response.body folders = Array(body['data']).map { |f| { id: f['id'], name: f['name'] }.compact } Response.new(success: true, data: { folders: folders, has_more: body['hasMore'], next_cursor: body['nextCursor'] }, error: nil) else error = extract_error(response) Response.new(success: false, data: nil, error: error) end rescue Faraday::Error => e Response.new(success: false, data: nil, error: "Connection error: #{e.}") end |
#list_themes(query: nil, limit: 50, after: nil) ⇒ Response
Retrieve available themes in your workspace.
Supports cursor-based pagination and name search.
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 |
# File 'app/services/gamma_client.rb', line 136 def list_themes(query: nil, limit: 50, after: nil) Rails.logger.info('[GammaClient] Listing themes') response = @connection.get('themes') do |req| req.params['query'] = query if query.present? req.params['limit'] = limit req.params['after'] = after if after.present? end if response.success? body = response.body themes = Array(body['data']).map do |t| { id: t['id'], name: t['name'], type: t['type'], color_keywords: t['colorKeywords'], tone_keywords: t['toneKeywords'] }.compact end Response.new(success: true, data: { themes: themes, has_more: body['hasMore'], next_cursor: body['nextCursor'] }, error: nil) else error = extract_error(response) Rails.logger.error("[GammaClient] List themes failed (#{response.status}): #{error}") Response.new(success: false, data: nil, error: error) end rescue Faraday::Error => e Response.new(success: false, data: nil, error: "Connection error: #{e.}") end |
#rework_and_wait(timeout: DEFAULT_TIMEOUT_SECONDS, **template_params) ⇒ Response
Rework an existing gamma and wait for the new one to complete.
Wraps create_from_template + polling. Suitable for use inside a tool call.
299 300 301 302 303 304 |
# File 'app/services/gamma_client.rb', line 299 def rework_and_wait(timeout: DEFAULT_TIMEOUT_SECONDS, **template_params) submit = create_from_template(**template_params) return submit unless submit.success? poll_until_complete(submit.data[:generation_id], timeout: timeout) end |