elevenlabs_client 0.7.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +144 -0
  3. data/README.md +163 -64
  4. data/lib/elevenlabs_client/client.rb +110 -272
  5. data/lib/elevenlabs_client/configuration.rb +119 -0
  6. data/lib/elevenlabs_client/endpoints/admin/pronunciation_dictionaries.rb +103 -0
  7. data/lib/elevenlabs_client/endpoints/admin/service_account_api_keys.rb +77 -0
  8. data/lib/elevenlabs_client/endpoints/admin/user.rb +12 -0
  9. data/lib/elevenlabs_client/endpoints/admin/workspace_groups.rb +56 -0
  10. data/lib/elevenlabs_client/endpoints/admin/workspace_invites.rb +52 -0
  11. data/lib/elevenlabs_client/endpoints/admin/workspace_members.rb +31 -0
  12. data/lib/elevenlabs_client/endpoints/admin/workspace_resources.rb +53 -0
  13. data/lib/elevenlabs_client/endpoints/admin/workspace_webhooks.rb +30 -0
  14. data/lib/elevenlabs_client/endpoints/agents_platform/agents.rb +165 -0
  15. data/lib/elevenlabs_client/endpoints/agents_platform/batch_calling.rb +89 -0
  16. data/lib/elevenlabs_client/endpoints/agents_platform/conversations.rb +121 -0
  17. data/lib/elevenlabs_client/endpoints/agents_platform/knowledge_base.rb +234 -0
  18. data/lib/elevenlabs_client/endpoints/agents_platform/llm_usage.rb +50 -0
  19. data/lib/elevenlabs_client/endpoints/agents_platform/mcp_servers.rb +139 -0
  20. data/lib/elevenlabs_client/endpoints/agents_platform/outbound_calling.rb +55 -0
  21. data/lib/elevenlabs_client/endpoints/agents_platform/phone_numbers.rb +86 -0
  22. data/lib/elevenlabs_client/endpoints/agents_platform/test_invocations.rb +44 -0
  23. data/lib/elevenlabs_client/endpoints/agents_platform/tests.rb +138 -0
  24. data/lib/elevenlabs_client/endpoints/agents_platform/tools.rb +107 -0
  25. data/lib/elevenlabs_client/endpoints/agents_platform/widgets.rb +52 -0
  26. data/lib/elevenlabs_client/endpoints/agents_platform/workspace.rb +130 -0
  27. data/lib/elevenlabs_client/errors.rb +4 -0
  28. data/lib/elevenlabs_client/http_client.rb +325 -0
  29. data/lib/elevenlabs_client/version.rb +1 -1
  30. data/lib/elevenlabs_client.rb +88 -7
  31. metadata +24 -2
@@ -1,32 +1,44 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "faraday"
4
- require "faraday/multipart"
5
- require "json"
6
-
7
3
  module ElevenlabsClient
4
+ # Main client class for the ElevenLabs API
5
+ # Refactored for better maintainability and architecture
8
6
  class Client
9
- DEFAULT_BASE_URL = "https://api.elevenlabs.io"
7
+ attr_reader :http_client
8
+
9
+ def initialize(api_key: nil, base_url: nil, **options)
10
+ # Create configuration
11
+ configuration = Configuration.new
12
+ configuration.api_key = api_key if api_key
13
+ configuration.base_url = base_url if base_url
14
+
15
+ # Apply any additional options
16
+ options.each do |key, value|
17
+ configuration.public_send("#{key}=", value) if configuration.respond_to?("#{key}=")
18
+ end
19
+
20
+ # Validate configuration
21
+ configuration.validate!
10
22
 
11
- attr_reader :base_url, :api_key, :dubs, :text_to_speech, :text_to_dialogue, :sound_generation, :text_to_voice, :models, :voices, :music, :audio_isolation, :audio_native, :forced_alignment, :speech_to_speech, :speech_to_text, :websocket_text_to_speech, :history, :usage, :user, :voice_library, :samples, :service_accounts, :webhooks
23
+ # Create HTTP client
24
+ @http_client = HttpClient.new(
25
+ api_key: configuration.resolved_api_key,
26
+ base_url: configuration.resolved_base_url
27
+ )
28
+
29
+ # Initialize endpoints directly for now (simpler approach)
30
+ initialize_endpoints
31
+ end
32
+
33
+ private
12
34
 
