feat(web): 角色与音频工作台 - 完善素材选择、生成参数与同步交互
This commit is contained in:
@@ -27,7 +27,7 @@ export type AgentProfileView = {
|
||||
layers: AgentProfileLayer[];
|
||||
};
|
||||
|
||||
export type AgentApprovalPreviewOperation = "add_node" | "update_node" | "connect_nodes" | "arrange_nodes" | "generate_media" | "create_storyboard" | "edit_storyboard" | "plan_step";
|
||||
export type AgentApprovalPreviewOperation = "add_node" | "update_node" | "connect_nodes" | "arrange_nodes" | "generate_media" | "create_storyboard" | "edit_storyboard" | "create_character" | "plan_step";
|
||||
|
||||
export type AgentApprovalPreviewItem = {
|
||||
operation: AgentApprovalPreviewOperation;
|
||||
|
||||
@@ -608,7 +608,7 @@ export function updateAdminDrawingEngineSetting(input: Pick<CanvasDrawingEngineS
|
||||
return http.patch<{ setting: CanvasDrawingEngineSetting }>("/admin/settings/drawing-engine", input);
|
||||
}
|
||||
|
||||
export type AdminApiLogParams = AdminListParams & { recordType?: "request" | "download" | "all" };
|
||||
export type AdminApiLogParams = AdminListParams & { recordType?: "request" | "download" | "all"; capability?: "text" | "image" | "video" | "audio" };
|
||||
|
||||
export function listAdminApiLogs(params: AdminApiLogParams = {}) {
|
||||
return http.get<{ logs: ApiCallLog[]; total: number; page: number; pageSize: number }>("/admin/api-logs", { params });
|
||||
@@ -618,8 +618,16 @@ export function getAdminApiLog(id: string) {
|
||||
return http.get<{ log: ApiCallLog }>(`/admin/api-logs/${encodeURIComponent(id)}`);
|
||||
}
|
||||
|
||||
export function queryAdminApiLogTask(id: string) {
|
||||
return http.post<AdminProviderTaskQueryResult>(`/admin/api-logs/${encodeURIComponent(id)}/query-task`);
|
||||
export function queryAdminApiLogTask(id: string, providerRequestId?: string) {
|
||||
return http.post<AdminProviderTaskQueryResult>(`/admin/api-logs/${encodeURIComponent(id)}/query-task`, providerRequestId ? { providerRequestId } : undefined, { timeout: 120_000 });
|
||||
}
|
||||
|
||||
export function recoverAdminApiLogVideoByURL(id: string, url: string, providerRequestId?: string) {
|
||||
return http.post<AdminProviderTaskQueryResult>(`/admin/api-logs/${encodeURIComponent(id)}/recover-url`, { url, providerRequestId }, { timeout: 120_000 });
|
||||
}
|
||||
|
||||
export function batchRecoverAdminApiLogs(ids: string[]) {
|
||||
return http.post<{ items: Array<{ logId: string; recovered: boolean; providerStatus?: string; error?: string }>; success: number; failed: number; skipped: number }>("/admin/api-logs/recover-batch", { ids }, { timeout: 180_000 });
|
||||
}
|
||||
|
||||
export async function exportAdminApiLogs(params: AdminApiLogParams & { ids?: string[] } = {}) {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { normalizeAudioFormatForConfig, normalizeAudioVoiceForConfig } from "@/lib/audio-generation";
|
||||
import { getMediaBlob } from "@/services/file-storage";
|
||||
import { getImageBlob } from "@/services/image-storage";
|
||||
import { resourceIdFromStorageKey, resourceStorageKey, uploadResourceFile } from "@/services/api/resources";
|
||||
@@ -306,7 +307,7 @@ function backendGenerationTaskInput(options: BackendGenerationTaskOptions, prepa
|
||||
referenceVideos: prepared.referenceVideos,
|
||||
referenceAudios: prepared.referenceAudios,
|
||||
mask: prepared.mask,
|
||||
metadata: generationMetadata(config, mode, {
|
||||
metadata: generationMetadata(config, {
|
||||
...metadata,
|
||||
...(options.clientOperationId ? { clientOperationId: options.clientOperationId } : {}),
|
||||
...(options.retryOf ? { retryOf: options.retryOf } : {}),
|
||||
@@ -316,7 +317,7 @@ function backendGenerationTaskInput(options: BackendGenerationTaskOptions, prepa
|
||||
};
|
||||
}
|
||||
|
||||
function generationMetadata(config: AiConfig, mode: BackendGenerationMode, metadata?: Record<string, unknown>) {
|
||||
function generationMetadata(config: AiConfig, metadata?: Record<string, unknown>) {
|
||||
const channel = resolveModelChannel(config, config.model);
|
||||
const model = modelOptionName(config.model);
|
||||
const modelCost = channel.modelCosts?.find((item) => item.model === model);
|
||||
@@ -329,20 +330,8 @@ function generationMetadata(config: AiConfig, mode: BackendGenerationMode, metad
|
||||
? existing[protocol] as Record<string, unknown>
|
||||
: {};
|
||||
const defaults = modelCost?.defaultOptions && typeof modelCost.defaultOptions === "object" ? modelCost.defaultOptions : {};
|
||||
const audioOptions = mode === "audio" ? {
|
||||
emo_control_method: config.audioEmotionControlMethod || "与音色参考音频相同",
|
||||
emo_random: config.audioEmotionRandom === "true",
|
||||
emo_happy: Number(config.audioEmotionHappy || 0),
|
||||
emo_angry: Number(config.audioEmotionAngry || 0),
|
||||
emo_sad: Number(config.audioEmotionSad || 0),
|
||||
emo_afraid: Number(config.audioEmotionAfraid || 0),
|
||||
emo_disgusted: Number(config.audioEmotionDisgusted || 0),
|
||||
emo_melancholic: Number(config.audioEmotionMelancholic || 0),
|
||||
emo_surprised: Number(config.audioEmotionSurprised || 0),
|
||||
emo_calm: Number(config.audioEmotionCalm || 0),
|
||||
} : {};
|
||||
if (!Object.keys(defaults).length && !Object.keys(audioOptions).length && !Object.keys(namespace).length) return metadata;
|
||||
return { ...metadata, providerOptions: { ...existing, [protocol]: { ...defaults, ...audioOptions, ...namespace } } };
|
||||
if (!Object.keys(defaults).length && !Object.keys(namespace).length) return metadata;
|
||||
return { ...metadata, providerOptions: { ...existing, [protocol]: { ...defaults, ...namespace } } };
|
||||
}
|
||||
|
||||
async function prepareBackendMediaReference(media: ReferenceVideo | ReferenceAudio) {
|
||||
@@ -420,9 +409,11 @@ export function backendProviderConfig(config: AiConfig, mode: BackendGenerationM
|
||||
videoGenerateAudio: config.videoGenerateAudio,
|
||||
videoWatermark: config.videoWatermark,
|
||||
videoArkPrivateAssetUpload: config.videoArkPrivateAssetUpload,
|
||||
audioVoice: config.audioVoice,
|
||||
audioFormat: config.audioFormat,
|
||||
audioVoice: normalizeAudioVoiceForConfig(config, config.audioVoice),
|
||||
audioFormat: normalizeAudioFormatForConfig(config, config.audioFormat),
|
||||
audioSpeed: config.audioSpeed,
|
||||
audioLanguage: config.audioLanguage,
|
||||
audioDialect: config.audioDialect,
|
||||
audioInstructions: config.audioInstructions,
|
||||
systemPrompt: config.systemPrompt,
|
||||
};
|
||||
@@ -462,9 +453,11 @@ function workflowProviderConfig(config: AiConfig, requestConfig: ReturnType<type
|
||||
videoGenerateAudio: config.videoGenerateAudio,
|
||||
videoWatermark: config.videoWatermark,
|
||||
videoArkPrivateAssetUpload: config.videoArkPrivateAssetUpload,
|
||||
audioVoice: config.audioVoice,
|
||||
audioFormat: config.audioFormat,
|
||||
audioVoice: normalizeAudioVoiceForConfig(config, config.audioVoice),
|
||||
audioFormat: normalizeAudioFormatForConfig(config, config.audioFormat),
|
||||
audioSpeed: config.audioSpeed,
|
||||
audioLanguage: config.audioLanguage,
|
||||
audioDialect: config.audioDialect,
|
||||
audioInstructions: config.audioInstructions,
|
||||
workflowId: workflow.workflowId,
|
||||
webappId: workflow.webappId,
|
||||
@@ -497,7 +490,7 @@ function logicalCapabilityOptions(config: AiConfig, mode: BackendGenerationMode)
|
||||
: mode === "video"
|
||||
? { size: config.size, videoSeconds: Number(config.videoSeconds), vquality: config.vquality, videoGenerateAudio: config.videoGenerateAudio === "true", videoWatermark: config.videoWatermark === "true" }
|
||||
: mode === "audio"
|
||||
? { audioVoice: config.audioVoice, audioFormat: config.audioFormat, audioSpeed: Number(config.audioSpeed) }
|
||||
? { audioVoice: config.audioVoice, audioFormat: config.audioFormat, audioSpeed: Number(config.audioSpeed), audioLanguage: config.audioLanguage, audioDialect: config.audioDialect }
|
||||
: {};
|
||||
const filtered = Object.fromEntries(Object.entries(candidates).filter(([key]) => Boolean(spec?.options?.[key])));
|
||||
// 只把前台模型声明过的参数送进能力匹配。未声明的 quality 不能因为画布选了 4K 档位
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { http } from "@/services/api/request";
|
||||
import { compactApiParams, http } from "@/services/api/request";
|
||||
import { normalizeAssetCategory, type AssetCategory } from "@/lib/asset-category";
|
||||
import type { GenerationTask } from "@/services/api/task-center";
|
||||
|
||||
@@ -519,6 +519,49 @@ export function listVoiceProfiles() {
|
||||
return http.get<{ profiles: VoiceProfile[] }>("/voice-profiles");
|
||||
}
|
||||
|
||||
export type CharacterListPage = {
|
||||
characters: ProjectCharacterDetail[];
|
||||
page: number;
|
||||
pageSize: number;
|
||||
total: number;
|
||||
hasMore: boolean;
|
||||
};
|
||||
|
||||
export function listCharacters(options: { page?: number; pageSize?: number; query?: string; ids?: string[] } = {}) {
|
||||
return http.get<CharacterListPage>("/characters", {
|
||||
params: compactApiParams({
|
||||
page: options.page,
|
||||
pageSize: options.pageSize,
|
||||
q: options.query,
|
||||
ids: options.ids?.length ? options.ids.join(",") : undefined,
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
export function getCharacter(assetId: string) {
|
||||
return http.get<ProjectCharacterDetail>(`/characters/${encodeURIComponent(assetId)}`);
|
||||
}
|
||||
|
||||
export function createCharacter(input: { name: string; definition?: Record<string, unknown>; imageResourceId?: string; audioResourceId?: string; voiceName?: string; instructions?: string }) {
|
||||
return http.post<ProjectCharacterDetail>("/characters", input);
|
||||
}
|
||||
|
||||
export function updateCharacter(assetId: string, input: { name: string; definition: Record<string, unknown> }) {
|
||||
return http.patch<ProjectCharacterDetail>(`/characters/${encodeURIComponent(assetId)}`, input);
|
||||
}
|
||||
|
||||
export function replaceCharacterRepresentations(assetId: string, representations: Array<{ role: string; resourceId: string; metadata?: Record<string, unknown> }>) {
|
||||
return http.put<ProjectCharacterDetail>(`/characters/${encodeURIComponent(assetId)}/representations`, { representations });
|
||||
}
|
||||
|
||||
export function bindCharacterVoice(assetId: string, input: { sampleResourceId?: string; voiceName?: string; instructions?: string }) {
|
||||
return http.put<ProjectCharacterDetail>(`/characters/${encodeURIComponent(assetId)}/voice`, input);
|
||||
}
|
||||
|
||||
export function unbindCharacterVoice(assetId: string) {
|
||||
return http.delete<ProjectCharacterDetail>(`/characters/${encodeURIComponent(assetId)}/voice`);
|
||||
}
|
||||
|
||||
export function createProjectCharacter(projectId: string, input: { name: string; definition?: Record<string, unknown> }) {
|
||||
return http.post<ProjectCharacterDetail>(`/projects/${encodeURIComponent(projectId)}/characters`, input);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user