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 =
URL for base.
'https://public-api.gamma.app/v1.0'- POLL_INTERVAL_SECONDS =
Polling configuration
4- DEFAULT_TIMEOUT_SECONDS =
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.
28 29 30 31 32 33 |
# File 'app/services/gamma_client.rb', line 28 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.
316 317 318 319 320 321 |
# File 'app/services/gamma_client.rb', line 316 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.
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 293 294 295 |
# File 'app/services/gamma_client.rb', line 259 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.
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 |
# File 'app/services/gamma_client.rb', line 51 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.
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 241 242 243 |
# File 'app/services/gamma_client.rb', line 213 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.
109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 |
# File 'app/services/gamma_client.rb', line 109 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.
179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 |
# File 'app/services/gamma_client.rb', line 179 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.
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 |
# File 'app/services/gamma_client.rb', line 139 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.
303 304 305 306 307 308 |
# File 'app/services/gamma_client.rb', line 303 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 |