13
- def initialize(api_key: nil, base_url: nil, api_key_env: "ELEVENLABS_API_KEY", base_url_env: "ELEVENLABS_BASE_URL")
14
- @api_key = api_key || fetch_api_key(api_key_env)
15
- @base_url = base_url || fetch_base_url(base_url_env)
16
- @conn = build_connection
35
+ def initialize_endpoints
36
+ # Core TTS/Audio endpoints
17
37
  @dubs = Dubs.new(self)
18
38
  @text_to_speech = TextToSpeech.new(self)
19
39
  @text_to_dialogue = TextToDialogue.new(self)
20
40
  @sound_generation = SoundGeneration.new(self)
21
41
  @text_to_voice = TextToVoice.new(self)
22
- @models = Admin::Models.new(self)
23
- @history = Admin::History.new(self)
24
- @usage = Admin::Usage.new(self)
25
- @user = Admin::User.new(self)
26
- @voice_library = Admin::VoiceLibrary.new(self)
27
- @samples = Admin::Samples.new(self)
28
- @service_accounts = Admin::ServiceAccounts.new(self)
29
- @webhooks = Admin::Webhooks.new(self)
30
42
  @voices = Voices.new(self)
31
43
  @music = Endpoints::Music.new(self)
32
44
  @audio_isolation = AudioIsolation.new(self)
@@ -35,306 +47,132 @@ module ElevenlabsClient
35
47
  @speech_to_speech = SpeechToSpeech.new(self)
36
48
  @speech_to_text = SpeechToText.new(self)
37
49
  @websocket_text_to_speech = WebSocketTextToSpeech.new(self)
38
- end
39
50
 
40
- # Makes an authenticated GET request
41
- # @param path [String] API endpoint path
42
- # @param params [Hash] Query parameters
43
- # @return [Hash] Response body
51
+ # Admin endpoints
52
+ @models = Admin::Models.new(self)
53
+ @history = Admin::History.new(self)
54
+ @usage = Admin::Usage.new(self)
55
+ @user = Admin::User.new(self)
56
+ @voice_library = Admin::VoiceLibrary.new(self)
57
+ @samples = Admin::Samples.new(self)
58
+ @pronunciation_dictionaries = Admin::PronunciationDictionaries.new(self)
59
+ @service_accounts = Admin::ServiceAccounts.new(self)
60
+ @webhooks = Admin::Webhooks.new(self)
61
+ @workspace_groups = Admin::WorkspaceGroups.new(self)
62
+ @workspace_invites = Admin::WorkspaceInvites.new(self)
63
+ @workspace_members = Admin::WorkspaceMembers.new(self)
64
+ @workspace_resources = Admin::WorkspaceResources.new(self)
65
+ @workspace_webhooks = Admin::WorkspaceWebhooks.new(self)
66
+ @service_account_api_keys = Admin::ServiceAccountApiKeys.new(self)
67
+
68
+ # Agents Platform endpoints
69
+ @agents = Endpoints::AgentsPlatform::Agents.new(self)
70
+ @conversations = Endpoints::AgentsPlatform::Conversations.new(self)
71
+ @tools = Endpoints::AgentsPlatform::Tools.new(self)
72
+ @knowledge_base = Endpoints::AgentsPlatform::KnowledgeBase.new(self)
73
+ @tests = Endpoints::AgentsPlatform::Tests.new(self)
74
+ @test_invocations = Endpoints::AgentsPlatform::TestInvocations.new(self)
75
+ @phone_numbers = Endpoints::AgentsPlatform::PhoneNumbers.new(self)
76
+ @widgets = Endpoints::AgentsPlatform::Widgets.new(self)
77
+ @outbound_calling = Endpoints::AgentsPlatform::OutboundCalling.new(self)
78
+ @batch_calling = Endpoints::AgentsPlatform::BatchCalling.new(self)
79
+ @workspace = Endpoints::AgentsPlatform::Workspace.new(self)
80
+ @llm_usage = Endpoints::AgentsPlatform::LlmUsage.new(self)
81
+ @mcp_servers = Endpoints::AgentsPlatform::McpServers.new(self)
82
+ end
83
+
84
+ public
85
+
86
+ # Endpoint accessors
87
+ attr_reader :dubs, :text_to_speech, :text_to_dialogue, :sound_generation, :text_to_voice,
88
+ :voices, :music, :audio_isolation, :audio_native, :forced_alignment,
89
+ :speech_to_speech, :speech_to_text, :websocket_text_to_speech,
90
+ :models, :history, :usage, :user, :voice_library, :samples,
91
+ :pronunciation_dictionaries, :service_accounts, :webhooks,
92
+ :workspace_groups, :workspace_invites, :workspace_members, :workspace_resources,
93
+ :workspace_webhooks, :service_account_api_keys,
94
+ :agents, :conversations, :tools, :knowledge_base, :tests, :test_invocations,
95
+ :phone_numbers, :widgets, :outbound_calling, :batch_calling, :workspace,
96
+ :llm_usage, :mcp_servers
97
+
98
+ # Delegate HTTP methods to http_client for backward compatibility
44
99
  def get(path, params = {})
