Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions ElevenLabs-DotNet-Proxy/Proxy/EndpointRouteBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ public static class EndpointRouteBuilder
// Copied from https://github.com/microsoft/reverse-proxy/blob/51d797986b1fea03500a1ad173d13a1176fb5552/src/ReverseProxy/Forwarder/RequestUtilities.cs#L61-L83
private static readonly HashSet<string> excludedHeaders = new()
{
HeaderNames.Authorization,
"xi-api-key",
HeaderNames.Authorization,
HeaderNames.Connection,
HeaderNames.TransferEncoding,
HeaderNames.KeepAlive,
Expand Down Expand Up @@ -65,6 +65,11 @@ async Task HandleRequest(HttpContext httpContext, string version, string endpoin
{
try
{
if (httpContext.WebSockets.IsWebSocketRequest)
{
throw new InvalidOperationException("Websockets not supported");
}

await authenticationFilter.ValidateAuthenticationAsync(httpContext.Request.Headers).ConfigureAwait(false);
var method = new HttpMethod(httpContext.Request.Method);
var originalQuery = QueryHelpers.ParseQuery(httpContext.Request.QueryString.Value ?? "");
Expand Down Expand Up @@ -93,7 +98,7 @@ async Task HandleRequest(HttpContext httpContext, string version, string endpoin
continue;
}

request.Headers.TryAddWithoutValidation(key, value.ToArray());
request.Headers.TryAddWithoutValidation(key, [.. value]);
}

if (httpContext.Request.ContentType != null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ namespace ElevenLabs
{
public sealed class ElevenLabsClientSettings
{
internal const string WSS = "wss://";
internal const string Http = "http://";
internal const string Https = "https://";
internal const string ElevenLabsDomain = "api.elevenlabs.io";
Expand All @@ -16,8 +17,8 @@ public sealed class ElevenLabsClientSettings
/// </summary>
public ElevenLabsClientSettings()
{
Domain = ElevenLabsDomain;
BaseRequestUrlFormat = $"{Https}{Domain}/{{0}}/{{1}}";
BaseRequestUrlFormat = $"{Https}{ElevenLabsDomain}/{{0}}/{{1}}";
BaseWebSocketUrlFormat = $"{WSS}{ElevenLabsDomain}/{{0}}/{{1}}";
}

/// <summary>
Expand Down Expand Up @@ -52,12 +53,15 @@ public ElevenLabsClientSettings(string domain)

Domain = $"{protocol}{domain}";
BaseRequestUrlFormat = $"{Domain}/{{0}}/{{1}}";
BaseWebSocketUrlFormat = $"{WSS}{ElevenLabsDomain}/{{0}}/{{1}}";
}

public string Domain { get; }

internal string BaseRequestUrlFormat { get; }

internal string BaseWebSocketUrlFormat { get; }

// ReSharper disable once CollectionNeverUpdated.Local reserved for future use.
private readonly Dictionary<string, string> defaultQueryParameters = new();

Expand Down
8 changes: 7 additions & 1 deletion ElevenLabs-DotNet/Common/ElevenLabsBaseEndPoint.cs
Original file line number Diff line number Diff line change
Expand Up @@ -100,8 +100,14 @@ internal Task<HttpResponseMessage> ServerSentEventStreamAsync(HttpRequestMessage
/// <param name="endpoint">The endpoint url.</param>
/// <param name="queryParameters">Optional, parameters to add to the endpoint.</param>
protected string GetUrl(string endpoint = "", Dictionary<string, string> queryParameters = null)
=> GetEndpoint(client.Settings.BaseRequestUrlFormat, endpoint, queryParameters);

protected string GetWebsocketUri(string endpoint = "", Dictionary<string, string> queryParameters = null)
=> GetEndpoint(client.Settings.BaseWebSocketUrlFormat, endpoint, queryParameters);

private string GetEndpoint(string baseUrlFormat, string endpoint = "", Dictionary<string, string> queryParameters = null)
{
var result = string.Format(client.Settings.BaseRequestUrlFormat, ApiVersion, $"{Root}{endpoint}");
var result = string.Format(baseUrlFormat, ApiVersion, $"{Root}{endpoint}");

foreach (var defaultQueryParameter in client.Settings.DefaultQueryParameters)
{
Expand Down
17 changes: 17 additions & 0 deletions ElevenLabs-DotNet/Common/IServerEvent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Licensed under the MIT License. See LICENSE in the project root for license information.

namespace ElevenLabs
{
public interface IWebsocketEvent
{
public string ToJsonString();
}

public interface IServerEvent : IWebsocketEvent
{
}

public interface IClientEvent : IWebsocketEvent
{
}
}
56 changes: 56 additions & 0 deletions ElevenLabs-DotNet/Common/PronunciationDictionary.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// Licensed under the MIT License. See LICENSE in the project root for license information.

using System;
using System.Text.Json.Serialization;

namespace ElevenLabs
{
public sealed record PronunciationDictionary
{
[JsonInclude]
[JsonPropertyName("id")]
public string Id { get; private set; }

[JsonInclude]
[JsonPropertyName("latest_version_id")]
public string LatestVersionId { get; private set; }

[JsonInclude]
[JsonPropertyName("latest_version_rules_num")]
public int LatestVersionRulesNum { get; private set; }

[JsonInclude]
[JsonPropertyName("name")]
public string Name { get; private set; }

[JsonInclude]
[JsonPropertyName("permission_on_resource")]
public string PermissionOnResource { get; private set; }

[JsonInclude]
[JsonPropertyName("created_by")]
public string CreatedBy { get; private set; }

[JsonInclude]
[JsonPropertyName("creation_time_unix")]
public long CreationTimeUnix { get; private set; }

[JsonIgnore]
public DateTime CreationTime
=> DateTimeOffset.FromUnixTimeSeconds(CreationTimeUnix).DateTime;

[JsonInclude]
[JsonPropertyName("archived_time_unix")]
public long? ArchivedTimeUnix { get; private set; }

[JsonIgnore]
public DateTime? ArchivedTime
=> ArchivedTimeUnix.HasValue
? DateTimeOffset.FromUnixTimeSeconds(ArchivedTimeUnix.Value).DateTime
: null;

[JsonInclude]
[JsonPropertyName("description")]
public string Description { get; private set; }
}
}
26 changes: 26 additions & 0 deletions ElevenLabs-DotNet/Common/PronunciationDictionaryLocator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Licensed under the MIT License. See LICENSE in the project root for license information.

using System.Text.Json.Serialization;

namespace ElevenLabs
{
public sealed class PronunciationDictionaryLocator
{
public PronunciationDictionaryLocator(string id, string version)
{
Id = id;
Version = version;
}

[JsonInclude]
[JsonPropertyName("pronunciation_dictionary_id")]
public string Id { get; private set; }

[JsonInclude]
[JsonPropertyName("version_id")]
public string Version { get; private set; }

public static implicit operator PronunciationDictionaryLocator(PronunciationDictionary dict)
=> new(dict.Id, dict.LatestVersionId);
}
}
16 changes: 16 additions & 0 deletions ElevenLabs-DotNet/Common/TextNormalization.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// Licensed under the MIT License. See LICENSE in the project root for license information.

using System.Runtime.Serialization;

namespace ElevenLabs
{
public enum TextNormalization
{
[EnumMember(Value = "auto")]
Auto,
[EnumMember(Value = "on")]
On,
[EnumMember(Value = "off")]
Off
}
}
4 changes: 3 additions & 1 deletion ElevenLabs-DotNet/ElevenLabs-DotNet.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,10 @@ All copyrights, trademarks, logos, and assets are the property of their respecti
<SignAssembly>false</SignAssembly>
<IncludeSymbols>true</IncludeSymbols>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<Version>3.7.0</Version>
<Version>4.0.0</Version>
<PackageReleaseNotes>
Version 4.0.0
- Added support for Websocket streaming for Text to Speech
Version 3.7.0
- Fix v2 Voice Query Serialization
- Added EnumMember json serialization
Expand Down
2 changes: 1 addition & 1 deletion ElevenLabs-DotNet/ElevenLabsClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -100,11 +100,11 @@ private void Dispose(bool disposing)
internal static JsonSerializerOptions JsonSerializationOptions { get; } = new()
{
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
ReferenceHandler = ReferenceHandler.IgnoreCycles,
Converters =
{
new JsonStringEnumConverterFactory(),
},
ReferenceHandler = ReferenceHandler.IgnoreCycles
};

/// <summary>
Expand Down
55 changes: 55 additions & 0 deletions ElevenLabs-DotNet/Extensions/QueryParamsExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// Licensed under the MIT License. See LICENSE in the project root for license information.

using System.Collections.Generic;
using System.Linq;
using System.Text.Json;

namespace ElevenLabs.Extensions
{
internal static class QueryParamsExtensions
{
public static Dictionary<string, string> ToQueryParams(this object @object)
{
var parameters = new Dictionary<string, string>();
var json = JsonSerializer.Serialize(@object, ElevenLabsClient.JsonSerializationOptions);
using var doc = JsonDocument.Parse(json);

foreach (var property in doc.RootElement.EnumerateObject())
{
switch (property.Value.ValueKind)
{
case JsonValueKind.Array:
{
// Flatten arrays as comma-separated values
var array = string.Join(",", property.Value.EnumerateArray().Select(e => e.GetString()));

if (!string.IsNullOrWhiteSpace(array))
{
parameters.Add(property.Name, array);
}

continue;
}
case JsonValueKind.Null:
case JsonValueKind.Undefined:
{
continue; // ignored
}
default:
{
var value = property.Value.ToString();

if (!string.IsNullOrWhiteSpace(value))
{
parameters.Add(property.Name, value);
}

continue;
}
}
}

return parameters;
}
}
}
59 changes: 59 additions & 0 deletions ElevenLabs-DotNet/Extensions/TaskExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// Licensed under the MIT License. See LICENSE in the project root for license information.

using System;
using System.Threading;
using System.Threading.Tasks;

namespace ElevenLabs.Extensions
{
internal static class TaskExtensions
{
/// <summary>
/// Runs <see cref="Task"/> with <see cref="CancellationToken"/>.
/// </summary>
/// <param name="task">The <see cref="Task"/> to run.</param>
/// <param name="cancellationToken"><see cref="CancellationToken"/>.</param>
/// <exception cref="OperationCanceledException"></exception>
public static async Task WithCancellation(this Task task, CancellationToken cancellationToken)
{
var tcs = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);

await using (cancellationToken.Register(state => ((TaskCompletionSource<object>)state).TrySetResult(null), tcs))
{
var resultTask = await Task.WhenAny(task, tcs.Task);

if (resultTask == tcs.Task)
{
throw new OperationCanceledException(cancellationToken);
}

await task;
}
}

/// <summary>
/// Runs <see cref="Task{T}"/> with <see cref="CancellationToken"/>.
/// </summary>
/// <typeparam name="T">Task return type.</typeparam>
/// <param name="task">The <see cref="Task{T}"/> to run.</param>
/// <param name="cancellationToken"><see cref="CancellationToken"/>.</param>
/// <exception cref="OperationCanceledException"></exception>
/// <returns><see cref="Task{T}"/> result.</returns>
public static async Task<T> WithCancellation<T>(this Task<T> task, CancellationToken cancellationToken)
{
var tcs = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);

await using (cancellationToken.Register(state => ((TaskCompletionSource<object>)state).TrySetResult(null), tcs))
{
var resultTask = await Task.WhenAny(task, tcs.Task);

if (resultTask == tcs.Task)
{
throw new OperationCanceledException(cancellationToken);
}

return await task;
}
}
}
}
Loading
Loading