feat: 长辈模式(大字大按钮 + 偏好按账号隔离)+ 语音输入(按住说话 + 实时转写 + 后端代理 DashScope ASR + 患者端专用)+ 健康仪表盘背景渐变
This commit is contained in:
69
backend/src/Health.WebApi/Endpoints/speech_endpoints.cs
Normal file
69
backend/src/Health.WebApi/Endpoints/speech_endpoints.cs
Normal file
@@ -0,0 +1,69 @@
|
||||
using System.Net.WebSockets;
|
||||
using System.Text.Json;
|
||||
using Health.Application.Speech;
|
||||
|
||||
namespace Health.WebApi.Endpoints;
|
||||
|
||||
public static class SpeechEndpoints
|
||||
{
|
||||
public static void MapSpeechEndpoints(this WebApplication app)
|
||||
{
|
||||
app.Map("/api/speech/realtime", async (
|
||||
HttpContext http,
|
||||
IRealtimeSpeechRecognitionProxy proxy,
|
||||
ILoggerFactory loggerFactory) =>
|
||||
{
|
||||
if (!http.WebSockets.IsWebSocketRequest)
|
||||
{
|
||||
http.Response.StatusCode = StatusCodes.Status400BadRequest;
|
||||
await http.Response.WriteAsJsonAsync(new
|
||||
{
|
||||
code = 40001,
|
||||
data = (object?)null,
|
||||
message = "该接口仅支持 WebSocket 连接",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (!proxy.IsConfigured)
|
||||
{
|
||||
http.Response.StatusCode = StatusCodes.Status503ServiceUnavailable;
|
||||
await http.Response.WriteAsJsonAsync(new
|
||||
{
|
||||
code = 50301,
|
||||
data = (object?)null,
|
||||
message = "实时语音识别服务尚未配置",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
using var socket = await http.WebSockets.AcceptWebSocketAsync();
|
||||
try
|
||||
{
|
||||
await proxy.ProxyAsync(socket, http.RequestAborted);
|
||||
}
|
||||
catch (OperationCanceledException) when (http.RequestAborted.IsCancellationRequested)
|
||||
{
|
||||
// 客户端离开页面或断开网络。
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
loggerFactory.CreateLogger("SpeechEndpoints")
|
||||
.LogError(ex, "Realtime speech proxy failed");
|
||||
if (socket.State == WebSocketState.Open)
|
||||
{
|
||||
var payload = JsonSerializer.SerializeToUtf8Bytes(new
|
||||
{
|
||||
type = "error",
|
||||
message = "语音服务暂时不可用,请稍后重试",
|
||||
});
|
||||
await socket.SendAsync(
|
||||
payload,
|
||||
WebSocketMessageType.Text,
|
||||
true,
|
||||
CancellationToken.None);
|
||||
}
|
||||
}
|
||||
}).RequireAuthorization(policy => policy.RequireRole("User"));
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,7 @@ using Health.Application.Medications;
|
||||
using Health.Application.Maintenance;
|
||||
using Health.Application.Notifications;
|
||||
using Health.Application.Reports;
|
||||
using Health.Application.Speech;
|
||||
using Health.Application.Users;
|
||||
using Health.Infrastructure.AI;
|
||||
using Health.Infrastructure.Admin;
|
||||
@@ -25,6 +26,7 @@ using Health.Infrastructure.Medications;
|
||||
using Health.Infrastructure.Maintenance;
|
||||
using Health.Infrastructure.Notifications;
|
||||
using Health.Infrastructure.Reports;
|
||||
using Health.Infrastructure.Speech;
|
||||
using Health.Infrastructure.Services;
|
||||
using Health.Infrastructure.Users;
|
||||
using Health.WebApi.BackgroundServices;
|
||||
@@ -150,6 +152,7 @@ builder.Services.AddScoped<IReportAnalysisService, ReportAnalysisService>();
|
||||
builder.Services.AddScoped<IReportAnalysisQueue, EfReportAnalysisQueue>();
|
||||
builder.Services.AddScoped<IUserService, UserService>();
|
||||
builder.Services.AddScoped<IUserRepository, EfUserRepository>();
|
||||
builder.Services.AddSingleton<IRealtimeSpeechRecognitionProxy, DashScopeSpeechRecognitionProxy>();
|
||||
builder.Services.AddScoped<IAccountFileCleanup>(_ => new LocalAccountFileCleanup(
|
||||
Path.Combine(Directory.GetCurrentDirectory(), "uploads")));
|
||||
|
||||
@@ -205,6 +208,10 @@ app.UseMiddleware<ExceptionMiddleware>();
|
||||
app.UseCors();
|
||||
app.UseAuthentication();
|
||||
app.UseAuthorization();
|
||||
app.UseWebSockets(new WebSocketOptions
|
||||
{
|
||||
KeepAliveInterval = TimeSpan.FromSeconds(20),
|
||||
});
|
||||
|
||||
// 默认 wwwroot 静态文件(法律文档 H5 页面等)
|
||||
app.UseDefaultFiles();
|
||||
@@ -240,6 +247,7 @@ app.MapNotificationEndpoints();
|
||||
app.MapDoctorEndpoints();
|
||||
app.MapAdminEndpoints();
|
||||
app.MapFollowUpEndpoints();
|
||||
app.MapSpeechEndpoints();
|
||||
|
||||
// SignalR Hub
|
||||
app.MapHub<Health.WebApi.Hubs.ConsultationHub>("/hubs/consultation");
|
||||
|
||||
Reference in New Issue
Block a user