45
- response = @conn.get(path, params) do |req|
46
- req.headers["xi-api-key"] = api_key
47
- end
48
-
49
- handle_response(response)
100
+ http_client.get(path, params)
50
101
  end
51
102
 
52
- # Makes an authenticated POST request
53
- # @param path [String] API endpoint path
54
- # @param body [Hash, nil] Request body
55
- # @return [Hash] Response body
56
103
  def post(path, body = nil)
57
- response = @conn.post(path) do |req|
58
- req.headers["xi-api-key"] = api_key
59
- req.headers["Content-Type"] = "application/json"
60
- req.body = body.to_json if body
61
- end
104
+ http_client.post(path, body)
105
+ end
62
106
 
63
- handle_response(response)
107
+ def patch(path, body = nil)
108
+ http_client.patch(path, body)
64
109
  end
65
110
 
66
- # Makes an authenticated DELETE request
67
- # @param path [String] API endpoint path
68
- # @return [Hash] Response body
69
111
  def delete(path)
70
- response = @conn.delete(path) do |req|
71
- req.headers["xi-api-key"] = api_key
72
- end
73
-
74
- handle_response(response)
112
+ http_client.delete(path)
75
113
  end
76
114
 
77
- # Makes an authenticated PATCH request
78
- # @param path [String] API endpoint path
79
- # @param body [Hash, nil] Request body
80
- # @return [Hash] Response body
81
- def patch(path, body = nil)
82
- response = @conn.patch(path) do |req|
83
- req.headers["xi-api-key"] = api_key
84
- req.headers["Content-Type"] = "application/json"
85
- req.body = body.to_json if body
86
- end
87
-
88
- handle_response(response)
115
+ def delete_with_body(path, body = nil)
116
+ http_client.delete_with_body(path, body)
89
117
  end
90
118
 
91
- # Makes an authenticated multipart POST request
92
- # @param path [String] API endpoint path
93
- # @param payload [Hash] Multipart payload
94
- # @return [Hash] Response body
95
119
  def post_multipart(path, payload)
96
- response = @conn.post(path) do |req|
97
- req.headers["xi-api-key"] = api_key
98
- req.body = payload
99
- end
100
-
101
- handle_response(response)
120
+ http_client.post_multipart(path, payload)
102
121
  end
103
122
 
104
- # Makes an authenticated GET request expecting binary response
105
- # @param path [String] API endpoint path
106
- # @return [String] Binary response body
107
123
  def get_binary(path)
108
- response = @conn.get(path) do |req|
109
- req.headers["xi-api-key"] = api_key
110
- end
111
-
112
- handle_response(response)
124
+ http_client.get_binary(path)
113
125
  end
114
126
 
115
- # Makes an authenticated POST request expecting binary response
116
- # @param path [String] API endpoint path
117
- # @param body [Hash, nil] Request body
118
- # @return [String] Binary response body
119
127
  def post_binary(path, body = nil)
120
- response = @conn.post(path) do |req|
121
- req.headers["xi-api-key"] = api_key
122
- req.headers["Content-Type"] = "application/json"
123
- req.body = body.to_json if body
124
- end
125
-
126
- handle_response(response)
128
+ http_client.post_binary(path, body)
127
129
  end
128
130
 
129
- # Makes an authenticated POST request with custom headers
130
- # @param path [String] API endpoint path
131
- # @param body [Hash, nil] Request body
132
- # @param custom_headers [Hash] Additional headers
133
- # @return [String] Response body (binary or text)
134
131
  def post_with_custom_headers(path, body = nil, custom_headers = {})
