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
2 changes: 1 addition & 1 deletion App.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
</Application.Styles>
<TrayIcon.Icons>
<TrayIcons>
<TrayIcon Icon="avares://LocalLLMServerManager/Assets/app-icon.ico" ToolTipText="Local LLM Server Manager v3.18.0" IsVisible="True">
<TrayIcon Icon="avares://LocalLLMServerManager/Assets/app-icon.ico" ToolTipText="Local LLM Server Manager v4.0.0" IsVisible="True">
<TrayIcon.Menu>
<NativeMenu>
<NativeMenuItem Header="Open Native Dashboard" Click="OnOpenDashboardClick"/>
Expand Down
65 changes: 33 additions & 32 deletions Endpoints/EngineEndpoints.cs
Original file line number Diff line number Diff line change
Expand Up @@ -121,37 +121,7 @@ public static void MapEngineEndpoints(this WebApplication app)
return Results.Ok(new { message = "Audio Engine Stopped" });
});

app.MapGet("/api/audio/voices", async (ISettingsService settingsService, System.Net.Http.IHttpClientFactory clientFactory) =>
{
var settings = settingsService.LoadSettings();
var baseUrl = (string.IsNullOrWhiteSpace(settings.AudioEngineUrl) ? "http://127.0.0.1:8880" : settings.AudioEngineUrl).TrimEnd('/');

try
{
var client = clientFactory.CreateClient();
using var cts = new System.Threading.CancellationTokenSource(TimeSpan.FromSeconds(3));

var response = await client.GetAsync($"{baseUrl}/v1/audio/voices", cts.Token);
if (!response.IsSuccessStatusCode)
{
response = await client.GetAsync($"{baseUrl}/voices", cts.Token);
}

if (response.IsSuccessStatusCode)
{
var json = await response.Content.ReadAsStringAsync(cts.Token);
return Results.Content(json, "application/json");
}
}
catch { }

var defaultVoices = new[]
{
"af_heart", "af_bella", "af_nicole", "af_sarah", "af_sky",
"am_adam", "am_michael", "bf_emma", "bf_isabella", "bm_george", "bm_fable"
};
return Results.Ok(new { voices = defaultVoices, preferred = settings.PreferredAudioVoice });
});
app.MapGet("/api/audio/voices", HandleGetVoicesAsync);

app.MapPost("/api/ollama/start", async (IAiEngineManager engineManager, ISettingsService settingsService, ILoggerFactory loggerFactory) =>
{
Expand Down Expand Up @@ -194,7 +164,38 @@ public static void MapEngineEndpoints(this WebApplication app)
return Results.Problem(result.Message);
});
}

public static async Task<IResult> HandleGetVoicesAsync(ISettingsService settingsService, System.Net.Http.IHttpClientFactory clientFactory)
{
var settings = settingsService.LoadSettings();
var baseUrl = (string.IsNullOrWhiteSpace(settings.AudioEngineUrl) ? "http://127.0.0.1:8880" : settings.AudioEngineUrl).TrimEnd('/');

try
{
var client = clientFactory.CreateClient();
using var cts = new System.Threading.CancellationTokenSource(TimeSpan.FromSeconds(3));

var response = await client.GetAsync($"{baseUrl}/v1/audio/voices", cts.Token);
if (!response.IsSuccessStatusCode)
{
response = await client.GetAsync($"{baseUrl}/voices", cts.Token);
}

if (response.IsSuccessStatusCode)
{
var json = await response.Content.ReadAsStringAsync(cts.Token);
return Results.Content(json, "application/json");
}
}
catch { }

var defaultVoices = new[]
{
"af_heart", "af_bella", "af_nicole", "af_sarah", "af_sky",
"am_adam", "am_michael", "bf_emma", "bf_isabella", "bm_george", "bm_fable"
};
return Results.Ok(new { voices = defaultVoices, preferred = settings.PreferredAudioVoice });
}
}

public record EngineToggleRequest(string? Engine);

2 changes: 1 addition & 1 deletion Endpoints/HealthEndpoints.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public static void MapHealthEndpoints(this WebApplication app)
StableDiffusion = forgeHealthy ? "Online" : "Offline",
ComfyUI = comfyHealthy ? "Online" : "Offline",
PreferredImageEngine = settings.PreferredImageEngine,
Version = "3.18.0",
Version = "4.0.0",
});
});
}
Expand Down
82 changes: 42 additions & 40 deletions Endpoints/ModelProxyEndpoints.cs
Original file line number Diff line number Diff line change
Expand Up @@ -349,58 +349,60 @@ async Task<IResult> HandleDeleteModel(HttpContext context, IHttpClientFactory cl
}
});

