Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1e779416cc |
14
.env
14
.env
@ -1,15 +1,11 @@
|
|||||||
# LLM API Configuration
|
# LLM API Configuration
|
||||||
LLM_API_URL=http://101.133.149.116:8777/v1
|
LLM_API_URL=http://tianchat.zenithsafe.com:5001/v1
|
||||||
LLM_API_KEY=app-7mh1IAGueaBodwdMflz8Omqv
|
LLM_API_KEY=app-k9WhnUvAPCVcSoPDEYVUxXgC
|
||||||
|
|
||||||
LLMOurApiUrl=https://ark.cn-beijing.volces.com/api/v3/bots/chat/completions
|
|
||||||
LLMOurApiKey=e999a241-6bf3-4ee0-99a8-e4de9b617f28
|
|
||||||
|
|
||||||
MiniMaxApiKey=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJHcm91cE5hbWUiOiLkuIrmtbfpopzpgJTnp5HmioDmnInpmZDlhazlj7giLCJVc2VyTmFtZSI6IuadqOmqpSIsIkFjY291bnQiOiIiLCJTdWJqZWN0SUQiOiIxNzI4NzEyMzI0OTc5NjI2ODM5IiwiUGhvbmUiOiIxMzM4MTU1OTYxOCIsIkdyb3VwSUQiOiIxNzI4NzEyMzI0OTcxMjM4MjMxIiwiUGFnZU5hbWUiOiIiLCJNYWlsIjoiIiwiQ3JlYXRlVGltZSI6IjIwMjUtMDYtMTYgMTY6Mjk6NTkiLCJUb2tlblR5cGUiOjEsImlzcyI6Im1pbmltYXgifQ.D_JF0-nO89NdMZCYq4ocEyqxtZ9SeEdtMvbeSkZTWspt0XfX2QpPAVh-DI3MCPZTeSmjNWLf4fA_Th2zpVrj4UxWMbGKBeLZWLulNpwAHGMUTdqenuih3daCDPCzs0duhlFyQnZgGcEOGQ476HL72N2klujP8BUy_vfAh_Zv0po-aujQa5RxardDSOsbs49NTPEw0SQEXwaJ5bVmiZ5s-ysJ9pZWSEiyJ6SX9z3JeZHKj9DxHdOw5roZR8izo54e4IoqyLlzEfhOMW7P15-ffDH3M6HGiEmeBaGRYGAIciELjZS19ONNMKsTj-wXNGWtKG-sjAB1uuqkkT5Ul9Dunw
|
MiniMaxApiKey=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJHcm91cE5hbWUiOiLkuIrmtbfpopzpgJTnp5HmioDmnInpmZDlhazlj7giLCJVc2VyTmFtZSI6IuadqOmqpSIsIkFjY291bnQiOiIiLCJTdWJqZWN0SUQiOiIxNzI4NzEyMzI0OTc5NjI2ODM5IiwiUGhvbmUiOiIxMzM4MTU1OTYxOCIsIkdyb3VwSUQiOiIxNzI4NzEyMzI0OTcxMjM4MjMxIiwiUGFnZU5hbWUiOiIiLCJNYWlsIjoiIiwiQ3JlYXRlVGltZSI6IjIwMjUtMDYtMTYgMTY6Mjk6NTkiLCJUb2tlblR5cGUiOjEsImlzcyI6Im1pbmltYXgifQ.D_JF0-nO89NdMZCYq4ocEyqxtZ9SeEdtMvbeSkZTWspt0XfX2QpPAVh-DI3MCPZTeSmjNWLf4fA_Th2zpVrj4UxWMbGKBeLZWLulNpwAHGMUTdqenuih3daCDPCzs0duhlFyQnZgGcEOGQ476HL72N2klujP8BUy_vfAh_Zv0po-aujQa5RxardDSOsbs49NTPEw0SQEXwaJ5bVmiZ5s-ysJ9pZWSEiyJ6SX9z3JeZHKj9DxHdOw5roZR8izo54e4IoqyLlzEfhOMW7P15-ffDH3M6HGiEmeBaGRYGAIciELjZS19ONNMKsTj-wXNGWtKG-sjAB1uuqkkT5Ul9Dunw
|
||||||
MiniMaxApiURL=https://api.minimaxi.com/v1/t2a_v2
|
MiniMaxApiURL=https://api.minimaxi.com/v1/t2a_v2
|
||||||
APP_ID=1364966010532270080
|
APP_ID=1364994890450210816
|
||||||
APP_KEY=a72c98fa-cbe3-449e-b004-36523437bc5d
|
APP_KEY=b4839cb2-cb81-4472-a2c1-2abf31e4bb27
|
||||||
SIG_EXP=3600
|
SIG_EXP=3600
|
||||||
FILE_URL=http://14.103.170.252:6200/
|
FILE_URL=http://172.17.0.1:6200/
|
||||||
# Server Configuration
|
# Server Configuration
|
||||||
PORT=8080
|
PORT=8080
|
||||||
@ -39,7 +39,8 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
docker run -d --rm --name gong-zheng-api \
|
docker run -d --rm --name gong-zheng-api \
|
||||||
-v /usr/share/fonts/opentype/noto:/usr/share/fonts \
|
-v /usr/share/fonts/opentype/noto:/usr/share/fonts \
|
||||||
-v /root/files:/usr/src/app/audio \
|
-v $(pwd)/audio:/app/audio \
|
||||||
-p 6211:8080 \
|
-p 6211:8080 \
|
||||||
|
-p 6212:8000 \
|
||||||
gong-zheng-api:${{ gitea.run_id }}
|
gong-zheng-api:${{ gitea.run_id }}
|
||||||
- run: echo "🍏 This job's status is ${{ job.status }}."
|
- run: echo "🍏 This job's status is ${{ job.status }}."
|
||||||
53
Dockerfile
53
Dockerfile
@ -1,31 +1,56 @@
|
|||||||
# 构建 Go 服务
|
# 构建 Go 服务
|
||||||
FROM golang:1.21-alpine AS go-builder
|
FROM golang:1.21-alpine AS go-builder
|
||||||
|
|
||||||
WORKDIR /usr/src/app
|
WORKDIR /app
|
||||||
|
|
||||||
# 安装必要的构建工具
|
# 安装必要的构建工具
|
||||||
RUN apk add --no-cache gcc musl-dev
|
RUN apk add --no-cache gcc musl-dev
|
||||||
|
|
||||||
# 设置 GOPROXY 环境变量
|
|
||||||
ENV GOPROXY=https://goproxy.cn,direct
|
|
||||||
|
|
||||||
# 复制 Go 项目文件
|
# 复制 Go 项目文件
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
# 构建 Go 服务
|
# 构建 Go 服务
|
||||||
RUN go build -o main ./main.go
|
RUN go build -o main ./main.go
|
||||||
|
|
||||||
# 运行阶段
|
# 构建 Python 服务
|
||||||
FROM alpine:latest
|
FROM python:3.11-slim
|
||||||
|
|
||||||
WORKDIR /usr/src/app
|
WORKDIR /app
|
||||||
|
|
||||||
# 从构建阶段复制编译好的二进制文件和配置文件
|
# 安装必要的系统依赖
|
||||||
COPY --from=go-builder /usr/src/app/main .
|
RUN apt-get update && apt-get install -y \
|
||||||
COPY --from=go-builder /usr/src/app/.env .
|
ca-certificates \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
# 暴露端口(根据你的 API 服务端口修改)
|
# 创建音频目录
|
||||||
EXPOSE 8080
|
RUN mkdir -p /app/audio
|
||||||
|
|
||||||
# 运行服务
|
# 复制 Python 文件服务器
|
||||||
CMD ["./main"]
|
COPY file_server.py .
|
||||||
|
|
||||||
|
# 从 go-builder 阶段复制编译好的 Go 服务
|
||||||
|
COPY --from=go-builder /app/main .
|
||||||
|
|
||||||
|
# 复制配置文件(如果有的话)
|
||||||
|
COPY --from=go-builder /app/config.yaml .
|
||||||
|
|
||||||
|
# 设置环境变量
|
||||||
|
ENV PORT=8000
|
||||||
|
ENV GO_PORT=8080
|
||||||
|
|
||||||
|
# 创建启动脚本
|
||||||
|
RUN echo '#!/bin/bash\n\
|
||||||
|
# 启动 Go 服务\n\
|
||||||
|
./main &\n\
|
||||||
|
# 启动 Python 文件服务器\n\
|
||||||
|
python file_server.py -p $PORT\n\
|
||||||
|
' > /app/start.sh && chmod +x /app/start.sh
|
||||||
|
|
||||||
|
# 暴露端口
|
||||||
|
EXPOSE 8000 8080
|
||||||
|
|
||||||
|
# 设置工作目录
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# 启动服务
|
||||||
|
CMD ["/app/start.sh"]
|
||||||
@ -4,9 +4,11 @@ services:
|
|||||||
app:
|
app:
|
||||||
build: .
|
build: .
|
||||||
ports:
|
ports:
|
||||||
|
- "8000:8000" # Python 文件服务器端口
|
||||||
- "8080:8080" # Go 服务端口
|
- "8080:8080" # Go 服务端口
|
||||||
volumes:
|
volumes:
|
||||||
- ./audio:/app/audio # 挂载音频目录
|
- ./audio:/app/audio # 挂载音频目录
|
||||||
environment:
|
environment:
|
||||||
|
- PORT=8000
|
||||||
- GO_PORT=8080
|
- GO_PORT=8080
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
@ -62,6 +62,7 @@ class FileHandler(http.server.SimpleHTTPRequestHandler):
|
|||||||
with open(file_path, 'rb') as f:
|
with open(file_path, 'rb') as f:
|
||||||
self.send_response(200)
|
self.send_response(200)
|
||||||
self.send_header('Content-type', content_type)
|
self.send_header('Content-type', content_type)
|
||||||
|
self.send_header('Content-Disposition', f'attachment; filename="{os.path.basename(file_path)}"')
|
||||||
self.end_headers()
|
self.end_headers()
|
||||||
self.wfile.write(f.read())
|
self.wfile.write(f.read())
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|||||||
5
main.go
5
main.go
@ -1,7 +1,6 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
@ -26,12 +25,8 @@ func main() {
|
|||||||
MiniMaxApiKey: os.Getenv("MiniMaxApiKey"),
|
MiniMaxApiKey: os.Getenv("MiniMaxApiKey"),
|
||||||
MiniMaxApiURL: os.Getenv("MiniMaxApiURL"),
|
MiniMaxApiURL: os.Getenv("MiniMaxApiURL"),
|
||||||
FILE_URL: os.Getenv("FILE_URL"),
|
FILE_URL: os.Getenv("FILE_URL"),
|
||||||
LLMOurApiUrl: os.Getenv("LLMOurApiUrl"),
|
|
||||||
LLMOurApiKey: os.Getenv("LLMOurApiKey"),
|
|
||||||
})
|
})
|
||||||
|
|
||||||
fmt.Println("config: ", llmService)
|
|
||||||
|
|
||||||
// Get token configuration from environment variables
|
// Get token configuration from environment variables
|
||||||
sigExp, err := strconv.Atoi(os.Getenv("SIG_EXP"))
|
sigExp, err := strconv.Atoi(os.Getenv("SIG_EXP"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@ -24,8 +24,6 @@ type Config struct {
|
|||||||
MiniMaxApiKey string
|
MiniMaxApiKey string
|
||||||
MiniMaxApiURL string
|
MiniMaxApiURL string
|
||||||
FILE_URL string
|
FILE_URL string
|
||||||
LLMOurApiUrl string
|
|
||||||
LLMOurApiKey string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// LLMService handles communication with the LLM API
|
// LLMService handles communication with the LLM API
|
||||||
@ -53,7 +51,6 @@ type RequestPayload struct {
|
|||||||
ConversationID string `json:"conversation_id"`
|
ConversationID string `json:"conversation_id"`
|
||||||
Files []interface{} `json:"files"`
|
Files []interface{} `json:"files"`
|
||||||
Audio string `json:"audio"`
|
Audio string `json:"audio"`
|
||||||
LlmType string `json:"llm_type"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// VoiceSetting represents voice configuration
|
// VoiceSetting represents voice configuration
|
||||||
@ -115,18 +112,6 @@ type SpeechResponse struct {
|
|||||||
BaseResp BaseResponse `json:"base_resp"`
|
BaseResp BaseResponse `json:"base_resp"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type LLMOurMessage struct {
|
|
||||||
Role string `json:"role"`
|
|
||||||
Content string `json:"content"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type LLMOurRequestPayload struct {
|
|
||||||
Model string `json:"model"`
|
|
||||||
Stream bool `json:"stream"`
|
|
||||||
StreamOptions map[string]interface{} `json:"stream_options"`
|
|
||||||
Messages []LLMOurMessage `json:"messages"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewLLMService creates a new instance of LLMService
|
// NewLLMService creates a new instance of LLMService
|
||||||
func NewLLMService(config Config) *LLMService {
|
func NewLLMService(config Config) *LLMService {
|
||||||
return &LLMService{
|
return &LLMService{
|
||||||
@ -145,7 +130,6 @@ func (s *LLMService) CallLLMAPI(data map[string]interface{}) (interface{}, error
|
|||||||
ConversationID: getString(data, "conversation_id"),
|
ConversationID: getString(data, "conversation_id"),
|
||||||
Files: make([]interface{}, 0),
|
Files: make([]interface{}, 0),
|
||||||
Audio: getString(data, "audio"),
|
Audio: getString(data, "audio"),
|
||||||
LlmType: getString(data, "llm_type"),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("前端传来的数据:%+v\n", payload)
|
fmt.Printf("前端传来的数据:%+v\n", payload)
|
||||||
@ -154,51 +138,12 @@ func (s *LLMService) CallLLMAPI(data map[string]interface{}) (interface{}, error
|
|||||||
return nil, fmt.Errorf("error marshaling payload: %v", err)
|
return nil, fmt.Errorf("error marshaling payload: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
currentUrl := s.config.LLMApiURL + "/chat-messages"
|
req, err := http.NewRequest("POST", s.config.LLMApiURL+"/chat-messages", bytes.NewBuffer(jsonData))
|
||||||
fmt.Println(currentUrl)
|
// req, err := http.NewRequest("GET", "http://localhost:8080/stream-text", nil)
|
||||||
req := &http.Request{}
|
|
||||||
if payload.LlmType == "ours" {
|
|
||||||
// 动态构造 messages
|
|
||||||
var messages []LLMOurMessage
|
|
||||||
if msgs, ok := data["messages"]; ok {
|
|
||||||
if arr, ok := msgs.([]interface{}); ok {
|
|
||||||
for _, m := range arr {
|
|
||||||
if mMap, ok := m.(map[string]interface{}); ok {
|
|
||||||
role, _ := mMap["role"].(string)
|
|
||||||
content, _ := mMap["content"].(string)
|
|
||||||
messages = append(messages, LLMOurMessage{Role: role, Content: content})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// fallback: 如果没有 messages,则用 query 作为 user 消息
|
|
||||||
if len(messages) == 0 && payload.Query != "" {
|
|
||||||
messages = append(messages, LLMOurMessage{Role: "user", Content: payload.Query})
|
|
||||||
}
|
|
||||||
ourPayload := LLMOurRequestPayload{
|
|
||||||
Model: "bot-20250522162100-44785", // 可根据 data 或配置传入
|
|
||||||
Stream: true,
|
|
||||||
StreamOptions: map[string]interface{}{"include_usage": true},
|
|
||||||
Messages: messages,
|
|
||||||
}
|
|
||||||
jsonData, err = json.Marshal(ourPayload)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error marshaling ourPayload: %v", err)
|
|
||||||
}
|
|
||||||
currentUrl = s.config.LLMOurApiUrl
|
|
||||||
req, err = http.NewRequest("POST", currentUrl, bytes.NewBuffer(jsonData))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error creating request: %v", err)
|
return nil, fmt.Errorf("error creating request: %v", err)
|
||||||
}
|
}
|
||||||
req.Header.Set("Authorization", "Bearer "+s.config.LLMOurApiKey)
|
|
||||||
req.Header.Set("Content-Type", "application/json")
|
|
||||||
return s.handleStreamingResponseV2(req, data, payload.Audio)
|
|
||||||
}
|
|
||||||
|
|
||||||
req, err = http.NewRequest("POST", currentUrl, bytes.NewBuffer(jsonData))
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error creating request: %v", err)
|
|
||||||
}
|
|
||||||
req.Header.Set("Authorization", "Bearer "+s.config.LLMApiKey)
|
req.Header.Set("Authorization", "Bearer "+s.config.LLMApiKey)
|
||||||
req.Header.Set("Content-Type", "application/json")
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
|
||||||
@ -210,135 +155,6 @@ func (s *LLMService) CallLLMAPI(data map[string]interface{}) (interface{}, error
|
|||||||
return s.handleNonStreamingResponse(req)
|
return s.handleNonStreamingResponse(req)
|
||||||
}
|
}
|
||||||
|
|
||||||
// processStreamSegment 处理流式文本分段、语音合成等逻辑,返回 new_message、audio、是否需要发送
|
|
||||||
func (s *LLMService) processStreamSegment(initialSessage *string, all_message *string, answer string, audio_type string) (string, string, bool) {
|
|
||||||
// 定义标点符号map
|
|
||||||
punctuations := map[string]bool{
|
|
||||||
",": true, ",": true, // 逗号
|
|
||||||
".": true, "。": true, // 句号
|
|
||||||
"!": true, "!": true, // 感叹号
|
|
||||||
"?": true, "?": true, // 问号
|
|
||||||
";": true, ";": true, // 分号
|
|
||||||
":": true, ":": true, // 冒号
|
|
||||||
"、": true,
|
|
||||||
}
|
|
||||||
|
|
||||||
// 删除字符串前后的标点符号
|
|
||||||
trimPunctuation := func(s string) string {
|
|
||||||
if len(s) > 0 {
|
|
||||||
lastRune, size := utf8.DecodeLastRuneInString(s)
|
|
||||||
if punctuations[string(lastRune)] {
|
|
||||||
s = s[:len(s)-size]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
// 判断字符串是否包含标点符号
|
|
||||||
containsPunctuation := func(s string) bool {
|
|
||||||
for _, char := range s {
|
|
||||||
if punctuations[string(char)] {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// 按标点符号分割文本
|
|
||||||
splitByPunctuation := func(s string) []string {
|
|
||||||
var result []string
|
|
||||||
var current string
|
|
||||||
for _, char := range s {
|
|
||||||
if punctuations[string(char)] {
|
|
||||||
if current != "" {
|
|
||||||
result = append(result, current+string(char))
|
|
||||||
current = ""
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
current += string(char)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if current != "" {
|
|
||||||
result = append(result, current)
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
*initialSessage += answer
|
|
||||||
*all_message += answer
|
|
||||||
new_message := ""
|
|
||||||
if containsPunctuation(*initialSessage) {
|
|
||||||
segments := splitByPunctuation(*initialSessage)
|
|
||||||
if len(segments) > 1 {
|
|
||||||
format_message := strings.Join(segments[:len(segments)-1], "")
|
|
||||||
if utf8.RuneCountInString(format_message) > 10 {
|
|
||||||
*initialSessage = segments[len(segments)-1]
|
|
||||||
new_message = strings.Join(segments[:len(segments)-1], "")
|
|
||||||
} else {
|
|
||||||
return "", "", false
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if utf8.RuneCountInString(*initialSessage) > 10 {
|
|
||||||
new_message = *initialSessage
|
|
||||||
*initialSessage = ""
|
|
||||||
} else if utf8.RuneCountInString(*initialSessage) <= 10 && strings.HasSuffix(*initialSessage, "。") {
|
|
||||||
new_message = *initialSessage
|
|
||||||
*initialSessage = ""
|
|
||||||
} else {
|
|
||||||
return "", "", false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if new_message == "" {
|
|
||||||
return "", "", false
|
|
||||||
}
|
|
||||||
s_msg := strings.TrimSpace(new_message)
|
|
||||||
new_message = trimPunctuation(s_msg)
|
|
||||||
|
|
||||||
audio := ""
|
|
||||||
for i := 0; i < 1; i++ {
|
|
||||||
speechResp, err := s.SynthesizeSpeech(new_message, audio_type)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("Error synthesizing speech: %v\n", err)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
fmt.Println("触发音频", speechResp)
|
|
||||||
audio = speechResp.Data.Audio
|
|
||||||
if audio != "" {
|
|
||||||
resp, err := http.Get(audio)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("Error downloading audio: %v\n", err)
|
|
||||||
} else {
|
|
||||||
defer resp.Body.Close()
|
|
||||||
audioBytes, err := io.ReadAll(resp.Body)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("Error reading audio data: %v\n", err)
|
|
||||||
} else {
|
|
||||||
originalPath := fmt.Sprintf("audio/original_%d.wav", time.Now().UnixNano())
|
|
||||||
if err := os.WriteFile(originalPath, audioBytes, 0644); err != nil {
|
|
||||||
fmt.Printf("Error saving original audio: %v\n", err)
|
|
||||||
}
|
|
||||||
audioBase64 := base64.StdEncoding.EncodeToString(audioBytes)
|
|
||||||
trimmedAudio, err := s.TrimAudioSilence(audioBase64)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("Error trimming audio silence: %v\n", err)
|
|
||||||
} else {
|
|
||||||
audio_path := fmt.Sprintf("trimmed_%d.wav", time.Now().UnixNano())
|
|
||||||
outputPath := "audio/" + audio_path
|
|
||||||
if err := s.SaveBase64AsWAV(trimmedAudio, outputPath); err != nil {
|
|
||||||
fmt.Printf("Error saving trimmed WAV file: %v\n", err)
|
|
||||||
}
|
|
||||||
audio = s.config.FILE_URL + audio_path
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return s_msg, audio, true
|
|
||||||
}
|
|
||||||
|
|
||||||
// handleStreamingResponse processes streaming responses
|
// handleStreamingResponse processes streaming responses
|
||||||
func (s *LLMService) handleStreamingResponse(req *http.Request, data map[string]interface{}, audio_type string) (chan Message, error) {
|
func (s *LLMService) handleStreamingResponse(req *http.Request, data map[string]interface{}, audio_type string) (chan Message, error) {
|
||||||
resp, err := s.client.Do(req)
|
resp, err := s.client.Do(req)
|
||||||
@ -351,7 +167,6 @@ func (s *LLMService) handleStreamingResponse(req *http.Request, data map[string]
|
|||||||
}
|
}
|
||||||
|
|
||||||
messageChan := make(chan Message, 100) // Buffered channel for better performance
|
messageChan := make(chan Message, 100) // Buffered channel for better performance
|
||||||
all_message := ""
|
|
||||||
initialSessage := ""
|
initialSessage := ""
|
||||||
go func() {
|
go func() {
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
@ -385,7 +200,6 @@ func (s *LLMService) handleStreamingResponse(req *http.Request, data map[string]
|
|||||||
switch event {
|
switch event {
|
||||||
case "message":
|
case "message":
|
||||||
answer := getString(jsonData, "answer")
|
answer := getString(jsonData, "answer")
|
||||||
fmt.Println("源文本:", answer)
|
|
||||||
var audio string
|
var audio string
|
||||||
|
|
||||||
// 定义标点符号map
|
// 定义标点符号map
|
||||||
@ -395,7 +209,7 @@ func (s *LLMService) handleStreamingResponse(req *http.Request, data map[string]
|
|||||||
"!": true, "!": true, // 感叹号
|
"!": true, "!": true, // 感叹号
|
||||||
"?": true, "?": true, // 问号
|
"?": true, "?": true, // 问号
|
||||||
";": true, ";": true, // 分号
|
";": true, ";": true, // 分号
|
||||||
":": true, // 冒号
|
":": true, ":": true, // 冒号
|
||||||
"、": true,
|
"、": true,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -442,7 +256,6 @@ func (s *LLMService) handleStreamingResponse(req *http.Request, data map[string]
|
|||||||
}
|
}
|
||||||
new_message := ""
|
new_message := ""
|
||||||
initialSessage += answer
|
initialSessage += answer
|
||||||
all_message += answer
|
|
||||||
if containsPunctuation(initialSessage) {
|
if containsPunctuation(initialSessage) {
|
||||||
segments := splitByPunctuation(initialSessage)
|
segments := splitByPunctuation(initialSessage)
|
||||||
// fmt.Printf("原始文本: %s\n", initialSessage)
|
// fmt.Printf("原始文本: %s\n", initialSessage)
|
||||||
@ -451,32 +264,11 @@ func (s *LLMService) handleStreamingResponse(req *http.Request, data map[string]
|
|||||||
// fmt.Printf("片段 %d: %s\n", i+1, segment)
|
// fmt.Printf("片段 %d: %s\n", i+1, segment)
|
||||||
// }
|
// }
|
||||||
if len(segments) > 1 {
|
if len(segments) > 1 {
|
||||||
|
|
||||||
format_message := strings.Join(segments[:len(segments)-1], "")
|
|
||||||
// 检查initialSessage的字符长度是否超过15个
|
|
||||||
if utf8.RuneCountInString(format_message) > 15 {
|
|
||||||
initialSessage = segments[len(segments)-1]
|
|
||||||
// 如果超过10个字符,将其添加到new_message中并清空initialSessage
|
|
||||||
new_message = strings.Join(segments[:len(segments)-1], "")
|
|
||||||
// initialSessage = ""
|
|
||||||
} else {
|
|
||||||
if containsPunctuation(format_message) && utf8.RuneCountInString(format_message) > 10 {
|
|
||||||
|
|
||||||
initialSessage = segments[len(segments)-1]
|
initialSessage = segments[len(segments)-1]
|
||||||
new_message = strings.Join(segments[:len(segments)-1], "")
|
new_message = strings.Join(segments[:len(segments)-1], "")
|
||||||
} else {
|
} else {
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
|
|
||||||
if utf8.RuneCountInString(initialSessage) > 15 {
|
|
||||||
new_message = initialSessage
|
new_message = initialSessage
|
||||||
initialSessage = ""
|
initialSessage = ""
|
||||||
} else {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
// fmt.Printf("新消息: %s\n", new_message)
|
// fmt.Printf("新消息: %s\n", new_message)
|
||||||
// fmt.Printf("剩余文本: %s\n", initialSessage)
|
// fmt.Printf("剩余文本: %s\n", initialSessage)
|
||||||
@ -488,8 +280,8 @@ func (s *LLMService) handleStreamingResponse(req *http.Request, data map[string]
|
|||||||
s_msg := strings.TrimSpace(new_message)
|
s_msg := strings.TrimSpace(new_message)
|
||||||
// Trim punctuation from the message
|
// Trim punctuation from the message
|
||||||
new_message = trimPunctuation(s_msg)
|
new_message = trimPunctuation(s_msg)
|
||||||
fmt.Println("new_message", new_message)
|
// fmt.Println("new_message", new_message)
|
||||||
// println(new_message)
|
|
||||||
// 最多重试一次
|
// 最多重试一次
|
||||||
for i := 0; i < 1; i++ {
|
for i := 0; i < 1; i++ {
|
||||||
speechResp, err := s.SynthesizeSpeech(new_message, audio_type)
|
speechResp, err := s.SynthesizeSpeech(new_message, audio_type)
|
||||||
@ -511,7 +303,7 @@ func (s *LLMService) handleStreamingResponse(req *http.Request, data map[string]
|
|||||||
fmt.Printf("Error reading audio data: %v\n", err)
|
fmt.Printf("Error reading audio data: %v\n", err)
|
||||||
} else {
|
} else {
|
||||||
// Save original audio first
|
// Save original audio first
|
||||||
originalPath := fmt.Sprintf("audio/original_%d.wav", time.Now().UnixNano())
|
originalPath := fmt.Sprintf("audio/original_%d.wav", time.Now().Unix())
|
||||||
if err := os.WriteFile(originalPath, audioBytes, 0644); err != nil {
|
if err := os.WriteFile(originalPath, audioBytes, 0644); err != nil {
|
||||||
fmt.Printf("Error saving original audio: %v\n", err)
|
fmt.Printf("Error saving original audio: %v\n", err)
|
||||||
}
|
}
|
||||||
@ -523,7 +315,7 @@ func (s *LLMService) handleStreamingResponse(req *http.Request, data map[string]
|
|||||||
fmt.Printf("Error trimming audio silence: %v\n", err)
|
fmt.Printf("Error trimming audio silence: %v\n", err)
|
||||||
} else {
|
} else {
|
||||||
// Save the trimmed audio as WAV file
|
// Save the trimmed audio as WAV file
|
||||||
audio_path := fmt.Sprintf("trimmed_%d.wav", time.Now().UnixNano())
|
audio_path := fmt.Sprintf("trimmed_%d.wav", time.Now().Unix())
|
||||||
outputPath := "audio/" + audio_path
|
outputPath := "audio/" + audio_path
|
||||||
if err := s.SaveBase64AsWAV(trimmedAudio, outputPath); err != nil {
|
if err := s.SaveBase64AsWAV(trimmedAudio, outputPath); err != nil {
|
||||||
fmt.Printf("Error saving trimmed WAV file: %v\n", err)
|
fmt.Printf("Error saving trimmed WAV file: %v\n", err)
|
||||||
@ -534,12 +326,12 @@ func (s *LLMService) handleStreamingResponse(req *http.Request, data map[string]
|
|||||||
}
|
}
|
||||||
break // 获取到音频就退出
|
break // 获取到音频就退出
|
||||||
}
|
}
|
||||||
// fmt.Println("audio is empty, retry", speechResp)
|
fmt.Println("audio is empty, retry", speechResp)
|
||||||
// time.Sleep(1 * time.Second)
|
// time.Sleep(1 * time.Second)
|
||||||
}
|
}
|
||||||
fmt.Println("所有消息:", all_message)
|
|
||||||
messageChan <- Message{
|
messageChan <- Message{
|
||||||
Answer: s_msg,
|
Answer: new_message,
|
||||||
IsEnd: false,
|
IsEnd: false,
|
||||||
ConversationID: getString(jsonData, "conversation_id"),
|
ConversationID: getString(jsonData, "conversation_id"),
|
||||||
TaskID: getString(jsonData, "task_id"),
|
TaskID: getString(jsonData, "task_id"),
|
||||||
@ -547,96 +339,6 @@ func (s *LLMService) handleStreamingResponse(req *http.Request, data map[string]
|
|||||||
AudioData: audio, // Update to use the correct path to audio data
|
AudioData: audio, // Update to use the correct path to audio data
|
||||||
}
|
}
|
||||||
case "message_end":
|
case "message_end":
|
||||||
// 在流结束前,处理剩余的文本生成音频
|
|
||||||
if initialSessage != "" {
|
|
||||||
// 不管文本长度,直接生成音频
|
|
||||||
s_msg := strings.TrimSpace(initialSessage)
|
|
||||||
// 定义标点符号map
|
|
||||||
punctuations := map[string]bool{
|
|
||||||
",": true, ",": true, // 逗号
|
|
||||||
".": true, "。": true, // 句号
|
|
||||||
"!": true, "!": true, // 感叹号
|
|
||||||
"?": true, "?": true, // 问号
|
|
||||||
";": true, ";": true, // 分号
|
|
||||||
":": true, ":": true, // 冒号
|
|
||||||
"、": true,
|
|
||||||
}
|
|
||||||
|
|
||||||
// 删除字符串前后的标点符号
|
|
||||||
trimPunctuation := func(s string) string {
|
|
||||||
if len(s) > 0 {
|
|
||||||
lastRune, size := utf8.DecodeLastRuneInString(s)
|
|
||||||
if punctuations[string(lastRune)] {
|
|
||||||
s = s[:len(s)-size]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
new_message := trimPunctuation(s_msg)
|
|
||||||
fmt.Println("最后一段文本生成音频:", new_message)
|
|
||||||
|
|
||||||
// 生成语音
|
|
||||||
var audio string
|
|
||||||
for i := 0; i < 1; i++ {
|
|
||||||
speechResp, err := s.SynthesizeSpeech(new_message, audio_type)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("Error synthesizing speech: %v\n", err)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
fmt.Println("语音:", speechResp)
|
|
||||||
audio = speechResp.Data.Audio
|
|
||||||
if audio != "" {
|
|
||||||
// 下载并处理音频
|
|
||||||
resp, err := http.Get(audio)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("Error downloading audio: %v\n", err)
|
|
||||||
} else {
|
|
||||||
defer resp.Body.Close()
|
|
||||||
audioBytes, err := io.ReadAll(resp.Body)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("Error reading audio data: %v\n", err)
|
|
||||||
} else {
|
|
||||||
// 保存原始音频
|
|
||||||
originalPath := fmt.Sprintf("audio/original_%d.wav", time.Now().UnixNano())
|
|
||||||
if err := os.WriteFile(originalPath, audioBytes, 0644); err != nil {
|
|
||||||
fmt.Printf("Error saving original audio: %v\n", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 静音裁剪
|
|
||||||
audioBase64 := base64.StdEncoding.EncodeToString(audioBytes)
|
|
||||||
trimmedAudio, err := s.TrimAudioSilence(audioBase64)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("Error trimming audio silence: %v\n", err)
|
|
||||||
} else {
|
|
||||||
audio_path := fmt.Sprintf("trimmed_%d.wav", time.Now().UnixNano())
|
|
||||||
outputPath := "audio/" + audio_path
|
|
||||||
if err := s.SaveBase64AsWAV(trimmedAudio, outputPath); err != nil {
|
|
||||||
fmt.Printf("Error saving trimmed WAV file: %v\n", err)
|
|
||||||
}
|
|
||||||
audio = s.config.FILE_URL + audio_path
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 发送最后一段文本的消息
|
|
||||||
messageChan <- Message{
|
|
||||||
Answer: s_msg,
|
|
||||||
IsEnd: false,
|
|
||||||
ConversationID: getString(jsonData, "conversation_id"),
|
|
||||||
TaskID: getString(jsonData, "task_id"),
|
|
||||||
ClientID: getString(data, "conversation_id"),
|
|
||||||
AudioData: audio,
|
|
||||||
}
|
|
||||||
|
|
||||||
// 清空剩余文本
|
|
||||||
initialSessage = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
// 发送结束消息
|
|
||||||
messageChan <- Message{
|
messageChan <- Message{
|
||||||
Answer: "",
|
Answer: "",
|
||||||
IsEnd: true,
|
IsEnd: true,
|
||||||
@ -651,95 +353,6 @@ func (s *LLMService) handleStreamingResponse(req *http.Request, data map[string]
|
|||||||
return messageChan, nil
|
return messageChan, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// handleStreamingResponseV2 适配新流式返回格式
|
|
||||||
func (s *LLMService) handleStreamingResponseV2(req *http.Request, data map[string]interface{}, audio_type string) (chan Message, error) {
|
|
||||||
resp, err := s.client.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error making request: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
|
||||||
return nil, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
|
|
||||||
}
|
|
||||||
|
|
||||||
messageChan := make(chan Message, 100)
|
|
||||||
all_message := ""
|
|
||||||
initialSessage := ""
|
|
||||||
go func() {
|
|
||||||
defer resp.Body.Close()
|
|
||||||
defer close(messageChan)
|
|
||||||
reader := bufio.NewReader(resp.Body)
|
|
||||||
for {
|
|
||||||
line, err := reader.ReadString('\n')
|
|
||||||
if err != nil {
|
|
||||||
if err == io.EOF {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
fmt.Printf("Error reading line: %v\n", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
line = strings.TrimSpace(line)
|
|
||||||
if line == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// line = strings.TrimSpace(line)
|
|
||||||
if strings.HasPrefix(line, "data:") {
|
|
||||||
line = strings.TrimSpace(strings.TrimPrefix(line, "data:"))
|
|
||||||
}
|
|
||||||
|
|
||||||
// fmt.Println("line: ", line)
|
|
||||||
|
|
||||||
if line == "[DONE]" {
|
|
||||||
messageChan <- Message{
|
|
||||||
Answer: "",
|
|
||||||
IsEnd: true,
|
|
||||||
ConversationID: getString(data, "conversation_id"),
|
|
||||||
TaskID: getString(data, "task_id"),
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var jsonData map[string]interface{}
|
|
||||||
if err := json.Unmarshal([]byte(line), &jsonData); err != nil {
|
|
||||||
fmt.Printf("Error unmarshaling JSON: %v\n", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
choices, ok := jsonData["choices"].([]interface{})
|
|
||||||
if !ok || len(choices) == 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
choice, ok := choices[0].(map[string]interface{})
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
delta, ok := choice["delta"].(map[string]interface{})
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
content, _ := delta["content"].(string)
|
|
||||||
if content == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
new_message, audio, needSend := s.processStreamSegment(&initialSessage, &all_message, content, audio_type)
|
|
||||||
if !needSend {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
messageChan <- Message{
|
|
||||||
Answer: new_message,
|
|
||||||
IsEnd: false,
|
|
||||||
ConversationID: getString(data, "conversation_id"),
|
|
||||||
TaskID: getString(data, "task_id"),
|
|
||||||
ClientID: getString(data, "conversation_id"),
|
|
||||||
AudioData: audio,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
return messageChan, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// handleNonStreamingResponse processes non-streaming responses
|
// handleNonStreamingResponse processes non-streaming responses
|
||||||
func (s *LLMService) handleNonStreamingResponse(req *http.Request) (map[string]interface{}, error) {
|
func (s *LLMService) handleNonStreamingResponse(req *http.Request) (map[string]interface{}, error) {
|
||||||
resp, err := s.client.Do(req)
|
resp, err := s.client.Do(req)
|
||||||
@ -811,7 +424,7 @@ func (s *LLMService) DeleteConversation(conversationID, user string) (map[string
|
|||||||
// SynthesizeSpeech converts text to speech
|
// SynthesizeSpeech converts text to speech
|
||||||
func (s *LLMService) SynthesizeSpeech(text string, audio string) (*SpeechResponse, error) {
|
func (s *LLMService) SynthesizeSpeech(text string, audio string) (*SpeechResponse, error) {
|
||||||
payload := SpeechRequest{
|
payload := SpeechRequest{
|
||||||
Model: "speech-02-hd",
|
Model: "speech-02-turbo",
|
||||||
Text: text,
|
Text: text,
|
||||||
Stream: false,
|
Stream: false,
|
||||||
LanguageBoost: "auto",
|
LanguageBoost: "auto",
|
||||||
@ -820,8 +433,8 @@ func (s *LLMService) SynthesizeSpeech(text string, audio string) (*SpeechRespons
|
|||||||
VoiceID: audio,
|
VoiceID: audio,
|
||||||
Speed: 1,
|
Speed: 1,
|
||||||
Vol: 1,
|
Vol: 1,
|
||||||
Pitch: -1,
|
Pitch: 0,
|
||||||
Emotion: "neutral",
|
Emotion: "happy",
|
||||||
},
|
},
|
||||||
AudioSetting: AudioSetting{
|
AudioSetting: AudioSetting{
|
||||||
SampleRate: 32000,
|
SampleRate: 32000,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user