Skip to content

feat(volcengine): support v3 TTS (ws bidir/uni, http chunked/sse) - #4710

Open
rangerTao wants to merge 4 commits into
QuantumNous:mainfrom
rangerTao:feat/volcengine-tts-v3
Open

feat(volcengine): support v3 TTS (ws bidir/uni, http chunked/sse)#4710
rangerTao wants to merge 4 commits into
QuantumNous:mainfrom
rangerTao:feat/volcengine-tts-v3

Conversation

@rangerTao

@rangerTao rangerTao commented May 9, 2026

Copy link
Copy Markdown

Summary

Closes #4709.

Adds Volcengine v3 TTS support alongside the existing v1 ws_binary path.
Four transports are now selectable per-channel (or per-request via OpenAI
metadata), with new-console (X-Api-Key) or legacy
(X-Api-App-Id + X-Api-Access-Key) auth modes.

Transport Endpoint
WS Bidirectional wss://openspeech.bytedance.com/api/v3/tts/bidirection
WS Unidirectional wss://openspeech.bytedance.com/api/v3/tts/unidirectional/stream
HTTP Chunked https://openspeech.bytedance.com/api/v3/tts/unidirectional
HTTP SSE (passthrough) https://openspeech.bytedance.com/api/v3/tts/unidirectional/sse

Default behavior is unchanged: leaving the new "TTS Protocol" field blank
falls back to v1 ws_binary, so existing channels keep working.

Backend

  • relay/channel/volcengine/protocols.go: align readers/writers ordering for
    MsgTypeFlagWithEvent frames; add EventClientRequest helper and
    ParseFrame shim. Fix writeSessionID symmetry for ConnectionFinished.
  • relay/channel/volcengine/tts_v3_ws.go: implement the bidirectional and
    unidirectional WS state machines (StartConnection -> StartSession ->
    TaskRequest? -> FinishSession), parse usage.text_words from
    SessionFinished.
  • relay/channel/volcengine/tts_v3_http.go: implement HTTP Chunked frame
    splitter (binary frames over a streaming reader) and HTTP SSE passthrough
    (raw text/event-stream forwarded to the client; usage parsed
    side-channel for billing).
  • relay/channel/volcengine/adaptor.go: dispatch on resolveVolcTTSConfig(info)
    covering all four v3 endpoints + retain v1 default. Per-request override
    via metadata.volc_tts_*.
  • dto/channel_settings.go: add VolcTTSConfig (Protocol / ResourceID /
    AuthMode / RequireUsage). No DDL — JSON-in-text on the existing settings
    column, cross-DB safe.
  • relay/common/relay_info.go: VolcTTSOverride *dto.VolcTTSConfig for
    per-request override.
  • service/log_info_generate.go: log volc_tts_protocol /
    volc_resource_id for audit.

Frontend

  • web/default/ (Base UI): Select / Input / Switch controls in
    channel-mutate-drawer.tsx for type=45; schema/defaults/parse/serialize
    in channel-form.ts; en/zh i18n keys.
  • web/classic/ (Semi UI): same four fields wired into
    EditChannelModal.jsx type=45 block.

Auth modes

  • new_console (default): channel key is the new-console-issued API Key,
    sent verbatim as X-Api-Key. Single-segment keys are accepted directly;
    legacy <app_id>|<access_token> is also accepted (second segment used)
    for ergonomic continuity.
  • legacy: channel key MUST be <app_id>|<access_token>; emitted as
    X-Api-App-Id + X-Api-Access-Key.

Defaults

  • Default ResourceID = seed-tts-1.0-concurr, matching the existing
    OpenAI->Volcengine voice map (alloy/echo/... -> *_mars_bigtts,
    v1.0 voices). Users with v2.0 voices (*_uranus_bigtts, saturn_*) must
    override to seed-tts-2.0 / seed-icl-2.0 accordingly.

Tests

relay/channel/volcengine/protocols_v3_test.go covers:

  • Round-trip encoding/decoding for StartConnection / StartSession /
    TaskRequest / FinishSession + AudioOnlyServer + ConnectionStarted.
  • HTTP Chunked frame splitter end-to-end.
  • SSE side-channel usage capture.
  • Auth header construction in both modes (new_console single & legacy
    formats, legacy mode rejects single-segment).
  • writers() / readers() symmetry guard.
