-
-
-
- {(open) => }
-
- {items.map((asset) => {
- const resourceId = resourceIdFromStorageKey(asset.data.storageKey);
- const current = resourceId === currentResourceId;
- const busy = busyKey === resourceId;
- const title = canvasTitles?.get(resourceId) || asset.title;
- return
;
- })}
+ return (
+
+
+
+
+ {(open) => (
+
+ )}
+
+ {items.map((asset) => {
+ const resourceId = resourceIdFromStorageKey(asset.data.storageKey);
+ const current = resourceId === currentResourceId;
+ const busy = busyKey === resourceId;
+ const title = canvasTitles?.get(resourceId) || asset.title;
+ return (
+
+ );
+ })}
+
+
+
-
-
-
;
+ );
}
/** 共用一个播放器试听,避免每行一个原生控件把列表挤乱。 */
@@ -130,7 +164,13 @@ function useAudition() {
}
/** 声音选择:列表 + 单击试听,右侧“使用”即绑定。 */
-export function CharacterVoiceLibrary({ currentResourceId, busyKey, canvasTitles, onPick, onUpload }: {
+export function CharacterVoiceLibrary({
+ currentResourceId,
+ busyKey,
+ canvasTitles,
+ onPick,
+ onUpload,
+}: {
currentResourceId: string;
canvasTitles?: CanvasMediaTitles;
busyKey: string;
@@ -140,38 +180,68 @@ export function CharacterVoiceLibrary({ currentResourceId, busyKey, canvasTitles
const [keyword, setKeyword] = useState("");
const { items, query, loading, loadingMore, sentinelRef } = useCharacterMediaPages("audio", keyword);
const { playing, toggle } = useAudition();
- return
-
-
-
- {(open) => }
-
-
-
- {items.map((asset) => {
- const resourceId = resourceIdFromStorageKey(asset.data.storageKey);
- const current = resourceId === currentResourceId;
- const seconds = asset.data.durationMs ? Math.round(asset.data.durationMs / 1000) : 0;
- const title = canvasTitles?.get(resourceId) || asset.title;
- return -
-
-
- {title}
- {seconds ? `${seconds} 秒` : "音频"}
-
- {current ? 使用中 : (
-
+ );
}
diff --git a/web/test/audio-generation.test.ts b/web/test/audio-generation.test.ts
index d35564108..7b35bb8c2 100644
--- a/web/test/audio-generation.test.ts
+++ b/web/test/audio-generation.test.ts
@@ -1,14 +1,15 @@
import { describe, expect, test } from "bun:test";
import { doubaoAudioVoiceOptions, isDoubaoAudioConfig, normalizeAudioVoiceForConfig, normalizeAudioVoiceValue } from "../src/lib/audio-generation";
-const config = (protocol: string) => ({
- provider: "custom",
- model: "models/index-tts",
- audioModel: "models/index-tts",
- apiKey: "",
- baseUrl: "",
- channels: [{ models: ["index-tts"], modelCosts: [], interfaceType: protocol, baseUrl: "", apiKey: "" }],
-} as any);
+const config = (protocol: string) =>
+ ({
+ provider: "custom",
+ model: "models/index-tts",
+ audioModel: "models/index-tts",
+ apiKey: "",
+ baseUrl: "",
+ channels: [{ models: ["index-tts"], modelCosts: [], interfaceType: protocol, baseUrl: "", apiKey: "" }],
+ }) as any;
describe("audio voice protocol handling", () => {
test("keeps custom voice identifiers for non-OpenAI channels", () => {
@@ -37,5 +38,4 @@ describe("audio voice protocol handling", () => {
const doubao = { ...config("doubao-streaming-tts"), model: "seed-audio-1.0", audioModel: "seed-audio-1.0" } as any;
expect(normalizeAudioVoiceForConfig(doubao, "zh_male_custom_official_speaker")).toBe("zh_male_custom_official_speaker");
});
-
});