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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +144 -0
- data/README.md +163 -64
- data/lib/elevenlabs_client/client.rb +110 -272
- data/lib/elevenlabs_client/configuration.rb +119 -0
- data/lib/elevenlabs_client/endpoints/admin/pronunciation_dictionaries.rb +103 -0
- data/lib/elevenlabs_client/endpoints/admin/service_account_api_keys.rb +77 -0
- data/lib/elevenlabs_client/endpoints/admin/user.rb +12 -0
- data/lib/elevenlabs_client/endpoints/admin/workspace_groups.rb +56 -0
- data/lib/elevenlabs_client/endpoints/admin/workspace_invites.rb +52 -0
- data/lib/elevenlabs_client/endpoints/admin/workspace_members.rb +31 -0
- data/lib/elevenlabs_client/endpoints/admin/workspace_resources.rb +53 -0
- data/lib/elevenlabs_client/endpoints/admin/workspace_webhooks.rb +30 -0
- data/lib/elevenlabs_client/endpoints/agents_platform/agents.rb +165 -0
- data/lib/elevenlabs_client/endpoints/agents_platform/batch_calling.rb +89 -0
- data/lib/elevenlabs_client/endpoints/agents_platform/conversations.rb +121 -0
- data/lib/elevenlabs_client/endpoints/agents_platform/knowledge_base.rb +234 -0
- data/lib/elevenlabs_client/endpoints/agents_platform/llm_usage.rb +50 -0
- data/lib/elevenlabs_client/endpoints/agents_platform/mcp_servers.rb +139 -0
- data/lib/elevenlabs_client/endpoints/agents_platform/outbound_calling.rb +55 -0
- data/lib/elevenlabs_client/endpoints/agents_platform/phone_numbers.rb +86 -0
- data/lib/elevenlabs_client/endpoints/agents_platform/test_invocations.rb +44 -0
- data/lib/elevenlabs_client/endpoints/agents_platform/tests.rb +138 -0
- data/lib/elevenlabs_client/endpoints/agents_platform/tools.rb +107 -0
- data/lib/elevenlabs_client/endpoints/agents_platform/widgets.rb +52 -0
- data/lib/elevenlabs_client/endpoints/agents_platform/workspace.rb +130 -0
- data/lib/elevenlabs_client/errors.rb +4 -0
- data/lib/elevenlabs_client/http_client.rb +325 -0
- data/lib/elevenlabs_client/version.rb +1 -1
- data/lib/elevenlabs_client.rb +88 -7
- 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
|
-
|
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
|
-
|
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
|
14
|
-
|
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
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
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
|
-
|
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
|
-
|
58
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
78
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
249
|
-
|
250
|
-
|
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
|
258
|
-
|
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
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
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
|
-
|
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
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
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
|
+
|