143 lines
4.2 KiB
Go
143 lines
4.2 KiB
Go
// Package controller 提供 transcribe 模块的 admin HTTP 接口
|
||
package controller
|
||
|
||
import (
|
||
"encoding/json"
|
||
"errors"
|
||
"net/http"
|
||
"strconv"
|
||
|
||
"github.com/echochat/backend/app/dto"
|
||
"github.com/echochat/backend/app/transcribe/dao"
|
||
"github.com/echochat/backend/app/transcribe/model"
|
||
"github.com/echochat/backend/app/transcribe/service"
|
||
"github.com/echochat/backend/pkg/utils"
|
||
"github.com/gin-gonic/gin"
|
||
)
|
||
|
||
// TranscribeController admin 端转写控制器
|
||
type TranscribeController struct {
|
||
svc *service.TranscribeService
|
||
}
|
||
|
||
// NewTranscribeController 创建实例
|
||
func NewTranscribeController(svc *service.TranscribeService) *TranscribeController {
|
||
return &TranscribeController{svc: svc}
|
||
}
|
||
|
||
// GetTranscript GET /admin/recordings/:id/transcript
|
||
//
|
||
// 返回值:
|
||
// - 200 + transcript:存在时返回最新状态
|
||
// - 200 + null:尚未发起转写(前端据此显示"开始转写"按钮)
|
||
func (ctl *TranscribeController) GetTranscript(c *gin.Context) {
|
||
id, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
||
if err != nil || id <= 0 {
|
||
utils.ResponseBadRequest(c, "无效的录制 ID")
|
||
return
|
||
}
|
||
|
||
t, err := ctl.svc.GetByRecording(c.Request.Context(), id)
|
||
if err != nil {
|
||
utils.ResponseError(c, "查询转写失败")
|
||
return
|
||
}
|
||
utils.ResponseOK(c, toDTO(t))
|
||
}
|
||
|
||
// SubmitTranscribe POST /admin/recordings/:id/transcribe
|
||
//
|
||
// body: { force: bool, language: string }
|
||
//
|
||
// 行为:异步启动 STT,返回 running 状态快照
|
||
func (ctl *TranscribeController) SubmitTranscribe(c *gin.Context) {
|
||
id, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
||
if err != nil || id <= 0 {
|
||
utils.ResponseBadRequest(c, "无效的录制 ID")
|
||
return
|
||
}
|
||
|
||
var req dto.AdminTranscribeRequest
|
||
// body 可选;POST 不带 body 也允许(默认 force=false language="")
|
||
_ = c.ShouldBindJSON(&req)
|
||
|
||
t, err := ctl.svc.Submit(c.Request.Context(), id, req.Force, req.Language)
|
||
if err != nil {
|
||
ctl.handleError(c, err, t)
|
||
return
|
||
}
|
||
utils.ResponseOK(c, toDTO(t))
|
||
}
|
||
|
||
// handleError 统一业务错误映射
|
||
func (ctl *TranscribeController) handleError(c *gin.Context, err error, t *model.Transcript) {
|
||
switch {
|
||
case errors.Is(err, service.ErrRecordingNotFound):
|
||
utils.ResponseNotFound(c, err.Error())
|
||
case errors.Is(err, service.ErrRecordingNotReady):
|
||
utils.ResponseBadRequest(c, err.Error())
|
||
case errors.Is(err, service.ErrSTTNotConfigured), errors.Is(err, dao.ErrLLMSourceDisabled):
|
||
// 503 表示服务暂不可用,前端可据此提示"请联系管理员配置 STT"
|
||
c.JSON(http.StatusServiceUnavailable, gin.H{
|
||
"code": 503,
|
||
"message": "语音转写服务未配置或不可用",
|
||
"data": nil,
|
||
})
|
||
case errors.Is(err, service.ErrTranscribeRunning):
|
||
// 已在跑:返回当前进行中行,前端可继续轮询
|
||
c.JSON(http.StatusAccepted, gin.H{
|
||
"code": 0,
|
||
"message": "已有转写任务进行中",
|
||
"data": toDTO(t),
|
||
})
|
||
case errors.Is(err, dao.ErrNoActiveSTT):
|
||
utils.ResponseBadRequest(c, "未找到可用的 STT 模型或 API Key,请检查 LLM 配置中心")
|
||
default:
|
||
utils.ResponseError(c, "发起转写失败: "+err.Error())
|
||
}
|
||
}
|
||
|
||
// toDTO 模型转 DTO,segments JSON 解码 + 状态中文标签
|
||
func toDTO(t *model.Transcript) *dto.AdminTranscriptDTO {
|
||
if t == nil {
|
||
return nil
|
||
}
|
||
segs := []dto.AdminTranscriptSeg{}
|
||
if t.Segments != "" {
|
||
_ = json.Unmarshal([]byte(t.Segments), &segs)
|
||
}
|
||
return &dto.AdminTranscriptDTO{
|
||
ID: t.ID,
|
||
RecordingID: t.RecordingID,
|
||
RoomID: t.RoomID,
|
||
Status: t.Status,
|
||
StatusLabel: statusLabel(t.Status),
|
||
Text: t.Text,
|
||
Segments: segs,
|
||
Language: t.Language,
|
||
DurationSec: t.DurationSec,
|
||
ProviderCode: t.ProviderCode,
|
||
ModelCode: t.ModelCode,
|
||
ErrorMsg: t.ErrorMsg,
|
||
StartedAt: dto.FormatTranscriptTimePtr(t.StartedAt),
|
||
FinishedAt: dto.FormatTranscriptTimePtr(t.FinishedAt),
|
||
CreatedAt: dto.FormatTranscriptTime(t.CreatedAt),
|
||
UpdatedAt: dto.FormatTranscriptTime(t.UpdatedAt),
|
||
}
|
||
}
|
||
|
||
func statusLabel(s string) string {
|
||
switch s {
|
||
case model.TranscriptStatusPending:
|
||
return "待处理"
|
||
case model.TranscriptStatusRunning:
|
||
return "转写中"
|
||
case model.TranscriptStatusReady:
|
||
return "已就绪"
|
||
case model.TranscriptStatusFailed:
|
||
return "失败"
|
||
default:
|
||
return s
|
||
}
|
||
}
|