app.MapPost("/v1/audio/speech", async (HttpContext context, ISettingsService settingsService, IHttpClientFactory clientFactory) =>
app.MapPost("/v1/audio/speech", HandleSpeechProxyAsync);
}

public static async Task HandleSpeechProxyAsync(HttpContext context, ISettingsService settingsService, IHttpClientFactory clientFactory)
{
try
{
try
{
var settings = settingsService.LoadSettings();
var baseUrl = (string.IsNullOrWhiteSpace(settings.AudioEngineUrl) ? "http://127.0.0.1:8880" : settings.AudioEngineUrl).TrimEnd('/');
var targetUrl = $"{baseUrl}/v1/audio/speech";
var settings = settingsService.LoadSettings();
var baseUrl = (string.IsNullOrWhiteSpace(settings.AudioEngineUrl) ? "http://127.0.0.1:8880" : settings.AudioEngineUrl).TrimEnd('/');
var targetUrl = $"{baseUrl}/v1/audio/speech";

using var reader = new StreamReader(context.Request.Body);
var requestBodyStr = await reader.ReadToEndAsync();
using var reader = new StreamReader(context.Request.Body);
var requestBodyStr = await reader.ReadToEndAsync();

string outgoingJson = requestBodyStr;
if (!string.IsNullOrWhiteSpace(requestBodyStr))
string outgoingJson = requestBodyStr;
if (!string.IsNullOrWhiteSpace(requestBodyStr))
{
try
{
try
{
using var doc = JsonDocument.Parse(requestBodyStr);
var root = doc.RootElement;
var hasVoice = root.TryGetProperty("voice", out var voiceProp) && !string.IsNullOrWhiteSpace(voiceProp.GetString());
using var doc = JsonDocument.Parse(requestBodyStr);
var root = doc.RootElement;
var hasVoice = root.TryGetProperty("voice", out var voiceProp) && !string.IsNullOrWhiteSpace(voiceProp.GetString());

if (!hasVoice)
{
var dict = JsonSerializer.Deserialize<Dictionary<string, object>>(requestBodyStr) ?? new Dictionary<string, object>();
dict["voice"] = string.IsNullOrWhiteSpace(settings.PreferredAudioVoice) ? "af_heart" : settings.PreferredAudioVoice;
outgoingJson = JsonSerializer.Serialize(dict);
}
if (!hasVoice)
{
var dict = JsonSerializer.Deserialize<Dictionary<string, object>>(requestBodyStr) ?? new Dictionary<string, object>();
dict["voice"] = string.IsNullOrWhiteSpace(settings.PreferredAudioVoice) ? "af_heart" : settings.PreferredAudioVoice;
outgoingJson = JsonSerializer.Serialize(dict);
}
catch { }
}
catch { }
}

var http = clientFactory.CreateClient();
using var targetReq = new HttpRequestMessage(HttpMethod.Post, targetUrl);
targetReq.Content = new StringContent(outgoingJson, System.Text.Encoding.UTF8, "application/json");
var http = clientFactory.CreateClient();
using var targetReq = new HttpRequestMessage(HttpMethod.Post, targetUrl);
targetReq.Content = new StringContent(outgoingJson, System.Text.Encoding.UTF8, "application/json");

var targetResponse = await http.SendAsync(targetReq, HttpCompletionOption.ResponseHeadersRead, context.RequestAborted);
var targetResponse = await http.SendAsync(targetReq, HttpCompletionOption.ResponseHeadersRead, context.RequestAborted);

context.Response.StatusCode = (int)targetResponse.StatusCode;
var contentType = targetResponse.Content.Headers.ContentType?.ToString() ?? "audio/mpeg";
context.Response.ContentType = contentType;
context.Response.StatusCode = (int)targetResponse.StatusCode;
var contentType = targetResponse.Content.Headers.ContentType?.ToString() ?? "audio/mpeg";
context.Response.ContentType = contentType;

await using var responseStream = await targetResponse.Content.ReadAsStreamAsync(context.RequestAborted);
await responseStream.CopyToAsync(context.Response.Body, context.RequestAborted);
}
catch (Exception ex)
await using var responseStream = await targetResponse.Content.ReadAsStreamAsync(context.RequestAborted);
await responseStream.CopyToAsync(context.Response.Body, context.RequestAborted);
}
catch (Exception ex)
{
if (!context.Response.HasStarted)
{
if (!context.Response.HasStarted)
{
context.Response.StatusCode = StatusCodes.Status502BadGateway;
context.Response.ContentType = "application/json";
await context.Response.WriteAsync(JsonSerializer.Serialize(new { error = ex.Message }));
}
context.Response.StatusCode = StatusCodes.Status502BadGateway;
context.Response.ContentType = "application/json";
await context.Response.WriteAsync(JsonSerializer.Serialize(new { error = ex.Message }));
}
});
}
}
}
Loading
Loading