Files
015/worker/internal/services/text.go

209 lines
5.4 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 services
import (
"context"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"net/url"
"pkg/utils"
"strings"
"worker/internal/services/llm"
"github.com/go-resty/resty/v2"
)
var (
ErrProviderNotConfigured = errors.New("ProviderNotConfigured")
ErrUnknownProvider = errors.New("UnknownProvider")
)
const microsoftAuthEndpoint = "https://edge.microsoft.com/translate/auth"
func TranslateText(text, from, to, provider string) (string, error) {
switch provider {
case "google":
return translateWithGoogle(text, from, to)
case "microsoft":
return translateWithMicrosoft(text, from, to)
case "deeplx":
return translateWithDeepLX(text, from, to)
case "deepseek":
return translateWithLLM(text, from, to)
default:
return "", fmt.Errorf("%w: %s", ErrUnknownProvider, provider)
}
}
// translateWithGoogle 调用 Google Translate 非官方 API。
// 响应格式:[[["翻译结果","原文",...]], null, "en"]
func translateWithGoogle(text, from, to string) (string, error) {
endpoint := fmt.Sprintf(
"https://translate.googleapis.com/translate_a/single?client=gtx&sl=%s&tl=%s&dt=t&q=%s",
url.QueryEscape(from), url.QueryEscape(to), url.QueryEscape(text),
)
resp, err := http.Get(endpoint) //nolint:noctx
if err != nil {
return "", err
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return "", err
}
var raw []any
if err := json.Unmarshal(body, &raw); err != nil {
return "", err
}
if len(raw) == 0 {
return "", errors.New("GoogleTranslateUnexpectedResponse")
}
segments, ok := raw[0].([]any)
if !ok {
return "", errors.New("GoogleTranslateUnexpectedResponse")
}
var parts []string
for _, seg := range segments {
pair, ok := seg.([]any)
if !ok || len(pair) == 0 {
continue
}
if s, ok := pair[0].(string); ok && s != "" {
parts = append(parts, s)
}
}
return strings.Join(parts, ""), nil
}
// translateWithMicrosoft 调用微软 Edge Translator 免费端点(无需 API Key
// 响应格式:[{"translations":[{"text":"翻译结果","to":"zh-Hans"}]}]
func translateWithMicrosoft(text, from, to string) (string, error) {
params := url.Values{}
params.Set("api-version", "3.0")
params.Set("to", to)
if from != "auto" {
params.Set("from", from)
}
endpoint := "https://api-edge.cognitive.microsofttranslator.com/translate?" + params.Encode()
reqBody, err := json.Marshal([]map[string]string{{"Text": text}})
if err != nil {
return "", err
}
token, err := getMicrosoftToken()
if err != nil {
return "", err
}
client := resty.New()
req := client.R().
SetHeader("Content-Type", "application/json").
SetBody(reqBody)
req.SetAuthToken(token)
resp, err := req.Post(endpoint)
if err != nil {
return "", err
}
if resp == nil {
return "", errors.New("MicrosoftTranslateUnexpectedResponse")
}
if resp.IsError() {
return "", fmt.Errorf("MicrosoftTranslateRequestFailed: status=%d body=%s", resp.StatusCode(), resp.String())
}
body := resp.Body()
if len(body) == 0 {
return "", errors.New("MicrosoftTranslateUnexpectedResponse")
}
var result []struct {
Translations []struct {
Text string `json:"text"`
} `json:"translations"`
}
if err := json.Unmarshal(body, &result); err != nil {
return "", err
}
if len(result) == 0 || len(result[0].Translations) == 0 {
return "", errors.New("MicrosoftTranslateUnexpectedResponse")
}
return result[0].Translations[0].Text, nil
}
func getMicrosoftToken() (string, error) {
resp, err := resty.New().R().Get(microsoftAuthEndpoint)
if err != nil {
return "", err
}
if resp == nil {
return "", errors.New("MicrosoftAuthUnexpectedResponse")
}
if resp.IsError() {
return "", fmt.Errorf("MicrosoftAuthRequestFailed: status=%d body=%s", resp.StatusCode(), resp.String())
}
token := strings.TrimSpace(resp.String())
if token == "" {
return "", errors.New("MicrosoftAuthUnexpectedResponse")
}
return token, nil
}
type deeplxRequest struct {
Text string `json:"text"`
SourceLang string `json:"source_lang"`
TargetLang string `json:"target_lang"`
}
type deeplxResponse struct {
Data string `json:"data"`
}
// translateWithDeepLX 调用自托管 DeepLX 服务,端点通过 deeplx.endpoint 配置。
func translateWithDeepLX(text, from, to string) (string, error) {
endpoint := utils.GetEnv("deeplx.endpoint")
if endpoint == "" {
return "", ErrProviderNotConfigured
}
reqBody, err := json.Marshal(deeplxRequest{Text: text, SourceLang: from, TargetLang: to})
if err != nil {
return "", err
}
resp, err := http.Post(endpoint, "application/json", strings.NewReader(string(reqBody))) //nolint:noctx
if err != nil {
return "", err
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return "", err
}
var result deeplxResponse
if err := json.Unmarshal(body, &result); err != nil {
return "", err
}
return result.Data, nil
}
// translateWithLLM 通过 OpenAI 兼容 API如 DeepSeek进行 AI 翻译。
func translateWithLLM(text, from, to string) (string, error) {
endpoint := utils.GetEnv("llm.endpoint")
apiKey := utils.GetEnv("llm.api_key")
_, err := llm.NewClient(endpoint, apiKey)
if err != nil {
return "", ErrProviderNotConfigured
}
model := utils.GetEnvWithDefault("llm.model", "deepseek-chat")
system := fmt.Sprintf(
"You are a professional translator. Translate the given text from %s to %s. "+
"Output only the translated text, with no explanations or surrounding quotes.",
from, to,
)
return llm.Chat(context.Background(), system, text, model)
}