Skip to content
Toggle navigation
P
Projects
G
Groups
S
Snippets
Help
phsl
/
new-api
This project
Loading...
Sign in
Toggle navigation
Go to a project
Project
Repository
Issues
0
Merge Requests
0
Pipelines
Wiki
Snippets
Members
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Commit
d52505ca
authored
Oct 20, 2025
by
feitianbubu
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat: opt minimax tts req struct
parent
a2d34b9e
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
100 additions
and
116 deletions
+100
-116
relay/channel/minimax/adaptor.go
+16
-14
relay/channel/minimax/tts.go
+84
-102
No files found.
relay/channel/minimax/adaptor.go
View file @
d52505ca
...
@@ -34,17 +34,20 @@ func (a *Adaptor) ConvertAudioRequest(c *gin.Context, info *relaycommon.RelayInf
...
@@ -34,17 +34,20 @@ func (a *Adaptor) ConvertAudioRequest(c *gin.Context, info *relaycommon.RelayInf
return
nil
,
errors
.
New
(
"unsupported audio relay mode"
)
return
nil
,
errors
.
New
(
"unsupported audio relay mode"
)
}
}
voiceID
:=
mapVoiceType
(
request
.
Voice
)
voiceID
:=
request
.
Voice
speed
:=
request
.
Speed
speed
:=
request
.
Speed
outputFormat
:=
mapOutputFormat
(
request
.
ResponseFormat
)
outputFormat
:=
request
.
ResponseFormat
c
.
Set
(
"response_format"
,
outputFormat
)
minimaxRequest
:=
MiniMaxTTSRequest
{
minimaxRequest
:=
MiniMaxTTSRequest
{
Model
:
getTTSModel
(
info
.
OriginModelName
),
Model
:
info
.
OriginModelName
,
Text
:
request
.
Input
,
Text
:
request
.
Input
,
VoiceID
:
voiceID
,
VoiceSetting
:
VoiceSetting
{
Speed
:
speed
,
VoiceID
:
voiceID
,
Speed
:
speed
,
},
AudioSetting
:
&
AudioSetting
{
Format
:
outputFormat
,
},
OutputFormat
:
outputFormat
,
OutputFormat
:
outputFormat
,
}
}
...
@@ -59,6 +62,11 @@ func (a *Adaptor) ConvertAudioRequest(c *gin.Context, info *relaycommon.RelayInf
...
@@ -59,6 +62,11 @@ func (a *Adaptor) ConvertAudioRequest(c *gin.Context, info *relaycommon.RelayInf
if
err
!=
nil
{
if
err
!=
nil
{
return
nil
,
fmt
.
Errorf
(
"error marshalling minimax request: %w"
,
err
)
return
nil
,
fmt
.
Errorf
(
"error marshalling minimax request: %w"
,
err
)
}
}
if
outputFormat
!=
"hex"
{
outputFormat
=
"url"
}
c
.
Set
(
"response_format"
,
outputFormat
)
// Debug: log the request structure
// Debug: log the request structure
fmt
.
Printf
(
"MiniMax TTS Request: %s
\n
"
,
string
(
jsonData
))
fmt
.
Printf
(
"MiniMax TTS Request: %s
\n
"
,
string
(
jsonData
))
...
@@ -79,12 +87,6 @@ func (a *Adaptor) GetRequestURL(info *relaycommon.RelayInfo) (string, error) {
...
@@ -79,12 +87,6 @@ func (a *Adaptor) GetRequestURL(info *relaycommon.RelayInfo) (string, error) {
func
(
a
*
Adaptor
)
SetupRequestHeader
(
c
*
gin
.
Context
,
req
*
http
.
Header
,
info
*
relaycommon
.
RelayInfo
)
error
{
func
(
a
*
Adaptor
)
SetupRequestHeader
(
c
*
gin
.
Context
,
req
*
http
.
Header
,
info
*
relaycommon
.
RelayInfo
)
error
{
channel
.
SetupApiRequestHeader
(
info
,
c
,
req
)
channel
.
SetupApiRequestHeader
(
info
,
c
,
req
)
if
info
.
RelayMode
==
constant
.
RelayModeAudioSpeech
{
req
.
Set
(
"Content-Type"
,
"application/json"
)
return
nil
}
req
.
Set
(
"Authorization"
,
"Bearer "
+
info
.
ApiKey
)
req
.
Set
(
"Authorization"
,
"Bearer "
+
info
.
ApiKey
)
return
nil
return
nil
}
}
...
...
relay/channel/minimax/tts.go
View file @
d52505ca
package
minimax
package
minimax
import
(
import
(
"encoding/
base64
"
"encoding/
hex
"
"encoding/json"
"encoding/json"
"errors"
"errors"
"fmt"
"io"
"io"
"net/http"
"net/http"
"strings"
"github.com/QuantumNous/new-api/dto"
"github.com/QuantumNous/new-api/dto"
relaycommon
"github.com/QuantumNous/new-api/relay/common"
relaycommon
"github.com/QuantumNous/new-api/relay/common"
...
@@ -14,96 +16,78 @@ import (
...
@@ -14,96 +16,78 @@ import (
)
)
type
MiniMaxTTSRequest
struct
{
type
MiniMaxTTSRequest
struct
{
Model
string
`json:"model"`
Model
string
`json:"model"`
Text
string
`json:"text"`
Text
string
`json:"text"`
VoiceID
string
`json:"voice_id"`
Stream
bool
`json:"stream,omitempty"`
Speed
float64
`json:"speed,omitempty"`
StreamOptions
*
StreamOptions
`json:"stream_options,omitempty"`
Vol
float64
`json:"vol,omitempty"`
VoiceSetting
VoiceSetting
`json:"voice_setting"`
Pitch
int
`json:"pitch,omitempty"`
PronunciationDict
*
PronunciationDict
`json:"pronunciation_dict,omitempty"`
AudioSampleRate
int
`json:"audio_sample_rate,omitempty"`
AudioSetting
*
AudioSetting
`json:"audio_setting,omitempty"`
OutputFormat
string
`json:"output_format,omitempty"`
TimbreWeights
[]
TimbreWeight
`json:"timbre_weights,omitempty"`
LanguageBoost
string
`json:"language_boost,omitempty"`
VoiceModify
*
VoiceModify
`json:"voice_modify,omitempty"`
SubtitleEnable
bool
`json:"subtitle_enable,omitempty"`
OutputFormat
string
`json:"output_format,omitempty"`
AigcWatermark
bool
`json:"aigc_watermark,omitempty"`
}
}
type
MiniMaxTTSResponse
struct
{
type
StreamOptions
struct
{
Created
int
`json:"created"`
ExcludeAggregatedAudio
bool
`json:"exclude_aggregated_audio,omitempty"`
Data
[]
MiniMaxTTSData
`json:"data"`
ID
string
`json:"id"`
Model
string
`json:"model"`
Object
string
`json:"object"`
Usage
MiniMaxTTSUsage
`json:"usage"`
}
}
type
MiniMaxTTSData
struct
{
type
VoiceSetting
struct
{
Index
int
`json:"index"`
VoiceID
string
`json:"voice_id"`
Audio
string
`json:"audio"`
Speed
float64
`json:"speed,omitempty"`
Text
string
`json:"text"`
Vol
float64
`json:"vol,omitempty"`
FinishReason
string
`json:"finish_reason"`
Pitch
int
`json:"pitch,omitempty"`
Emotion
string
`json:"emotion,omitempty"`
TextNormalization
bool
`json:"text_normalization,omitempty"`
LatexRead
bool
`json:"latex_read,omitempty"`
}
}
type
MiniMaxTTSUsage
struct
{
type
PronunciationDict
struct
{
To
talTokens
int
`json:"total_tokens
"`
To
ne
[]
string
`json:"tone,omitempty
"`
}
}
type
MiniMaxTTSErrorResponse
struct
{
type
AudioSetting
struct
{
Error
MiniMaxTTSError
`json:"error"`
SampleRate
int
`json:"sample_rate,omitempty"`
Bitrate
int
`json:"bitrate,omitempty"`
Format
string
`json:"format,omitempty"`
Channel
int
`json:"channel,omitempty"`
ForceCbr
bool
`json:"force_cbr,omitempty"`
}
}
type
MiniMaxTTSError
struct
{
type
TimbreWeight
struct
{
Code
string
`json:"code"`
VoiceID
string
`json:"voice_id"`
Message
string
`json:"message"`
Weight
int
`json:"weight"`
Type
string
`json:"type"`
}
}
// OpenAI voice to MiniMax voice_id mapping
type
VoiceModify
struct
{
var
openAIToMiniMaxVoiceMap
=
map
[
string
]
string
{
Pitch
int
`json:"pitch,omitempty"`
"alloy"
:
"male-qn-qingse"
,
Intensity
int
`json:"intensity,omitempty"`
"echo"
:
"male-qn-jingying"
,
Timbre
int
`json:"timbre,omitempty"`
"fable"
:
"female-shaonv"
,
SoundEffects
string
`json:"sound_effects,omitempty"`
"onyx"
:
"male-qn-badao"
,
"nova"
:
"female-shaonv-jingpin"
,
"shimmer"
:
"female-yujie"
,
// Add some standard MiniMax voice IDs
"voice-1"
:
"male-qn-qingse"
,
"voice-2"
:
"female-shaonv"
,
}
}
// OpenAI response format to MiniMax output format mapping
type
MiniMaxTTSResponse
struct
{
var
responseFormatToOutputFormatMap
=
map
[
string
]
string
{
Data
MiniMaxTTSData
`json:"data"`
"mp3"
:
"mp3"
,
ExtraInfo
MiniMaxExtraInfo
`json:"extra_info"`
"opus"
:
"mp3"
,
TraceID
string
`json:"trace_id"`
"aac"
:
"aac"
,
BaseResp
MiniMaxBaseResp
`json:"base_resp"`
"flac"
:
"flac"
,
"wav"
:
"wav"
,
"pcm"
:
"pcm"
,
}
}
// TTS model mapping - MiniMax uses speech-01 or speech-01-turbo
type
MiniMaxTTSData
struct
{
var
modelToTTSModelMap
=
map
[
string
]
string
{
Audio
string
`json:"audio"`
"speech-01"
:
"speech-01"
,
Status
int
`json:"status"`
"speech-01-turbo"
:
"speech-01-turbo"
,
"tts-1"
:
"speech-01-turbo"
,
"tts-1-hd"
:
"speech-01"
,
}
}
func
mapVoiceType
(
openAIVoice
string
)
string
{
type
MiniMaxExtraInfo
struct
{
if
voice
,
ok
:=
openAIToMiniMaxVoiceMap
[
openAIVoice
];
ok
{
UsageCharacters
int64
`json:"usage_characters"`
return
voice
}
return
"female-shaonv"
// default voice
}
}
func
mapOutputFormat
(
responseFormat
string
)
string
{
type
MiniMaxBaseResp
struct
{
if
format
,
ok
:=
responseFormatToOutputFormatMap
[
responseFormat
];
ok
{
StatusCode
int64
`json:"status_code"`
return
format
StatusMsg
string
`json:"status_msg"`
}
return
"mp3"
// default format
}
func
getTTSModel
(
modelName
string
)
string
{
if
ttsModel
,
ok
:=
modelToTTSModelMap
[
modelName
];
ok
{
return
ttsModel
}
return
"speech-01-turbo"
// default model
}
}
func
getContentTypeByFormat
(
format
string
)
string
{
func
getContentTypeByFormat
(
format
string
)
string
{
...
@@ -124,66 +108,64 @@ func handleTTSResponse(c *gin.Context, resp *http.Response, info *relaycommon.Re
...
@@ -124,66 +108,64 @@ func handleTTSResponse(c *gin.Context, resp *http.Response, info *relaycommon.Re
body
,
readErr
:=
io
.
ReadAll
(
resp
.
Body
)
body
,
readErr
:=
io
.
ReadAll
(
resp
.
Body
)
if
readErr
!=
nil
{
if
readErr
!=
nil
{
return
nil
,
types
.
NewErrorWithStatusCode
(
return
nil
,
types
.
NewErrorWithStatusCode
(
errors
.
New
(
"failed to read minimax response"
),
fmt
.
Errorf
(
"failed to read minimax response: %w"
,
readErr
),
types
.
ErrorCodeReadResponseBodyFailed
,
types
.
ErrorCodeReadResponseBodyFailed
,
http
.
StatusInternalServerError
,
http
.
StatusInternalServerError
,
)
)
}
}
defer
resp
.
Body
.
Close
()
defer
resp
.
Body
.
Close
()
// First try to parse as error response
// Parse response
var
errorResp
MiniMaxTTSErrorResponse
if
unmarshalErr
:=
json
.
Unmarshal
(
body
,
&
errorResp
);
unmarshalErr
==
nil
&&
errorResp
.
Error
.
Code
!=
""
{
return
nil
,
types
.
NewErrorWithStatusCode
(
errors
.
New
(
errorResp
.
Error
.
Message
),
types
.
ErrorCodeBadResponse
,
http
.
StatusBadRequest
,
)
}
// Parse as successful response
var
minimaxResp
MiniMaxTTSResponse
var
minimaxResp
MiniMaxTTSResponse
if
unmarshalErr
:=
json
.
Unmarshal
(
body
,
&
minimaxResp
);
unmarshalErr
!=
nil
{
if
unmarshalErr
:=
json
.
Unmarshal
(
body
,
&
minimaxResp
);
unmarshalErr
!=
nil
{
return
nil
,
types
.
NewErrorWithStatusCode
(
return
nil
,
types
.
NewErrorWithStatusCode
(
errors
.
New
(
"failed to parse minimax response"
),
fmt
.
Errorf
(
"failed to unmarshal minimax TTS response: %w"
,
unmarshalErr
),
types
.
ErrorCodeBadResponseBody
,
types
.
ErrorCodeBadResponseBody
,
http
.
StatusInternalServerError
,
http
.
StatusInternalServerError
,
)
)
}
}
// Check
if we have audio data
// Check
base_resp status code
if
len
(
minimaxResp
.
Data
)
==
0
||
minimaxResp
.
Data
[
0
]
.
Audio
==
""
{
if
minimaxResp
.
BaseResp
.
StatusCode
!=
0
{
return
nil
,
types
.
NewErrorWithStatusCode
(
return
nil
,
types
.
NewErrorWithStatusCode
(
errors
.
New
(
"no audio data in response"
),
fmt
.
Errorf
(
"minimax TTS error: %d - %s"
,
minimaxResp
.
BaseResp
.
StatusCode
,
minimaxResp
.
BaseResp
.
StatusMsg
),
types
.
ErrorCodeBadResponse
,
types
.
ErrorCodeBadResponse
,
http
.
StatusBadRequest
,
http
.
StatusBadRequest
,
)
)
}
}
// Decode base64 audio data
// Check if we have audio data
audioData
,
decodeErr
:=
base64
.
StdEncoding
.
DecodeString
(
minimaxResp
.
Data
[
0
]
.
Audio
)
if
minimaxResp
.
Data
.
Audio
==
""
{
if
decodeErr
!=
nil
{
return
nil
,
types
.
NewErrorWithStatusCode
(
return
nil
,
types
.
NewErrorWithStatusCode
(
errors
.
New
(
"failed to decode audio data
"
),
fmt
.
Errorf
(
"no audio data in minimax TTS response
"
),
types
.
ErrorCodeBadResponse
Body
,
types
.
ErrorCodeBadResponse
,
http
.
Status
InternalServerError
,
http
.
Status
BadRequest
,
)
)
}
}
// Get output format from context or default to mp3
if
strings
.
HasPrefix
(
minimaxResp
.
Data
.
Audio
,
"http"
)
{
outputFormat
:=
c
.
GetString
(
"response_format"
)
c
.
Redirect
(
http
.
StatusFound
,
minimaxResp
.
Data
.
Audio
)
if
outputFormat
==
""
{
}
else
{
outputFormat
=
"mp3"
// Handle hex-encoded audio data
}
audioData
,
decodeErr
:=
hex
.
DecodeString
(
minimaxResp
.
Data
.
Audio
)
if
decodeErr
!=
nil
{
return
nil
,
types
.
NewErrorWithStatusCode
(
fmt
.
Errorf
(
"failed to decode hex audio data: %w"
,
decodeErr
),
types
.
ErrorCodeBadResponse
,
http
.
StatusInternalServerError
,
)
}
// Determine content type - default to mp3
contentType
:=
"audio/mpeg"
contentType
:=
getContentTypeByFormat
(
outputFormat
)
c
.
Data
(
http
.
StatusOK
,
contentType
,
audioData
)
c
.
Header
(
"Content-Type"
,
contentType
)
}
c
.
Data
(
http
.
StatusOK
,
contentType
,
audioData
)
usage
=
&
dto
.
Usage
{
usage
=
&
dto
.
Usage
{
PromptTokens
:
info
.
PromptTokens
,
PromptTokens
:
info
.
PromptTokens
,
CompletionTokens
:
0
,
CompletionTokens
:
0
,
TotalTokens
:
minimaxResp
.
Usage
.
TotalTokens
,
TotalTokens
:
int
(
minimaxResp
.
ExtraInfo
.
UsageCharacters
)
,
}
}
return
usage
,
nil
return
usage
,
nil
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment