201 lines
4.8 KiB
Go
201 lines
4.8 KiB
Go
package http
|
|
|
|
import (
|
|
"context"
|
|
"net/http"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/mark3labs/mcp-go/server"
|
|
"github.com/rs/zerolog/log"
|
|
|
|
"github.com/sjzar/chatlog/internal/chatlog/conf"
|
|
"github.com/sjzar/chatlog/internal/chatlog/database"
|
|
"github.com/sjzar/chatlog/internal/errors"
|
|
"github.com/sjzar/chatlog/internal/whisper"
|
|
)
|
|
|
|
type Service struct {
|
|
conf Config
|
|
db *database.Service
|
|
|
|
router *gin.Engine
|
|
server *http.Server
|
|
|
|
mcpServer *server.MCPServer
|
|
mcpSSEServer *server.SSEServer
|
|
mcpStreamableServer *server.StreamableHTTPServer
|
|
|
|
speechTranscriber whisper.Transcriber
|
|
speechOptions whisper.Options
|
|
}
|
|
|
|
type Config interface {
|
|
GetHTTPAddr() string
|
|
GetDataDir() string
|
|
GetSpeech() *conf.SpeechConfig
|
|
}
|
|
|
|
func NewService(conf Config, db *database.Service) *Service {
|
|
gin.SetMode(gin.ReleaseMode)
|
|
router := gin.New()
|
|
|
|
// Handle error from SetTrustedProxies
|
|
if err := router.SetTrustedProxies(nil); err != nil {
|
|
log.Err(err).Msg("Failed to set trusted proxies")
|
|
}
|
|
|
|
// Middleware
|
|
router.Use(
|
|
errors.RecoveryMiddleware(),
|
|
errors.ErrorHandlerMiddleware(),
|
|
gin.LoggerWithWriter(log.Logger, "/health"),
|
|
corsMiddleware(),
|
|
)
|
|
|
|
s := &Service{
|
|
conf: conf,
|
|
db: db,
|
|
router: router,
|
|
}
|
|
|
|
s.initMCPServer()
|
|
s.initRouter()
|
|
s.initSpeech(conf)
|
|
return s
|
|
}
|
|
|
|
func (s *Service) initSpeech(cfg Config) {
|
|
if s.speechTranscriber != nil {
|
|
s.speechTranscriber.Close()
|
|
s.speechTranscriber = nil
|
|
}
|
|
|
|
speechCfg := cfg.GetSpeech()
|
|
if speechCfg == nil || !speechCfg.Enabled {
|
|
return
|
|
}
|
|
|
|
speechCfg.Normalize()
|
|
|
|
opts := speechCfg.ToOptions()
|
|
timeout := time.Duration(speechCfg.RequestTimeoutSeconds) * time.Second
|
|
|
|
provider := strings.ToLower(speechCfg.Provider)
|
|
switch provider {
|
|
case "openai":
|
|
transcriber, err := whisper.NewOpenAITranscriber(whisper.OpenAIConfig{
|
|
Model: speechCfg.Model,
|
|
TranslateModel: speechCfg.TranslateModel,
|
|
APIKey: speechCfg.APIKey,
|
|
BaseURL: speechCfg.BaseURL,
|
|
Organization: speechCfg.Organization,
|
|
ProxyURL: speechCfg.Proxy,
|
|
RequestTimeout: timeout,
|
|
DefaultOptions: opts,
|
|
})
|
|
if err != nil {
|
|
log.Err(err).Msg("initialise openai whisper transcriber failed")
|
|
return
|
|
}
|
|
s.speechTranscriber = transcriber
|
|
s.speechOptions = opts
|
|
log.Info().Str("model", transcriber.ModelName()).Msg("speech transcription backend initialised via openai whisper")
|
|
case "webservice", "local", "docker", "http", "whisper-asr":
|
|
transcriber, err := whisper.NewWebServiceTranscriber(whisper.WebServiceConfig{
|
|
BaseURL: speechCfg.ServiceURL,
|
|
OutputFormat: speechCfg.ServiceOutput,
|
|
WordTimestamps: speechCfg.WordTimestamps,
|
|
VADFilter: speechCfg.VADFilter,
|
|
RequestTimeout: timeout,
|
|
DefaultOptions: opts,
|
|
})
|
|
if err != nil {
|
|
log.Err(err).Msg("initialise webservice whisper transcriber failed")
|
|
return
|
|
}
|
|
s.speechTranscriber = transcriber
|
|
s.speechOptions = opts
|
|
log.Info().Str("base_url", speechCfg.ServiceURL).Msg("speech transcription backend initialised via whisper webservice")
|
|
case "whispercpp", "whisper.cpp", "cpp":
|
|
modelPath := strings.TrimSpace(speechCfg.Model)
|
|
transcriber, err := whisper.NewWhisperCPPTranscriber(whisper.WhisperCPPConfig{
|
|
ModelPath: modelPath,
|
|
Threads: speechCfg.Threads,
|
|
DefaultOptions: opts,
|
|
})
|
|
if err != nil {
|
|
log.Err(err).Msg("initialise whisper.cpp transcriber failed")
|
|
return
|
|
}
|
|
s.speechTranscriber = transcriber
|
|
s.speechOptions = opts
|
|
log.Info().Str("model_path", modelPath).Msg("speech transcription backend initialised via whisper.cpp")
|
|
default:
|
|
log.Warn().Str("provider", speechCfg.Provider).Msg("unsupported speech provider; speech transcription disabled")
|
|
}
|
|
}
|
|
|
|
func (s *Service) ReloadSpeech() {
|
|
s.initSpeech(s.conf)
|
|
}
|
|
|
|
func (s *Service) Start() error {
|
|
|
|
s.server = &http.Server{
|
|
Addr: s.conf.GetHTTPAddr(),
|
|
Handler: s.router,
|
|
}
|
|
|
|
go func() {
|
|
// Handle error from Run
|
|
if err := s.server.ListenAndServe(); err != nil {
|
|
log.Err(err).Msg("Failed to start HTTP server")
|
|
}
|
|
}()
|
|
|
|
log.Info().Msg("Starting HTTP server on " + s.conf.GetHTTPAddr())
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *Service) ListenAndServe() error {
|
|
|
|
s.server = &http.Server{
|
|
Addr: s.conf.GetHTTPAddr(),
|
|
Handler: s.router,
|
|
}
|
|
|
|
log.Info().Msg("Starting HTTP server on " + s.conf.GetHTTPAddr())
|
|
return s.server.ListenAndServe()
|
|
}
|
|
|
|
func (s *Service) Stop() error {
|
|
|
|
if s.server == nil {
|
|
return nil
|
|
}
|
|
|
|
// 使用超时上下文优雅关闭
|
|
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
|
|
defer cancel()
|
|
|
|
if err := s.server.Shutdown(ctx); err != nil {
|
|
log.Debug().Err(err).Msg("Failed to shutdown HTTP server")
|
|
return nil
|
|
}
|
|
|
|
if s.speechTranscriber != nil {
|
|
s.speechTranscriber.Close()
|
|
s.speechTranscriber = nil
|
|
}
|
|
|
|
log.Info().Msg("HTTP server stopped")
|
|
return nil
|
|
}
|
|
|
|
func (s *Service) GetRouter() *gin.Engine {
|
|
return s.router
|
|
}
|