超时时间
All checks were successful
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 1m7s
All checks were successful
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 1m7s
This commit is contained in:
parent
04072dc94b
commit
e3c4b0f697
@ -13,6 +13,7 @@ import {
|
|||||||
resolveMiniMaxTtsConfig,
|
resolveMiniMaxTtsConfig,
|
||||||
} from './src/server/minimaxTts';
|
} from './src/server/minimaxTts';
|
||||||
import { generateSubtitlePipeline } from './src/server/subtitleGeneration';
|
import { generateSubtitlePipeline } from './src/server/subtitleGeneration';
|
||||||
|
import { resolveLlmProviderConfig } from './src/server/llmProvider';
|
||||||
import { parseSubtitleRequest } from './src/server/subtitleRequest';
|
import { parseSubtitleRequest } from './src/server/subtitleRequest';
|
||||||
import {
|
import {
|
||||||
buildAssSubtitleContent,
|
buildAssSubtitleContent,
|
||||||
@ -262,6 +263,9 @@ async function startServer() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const { provider, targetLanguage, ttsLanguage, fileId } = parseSubtitleRequest(req.body);
|
const { provider, targetLanguage, ttsLanguage, fileId } = parseSubtitleRequest(req.body);
|
||||||
|
const providerConfig = resolveLlmProviderConfig(provider, process.env);
|
||||||
|
const pollTimeoutMs =
|
||||||
|
providerConfig.provider === 'doubao' ? providerConfig.timeoutMs : undefined;
|
||||||
if (!videoPath && !fileId) {
|
if (!videoPath && !fileId) {
|
||||||
logEvent({
|
logEvent({
|
||||||
level: 'warn',
|
level: 'warn',
|
||||||
@ -286,6 +290,7 @@ async function startServer() {
|
|||||||
provider,
|
provider,
|
||||||
targetLanguage,
|
targetLanguage,
|
||||||
ttsLanguage,
|
ttsLanguage,
|
||||||
|
pollTimeoutMs,
|
||||||
fileId,
|
fileId,
|
||||||
filePath: videoPath,
|
filePath: videoPath,
|
||||||
});
|
});
|
||||||
|
|||||||
@ -56,4 +56,18 @@ describe('subtitleJobs', () => {
|
|||||||
|
|
||||||
expect(toSubtitleJobResponse(job)).not.toHaveProperty('filePath');
|
expect(toSubtitleJobResponse(job)).not.toHaveProperty('filePath');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('includes poll timeout metadata in api responses when provided', () => {
|
||||||
|
const store = createSubtitleJobStore();
|
||||||
|
const job = createSubtitleJob(store, {
|
||||||
|
requestId: 'req-1',
|
||||||
|
provider: 'doubao',
|
||||||
|
targetLanguage: 'English',
|
||||||
|
pollTimeoutMs: 900000,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(toSubtitleJobResponse(job)).toMatchObject({
|
||||||
|
pollTimeoutMs: 900000,
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -22,6 +22,7 @@ export interface SubtitleJob {
|
|||||||
provider?: string | null;
|
provider?: string | null;
|
||||||
targetLanguage: string;
|
targetLanguage: string;
|
||||||
ttsLanguage?: string;
|
ttsLanguage?: string;
|
||||||
|
pollTimeoutMs?: number;
|
||||||
fileId?: string;
|
fileId?: string;
|
||||||
filePath?: string;
|
filePath?: string;
|
||||||
error?: string;
|
error?: string;
|
||||||
@ -73,6 +74,7 @@ export const createSubtitleJob = (
|
|||||||
provider,
|
provider,
|
||||||
targetLanguage,
|
targetLanguage,
|
||||||
ttsLanguage,
|
ttsLanguage,
|
||||||
|
pollTimeoutMs,
|
||||||
fileId,
|
fileId,
|
||||||
filePath,
|
filePath,
|
||||||
}: {
|
}: {
|
||||||
@ -80,6 +82,7 @@ export const createSubtitleJob = (
|
|||||||
provider?: string | null;
|
provider?: string | null;
|
||||||
targetLanguage: string;
|
targetLanguage: string;
|
||||||
ttsLanguage?: string;
|
ttsLanguage?: string;
|
||||||
|
pollTimeoutMs?: number;
|
||||||
fileId?: string;
|
fileId?: string;
|
||||||
filePath?: string;
|
filePath?: string;
|
||||||
},
|
},
|
||||||
@ -91,6 +94,7 @@ export const createSubtitleJob = (
|
|||||||
provider,
|
provider,
|
||||||
targetLanguage,
|
targetLanguage,
|
||||||
ttsLanguage,
|
ttsLanguage,
|
||||||
|
pollTimeoutMs,
|
||||||
fileId,
|
fileId,
|
||||||
filePath,
|
filePath,
|
||||||
status: 'queued',
|
status: 'queued',
|
||||||
@ -159,6 +163,7 @@ export const toSubtitleGenerationProgress = (job: SubtitleJob): SubtitleGenerati
|
|||||||
stage: job.stage,
|
stage: job.stage,
|
||||||
progress: job.progress,
|
progress: job.progress,
|
||||||
message: job.message,
|
message: job.message,
|
||||||
|
...(job.pollTimeoutMs ? { pollTimeoutMs: job.pollTimeoutMs } : {}),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const toSubtitleJobResponse = (job: SubtitleJob) => ({
|
export const toSubtitleJobResponse = (job: SubtitleJob) => ({
|
||||||
|
|||||||
@ -497,4 +497,87 @@ describe('generateSubtitlePipeline', () => {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('uses the server-provided poll timeout for doubao jobs', async () => {
|
||||||
|
vi.useFakeTimers();
|
||||||
|
const fetchMock = vi
|
||||||
|
.fn()
|
||||||
|
.mockResolvedValueOnce(
|
||||||
|
new Response(
|
||||||
|
JSON.stringify({
|
||||||
|
id: 'file-123',
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
status: 200,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.mockResolvedValueOnce(
|
||||||
|
new Response(
|
||||||
|
JSON.stringify({
|
||||||
|
id: 'file-123',
|
||||||
|
status: 'active',
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
status: 200,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.mockResolvedValueOnce(
|
||||||
|
new Response(
|
||||||
|
JSON.stringify({
|
||||||
|
jobId: 'job-1',
|
||||||
|
requestId: 'req-1',
|
||||||
|
status: 'queued',
|
||||||
|
stage: 'queued',
|
||||||
|
progress: 5,
|
||||||
|
pollTimeoutMs: 1000,
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
status: 202,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.mockImplementation(async () =>
|
||||||
|
new Response(
|
||||||
|
JSON.stringify({
|
||||||
|
jobId: 'job-1',
|
||||||
|
requestId: 'req-1',
|
||||||
|
status: 'running',
|
||||||
|
stage: 'calling_provider',
|
||||||
|
progress: 70,
|
||||||
|
message: 'Calling provider',
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
status: 200,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
const promise = generateSubtitlePipeline(
|
||||||
|
new File(['video'], 'clip.mp4', { type: 'video/mp4' }),
|
||||||
|
'English',
|
||||||
|
'doubao',
|
||||||
|
null,
|
||||||
|
fetchMock as unknown as typeof fetch,
|
||||||
|
);
|
||||||
|
const rejection = expect(promise).rejects.toThrow(
|
||||||
|
/timed out while waiting for subtitle generation to complete/i,
|
||||||
|
);
|
||||||
|
|
||||||
|
await vi.runAllTimersAsync();
|
||||||
|
await rejection;
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -11,6 +11,11 @@ const ARK_FILE_STATUS_TIMEOUT_MS = 120000;
|
|||||||
const SUBTITLE_JOB_POLL_INTERVAL_MS = 5000;
|
const SUBTITLE_JOB_POLL_INTERVAL_MS = 5000;
|
||||||
const SUBTITLE_JOB_TIMEOUT_MS = 20 * 60 * 1000;
|
const SUBTITLE_JOB_TIMEOUT_MS = 20 * 60 * 1000;
|
||||||
|
|
||||||
|
const resolvePollTimeoutMs = (value?: number) =>
|
||||||
|
Number.isFinite(value) && (value as number) > 0
|
||||||
|
? Math.floor(value as number)
|
||||||
|
: SUBTITLE_JOB_TIMEOUT_MS;
|
||||||
|
|
||||||
interface SubtitleJobResponse extends SubtitleGenerationProgress {
|
interface SubtitleJobResponse extends SubtitleGenerationProgress {
|
||||||
error?: string;
|
error?: string;
|
||||||
result?: Partial<SubtitlePipelineResult>;
|
result?: Partial<SubtitlePipelineResult>;
|
||||||
@ -109,10 +114,11 @@ const pollSubtitleJob = async (
|
|||||||
jobId: string,
|
jobId: string,
|
||||||
targetLanguage: string,
|
targetLanguage: string,
|
||||||
ttsLanguage: string,
|
ttsLanguage: string,
|
||||||
|
pollTimeoutMs: number,
|
||||||
fetchImpl: typeof fetch,
|
fetchImpl: typeof fetch,
|
||||||
onProgress?: (progress: SubtitleGenerationProgress) => void,
|
onProgress?: (progress: SubtitleGenerationProgress) => void,
|
||||||
): Promise<SubtitlePipelineResult> => {
|
): Promise<SubtitlePipelineResult> => {
|
||||||
const deadline = Date.now() + SUBTITLE_JOB_TIMEOUT_MS;
|
const deadline = Date.now() + resolvePollTimeoutMs(pollTimeoutMs);
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
const resp = await fetchImpl(apiUrl(`/generate-subtitles/${jobId}`), {
|
const resp = await fetchImpl(apiUrl(`/generate-subtitles/${jobId}`), {
|
||||||
@ -226,7 +232,14 @@ export const generateSubtitlePipeline = async (
|
|||||||
}
|
}
|
||||||
const job = parsed.data as unknown as SubtitleJobResponse;
|
const job = parsed.data as unknown as SubtitleJobResponse;
|
||||||
onProgress?.(job);
|
onProgress?.(job);
|
||||||
return pollSubtitleJob(job.jobId, targetLanguage, resolvedTtsLanguage, fetchImpl, onProgress);
|
return pollSubtitleJob(
|
||||||
|
job.jobId,
|
||||||
|
targetLanguage,
|
||||||
|
resolvedTtsLanguage,
|
||||||
|
job.pollTimeoutMs ?? SUBTITLE_JOB_TIMEOUT_MS,
|
||||||
|
fetchImpl,
|
||||||
|
onProgress,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
@ -250,5 +263,12 @@ export const generateSubtitlePipeline = async (
|
|||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
onProgress?.(parsed.data);
|
onProgress?.(parsed.data);
|
||||||
return pollSubtitleJob(parsed.data.jobId, targetLanguage, resolvedTtsLanguage, fetchImpl, onProgress);
|
return pollSubtitleJob(
|
||||||
|
parsed.data.jobId,
|
||||||
|
targetLanguage,
|
||||||
|
resolvedTtsLanguage,
|
||||||
|
parsed.data.pollTimeoutMs ?? SUBTITLE_JOB_TIMEOUT_MS,
|
||||||
|
fetchImpl,
|
||||||
|
onProgress,
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -66,6 +66,7 @@ export interface SubtitleGenerationProgress {
|
|||||||
stage: SubtitleJobStage;
|
stage: SubtitleJobStage;
|
||||||
progress: number;
|
progress: number;
|
||||||
message: string;
|
message: string;
|
||||||
|
pollTimeoutMs?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TextStyles {
|
export interface TextStyles {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user