Files
voicepaste/main.go
imbytecat 8c7b9b45fd feat: 启用豆包二遍识别模式以提升实时性和准确率
- 切换到 bigmodel_async endpoint 并启用 enable_nonstream
- 第一遍流式识别提供实时文字预览
- VAD 分句后自动触发第二遍非流式识别提升准确率
- 修改文本处理逻辑从累加改为替换(适配 full 模式)
- 统一配置字段命名:app_key → app_id, access_key → access_token
2026-03-01 21:34:54 +08:00

156 lines
4.1 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 main
import (
crypto_tls "crypto/tls"
"embed"
"fmt"
"io/fs"
"log/slog"
"os"
"os/signal"
"syscall"
"github.com/imbytecat/voicepaste/internal/asr"
"github.com/imbytecat/voicepaste/internal/config"
"github.com/imbytecat/voicepaste/internal/paste"
"github.com/imbytecat/voicepaste/internal/server"
vpTLS "github.com/imbytecat/voicepaste/internal/tls"
"github.com/imbytecat/voicepaste/internal/ws"
)
//go:embed all:web/dist
var webFS embed.FS
var version = "dev"
func main() {
slog.SetDefault(slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{
Level: slog.LevelInfo,
})))
slog.Info("VoicePaste", "version", version)
// Load config
cfg, err := config.Load("")
if err != nil {
slog.Error("failed to load config", "error", err)
os.Exit(1)
}
// Start config hot-reload watcher
config.WatchAndReload("")
// Initialize clipboard
if err := paste.Init(); err != nil {
slog.Warn("clipboard init failed, paste will be unavailable", "err", err)
}
// Detect LAN IPs
lanIPs, err := server.GetLANIPs()
if err != nil {
slog.Error("failed to detect LAN IP", "error", err)
os.Exit(1)
}
lanIP := lanIPs[0] // Use first IP for TLS and server binding
// Read token from config (empty = no auth required)
token := cfg.Security.Token
// TLS setup
var tlsResult *vpTLS.Result
scheme := "http"
host := lanIP
if cfg.Server.TLSAuto {
var err error
tlsResult, err = vpTLS.GetTLSConfig(lanIP)
if err != nil {
slog.Error("TLS setup failed", "error", err)
os.Exit(1)
}
scheme = "https"
host = tlsResult.Host
}
// Print connection info
fmt.Println()
fmt.Println("╔══════════════════════════════════════╗")
fmt.Println("║ VoicePaste 就绪 ║")
fmt.Println("╚══════════════════════════════════════╝")
fmt.Println()
// Print all accessible addresses
if len(lanIPs) == 1 {
fmt.Printf(" 地址: %s\n", buildURL(scheme, host, cfg.Server.Port, token))
} else {
fmt.Println(" 地址:")
for _, ip := range lanIPs {
h := ip
if tlsResult != nil && tlsResult.AnyIP {
h = vpTLS.AnyIPHost(ip)
}
fmt.Printf(" - %s\n", buildURL(scheme, h, cfg.Server.Port, token))
}
}
if tlsResult != nil && tlsResult.AnyIP {
fmt.Println(" 证书: AnyIP浏览器信任")
} else if cfg.Server.TLSAuto {
fmt.Println(" 证书: 自签名(浏览器会警告)")
}
if token != "" {
fmt.Println(" 认证: 已启用")
} else {
fmt.Println(" 认证: 未启用(无需 token")
}
fmt.Println()
fmt.Println(" 在手机浏览器中打开上方地址")
fmt.Println(" 按 Ctrl+C 停止服务")
fmt.Println()
// Create and start server
webContent, _ := fs.Sub(webFS, "web/dist")
var serverTLSCfg *crypto_tls.Config
if tlsResult != nil {
serverTLSCfg = tlsResult.Config
}
srv := server.New(token, lanIP, webContent, serverTLSCfg)
// Build ASR factory from config
asrCfg := asr.Config{
AppID: cfg.Doubao.AppID,
AccessToken: cfg.Doubao.AccessToken,
ResourceID: cfg.Doubao.ResourceID,
}
asrFactory := func(resultCh chan<- ws.ServerMsg) (func([]byte), func(), error) {
client, err := asr.Dial(asrCfg, resultCh)
if err != nil {
return nil, nil, err
}
sendAudio := func(pcm []byte) {
if err := client.SendAudio(pcm, false); err != nil {
slog.Warn("send audio to asr", "err", err)
}
}
cleanup := func() {
client.Finish()
}
return sendAudio, cleanup, nil
}
// Register WebSocket handler
wsHandler := ws.NewHandler(token, paste.Paste, asrFactory)
wsHandler.Register(srv.App())
// Graceful shutdown
go func() {
sigCh := make(chan os.Signal, 1)
signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
<-sigCh
slog.Info("shutting down...")
srv.Shutdown()
}()
if err := srv.Start(); err != nil {
slog.Error("server error", "error", err)
os.Exit(1)
}
}
func buildURL(scheme, host string, port int, token string) string {
if token != "" {
return fmt.Sprintf("%s://%s:%d/?token=%s", scheme, host, port, token)
}
return fmt.Sprintf("%s://%s:%d/", scheme, host, port)
}