go test ./relay/channel/volcengine/...   # all green
go build ./...                           # clean

Cross-DB compatibility (Rule 2)

No schema migration. New fields are nested JSON under the existing
channels.settings text column, identical on SQLite / MySQL >= 5.7.8 /
PostgreSQL >= 9.6.

Conventions

  • All marshal/unmarshal goes through common.Marshal / common.Unmarshal
    (Rule 1).
  • Optional scalars use pointer + omitempty (Rule 6).
  • No protected-identifier changes (Rule 5).

Test plan

  • go test ./relay/channel/volcengine/... passes.
  • go build ./... passes.
  • Manual smoke: configure type=45 channel with TTS Protocol = WS
    Bidirectional (v3), call /v1/audio/speech with stream:true,
    verify audio frames stream to client.
  • Regression: leave TTS Protocol blank, confirm v1 ws_binary path
    still works for existing flows.
  • Manual smoke for ws-uni / http-chunked / http-sse transports
    (covered in unit tests; live verification per deploy).

Summary by CodeRabbit

  • New Features
    • Volcengine TTS v3 support (WS bidir/uni, HTTP chunked, HTTP SSE), per-channel and per-request TTS overrides, token-usage return behavior, and UI controls to configure TTS v3 settings.
  • Bug Fixes
    • Fixed session-ID serialization for protocol messages.
  • Tests
    • Added comprehensive protocol framing, header, streaming and header-mode tests.
  • Documentation
    • Added English and Chinese UI/help strings describing TTS transports and auth modes.

Review Change Stack

rangerTao added 2 commits May 9, 2026 12:09
Adds Volcengine v3 TTS support alongside the existing v1 ws_binary path.
Channels can now select one of four v3 transports per-channel (or per-request
via OpenAI metadata), with the appropriate auth header layout (new-console
X-Api-Key vs legacy X-Api-App-Id + X-Api-Access-Key) and resource id
(seed-tts-2.0 / seed-icl-2.0 / etc.).

Backend:
- protocols.go: align readers/writers ordering, add EventClientRequest
  helper for v3 event-tagged frames, fix writeSessionID symmetry for
  ConnectionFinished.
- tts_v3_ws.go: implement bidirectional + unidirectional WS state machines
  (StartConnection -> StartSession -> TaskRequest? -> FinishSession), parse
  SessionFinished usage.text_words.
- tts_v3_http.go: implement HTTP Chunked frame splitter (binary frames) and
  HTTP SSE passthrough (raw text/event-stream forwarded to client; usage
  side-parsed for billing).
- adaptor.go: dispatch by resolveVolcTTSConfig(info) covering the four
  v3 endpoints + retain v1 default. Metadata override via volc_tts_*.
- dto/channel_settings.go: add VolcTTSConfig (protocol/resource_id/auth_mode/
  require_usage). No DDL — JSON-in-text on existing settings column.
- relay/common/relay_info.go: VolcTTSOverride field for per-request override.
- service/log_info_generate.go: log volc_tts_protocol / volc_resource_id.

Frontend (both themes):
- web/default/ (Base UI): Select / Input / Switch controls in
  channel-mutate-drawer; channel-form schema/defaults/parse/serialize.
- web/classic/ (Semi UI): same four fields in EditChannelModal type=45 block.
- i18n: en/zh keys for new labels and descriptions.

Tests: round-trip + frame-splitter + SSE side-channel + auth-header tests in
relay/channel/volcengine/protocols_v3_test.go (all green).

Default behavior unchanged: empty Protocol falls back to v1 ws_binary.
…fault Resource ID with default voice map

Two follow-up fixes after end-to-end smoke testing v3 TTS:

1. new_console mode now accepts a SINGLE-SEGMENT API Key, sent verbatim as
   X-Api-Key. Previously the code force-split on '|' and used segment[1] as
   X-Api-Key, which fails because new-console-issued API Keys are independent
   credentials (NOT the same as legacy access tokens). Operators using legacy
   "<app_id>|<access_token>" format are still supported for backwards compat
   (second segment is taken).

   legacy mode is unchanged: still requires "<app_id>|<access_token>" and
   emits X-Api-App-Id + X-Api-Access-Key.