135
- response = @conn.post(path) do |req|
136
- req.headers["xi-api-key"] = api_key
137
- req.headers["Content-Type"] = "application/json"
138
- custom_headers.each { |key, value| req.headers[key] = value }
139
- req.body = body.to_json if body
140
- end
141
-
142
- # For streaming/binary responses, return raw body
143
- if custom_headers["Accept"]&.include?("audio") || custom_headers["Transfer-Encoding"] == "chunked"
144
- handle_response(response)
145
- else
146
- handle_response(response)
147
- end
132
+ http_client.post_with_custom_headers(path, body, custom_headers)
148
133
  end
149
134
 
150
- # Makes an authenticated POST request with streaming response
151
- # @param path [String] API endpoint path
152
- # @param body [Hash, nil] Request body
153
- # @param block [Proc] Block to handle each chunk
154
- # @return [Faraday::Response] Response object
155
135
  def post_streaming(path, body = nil, &block)
156
- response = @conn.post(path) do |req|
157
- req.headers["xi-api-key"] = api_key
158
- req.headers["Content-Type"] = "application/json"
159
- req.headers["Accept"] = "audio/mpeg"
160
- req.body = body.to_json if body
161
-
162
- # Set up streaming callback
163
- req.options.on_data = proc do |chunk, _|
164
- block.call(chunk) if block_given?
165
- end
166
- end
167
-
168
- handle_response(response)
136
+ http_client.post_streaming(path, body, &block)
169
137
  end
170
138
 
171
- # Makes an authenticated GET request with streaming response
172
- # @param path [String] API endpoint path
173
- # @param block [Proc] Block to handle each chunk
174
- # @return [Faraday::Response] Response object
175
139
  def get_streaming(path, &block)
176
- response = @conn.get(path) do |req|
177
- req.headers["xi-api-key"] = api_key
178
- req.headers["Accept"] = "audio/mpeg"
179
-
180
- # Set up streaming callback
181
- req.options.on_data = proc do |chunk, _|
182
- block.call(chunk) if block_given?
183
- end
184
- end
185
-
186
- handle_response(response)
140
+ http_client.get_streaming(path, &block)
187
141
  end
188
142
 
189
- # Makes an authenticated POST request with streaming response for timestamp data
190
- # @param path [String] API endpoint path
191
- # @param body [Hash, nil] Request body
192
- # @param block [Proc] Block to handle each JSON chunk with timestamps
193
- # @return [Faraday::Response] Response object
194
143
  def post_streaming_with_timestamps(path, body = nil, &block)
195
- buffer = ""
196
-
197
- response = @conn.post(path) do |req|
198
- req.headers["xi-api-key"] = api_key
199
- req.headers["Content-Type"] = "application/json"
200
- req.body = body.to_json if body
201
-
202
- # Set up streaming callback for JSON chunks
203
- req.options.on_data = proc do |chunk, _|
204
- if block_given?
205
- buffer += chunk
206
-
207
- # Process complete JSON objects
208
- while buffer.include?("\n")
209
- line, buffer = buffer.split("\n", 2)
210
- next if line.strip.empty?
211
-
212
- begin
213
- json_data = JSON.parse(line)
214
- block.call(json_data)
215
- rescue JSON::ParserError
216
- # Skip malformed JSON lines
217
- next
218
- end
219
- end
220
- end
221
- end
222
- end
223
-
224
- handle_response(response)
144
+ http_client.post_streaming_with_timestamps(path, body, &block)
225
145
  end
226
146
 
227
- # Helper method to create Faraday::Multipart::FilePart
228
- # @param file_io [IO] File IO object
229
- # @param filename [String] Original filename
230
- # @return [Faraday::Multipart::FilePart]
231
147
  def file_part(file_io, filename)
232
- Faraday::Multipart::FilePart.new(file_io, mime_for(filename), filename)
233
- end
234
-
235
- private
236
-
237
- def fetch_api_key(env_key = "ELEVENLABS_API_KEY")
238
- # First try Settings, then ENV, then raise error
239
- if Settings.properties&.dig(:elevenlabs_api_key)
240
- Settings.elevenlabs_api_key
241
- else
242
- ENV.fetch(env_key) do
243
- raise AuthenticationError, "#{env_key} environment variable is required but not set and Settings.properties[:elevenlabs_api_key] is not configured"
244
- end
245
- end
148
+ http_client.file_part(file_io, filename)
246
149
  end
247
150
 
248
- def fetch_base_url(env_key = "ELEVENLABS_BASE_URL")
249
- # First try Settings, then ENV, then default
250
- if Settings.properties&.dig(:elevenlabs_base_uri)
251
- Settings.elevenlabs_base_uri
252
- else
253
- ENV.fetch(env_key, DEFAULT_BASE_URL)
254
- end
151
+ # Convenience accessors for backward compatibility
152
+ def base_url
153
+ http_client.base_url
255
154
  end
256
155
 
257
- def build_connection
258
- Faraday.new(url: base_url) do |f|
259
- f.request :multipart
260
- f.request :url_encoded
261
- f.response :json, content_type: /\bjson$/
262
- f.adapter Faraday.default_adapter
263
- end
156
+ def api_key
157
+ http_client.api_key
264
158
  end
265
159
 
266
- def handle_response(response)
267
- case response.status
268
- when 200..299
269
- response.body
270
- when 400
271
- error_message = extract_error_message(response.body)
272
- raise BadRequestError, error_message.empty? ? "Bad request - invalid parameters" : error_message
273
- when 401
274
- error_message = extract_error_message(response.body)
275
- raise AuthenticationError, error_message.empty? ? "Invalid API key or authentication failed" : error_message
276
- when 404
277
- error_message = extract_error_message(response.body)
278
- raise NotFoundError, error_message.empty? ? "Resource not found" : error_message
279
- when 422
280
- error_message = extract_error_message(response.body)
281
- raise UnprocessableEntityError, error_message.empty? ? "Unprocessable entity - invalid data" : error_message
282
- when 429
283
- error_message = extract_error_message(response.body)
284
- raise RateLimitError, error_message.empty? ? "Rate limit exceeded" : error_message
285
- when 400..499
286
- error_message = extract_error_message(response.body)
287
- raise ValidationError, error_message.empty? ? "Client error occurred with status #{response.status}" : error_message
288
- else
289
- error_message = extract_error_message(response.body)
290
- raise APIError, error_message.empty? ? "API request failed with status #{response.status}" : error_message
291
- end
160
+ # Debug information
161
+ def inspect
162
+ "#<#{self.class.name}:0x#{object_id.to_s(16)} " \
163
+ "base_url=#{base_url.inspect}>"
292
164
  end
293
165
 
294
- private
295
-
296
- def extract_error_message(response_body)
297
- return "" if response_body.nil? || response_body.empty?
298
-
299
- # Handle non-string response bodies
300
- body_str = response_body.is_a?(String) ? response_body : response_body.to_s
301
-
166
+ # Health check method
167
+ def health_check
302
168
  begin
303
- error_info = JSON.parse(body_str)
304
-
305
- # Try different common error message fields
306
- message = error_info["detail"] ||
307
- error_info["message"] ||
308
- error_info["error"] ||
309
- error_info["errors"]
310
-
311
- # Handle nested detail objects
312
- if message.is_a?(Hash)
313
- message = message["message"] || message.to_s
314
- elsif message.is_a?(Array)
315
- message = message.first.to_s
316
- end
317
-
318
- message.to_s
319
- rescue JSON::ParserError, TypeError
320
- # If not JSON or can't be parsed, return the raw body (truncated if too long)
321
- body_str.length > 200 ? "#{body_str[0..200]}..." : body_str
169
+ # Try a simple API call to verify connectivity
170
+ get("/v1/models")
171
+ { status: :ok, message: "Client is healthy" }
172
+ rescue => e
173
+ { status: :error, message: e.message, error_class: e.class.name }
322
174
  end
323
175
  end
324
176
 
325
- def mime_for(filename)
326
- ext = File.extname(filename).downcase
327
- case ext
328
- when ".mp4" then "video/mp4"
329
- when ".mov" then "video/quicktime"
330
- when ".avi" then "video/x-msvideo"
331
- when ".mkv" then "video/x-matroska"
332
- when ".mp3" then "audio/mpeg"
333
- when ".wav" then "audio/wav"
334
- when ".flac" then "audio/flac"
335
- when ".m4a" then "audio/mp4"
336
- else "application/octet-stream"
337
- end
338
- end
339
177
  end
340
178
  end
@@ -0,0 +1,119 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ElevenlabsClient
4
+ # Configuration management for the ElevenLabs client
5
+ class Configuration
6
+ DEFAULT_BASE_URL = "https://api.elevenlabs.io"
7
+ DEFAULT_API_KEY_ENV = "ELEVENLABS_API_KEY"
8
+ DEFAULT_BASE_URL_ENV = "ELEVENLABS_BASE_URL"
9
+
10
+ attr_accessor :api_key, :base_url, :api_key_env, :base_url_env, :timeout, :open_timeout,
11
+ :retry_count, :retry_delay, :user_agent, :logger, :log_level
12
+
13
+ def initialize
14
+ @api_key = nil
15
+ @base_url = DEFAULT_BASE_URL
16
+ @api_key_env = DEFAULT_API_KEY_ENV
17
+ @base_url_env = DEFAULT_BASE_URL_ENV
18
+ @timeout = 30
19
+ @open_timeout = 10
20
+ @retry_count = 3
21
+ @retry_delay = 1
22
+ @user_agent = "ElevenlabsClient/#{ElevenlabsClient::VERSION}"
23
+ @logger = nil
24
+ @log_level = :info
25
+ end
26
+
27
+ # Get API key from configuration, environment, or Settings
28
+ def resolved_api_key
29
+ return @api_key if @api_key
30
+
31
+ # Try Settings first
32
+ if Settings.properties&.dig(:elevenlabs_api_key)
33
+ return Settings.elevenlabs_api_key
34
+ end
35
+
36
+ # Then try environment variable
37
+ env_key = ENV.fetch(@api_key_env) do
38
+ raise AuthenticationError,
39
+ "#{@api_key_env} environment variable is required but not set and " \
40
+ "Settings.properties[:elevenlabs_api_key] is not configured"
41
+ end
42
+
43
+ env_key
44
+ end
45
+
46
+ # Get base URL from configuration, environment, or Settings
47
+ def resolved_base_url
48
+ return @base_url if @base_url != DEFAULT_BASE_URL
49
+
50
+ # Try Settings first
51
+ if Settings.properties&.dig(:elevenlabs_base_uri)
52
+ return Settings.elevenlabs_base_uri
53
+ end
54
+
55
+ # Then try environment variable, with default fallback
56
+ ENV.fetch(@base_url_env, DEFAULT_BASE_URL)
57
+ end
58
+
59
+ # Validate configuration
60
+ def validate!
61
+ resolved_api_key # This will raise if API key is missing
62
+
63
+ unless resolved_base_url.match?(/\Ahttps?:\/\//)
64
+ raise ArgumentError, "Invalid base URL: #{resolved_base_url}"
65
+ end
66
+
67
+ unless timeout.is_a?(Numeric) && timeout > 0
68
+ raise ArgumentError, "Timeout must be a positive number"
69
+ end
70
+
71
+ unless retry_count.is_a?(Integer) && retry_count >= 0
72
+ raise ArgumentError, "Retry count must be a non-negative integer"
73
+ end
74
+ end
75
+
76
+ # Create a hash representation of the configuration
77
+ def to_h
78
+ {
79
+ api_key: api_key ? "[REDACTED]" : nil,
80
+ base_url: resolved_base_url,
81
+ timeout: timeout,
82
+ open_timeout: open_timeout,
83
+ retry_count: retry_count,
84
+ retry_delay: retry_delay,
85
+ user_agent: user_agent,
86
+ log_level: log_level
87
+ }
88
+ end
89
+
90
+ # Reset configuration to defaults
91
+ def reset!
92
+ initialize
93
+ end
94
+ end
95
+
96
+ class << self
97
+ # Global configuration instance
98
+ def configuration
99
+ @configuration ||= Configuration.new
100
+ end
101
+
102
+ # Configure the client globally
103
+ def configure
104
+ yield(configuration) if block_given?
105
+ configuration.validate!
106
+ configuration
107
+ end
108
+
109
+ # Reset global configuration
110
+ def reset_configuration!
111
+ @configuration = Configuration.new
112
+ end
113
+
114
+ # Convenience method to get configured client
115
+ def client
116
+ Client.new
117
+ end
118
+ end
119
+ end
@@ -0,0 +1,103 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ElevenlabsClient
4
+ module Admin
5
+ class PronunciationDictionaries
6
+ def initialize(client)
7
+ @client = client
8
+ end
9
+
10
+ # POST /v1/pronunciation-dictionaries/add-from-file (multipart)
11
+ # Creates a new pronunciation dictionary from a lexicon .PLS file
12
+ # Required: name
13
+ # Optional: file (IO + filename), description, workspace_access
14
+ def add_from_file(name:, file_io: nil, filename: nil, description: nil, workspace_access: nil)
15
+ raise ArgumentError, "name is required" if name.nil? || name.to_s.strip.empty?
16
+
17
+ endpoint = "/v1/pronunciation-dictionaries/add-from-file"
18
+ payload = { name: name }
19
+ payload[:description] = description if description
20
+ payload[:workspace_access] = workspace_access if workspace_access
21
+
22
+ if file_io && filename
23
+ payload[:file] = @client.file_part(file_io, filename)
24
+ end
25
+
26
+ @client.post_multipart(endpoint, payload)
27
+ end
28
+
29
+ alias_method :create_from_file, :add_from_file
30
+
31
+ # POST /v1/pronunciation-dictionaries/add-from-rules (json)
32
+ # Creates a new pronunciation dictionary from provided rules
33
+ # Required: name, rules (Array)
34
+ # Optional: description, workspace_access
35
+ def add_from_rules(name:, rules:, description: nil, workspace_access: nil)
36
+ raise ArgumentError, "name is required" if name.nil? || name.to_s.strip.empty?
37
+ raise ArgumentError, "rules must be a non-empty Array" unless rules.is_a?(Array) && !rules.empty?
38
+
39
+ endpoint = "/v1/pronunciation-dictionaries/add-from-rules"
40
+ body = { name: name, rules: rules }
41
+ body[:description] = description if description
42
+ body[:workspace_access] = workspace_access if workspace_access
43
+
44
+ @client.post(endpoint, body)
45
+ end
46
+
47
+ alias_method :create_from_rules, :add_from_rules
48
+
49
+ # GET /v1/pronunciation-dictionaries/:pronunciation_dictionary_id
50
+ def get_pronunciation_dictionary(pronunciation_dictionary_id)
51
+ raise ArgumentError, "pronunciation_dictionary_id is required" if pronunciation_dictionary_id.nil? || pronunciation_dictionary_id.to_s.strip.empty?
52
+
53
+ endpoint = "/v1/pronunciation-dictionaries/#{pronunciation_dictionary_id}"
54
+ @client.get(endpoint)
55
+ end
56
+
57
+ alias_method :get, :get_pronunciation_dictionary
58
+
59
+ # PATCH /v1/pronunciation-dictionaries/:pronunciation_dictionary_id
60
+ # Accepts partial attributes like archived, name, description, workspace_access
61
+ def update_pronunciation_dictionary(pronunciation_dictionary_id, **attributes)
62
+ raise ArgumentError, "pronunciation_dictionary_id is required" if pronunciation_dictionary_id.nil? || pronunciation_dictionary_id.to_s.strip.empty?
63
+ endpoint = "/v1/pronunciation-dictionaries/#{pronunciation_dictionary_id}"
64
+ @client.patch(endpoint, attributes)
65
+ end
66
+
67
+ alias_method :update, :update_pronunciation_dictionary
68
+
69
+ # GET /v1/pronunciation-dictionaries/:dictionary_id/:version_id/download
70
+ # Returns raw PLS file contents
71
+ def download_pronunciation_dictionary_version(dictionary_id:, version_id:)
72
+ raise ArgumentError, "dictionary_id is required" if dictionary_id.nil? || dictionary_id.to_s.strip.empty?
73
+ raise ArgumentError, "version_id is required" if version_id.nil? || version_id.to_s.strip.empty?
74
+
75
+ endpoint = "/v1/pronunciation-dictionaries/#{dictionary_id}/#{version_id}/download"
76
+ @client.get_binary(endpoint)
77
+ end
78
+
79
+ alias_method :download_version, :download_pronunciation_dictionary_version
80
+
81
+ # GET /v1/pronunciation-dictionaries
82
+ # Optional query: cursor, page_size, sort, sort_direction
83
+ def list_pronunciation_dictionaries(cursor: nil, page_size: nil, sort: nil, sort_direction: nil)
84
+ params = {
85
+ cursor: cursor,
86
+ page_size: page_size,
87
+ sort: sort,
88
+ sort_direction: sort_direction
89
+ }.compact
90
+
91
+ @client.get("/v1/pronunciation-dictionaries", params)
92
+ end
93
+
94
+ alias_method :list, :list_pronunciation_dictionaries
95
+
96
+ private
97
+
98
+ attr_reader :client
99
+ end
100
+ end
101
+ end
102
+
103
+