elevenlabs_client 0.6.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 +220 -0
- data/README.md +181 -58
- data/lib/elevenlabs_client/client.rb +110 -269
- data/lib/elevenlabs_client/configuration.rb +119 -0
- data/lib/elevenlabs_client/endpoints/admin/history.rb +1 -1
- data/lib/elevenlabs_client/endpoints/admin/pronunciation_dictionaries.rb +103 -0
- data/lib/elevenlabs_client/endpoints/admin/samples.rb +30 -0
- data/lib/elevenlabs_client/endpoints/admin/service_account_api_keys.rb +77 -0
- data/lib/elevenlabs_client/endpoints/admin/service_accounts.rb +29 -0
- data/lib/elevenlabs_client/endpoints/admin/user.rb +12 -0
- data/lib/elevenlabs_client/endpoints/admin/webhooks.rb +33 -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 +99 -15
- metadata +27 -2
@@ -1,29 +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
|
10
19
|
|
11
|
-
|
20
|
+
# Validate configuration
|
21
|
+
configuration.validate!
|
12
22
|
|
13
|
-
|
14
|
-
@
|
15
|
-
|
16
|
-
|
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
|
34
|
+
|
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
42
|
@voices = Voices.new(self)
|
28
43
|
@music = Endpoints::Music.new(self)
|
29
44
|
@audio_isolation = AudioIsolation.new(self)
|
@@ -32,306 +47,132 @@ module ElevenlabsClient
|
|
32
47
|
@speech_to_speech = SpeechToSpeech.new(self)
|
33
48
|
@speech_to_text = SpeechToText.new(self)
|
34
49
|
@websocket_text_to_speech = WebSocketTextToSpeech.new(self)
|
35
|
-
end
|
36
50
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
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
|
41
99
|
def get(path, params = {})
|
42
|
-
|
43
|
-
req.headers["xi-api-key"] = api_key
|
44
|
-
end
|
45
|
-
|
46
|
-
handle_response(response)
|
100
|
+
http_client.get(path, params)
|
47
101
|
end
|
48
102
|
|
49
|
-
# Makes an authenticated POST request
|
50
|
-
# @param path [String] API endpoint path
|
51
|
-
# @param body [Hash, nil] Request body
|
52
|
-
# @return [Hash] Response body
|
53
103
|
def post(path, body = nil)
|
54
|
-
|
55
|
-
|
56
|
-
req.headers["Content-Type"] = "application/json"
|
57
|
-
req.body = body.to_json if body
|
58
|
-
end
|
104
|
+
http_client.post(path, body)
|
105
|
+
end
|
59
106
|
|
60
|
-
|
107
|
+
def patch(path, body = nil)
|
108
|
+
http_client.patch(path, body)
|
61
109
|
end
|
62
110
|
|
63
|
-
# Makes an authenticated DELETE request
|
64
|
-
# @param path [String] API endpoint path
|
65
|
-
# @return [Hash] Response body
|
66
111
|
def delete(path)
|
67
|
-
|
68
|
-
req.headers["xi-api-key"] = api_key
|
69
|
-
end
|
70
|
-
|
71
|
-
handle_response(response)
|
112
|
+
http_client.delete(path)
|
72
113
|
end
|
73
114
|
|
74
|
-
|
75
|
-
|
76
|
-
# @param body [Hash, nil] Request body
|
77
|
-
# @return [Hash] Response body
|
78
|
-
def patch(path, body = nil)
|
79
|
-
response = @conn.patch(path) do |req|
|
80
|
-
req.headers["xi-api-key"] = api_key
|
81
|
-
req.headers["Content-Type"] = "application/json"
|
82
|
-
req.body = body.to_json if body
|
83
|
-
end
|
84
|
-
|
85
|
-
handle_response(response)
|
115
|
+
def delete_with_body(path, body = nil)
|
116
|
+
http_client.delete_with_body(path, body)
|
86
117
|
end
|
87
118
|
|
88
|
-
# Makes an authenticated multipart POST request
|
89
|
-
# @param path [String] API endpoint path
|
90
|
-
# @param payload [Hash] Multipart payload
|
91
|
-
# @return [Hash] Response body
|
92
119
|
def post_multipart(path, payload)
|
93
|
-
|
94
|
-
req.headers["xi-api-key"] = api_key
|
95
|
-
req.body = payload
|
96
|
-
end
|
97
|
-
|
98
|
-
handle_response(response)
|
120
|
+
http_client.post_multipart(path, payload)
|
99
121
|
end
|
100
122
|
|
101
|
-
# Makes an authenticated GET request expecting binary response
|
102
|
-
# @param path [String] API endpoint path
|
103
|
-
# @return [String] Binary response body
|
104
123
|
def get_binary(path)
|
105
|
-
|
106
|
-
req.headers["xi-api-key"] = api_key
|
107
|
-
end
|
108
|
-
|
109
|
-
handle_response(response)
|
124
|
+
http_client.get_binary(path)
|
110
125
|
end
|
111
126
|
|
112
|
-
# Makes an authenticated POST request expecting binary response
|
113
|
-
# @param path [String] API endpoint path
|
114
|
-
# @param body [Hash, nil] Request body
|
115
|
-
# @return [String] Binary response body
|
116
127
|
def post_binary(path, body = nil)
|
117
|
-
|
118
|
-
req.headers["xi-api-key"] = api_key
|
119
|
-
req.headers["Content-Type"] = "application/json"
|
120
|
-
req.body = body.to_json if body
|
121
|
-
end
|
122
|
-
|
123
|
-
handle_response(response)
|
128
|
+
http_client.post_binary(path, body)
|
124
129
|
end
|
125
130
|
|
126
|
-
# Makes an authenticated POST request with custom headers
|
127
|
-
# @param path [String] API endpoint path
|
128
|
-
# @param body [Hash, nil] Request body
|
129
|
-
# @param custom_headers [Hash] Additional headers
|
130
|
-
# @return [String] Response body (binary or text)
|
131
131
|
def post_with_custom_headers(path, body = nil, custom_headers = {})
|
132
|
-
|
133
|
-
req.headers["xi-api-key"] = api_key
|
134
|
-
req.headers["Content-Type"] = "application/json"
|
135
|
-
custom_headers.each { |key, value| req.headers[key] = value }
|
136
|
-
req.body = body.to_json if body
|
137
|
-
end
|
138
|
-
|
139
|
-
# For streaming/binary responses, return raw body
|
140
|
-
if custom_headers["Accept"]&.include?("audio") || custom_headers["Transfer-Encoding"] == "chunked"
|
141
|
-
handle_response(response)
|
142
|
-
else
|
143
|
-
handle_response(response)
|
144
|
-
end
|
132
|
+
http_client.post_with_custom_headers(path, body, custom_headers)
|
145
133
|
end
|
146
134
|
|
147
|
-
# Makes an authenticated POST request with streaming response
|
148
|
-
# @param path [String] API endpoint path
|
149
|
-
# @param body [Hash, nil] Request body
|
150
|
-
# @param block [Proc] Block to handle each chunk
|
151
|
-
# @return [Faraday::Response] Response object
|
152
135
|
def post_streaming(path, body = nil, &block)
|
153
|
-
|
154
|
-
req.headers["xi-api-key"] = api_key
|
155
|
-
req.headers["Content-Type"] = "application/json"
|
156
|
-
req.headers["Accept"] = "audio/mpeg"
|
157
|
-
req.body = body.to_json if body
|
158
|
-
|
159
|
-
# Set up streaming callback
|
160
|
-
req.options.on_data = proc do |chunk, _|
|
161
|
-
block.call(chunk) if block_given?
|
162
|
-
end
|
163
|
-
end
|
164
|
-
|
165
|
-
handle_response(response)
|
136
|
+
http_client.post_streaming(path, body, &block)
|
166
137
|
end
|
167
138
|
|
168
|
-
# Makes an authenticated GET request with streaming response
|
169
|
-
# @param path [String] API endpoint path
|
170
|
-
# @param block [Proc] Block to handle each chunk
|
171
|
-
# @return [Faraday::Response] Response object
|
172
139
|
def get_streaming(path, &block)
|
173
|
-
|
174
|
-
req.headers["xi-api-key"] = api_key
|
175
|
-
req.headers["Accept"] = "audio/mpeg"
|
176
|
-
|
177
|
-
# Set up streaming callback
|
178
|
-
req.options.on_data = proc do |chunk, _|
|
179
|
-
block.call(chunk) if block_given?
|
180
|
-
end
|
181
|
-
end
|
182
|
-
|
183
|
-
handle_response(response)
|
140
|
+
http_client.get_streaming(path, &block)
|
184
141
|
end
|
185
142
|
|
186
|
-
# Makes an authenticated POST request with streaming response for timestamp data
|
187
|
-
# @param path [String] API endpoint path
|
188
|
-
# @param body [Hash, nil] Request body
|
189
|
-
# @param block [Proc] Block to handle each JSON chunk with timestamps
|
190
|
-
# @return [Faraday::Response] Response object
|
191
143
|
def post_streaming_with_timestamps(path, body = nil, &block)
|
192
|
-
|
193
|
-
|
194
|
-
response = @conn.post(path) do |req|
|
195
|
-
req.headers["xi-api-key"] = api_key
|
196
|
-
req.headers["Content-Type"] = "application/json"
|
197
|
-
req.body = body.to_json if body
|
198
|
-
|
199
|
-
# Set up streaming callback for JSON chunks
|
200
|
-
req.options.on_data = proc do |chunk, _|
|
201
|
-
if block_given?
|
202
|
-
buffer += chunk
|
203
|
-
|
204
|
-
# Process complete JSON objects
|
205
|
-
while buffer.include?("\n")
|
206
|
-
line, buffer = buffer.split("\n", 2)
|
207
|
-
next if line.strip.empty?
|
208
|
-
|
209
|
-
begin
|
210
|
-
json_data = JSON.parse(line)
|
211
|
-
block.call(json_data)
|
212
|
-
rescue JSON::ParserError
|
213
|
-
# Skip malformed JSON lines
|
214
|
-
next
|
215
|
-
end
|
216
|
-
end
|
217
|
-
end
|
218
|
-
end
|
219
|
-
end
|
220
|
-
|
221
|
-
handle_response(response)
|
144
|
+
http_client.post_streaming_with_timestamps(path, body, &block)
|
222
145
|
end
|
223
146
|
|
224
|
-
# Helper method to create Faraday::Multipart::FilePart
|
225
|
-
# @param file_io [IO] File IO object
|
226
|
-
# @param filename [String] Original filename
|
227
|
-
# @return [Faraday::Multipart::FilePart]
|
228
147
|
def file_part(file_io, filename)
|
229
|
-
|
148
|
+
http_client.file_part(file_io, filename)
|
230
149
|
end
|
231
150
|
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
# First try Settings, then ENV, then raise error
|
236
|
-
if Settings.properties&.dig(:elevenlabs_api_key)
|
237
|
-
Settings.elevenlabs_api_key
|
238
|
-
else
|
239
|
-
ENV.fetch(env_key) do
|
240
|
-
raise AuthenticationError, "#{env_key} environment variable is required but not set and Settings.properties[:elevenlabs_api_key] is not configured"
|
241
|
-
end
|
242
|
-
end
|
151
|
+
# Convenience accessors for backward compatibility
|
152
|
+
def base_url
|
153
|
+
http_client.base_url
|
243
154
|
end
|
244
155
|
|
245
|
-
def
|
246
|
-
|
247
|
-
if Settings.properties&.dig(:elevenlabs_base_uri)
|
248
|
-
Settings.elevenlabs_base_uri
|
249
|
-
else
|
250
|
-
ENV.fetch(env_key, DEFAULT_BASE_URL)
|
251
|
-
end
|
156
|
+
def api_key
|
157
|
+
http_client.api_key
|
252
158
|
end
|
253
159
|
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
f.response :json, content_type: /\bjson$/
|
259
|
-
f.adapter Faraday.default_adapter
|
260
|
-
end
|
160
|
+
# Debug information
|
161
|
+
def inspect
|
162
|
+
"#<#{self.class.name}:0x#{object_id.to_s(16)} " \
|
163
|
+
"base_url=#{base_url.inspect}>"
|
261
164
|
end
|
262
165
|
|
263
|
-
|
264
|
-
|
265
|
-
when 200..299
|
266
|
-
response.body
|
267
|
-
when 400
|
268
|
-
error_message = extract_error_message(response.body)
|
269
|
-
raise BadRequestError, error_message.empty? ? "Bad request - invalid parameters" : error_message
|
270
|
-
when 401
|
271
|
-
error_message = extract_error_message(response.body)
|
272
|
-
raise AuthenticationError, error_message.empty? ? "Invalid API key or authentication failed" : error_message
|
273
|
-
when 404
|
274
|
-
error_message = extract_error_message(response.body)
|
275
|
-
raise NotFoundError, error_message.empty? ? "Resource not found" : error_message
|
276
|
-
when 422
|
277
|
-
error_message = extract_error_message(response.body)
|
278
|
-
raise UnprocessableEntityError, error_message.empty? ? "Unprocessable entity - invalid data" : error_message
|
279
|
-
when 429
|
280
|
-
error_message = extract_error_message(response.body)
|
281
|
-
raise RateLimitError, error_message.empty? ? "Rate limit exceeded" : error_message
|
282
|
-
when 400..499
|
283
|
-
error_message = extract_error_message(response.body)
|
284
|
-
raise ValidationError, error_message.empty? ? "Client error occurred with status #{response.status}" : error_message
|
285
|
-
else
|
286
|
-
error_message = extract_error_message(response.body)
|
287
|
-
raise APIError, error_message.empty? ? "API request failed with status #{response.status}" : error_message
|
288
|
-
end
|
289
|
-
end
|
290
|
-
|
291
|
-
private
|
292
|
-
|
293
|
-
def extract_error_message(response_body)
|
294
|
-
return "" if response_body.nil? || response_body.empty?
|
295
|
-
|
296
|
-
# Handle non-string response bodies
|
297
|
-
body_str = response_body.is_a?(String) ? response_body : response_body.to_s
|
298
|
-
|
166
|
+
# Health check method
|
167
|
+
def health_check
|
299
168
|
begin
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
error_info["error"] ||
|
306
|
-
error_info["errors"]
|
307
|
-
|
308
|
-
# Handle nested detail objects
|
309
|
-
if message.is_a?(Hash)
|
310
|
-
message = message["message"] || message.to_s
|
311
|
-
elsif message.is_a?(Array)
|
312
|
-
message = message.first.to_s
|
313
|
-
end
|
314
|
-
|
315
|
-
message.to_s
|
316
|
-
rescue JSON::ParserError, TypeError
|
317
|
-
# If not JSON or can't be parsed, return the raw body (truncated if too long)
|
318
|
-
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 }
|
319
174
|
end
|
320
175
|
end
|
321
176
|
|
322
|
-
def mime_for(filename)
|
323
|
-
ext = File.extname(filename).downcase
|
324
|
-
case ext
|
325
|
-
when ".mp4" then "video/mp4"
|
326
|
-
when ".mov" then "video/quicktime"
|
327
|
-
when ".avi" then "video/x-msvideo"
|
328
|
-
when ".mkv" then "video/x-matroska"
|
329
|
-
when ".mp3" then "audio/mpeg"
|
330
|
-
when ".wav" then "audio/wav"
|
331
|
-
when ".flac" then "audio/flac"
|
332
|
-
when ".m4a" then "audio/mp4"
|
333
|
-
else "application/octet-stream"
|
334
|
-
end
|
335
|
-
end
|
336
177
|
end
|
337
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
|
@@ -22,7 +22,7 @@ module ElevenlabsClient
|
|
22
22
|
# @return [Hash] Response containing history items, pagination info
|
23
23
|
def list(**options)
|
24
24
|
endpoint = "/v1/history"
|
25
|
-
|
25
|
+
|
26
26
|
# Build query parameters
|
27
27
|
query_params = {}
|
28
28
|
query_params[:page_size] = options[:page_size] if options[:page_size]
|
@@ -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
|
+
|