Files
EchoChat/backend/go-service/app/transcribe/controller/transcribe_controller.go
2026-05-18 21:23:00 +08:00

143 lines
4.2 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 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 模型转 DTOsegments 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
}
}