2. VolcTTSDefaultResourceID changed from "seed-tts-2.0" to
   "seed-tts-1.0-concurr" to match the default OpenAI->Volcengine voice map
   (alloy/echo/fable/... all map to *_mars_bigtts, which are v1.0 voices).
   The previous default produced upstream code=55000000 ("resource ID is
   mismatched with speaker related resource") for any client that didn't
   override voice. Users wanting v2.0 still set ResourceID=seed-tts-2.0 in
   the channel config or via metadata.volc_tts_resource_id, AND must pass
   a v2.0 voice such as zh_female_xiaohe_uranus_bigtts.

Tests updated and pass: go test ./relay/channel/volcengine/...
@coderabbitai

coderabbitai Bot commented May 9, 2026

Copy link
Copy Markdown
Contributor

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 65e71832-e562-4b89-b1fa-8e75d324cb80

📥 Commits

Reviewing files that changed from the base of the PR and between 382f130 and bd8df3d.

📒 Files selected for processing (1)
  • relay/channel/volcengine/tts_v3_ws.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • relay/channel/volcengine/tts_v3_ws.go

Walkthrough

Implements Volcengine TTS v3 support: adds per-channel/request config, selects v3 transport (WS bidir/uni, HTTP chunked, HTTP SSE), implements v3 transport handlers and frame decoder, routes adapter logic, records audit fields, and adds modern and classic UI controls plus i18n strings.

Changes

Volcengine v3 TTS Protocol Support

Layer / File(s) Summary
Configuration Schema
dto/channel_settings.go, relay/common/relay_info.go
Adds VolcTTSConfig struct (protocol/resource_id/auth_mode/require_usage), constants, helper methods; extends ChannelOtherSettings with VolcTTS and RelayInfo with VolcTTSOverride.
Protocol Primitives & Tests
relay/channel/volcengine/protocols.go, relay/channel/volcengine/protocols_v3_test.go
Fixes sessionID serialization/read ordering for connection-finish events; adds EventClientRequest() and ParseFrame(); adds comprehensive v3 framing/unit tests validating round-trip, streaming reads, payload defaults, header modes, and usage detection.
V3 WebSocket Transport
relay/channel/volcengine/tts_v3_ws.go
Implements WS bidirectional/unidirectional handlers, v3 header building (new-console vs legacy), StartSession/Task payload mapping, lifecycle orchestration, streaming audio output, and usage parsing.
V3 HTTP Transports
relay/channel/volcengine/tts_v3_http.go
Implements HTTP chunked and HTTP SSE handlers, v3 JSON body builder, streaming binary frame decoding (ReadOneFrame), SSE passthrough, and usage extraction with fallback to estimates.
Request Routing & Integration
relay/channel/volcengine/adaptor.go
Parses per-request volc_tts_* metadata overrides in ConvertAudioRequest, resolves effective VolcTTSConfig, selects v3 endpoints in GetRequestURL, bypasses generic relay for streaming/v3, and routes DoResponse to v3 handlers; adds resolveVolcTTSConfig and applyV3MetadataOverride.
Audit Logging
service/log_info_generate.go
Appends resolved volc_tts_protocol and volc_resource_id into audio audit info from per-request override or channel defaults.
Modern UI Form
web/default/src/features/channels/lib/channel-form.ts, .../channel-mutate-drawer.tsx, .../constants.ts
Adds form schema/defaults for volc_tts_* fields, transforms persisted settings.volc_tts into form fields, conditionally emits settings.volc_tts for type 45, adds drawer controls and sentinel handling, updates credential prompt for type 45.
Classic UI Form
web/classic/src/components/table/channels/modals/EditChannelModal.jsx
Adds volc_tts_* inputs for type 45, hydrates defaults (require_usage true), conditionally constructs settings.volc_tts on submit, and removes top-level override keys before sending.
Localization
web/default/src/i18n/locales/en.json, zh.json
Adds English and Chinese translations for protocol labels, transport options, auth mode descriptions, resource-id help, and usage token guidance.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

Suggested reviewers

  • seefs001
  • creamlike1024

Poem

🐰 Hops through v3 gates with a cheer,
New transports hum and audio’s near.
Headers set, frames dance in flight,
SessionFinished counts each byte.
🥕✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 54.55% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title clearly describes the main feature: adding Volcengine v3 TTS support with specific transport protocols (ws bidir/uni, http chunked/sse).
Linked Issues check ✅ Passed The PR comprehensively implements all coding requirements from issue #4709: v3 TTS transport support (WS/HTTP variants), new auth modes, per-channel and per-request overrides, wire protocols, usage tracking, and frontend UI controls.
Out of Scope Changes check ✅ Passed All changes directly support Volcengine v3 TTS implementation: protocol changes, new adaptor logic, configuration structs, HTTP/WS handlers, logging, and UI components for channel management. No unrelated modifications detected.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

🧹 Nitpick comments (4)
relay/channel/volcengine/tts_v3_ws.go (1)

288-346: ⚡ Quick win

Drain loop has no read deadline; a stalled upstream will pin the request goroutine forever.

ReceiveMessage(conn) blocks on conn.ReadMessage() indefinitely; nothing here propagates c.Request.Context() cancellation into the gorilla WebSocket, and no SetReadDeadline is set inside the loop (only the best-effort 2s deadline at Line 350 after we exit). If the Volcengine endpoint accepts the session but stops sending frames before SessionFinished, this handler hangs until the OS TCP keepalive eventually trips — which can be many minutes — holding an HTTP server worker the whole time.

Consider setting a per-frame SetReadDeadline (sliding deadline, e.g. 30s, refreshed on each successful read) and/or spawning a watcher goroutine that closes conn when c.Request.Context().Done() fires.

♻️ Sketch — sliding deadline + context watcher
+	// Cancel the read if the client goes away.
+	go func() {
+		<-c.Request.Context().Done()
+		_ = conn.Close()
+	}()
+
+	const frameIdleTimeout = 30 * time.Second
+
 drainLoop:
 	for {
+		_ = conn.SetReadDeadline(time.Now().Add(frameIdleTimeout))
 		msg, recvErr := ReceiveMessage(conn)
 		if recvErr != nil {
 			if websocket.IsCloseError(recvErr, websocket.CloseNormalClosure, websocket.CloseGoingAway) {
 				break drainLoop
 			}
 			return nil, v3WrapError(recvErr, "recv frame failed")
 		}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@relay/channel/volcengine/tts_v3_ws.go` around lines 288 - 346, The drainLoop
can hang because ReceiveMessage(conn) blocks without a read deadline or context
cancellation; before each ReceiveMessage call in drainLoop set a sliding
per-frame read deadline on conn (e.g.,
conn.SetReadDeadline(time.Now().Add(30*time.Second))) and handle any
SetReadDeadline error, and after each successful read refresh the deadline;
additionally spawn a small watcher goroutine that listens for
c.Request.Context().Done() and closes conn (or calls conn.Close()) so the
blocking ReadMessage unblocks, making sure the watcher exits when drainLoop ends
to avoid leaks (references: ReceiveMessage(conn), drainLoop,
c.Request.Context()).
relay/channel/volcengine/tts_v3_http.go (1)

79-89: ⚡ Quick win

Use a dedicated http.Client with header-read and dial timeouts instead of http.DefaultClient.

Both v3 HTTP handlers issue http.DefaultClient.Do(req) with no transport-level timeouts. The request context (c.Request.Context()) covers client-side cancellation, but if Volcengine accepts the TCP connection and then stalls before sending response headers (or stops sending bytes), the goroutine and worker slot remain held until the kernel's TCP keepalive eventually fails.

For TTS streaming you don't want a hard Client.Timeout (it kills long audio streams), but a custom http.Transport with bounded DialContext, TLSHandshakeTimeout, and ResponseHeaderTimeout is essential. Implement a package-level client matching the pattern in controller/ratio_sync.go:

♻️ Sketch — package-level client with bounded handshake/header/dial timeouts
var v3HTTPClient = &http.Client{
    Transport: &http.Transport{
        DialContext: (&net.Dialer{
            Timeout:   30 * time.Second,
            KeepAlive: 30 * time.Second,
        }).DialContext,
        TLSHandshakeTimeout:   10 * time.Second,
        ResponseHeaderTimeout: 10 * time.Second,
        ExpectContinueTimeout: 1 * time.Second,
        IdleConnTimeout:       90 * time.Second,
    },
    // No Client.Timeout — body streaming must remain open.
}
-resp, doErr := http.DefaultClient.Do(req)
+resp, doErr := v3HTTPClient.Do(req)

Applies to lines 85 and 196.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@relay/channel/volcengine/tts_v3_http.go` around lines 79 - 89, Replace the
use of http.DefaultClient.Do(req) in volcengine/tts_v3_http.go with a
package-level custom client (e.g., v3HTTPClient) that uses a custom
http.Transport; define v3HTTPClient as a package var and configure Transport
with a net.Dialer (Timeout and KeepAlive), TLSHandshakeTimeout,
ResponseHeaderTimeout, ExpectContinueTimeout and IdleConnTimeout as suggested
(do not set Client.Timeout so streaming bodies stay open), then call
v3HTTPClient.Do(req) instead of http.DefaultClient.Do(req) wherever resp, doErr
:= http.DefaultClient.Do(req) appears.
web/default/src/features/channels/constants.ts (1)

374-374: ⚡ Quick win

Prefer a stable i18n key instead of a long inline prompt literal.

Using a full sentence literal here is brittle and easy to desync from locale files. Store an i18n key (e.g., channels.volcengine.keyPrompt) and move text to locale resources.

As per coding guidelines, web/default/**/*.{ts,tsx} requires user-facing text to support i18n, and web/default/**/constants.ts requires consistent key-based message handling for constants used in UI display.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/default/src/features/channels/constants.ts` at line 374, Replace the long
inline literal in web/default/src/features/channels/constants.ts with a stable
i18n key (e.g., "channels.volcengine.keyPrompt") and move the full sentence into
the locale resource files; update any consumer of that constant to use the i18n
lookup (the app's translation function) instead of rendering the raw string so
constants.ts only exports the key, not the literal, ensuring all UI text goes
through i18n as required by web/default/**/*.{ts,tsx} and the constants handling
guideline for channels.
web/default/src/i18n/locales/zh.json (1)

4290-4304: ⚡ Quick win

Use hierarchical keys for these new TTS i18n entries.

Lines 4290-4304 continue the sentence-as-key pattern. For new additions, switch to semantic keys (e.g., channels.volcengine.tts.protocol.label) to prevent key drift and improve consistency.

As per coding guidelines, web/default/src/i18n/**/*.{ts,tsx,json}: Use hierarchical and semantically clear translation key names such as dashboard.overview.title and maintain naming consistency.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/default/src/i18n/locales/zh.json` around lines 4290 - 4304, The current
zh.json entries use sentence-as-key strings ("TTS Protocol", "TTS Resource ID",
"TTS Auth Mode", "Auto / WS Binary (v1)", "WS Bidirectional (v3)", "WS
Unidirectional (v3)", "HTTP Chunked (v3)", "HTTP SSE (v3, passthrough)", the
long help text, and the two console/auth descriptions) — refactor these into
hierarchical semantic keys (for example channels.volcengine.tts.protocol.label,
channels.volcengine.tts.resourceId.label,
channels.volcengine.tts.authMode.label,
channels.volcengine.tts.transport.auto_ws_binary,
channels.volcengine.tts.transport.ws_bidi,
channels.volcengine.tts.transport.ws_unidi,
channels.volcengine.tts.transport.http_chunked,
channels.volcengine.tts.transport.http_sse,
channels.volcengine.tts.help.sse_passthrough,
channels.volcengine.tts.console.new, channels.volcengine.tts.console.legacy,
channels.volcengine.tts.return_usage) and move the Chinese strings as their
values; then update any code that references the old literal keys to use the new
hierarchical keys (search for usages of "TTS Protocol", "TTS Resource ID", etc.)
and run i18n lint/tests to ensure no missing keys remain.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@web/classic/src/components/table/channels/modals/EditChannelModal.jsx`:
- Around line 3539-3541: The displayed helper text in EditChannelModal.jsx
mistakenly states the default resource ID is "seed-tts-2.0" while the backend
constant VolcTTSDefaultResourceID (in dto/channel_settings.go) uses
"seed-tts-1.0-concurr"; update the extraText for the X-Api-Resource-Id field (in
EditChannelModal.jsx) to reflect the backend default ("留空时默认
seed-tts-1.0-concurr") or otherwise make the text indicate it must match
VolcTTSDefaultResourceID so frontend and backend defaults stay consistent.

In
`@web/default/src/features/channels/components/drawers/channel-mutate-drawer.tsx`:
- Around line 1822-1830: Update the user-facing TTS resource strings in the
ChannelMutateDrawer component: change the displayed default/version text in the
FormDescription from "seed-tts-2.0" to "seed-tts-1.0-concurr" and wrap that
entire message with the t() i18n call, and also replace the hard-coded
placeholder prop value 'seed-tts-2.0' on the input (the JSX element using
{...field} and value={field.value || ''}) with a call to
t('seed-tts-1.0-concurr') (or another suitably keyed translation) so both the
placeholder and the description are localized and reflect the correct default
version.
- Around line 1788-1790: The SelectItem options in the ChannelMutateDrawer are
using empty-string values which `@base-ui/react` treats as a selected value;
replace any SelectItem value='' (e.g., the "Auto / WS Binary (v1)" option) with
value={null} and ensure there is a SelectItem value={null} representing the "no
selection" sentinel so placeholder and state styling behave correctly; also
update the raw TTS Resource ID placeholder string (and the TTS Auth Mode strings
at the other mentioned spots) to wrap them in the translation function t(...)
instead of leaving raw strings so they are localized (reference the SelectItem
JSX in channel-mutate-drawer.tsx and the TTS Resource ID / Auth Mode input
placeholders).

In `@web/default/src/features/channels/constants.ts`:
- Line 374: The prompt string that currently states "Format: AppId|AccessToken
(TTS / Realtime)..." is stale; update the constant in features/channels (the
descriptive message string) to clearly state both accepted credential formats:
for new-console v3 one-segment API keys may be provided as X-Api-Key, while
legacy mode still accepts AppId|AccessToken sent as X-Api-App-Id +
X-Api-Access-Key, and clarify that Chat/embedding still use the AccessToken as
Bearer; replace the old literal with this clarified wording to prevent
misconfiguration.

In `@web/default/src/i18n/locales/en.json`:
- Line 4299: The help string for the X-Api-Resource-Id entry is incorrect:
update the value for the JSON key "X-Api-Resource-Id header value. Defaults to
seed-tts-2.0 when empty. Common values: seed-tts-2.0, seed-tts-1.0,
seed-tts-1.0-concurr, seed-icl-2.0, seed-icl-1.0, seed-icl-1.0-concurr." to
reflect the backend default "seed-tts-1.0-concurr" (i.e., change the phrase
"Defaults to seed-tts-2.0" to "Defaults to seed-tts-1.0-concurr") so the
frontend help text matches the backend behavior.
- Line 4302: Update the i18n copy string that currently reads "New console:
AccessToken (second segment of the API key) is sent as X-Api-Key. Legacy: AppId
+ AccessToken are sent as X-Api-App-Id + X-Api-Access-Key." to a wording that
covers both single-segment and multi-segment API keys (e.g., "New console: the
AccessToken (or the second segment of a multi-segment API key) is sent as
X-Api-Key; legacy keys use AppId + AccessToken sent as X-Api-App-Id and
X-Api-Access-Key.") so the message for the JSON key "New console: AccessToken
(second segment of the API key) is sent as X-Api-Key. Legacy: AppId +
AccessToken are sent as X-Api-App-Id + X-Api-Access-Key." accurately reflects
current behavior for single- and multi-segment keys.

In `@web/default/src/i18n/locales/zh.json`:
- Line 4302: Update the zh.json translation for the exact key "New console:
AccessToken (second segment of the API key) is sent as X-Api-Key. Legacy: AppId
+ AccessToken are sent as X-Api-App-Id + X-Api-Access-Key." to reflect that the
new console accepts both single-segment API keys and the second segment of
multi-segment keys; modify the value to mention "single-segment key or the
second segment of the API key" and keep the legacy explanation for X-Api-App-Id
+ X-Api-Access-Key intact so the message covers both accepted new-console
formats and the old-console format.
- Line 4299: The translation for the tooltip/string that currently states
"Defaults to seed-tts-2.0 when empty" is out of sync; update the Chinese entry
(the JSON value whose English key starts with "X-Api-Resource-Id header value.
Defaults to seed-tts-2.0 when empty.") to say the backend default is
"seed-tts-1.0-concurr" instead of "seed-tts-2.0" so it matches the backend
change.

---

Nitpick comments:
In `@relay/channel/volcengine/tts_v3_http.go`:
- Around line 79-89: Replace the use of http.DefaultClient.Do(req) in
volcengine/tts_v3_http.go with a package-level custom client (e.g.,
v3HTTPClient) that uses a custom http.Transport; define v3HTTPClient as a
package var and configure Transport with a net.Dialer (Timeout and KeepAlive),
TLSHandshakeTimeout, ResponseHeaderTimeout, ExpectContinueTimeout and
IdleConnTimeout as suggested (do not set Client.Timeout so streaming bodies stay
open), then call v3HTTPClient.Do(req) instead of http.DefaultClient.Do(req)
wherever resp, doErr := http.DefaultClient.Do(req) appears.

In `@relay/channel/volcengine/tts_v3_ws.go`:
- Around line 288-346: The drainLoop can hang because ReceiveMessage(conn)
blocks without a read deadline or context cancellation; before each
ReceiveMessage call in drainLoop set a sliding per-frame read deadline on conn
(e.g., conn.SetReadDeadline(time.Now().Add(30*time.Second))) and handle any
SetReadDeadline error, and after each successful read refresh the deadline;
additionally spawn a small watcher goroutine that listens for
c.Request.Context().Done() and closes conn (or calls conn.Close()) so the
blocking ReadMessage unblocks, making sure the watcher exits when drainLoop ends
to avoid leaks (references: ReceiveMessage(conn), drainLoop,
c.Request.Context()).

In `@web/default/src/features/channels/constants.ts`:
- Line 374: Replace the long inline literal in
web/default/src/features/channels/constants.ts with a stable i18n key (e.g.,
"channels.volcengine.keyPrompt") and move the full sentence into the locale
resource files; update any consumer of that constant to use the i18n lookup (the
app's translation function) instead of rendering the raw string so constants.ts
only exports the key, not the literal, ensuring all UI text goes through i18n as
required by web/default/**/*.{ts,tsx} and the constants handling guideline for
channels.

In `@web/default/src/i18n/locales/zh.json`:
- Around line 4290-4304: The current zh.json entries use sentence-as-key strings
("TTS Protocol", "TTS Resource ID", "TTS Auth Mode", "Auto / WS Binary (v1)",
"WS Bidirectional (v3)", "WS Unidirectional (v3)", "HTTP Chunked (v3)", "HTTP
SSE (v3, passthrough)", the long help text, and the two console/auth
descriptions) — refactor these into hierarchical semantic keys (for example
channels.volcengine.tts.protocol.label,
channels.volcengine.tts.resourceId.label,
channels.volcengine.tts.authMode.label,
channels.volcengine.tts.transport.auto_ws_binary,
channels.volcengine.tts.transport.ws_bidi,
channels.volcengine.tts.transport.ws_unidi,
channels.volcengine.tts.transport.http_chunked,
channels.volcengine.tts.transport.http_sse,
channels.volcengine.tts.help.sse_passthrough,
channels.volcengine.tts.console.new, channels.volcengine.tts.console.legacy,
channels.volcengine.tts.return_usage) and move the Chinese strings as their
values; then update any code that references the old literal keys to use the new
hierarchical keys (search for usages of "TTS Protocol", "TTS Resource ID", etc.)
and run i18n lint/tests to ensure no missing keys remain.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 084de4c9-3bc4-4884-9281-a628da22ea24

📥 Commits

Reviewing files that changed from the base of the PR and between d146e45 and a92f378.

📒 Files selected for processing (14)
  • dto/channel_settings.go
  • relay/channel/volcengine/adaptor.go
  • relay/channel/volcengine/protocols.go
  • relay/channel/volcengine/protocols_v3_test.go
  • relay/channel/volcengine/tts_v3_http.go
  • relay/channel/volcengine/tts_v3_ws.go
  • relay/common/relay_info.go
  • service/log_info_generate.go
  • web/classic/src/components/table/channels/modals/EditChannelModal.jsx
  • web/default/src/features/channels/components/drawers/channel-mutate-drawer.tsx
  • web/default/src/features/channels/constants.ts
  • web/default/src/features/channels/lib/channel-form.ts
  • web/default/src/i18n/locales/en.json
  • web/default/src/i18n/locales/zh.json

Comment thread web/default/src/features/channels/components/drawers/channel-mutate-drawer.tsx Outdated
Comment thread web/default/src/features/channels/components/drawers/channel-mutate-drawer.tsx Outdated
Comment thread web/default/src/features/channels/constants.ts Outdated
Comment thread web/default/src/i18n/locales/en.json Outdated
Comment thread web/default/src/i18n/locales/en.json Outdated
Comment thread web/default/src/i18n/locales/zh.json Outdated
Comment thread web/default/src/i18n/locales/zh.json Outdated
rangerTao added 2 commits May 9, 2026 14:00
…pty Select, sync UI copy with default ResourceID

Addresses 8 CodeRabbit review comments on PR QuantumNous#4710:

- web/default drawer: replace SelectItem value="" with a non-empty sentinel
  (VOLC_TTS_DEFAULT_SENTINEL = "__default__") for both volc_tts_protocol and
  volc_tts_auth_mode. Base UI Select treats "" as a filled value and would
  break placeholder semantics; the sentinel maps back to "" before persisting.
- Sync TTS Resource ID help text + placeholder across both themes (default
  drawer, classic EditChannelModal, en.json, zh.json) to reflect that the
  backend default is now seed-tts-1.0-concurr (matching the *_mars_bigtts v1.0
  voice map) and that v2.0 voices require an explicit override.
- Sync auth-mode help text to cover both single-segment new-console keys and
  multi-segment legacy AppId|AccessToken keys.
- Update TYPE_TO_KEY_PROMPT[45] to document the single-segment new-console key
  format alongside the legacy two-segment format.
- Wrap raw user-facing placeholder string in t() per i18n convention.
…frame deadline

Addresses the CodeRabbit nit on PR QuantumNous#4710 about handleTTSV3WSResponse pinning
a goroutine when the client disconnects or upstream stops sending frames:

- Add a watcher goroutine that closes the upstream WS as soon as
  c.Request.Context() is cancelled (i.e. the gin client went away). Without
  this, ReceiveMessage blocked on the underlying TCP socket until OS
  keepalive eventually tripped, holding the gin worker for many minutes.
- Apply a sliding 30s read deadline to every ReceiveMessage call inside
  drainLoop and inside v3ExpectEvent (handshake). A healthy stream keeps
  refreshing the deadline; a stalled upstream times out and surfaces a 502
  / 504 instead of leaking the goroutine.
- When the client cancellation closes the conn, the loop returns
  http.StatusGatewayTimeout with a clear "stream cancelled by client" error
  rather than a generic recv failure.

HTTP chunked / SSE paths already react to client cancellation through
http.NewRequestWithContext(c.Request.Context(), ...); only the bespoke WS
path needed this hardening.
@rangerTao

Copy link
Copy Markdown
Author

Addressed the review-body nit about the WS drain loop in bd8df3d09:

  • A watcher goroutine now closes the upstream WS as soon as c.Request.Context() is cancelled, so client disconnects don't pin the gin worker until OS TCP keepalive trips.
  • Every ReceiveMessage call inside drainLoop and v3ExpectEvent now refreshes a sliding 30 s read deadline. Stalled upstreams surface a 502 / 504 instead of leaking goroutines.
  • HTTP Chunked / SSE paths already responded to client cancellation via http.NewRequestWithContext(c.Request.Context(), ...), so only the bespoke WS handler needed this hardening.

Tests + build still green.

rangerTao added a commit to rangerTao/new-api that referenced this pull request May 25, 2026
- bound v3 WS receive blocking with context cancel + per-frame deadline
- address PR QuantumNous#4710 review (sentinel for empty Select, sync default ResourceID copy)
@Calcium-Ion
Calcium-Ion force-pushed the main branch 2 times, most recently from 51fdfc5 to 2b6f1df Compare August 30, 2026 15:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support Volcengine v3 TTS protocols (ws bidirectional/unidirectional, http chunked, http sse)

1 participant