mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-12 12:28:42 +00:00
feat(server): retry transcript job (#11414)
This commit is contained in:
@@ -705,6 +705,10 @@ export const USER_FRIENDLY_ERRORS = {
|
||||
type: 'bad_request',
|
||||
message: () => 'Transcription job already exists',
|
||||
},
|
||||
copilot_transcription_job_not_found: {
|
||||
type: 'bad_request',
|
||||
message: () => `Transcription job not found.`,
|
||||
},
|
||||
|
||||
// Quota & Limit errors
|
||||
blob_quota_exceeded: {
|
||||
|
||||
@@ -765,6 +765,12 @@ export class CopilotTranscriptionJobExists extends UserFriendlyError {
|
||||
}
|
||||
}
|
||||
|
||||
export class CopilotTranscriptionJobNotFound extends UserFriendlyError {
|
||||
constructor(message?: string) {
|
||||
super('bad_request', 'copilot_transcription_job_not_found', message);
|
||||
}
|
||||
}
|
||||
|
||||
export class BlobQuotaExceeded extends UserFriendlyError {
|
||||
constructor(message?: string) {
|
||||
super('quota_exceeded', 'blob_quota_exceeded', message);
|
||||
@@ -1020,6 +1026,7 @@ export enum ErrorNames {
|
||||
COPILOT_FAILED_TO_MATCH_CONTEXT,
|
||||
COPILOT_EMBEDDING_UNAVAILABLE,
|
||||
COPILOT_TRANSCRIPTION_JOB_EXISTS,
|
||||
COPILOT_TRANSCRIPTION_JOB_NOT_FOUND,
|
||||
BLOB_QUOTA_EXCEEDED,
|
||||
STORAGE_QUOTA_EXCEEDED,
|
||||
MEMBER_QUOTA_EXCEEDED,
|
||||
|
||||
@@ -13,7 +13,10 @@ import {
|
||||
import { AiJobStatus } from '@prisma/client';
|
||||
import GraphQLUpload from 'graphql-upload/GraphQLUpload.mjs';
|
||||
|
||||
import type { FileUpload } from '../../../base';
|
||||
import {
|
||||
CopilotTranscriptionJobNotFound,
|
||||
type FileUpload,
|
||||
} from '../../../base';
|
||||
import { CurrentUser } from '../../../core/auth';
|
||||
import { AccessController } from '../../../core/permission';
|
||||
import { CopilotType } from '../resolver';
|
||||
@@ -106,14 +109,44 @@ export class CopilotTranscriptionResolver {
|
||||
.allowLocal()
|
||||
.assert('Workspace.Copilot');
|
||||
|
||||
const job = await this.service.submitTranscriptionJob(
|
||||
const jobResult = await this.service.submitTranscriptionJob(
|
||||
user.id,
|
||||
workspaceId,
|
||||
blobId,
|
||||
blob
|
||||
);
|
||||
|
||||
return this.handleJobResult(job);
|
||||
return this.handleJobResult(jobResult);
|
||||
}
|
||||
|
||||
@Mutation(() => TranscriptionResultType, { nullable: true })
|
||||
async retryAudioTranscription(
|
||||
@CurrentUser() user: CurrentUser,
|
||||
@Args('workspaceId') workspaceId: string,
|
||||
@Args('jobId') jobId: string
|
||||
): Promise<TranscriptionResultType | null> {
|
||||
await this.ac
|
||||
.user(user.id)
|
||||
.workspace(workspaceId)
|
||||
.allowLocal()
|
||||
.assert('Workspace.Copilot');
|
||||
|
||||
const job = await this.service.queryTranscriptionJob(
|
||||
user.id,
|
||||
workspaceId,
|
||||
jobId
|
||||
);
|
||||
if (!job || !job.url || !job.mimeType) {
|
||||
throw new CopilotTranscriptionJobNotFound();
|
||||
}
|
||||
|
||||
const jobResult = await this.service.executeTranscriptionJob(
|
||||
job.id,
|
||||
job.url,
|
||||
job.mimeType
|
||||
);
|
||||
|
||||
return this.handleJobResult(jobResult);
|
||||
}
|
||||
|
||||
@Mutation(() => TranscriptionResultType, { nullable: true })
|
||||
|
||||
@@ -5,6 +5,7 @@ import { ZodType } from 'zod';
|
||||
import {
|
||||
CopilotPromptNotFound,
|
||||
CopilotTranscriptionJobExists,
|
||||
CopilotTranscriptionJobNotFound,
|
||||
EventBus,
|
||||
type FileUpload,
|
||||
JobQueue,
|
||||
@@ -31,6 +32,8 @@ import { readStream } from './utils';
|
||||
export type TranscriptionJob = {
|
||||
id: string;
|
||||
status: AiJobStatus;
|
||||
url?: string;
|
||||
mimeType?: string;
|
||||
transcription?: TranscriptionPayload;
|
||||
};
|
||||
|
||||
@@ -55,7 +58,7 @@ export class CopilotTranscriptionService {
|
||||
throw new CopilotTranscriptionJobExists();
|
||||
}
|
||||
|
||||
const { id: jobId, status } = await this.models.copilotJob.create({
|
||||
const { id: jobId } = await this.models.copilotJob.create({
|
||||
workspaceId,
|
||||
blobId,
|
||||
createdBy: userId,
|
||||
@@ -65,14 +68,28 @@ export class CopilotTranscriptionService {
|
||||
const buffer = await readStream(blob.createReadStream());
|
||||
const url = await this.storage.put(userId, workspaceId, blobId, buffer);
|
||||
|
||||
await this.models.copilotJob.update(jobId, {
|
||||
status: AiJobStatus.running,
|
||||
return await this.executeTranscriptionJob(jobId, url, blob.mimetype);
|
||||
}
|
||||
|
||||
async executeTranscriptionJob(
|
||||
jobId: string,
|
||||
url: string,
|
||||
mimeType: string
|
||||
): Promise<TranscriptionJob> {
|
||||
const status = AiJobStatus.running;
|
||||
const success = await this.models.copilotJob.update(jobId, {
|
||||
status,
|
||||
payload: { url, mimeType },
|
||||
});
|
||||
|
||||
if (!success) {
|
||||
throw new CopilotTranscriptionJobNotFound();
|
||||
}
|
||||
|
||||
await this.job.add('copilot.transcript.submit', {
|
||||
jobId,
|
||||
url,
|
||||
mimeType: blob.mimetype,
|
||||
mimeType,
|
||||
});
|
||||
|
||||
return { id: jobId, status };
|
||||
@@ -113,9 +130,11 @@ export class CopilotTranscriptionService {
|
||||
|
||||
const ret: TranscriptionJob = { id: job.id, status: job.status };
|
||||
|
||||
if (job.status === AiJobStatus.claimed) {
|
||||
const payload = TranscriptPayloadSchema.safeParse(job.payload);
|
||||
if (payload.success) {
|
||||
const payload = TranscriptPayloadSchema.safeParse(job.payload);
|
||||
if (payload.success) {
|
||||
ret.url = payload.data.url || undefined;
|
||||
ret.mimeType = payload.data.mimeType || undefined;
|
||||
if (job.status === AiJobStatus.claimed) {
|
||||
ret.transcription = payload.data;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,8 @@ const TranscriptionItemSchema = z.object({
|
||||
export const TranscriptionSchema = z.array(TranscriptionItemSchema);
|
||||
|
||||
export const TranscriptPayloadSchema = z.object({
|
||||
url: z.string().nullable().optional(),
|
||||
mimeType: z.string().nullable().optional(),
|
||||
title: z.string().nullable().optional(),
|
||||
summary: z.string().nullable().optional(),
|
||||
transcription: TranscriptionSchema.nullable().optional(),
|
||||
|
||||
@@ -436,6 +436,7 @@ enum ErrorNames {
|
||||
COPILOT_SESSION_DELETED
|
||||
COPILOT_SESSION_NOT_FOUND
|
||||
COPILOT_TRANSCRIPTION_JOB_EXISTS
|
||||
COPILOT_TRANSCRIPTION_JOB_NOT_FOUND
|
||||
CUSTOMER_PORTAL_CREATE_FAILED
|
||||
DOC_ACTION_DENIED
|
||||
DOC_DEFAULT_ROLE_CAN_NOT_BE_OWNER
|
||||
@@ -990,6 +991,7 @@ type Mutation {
|
||||
removeContextFile(options: RemoveContextFileInput!): Boolean!
|
||||
removeWorkspaceFeature(feature: FeatureType!, workspaceId: String!): Boolean!
|
||||
resumeSubscription(idempotencyKey: String @deprecated(reason: "use header `Idempotency-Key`"), plan: SubscriptionPlan = Pro, workspaceId: String): SubscriptionType!
|
||||
retryAudioTranscription(jobId: String!, workspaceId: String!): TranscriptionResultType
|
||||
revoke(userId: String!, workspaceId: String!): Boolean!
|
||||
revokeDocUserRoles(input: RevokeDocUserRoleInput!): Boolean!
|
||||
revokeInviteLink(workspaceId: String!): Boolean!
|
||||
|
||||
Reference in New Issue
Block a user