Skip to content
Merged
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
1 change: 1 addition & 0 deletions src/cli/Gradium.CLI/Commands/ApiCommand.g.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ public static Command Create()
command.Subcommands.Add(S2SApiGroupCommand.Create());
command.Subcommands.Add(STTApiGroupCommand.Create());
command.Subcommands.Add(TTSApiGroupCommand.Create());
command.Subcommands.Add(VoiceDesignApiGroupCommand.Create());
command.Subcommands.Add(VoicesApiGroupCommand.Create());
return command;
}
Expand Down
18 changes: 18 additions & 0 deletions src/cli/Gradium.CLI/Commands/VoiceDesignApiGroupCommand.g.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#nullable enable

using System.CommandLine;

namespace Gradium.CLI.Commands;

internal static class VoiceDesignApiGroupCommand
{
public static Command Create()
{
var command = new Command(@"voice-design", @"Voice Design endpoint commands.");
command.Subcommands.Add(VoiceDesignCreateVoiceFromEmbeddingVoicesFromEmbeddingPostCommandApiCommand.Create());
command.Subcommands.Add(VoiceDesignDeleteVoiceEmbeddingVoiceGeneratorEmbeddingsEmbeddingIdDeleteCommandApiCommand.Create());
command.Subcommands.Add(VoiceDesignGenerateVoiceVoiceGeneratorGeneratePostCommandApiCommand.Create());
command.Subcommands.Add(VoiceDesignListVoiceEmbeddingsVoiceGeneratorEmbeddingsGetCommandApiCommand.Create());
return command;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
#nullable enable
#pragma warning disable CS0618

using System.CommandLine;

namespace Gradium.CLI.Commands;

internal static partial class VoiceDesignCreateVoiceFromEmbeddingVoicesFromEmbeddingPostCommandApiCommand
{
private static Argument<string> NameOption { get; } = new(
name: @"name")
{
Description = @"A name for the voice.",
};

private static Option<string> VoxiumEmbeddingId { get; } = new(
name: @"--voxium-embedding-id")
{
Description = @"Id of the candidate to keep, as returned by `POST /voice-generator/generate`.",
Required = true,
};

private static Option<string?> DescriptionOption { get; } = new(
name: @"--description")
{
Description = @"An optional description of the voice.",
};
private static Option<string?> Input { get; } = new(@"--input")
{
Description = "Load request JSON from a file path, '-' for stdin, or an inline JSON object/array string.",
};

private static Option<string?> RequestJson { get; } = new(@"--request-json")
{
Description = "Request body as JSON.",
Hidden = true,
};

private static Option<string?> RequestFile { get; } = new(@"--request-file")
{
Description = "Path to a JSON request file, or '-' for stdin.",
Hidden = true,
};

private static string FormatResponse(ParseResult parseResult, global::Gradium.VoiceFromEmbeddingResponse value, global::System.Text.Json.Serialization.JsonSerializerContext context, bool truncateLongStrings)
{
string? text = null;
CustomizeResponseText(parseResult, value, ref text);
if (!string.IsNullOrWhiteSpace(text))
{
return text;
}

var hints = new Dictionary<string, CliFormatHint>(StringComparer.OrdinalIgnoreCase)
{
};
CustomizeResponseFormatHints(hints);
return CliRuntime.FormatHumanReadable(value, context, truncateLongStrings, hints);
}

static partial void CustomizeResponseText(ParseResult parseResult, global::Gradium.VoiceFromEmbeddingResponse value, ref string? text);
static partial void CustomizeResponseFormatHints(Dictionary<string, CliFormatHint> hints);


public static Command Create()
{
var command = new Command(@"create-voice-from-embedding-voices-from-embedding-post", @"Create Voice From Candidate
Keep a generated candidate as a permanent voice in your organization.

The response field `uid` is the `voice_id` every other endpoint expects: the same value under two names. From here the voice behaves like any other Gradium voice, on one-shot Text-to-Speech, the streaming WebSockets and Speech-to-Speech.

Keeping a candidate is free, since generation is already accounted for, and it clears the candidate's expiry. Store the `embedding_id` to `voice_id` mapping: keeping the same candidate twice returns `409`, and your own record is the cleanest way to find the voice you already made.");
command.Arguments.Add(NameOption);
command.Options.Add(VoxiumEmbeddingId);
command.Options.Add(DescriptionOption);
command.Options.Add(Input);
command.Options.Add(RequestJson);
command.Options.Add(RequestFile);
command.Validators.Add(result =>
{
var hasInput = result.GetResult(Input) is not null;
var hasRequestJson = result.GetResult(RequestJson) is not null;
var hasRequestFile = result.GetResult(RequestFile) is not null;
var specifiedCount = (hasInput ? 1 : 0) + (hasRequestJson ? 1 : 0) + (hasRequestFile ? 1 : 0);
if (specifiedCount > 1)
{
result.AddError(@"Specify at most one of --input, --request-json, or --request-file.");
}
});

command.SetAction(async (ParseResult parseResult, CancellationToken cancellationToken) =>
await CliRuntime.RunAsync(async () =>
{
var __requestBase = await CliRuntime.ReadRequestOrDefaultAsync<global::Gradium.VoiceFromEmbeddingCreate>(
parseResult,
Input,
RequestJson,
RequestFile,
global::Gradium.SourceGenerationContext.Default,
cancellationToken).ConfigureAwait(false);
var name = parseResult.GetRequiredValue(NameOption);
var voxiumEmbeddingId = parseResult.GetRequiredValue(VoxiumEmbeddingId);
var description = CliRuntime.WasSpecified(parseResult, DescriptionOption) ? parseResult.GetValue(DescriptionOption) : (__requestBase is { } __DescriptionBaseValue ? __DescriptionBaseValue.Description : default);
using var client = await CliRuntime.CreateClientAsync(parseResult, cancellationToken).ConfigureAwait(false);


var response = await client.VoiceDesign.CreateVoiceFromEmbeddingVoicesFromEmbeddingPostAsync(
name: name,
voxiumEmbeddingId: voxiumEmbeddingId,
description: description,
cancellationToken: cancellationToken).ConfigureAwait(false);


await CliRuntime.WriteResponseAsync(
parseResult,
response,
global::Gradium.SourceGenerationContext.Default,
FormatResponse,
cancellationToken).ConfigureAwait(false);
}, cancellationToken).ConfigureAwait(false));
return command;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
#nullable enable
#pragma warning disable CS0618

using System.CommandLine;

namespace Gradium.CLI.Commands;

internal static partial class VoiceDesignDeleteVoiceEmbeddingVoiceGeneratorEmbeddingsEmbeddingIdDeleteCommandApiCommand
{
private static Argument<string> EmbeddingId { get; } = new(
name: @"embedding-id")
{
Description = @"",
};

public static Command Create()
{
var command = new Command(@"delete-voice-embedding-voice-generator-embeddings-embedding-id-delete", @"Delete Voice Candidate
Remove a candidate you are not keeping. Candidates you leave alone are removed automatically after 30 days.

Safe at any time: a voice kept from a candidate holds its own copy, so deleting the candidate leaves the voice untouched. An id that is already gone returns `404`, so treat cleanup as best effort.");
command.Arguments.Add(EmbeddingId);


command.SetAction(async (ParseResult parseResult, CancellationToken cancellationToken) =>
await CliRuntime.RunAsync(async () =>
{
var embeddingId = parseResult.GetRequiredValue(EmbeddingId);
using var client = await CliRuntime.CreateClientAsync(parseResult, cancellationToken).ConfigureAwait(false);


await client.VoiceDesign.DeleteVoiceEmbeddingVoiceGeneratorEmbeddingsEmbeddingIdDeleteAsync(
embeddingId: embeddingId,
cancellationToken: cancellationToken).ConfigureAwait(false);

await CliRuntime.WriteSuccessAsync(parseResult, cancellationToken).ConfigureAwait(false);
}, cancellationToken).ConfigureAwait(false));
return command;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
#nullable enable
#pragma warning disable CS0618

using System.CommandLine;

namespace Gradium.CLI.Commands;

internal static partial class VoiceDesignGenerateVoiceVoiceGeneratorGeneratePostCommandApiCommand
{
private static Option<string> Prompt { get; } = new(
name: @"--prompt")
{
Description = @"The voice description, up to 500 characters. Must contain actual text, not only spaces.",
Required = true,
};

private static Option<global::Gradium.VoiceGenerationRequestLanguage> Language { get; } = new(
name: @"--language")
{
Description = @"Language the voice will speak. Shapes the accent and the delivery.",
Required = true,
};

private static Option<int?> NSamples { get; } = new(
name: @"--n-samples")
{
Description = @"Number of candidate voices to sample from the description. All candidates in one request are variations on the same character.",
};

private static Option<global::Gradium.VoiceGeneratorConfig?> JsonConfig { get; } = new(
name: @"--json-config")
{
Description = @"Advanced sampling options.",
};
private static Option<string?> Input { get; } = new(@"--input")
{
Description = "Load request JSON from a file path, '-' for stdin, or an inline JSON object/array string.",
};

private static Option<string?> RequestJson { get; } = new(@"--request-json")
{
Description = "Request body as JSON.",
Hidden = true,
};

private static Option<string?> RequestFile { get; } = new(@"--request-file")
{
Description = "Path to a JSON request file, or '-' for stdin.",
Hidden = true,
};

private static string FormatResponse(ParseResult parseResult, global::Gradium.VoiceGenerationResponse value, global::System.Text.Json.Serialization.JsonSerializerContext context, bool truncateLongStrings)
{
string? text = null;
CustomizeResponseText(parseResult, value, ref text);
if (!string.IsNullOrWhiteSpace(text))
{
return text;
}

var hints = new Dictionary<string, CliFormatHint>(StringComparer.OrdinalIgnoreCase)
{
};
CustomizeResponseFormatHints(hints);
return CliRuntime.FormatHumanReadable(value, context, truncateLongStrings, hints);
}

static partial void CustomizeResponseText(ParseResult parseResult, global::Gradium.VoiceGenerationResponse value, ref string? text);
static partial void CustomizeResponseFormatHints(Dictionary<string, CliFormatHint> hints);


public static Command Create()
{
var command = new Command(@"generate-voice-voice-generator-generate-post", @"Generate Voice Candidates
Sample candidate voices from a written description. No reference audio is involved.

Generation runs in the background, so the candidate ids come back immediately with `ready: false`. Poll `GET /voice-generator/embeddings` every two seconds until each is ready, which typically takes three to five seconds for three candidates.

Every request mints new ids, and the description is expanded before sampling, so the same description gives a fresh voice each time. Capture the ids from the response and carry them through the rest of the flow.");
command.Options.Add(Prompt);
command.Options.Add(Language);
command.Options.Add(NSamples);
command.Options.Add(JsonConfig);
command.Options.Add(Input);
command.Options.Add(RequestJson);
command.Options.Add(RequestFile);
command.Validators.Add(result =>
{
var hasInput = result.GetResult(Input) is not null;
var hasRequestJson = result.GetResult(RequestJson) is not null;
var hasRequestFile = result.GetResult(RequestFile) is not null;
var specifiedCount = (hasInput ? 1 : 0) + (hasRequestJson ? 1 : 0) + (hasRequestFile ? 1 : 0);
if (specifiedCount > 1)
{
result.AddError(@"Specify at most one of --input, --request-json, or --request-file.");
}
});

command.SetAction(async (ParseResult parseResult, CancellationToken cancellationToken) =>
await CliRuntime.RunAsync(async () =>
{
var __requestBase = await CliRuntime.ReadRequestOrDefaultAsync<global::Gradium.VoiceGenerationRequest>(
parseResult,
Input,
RequestJson,
RequestFile,
global::Gradium.SourceGenerationContext.Default,
cancellationToken).ConfigureAwait(false);
var prompt = parseResult.GetRequiredValue(Prompt);
var language = parseResult.GetRequiredValue(Language);
var nSamples = CliRuntime.WasSpecified(parseResult, NSamples) ? parseResult.GetValue(NSamples) : (__requestBase is { } __NSamplesBaseValue ? __NSamplesBaseValue.NSamples : default);
var jsonConfig = CliRuntime.WasSpecified(parseResult, JsonConfig) ? parseResult.GetValue(JsonConfig) : (__requestBase is { } __JsonConfigBaseValue ? __JsonConfigBaseValue.JsonConfig : default);
using var client = await CliRuntime.CreateClientAsync(parseResult, cancellationToken).ConfigureAwait(false);


var response = await client.VoiceDesign.GenerateVoiceVoiceGeneratorGeneratePostAsync(
prompt: prompt,
language: language,
nSamples: nSamples,
jsonConfig: jsonConfig,
cancellationToken: cancellationToken).ConfigureAwait(false);


if (!await CliRuntime.TryWriteOutputDirectoryAsync(
parseResult,
response,
global::Gradium.SourceGenerationContext.Default,
@"Embeddings",
cancellationToken).ConfigureAwait(false))
{
await CliRuntime.WriteResponseAsync(
parseResult,
response,
global::Gradium.SourceGenerationContext.Default,
FormatResponse,
cancellationToken).ConfigureAwait(false);
}
}, cancellationToken).ConfigureAwait(false));
return command;
}
}
Loading